Compare commits
12 Commits
3b173931d5
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 9d841358ee | |||
| e48076a452 | |||
| b17f84cb40 | |||
| 7f979dacd4 | |||
| b51f253705 | |||
| 61983f5903 | |||
| fb259b019b | |||
| 4d63fe9b71 | |||
| 4a0d387328 | |||
| 9d3b5d25b2 | |||
| 0fbf8c715c | |||
| 430cb5bc5b |
+1
-1
@@ -32,7 +32,7 @@ if (SEALLIB_ASSERT)
|
||||
endif()
|
||||
|
||||
if (SEALLIB_EVENTS)
|
||||
list(APPEND SEALLIB_SOURCES "src/lib/seallib/events.cpp" "src/lib/seallib/events.h")
|
||||
list(APPEND SEALLIB_SOURCES "src/lib/seallib/events.h")
|
||||
endif()
|
||||
|
||||
if (SEALLIB_LOG)
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
<h1 align="center">
|
||||
<b>seallib</b>
|
||||
<b>🦭seallib🦭</b>
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
A modular and tiny C++20 powered library collection of random stuff I've needed for other projects.
|
||||
A <b>modular and tiny C++20</b> powered library collection of random stuff I've needed for other projects.
|
||||
</p>
|
||||
|
||||
## Table of Contents
|
||||
* [Installation & Integration](#installation--integration)
|
||||
* [Build Options](#build-options)
|
||||
* [Modules](#modules)
|
||||
* [Logging](#1-logging)
|
||||
* [Assert](#2-assert)
|
||||
* [Events](#3-events)
|
||||
* [VFS](#4-vfs)
|
||||
* [Running tests](#running-tests)
|
||||
|
||||
## Installation & Integration
|
||||
|
||||
### 1. Requirements
|
||||
@@ -49,6 +59,7 @@ target_link_libraries(your_project PRIVATE seallib)
|
||||
---
|
||||
|
||||
## Build Options
|
||||
Toggle features by setting these variables to ``ON`` or ``OFF`` in your CMake configuration.
|
||||
| Option | Description | Default |
|
||||
| :--- | :--- | :--- |
|
||||
| SEALLIB_ASSERT | Enable Assertion utility | OFF |
|
||||
@@ -59,9 +70,8 @@ target_link_libraries(your_project PRIVATE seallib)
|
||||
|
||||
---
|
||||
|
||||
## Module Instructions
|
||||
## Modules
|
||||
|
||||
<a name="module-logging"></a>
|
||||
### 1. Logging
|
||||
The Log module uses a Sink architecture. You create a Logger, attach an ILogSink, and log messages using std::format syntax.
|
||||
|
||||
@@ -91,15 +101,44 @@ int main() {
|
||||
}
|
||||
```
|
||||
|
||||
<a name="module-assertion"></a>
|
||||
### 2. Assertion
|
||||
### 2. Assert
|
||||
(Documentation pending implementation)
|
||||
|
||||
<a name="module-events"></a>
|
||||
### 3. Events
|
||||
(Documentation pending implementation)
|
||||
The Events module provides a thread and type safe signal/slot mechanism. It allows you to define custom events with any number of parameters and subscribe to them using callbacks.
|
||||
#### Basic Usage
|
||||
```cpp
|
||||
#include <seallib/event.h>
|
||||
#include <iostream>
|
||||
|
||||
using namespace seallib;
|
||||
|
||||
// Define an event that takes an int and a string
|
||||
Event<int, std::string> OnUserLogin;
|
||||
|
||||
void onLogin(int id, std::string name) {
|
||||
std::cout << "User " << name << " (ID: " << id << ") logged in!" << std::endl;
|
||||
}
|
||||
|
||||
int main() {
|
||||
// 1. Subscribe to the event
|
||||
auto connection = OnUserLogin.addListener(onLogin);
|
||||
|
||||
// 2. Add a lambda listener
|
||||
OnUserLogin.addListener([](int id, auto name) {
|
||||
std::clog << "[Log] Login detected for ID: " << id << std::endl;
|
||||
});
|
||||
|
||||
// 3. Trigger the event
|
||||
OnUserLogin.run(42, "SomeUser");
|
||||
|
||||
// 4. Remove a listener when no longer needed
|
||||
OnUserLogin.removeListener(connection);
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
<a name="module-vfs"></a>
|
||||
### 4. VFS
|
||||
The VFS (Virtual File System) module provides a unified interface for file operations by mapping virtual paths to user defined providers.
|
||||
|
||||
@@ -182,7 +221,7 @@ int main() {
|
||||
|
||||
---
|
||||
|
||||
## Running Tests
|
||||
## Running tests
|
||||
|
||||
### Windows (PowerShell + Visual Studio)
|
||||
A script is provided to automate generation and open the solution in Visual Studio:
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <algorithm>
|
||||
|
||||
namespace seallib
|
||||
{
|
||||
template <typename... T> class Event
|
||||
{
|
||||
public:
|
||||
using CallbackFunc = std::function<void(T...)>;
|
||||
using ConnectionId = size_t;
|
||||
|
||||
ConnectionId addListener(CallbackFunc listener)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
ConnectionId id = ++_nextId;
|
||||
_listeners.push_back({id, std::move(listener)});
|
||||
return id;
|
||||
}
|
||||
|
||||
bool removeListener(ConnectionId id)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
|
||||
auto it =
|
||||
std::find_if(_listeners.begin(), _listeners.end(), [id](const Listener& l) { return l.id == id; });
|
||||
|
||||
if (it != _listeners.end())
|
||||
{
|
||||
_listeners.erase(it);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename... Args> void run(Args&&... args)
|
||||
{
|
||||
std::vector<CallbackFunc> snapshot;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
snapshot.reserve(_listeners.size());
|
||||
for (const auto& l : _listeners)
|
||||
{
|
||||
snapshot.push_back(l.func);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& func : snapshot)
|
||||
{
|
||||
func(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(_mtx);
|
||||
_listeners.clear();
|
||||
}
|
||||
|
||||
private:
|
||||
struct Listener
|
||||
{
|
||||
ConnectionId id;
|
||||
CallbackFunc func;
|
||||
};
|
||||
|
||||
std::vector<Listener> _listeners;
|
||||
std::mutex _mtx;
|
||||
std::atomic<ConnectionId> _nextId{0};
|
||||
};
|
||||
} // namespace seallib
|
||||
+66
-15
@@ -3,6 +3,7 @@
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <cctype>
|
||||
#include <set>
|
||||
|
||||
using namespace seallib;
|
||||
|
||||
@@ -87,20 +88,32 @@ void VirtualFileSystem::unmountAll()
|
||||
bool VirtualFileSystem::exists(const std::string& path) const
|
||||
{
|
||||
std::shared_lock lock(_mutex);
|
||||
std::string relativePath;
|
||||
const MountPoint* mount = findBestMount(path, relativePath);
|
||||
std::string searchPath = trimPathSeparators(normalizePath(path));
|
||||
|
||||
return mount ? mount->provider->exists(relativePath) : false;
|
||||
for (const auto& mount : _mounts)
|
||||
{
|
||||
std::string relativePath;
|
||||
if (getRelativePath(mount, searchPath, relativePath))
|
||||
{
|
||||
if (mount.provider->exists(relativePath)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
VFSResult VirtualFileSystem::readFile(const std::string& path, FileBuffer& outBuffer) const
|
||||
{
|
||||
std::shared_lock lock(_mutex);
|
||||
std::string relativePath;
|
||||
const MountPoint* mount = findBestMount(path, relativePath);
|
||||
std::string searchPath = trimPathSeparators(normalizePath(path));
|
||||
|
||||
if (!mount) return VFSResult::FileNotFound;
|
||||
return mount->provider->readFile(relativePath, outBuffer);
|
||||
for (const auto& mount : _mounts)
|
||||
{
|
||||
std::string relativePath;
|
||||
if (getRelativePath(mount, searchPath, relativePath))
|
||||
if (mount.provider->exists(relativePath)) return mount.provider->readFile(relativePath, outBuffer);
|
||||
}
|
||||
|
||||
return VFSResult::FileNotFound;
|
||||
}
|
||||
|
||||
VFSResult VirtualFileSystem::writeFile(const std::string& path, const FileBuffer& buffer)
|
||||
@@ -118,11 +131,16 @@ VFSResult VirtualFileSystem::writeFile(const std::string& path, const FileBuffer
|
||||
VFSResult VirtualFileSystem::deleteFile(const std::string& path)
|
||||
{
|
||||
std::shared_lock lock(_mutex);
|
||||
std::string relativePath;
|
||||
MountPoint* mount = findBestMount(path, relativePath);
|
||||
std::string searchPath = trimPathSeparators(normalizePath(path));
|
||||
|
||||
if (!mount) return VFSResult::FileNotFound;
|
||||
return mount->provider->deleteFile(relativePath);
|
||||
for (auto& mount : _mounts)
|
||||
{
|
||||
std::string relativePath;
|
||||
if (getRelativePath(mount, searchPath, relativePath))
|
||||
if (mount.provider->exists(relativePath)) return mount.provider->deleteFile(relativePath);
|
||||
}
|
||||
|
||||
return VFSResult::FileNotFound;
|
||||
}
|
||||
|
||||
VFSResult VirtualFileSystem::createDirectory(const std::string& path)
|
||||
@@ -138,11 +156,22 @@ VFSResult VirtualFileSystem::createDirectory(const std::string& path)
|
||||
std::vector<std::string> VirtualFileSystem::listDirectory(const std::string& path) const
|
||||
{
|
||||
std::shared_lock lock(_mutex);
|
||||
std::string relativePath;
|
||||
const MountPoint* mount = findBestMount(path, relativePath);
|
||||
std::string searchPath = trimPathSeparators(normalizePath(path));
|
||||
|
||||
if (!mount) return {};
|
||||
return mount->provider->listDirectory(relativePath);
|
||||
std::set<std::string> mergedFiles;
|
||||
|
||||
for (const auto& mount : _mounts)
|
||||
{
|
||||
std::string relativePath;
|
||||
if (getRelativePath(mount, searchPath, relativePath))
|
||||
{
|
||||
auto files = mount.provider->listDirectory(relativePath);
|
||||
for (const auto& f : files)
|
||||
mergedFiles.insert(f);
|
||||
}
|
||||
}
|
||||
|
||||
return {mergedFiles.begin(), mergedFiles.end()};
|
||||
}
|
||||
|
||||
std::string VirtualFileSystem::normalizePath(const std::string& path) const
|
||||
@@ -240,6 +269,28 @@ std::string VirtualFileSystem::trimPathSeparators(const std::string& path)
|
||||
return path.substr(start, end - start + 1);
|
||||
}
|
||||
|
||||
bool VirtualFileSystem::getRelativePath(const MountPoint& mount, const std::string& searchPath,
|
||||
std::string& outRelative) const
|
||||
{
|
||||
if (searchPath == mount.virtualPath)
|
||||
{
|
||||
outRelative = "/";
|
||||
return true;
|
||||
}
|
||||
if (mount.virtualPath == "/")
|
||||
{
|
||||
outRelative = searchPath;
|
||||
return true;
|
||||
}
|
||||
std::string prefix = mount.virtualPath + '/';
|
||||
if (searchPath.compare(0, prefix.length(), prefix) == 0)
|
||||
{
|
||||
outRelative = searchPath.substr(prefix.length());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
mountpoint
|
||||
*/
|
||||
|
||||
@@ -99,6 +99,9 @@ namespace seallib
|
||||
|
||||
static bool isPathSeparator(char c);
|
||||
static std::string trimPathSeparators(const std::string& path);
|
||||
|
||||
bool getRelativePath(const MountPoint& mount, const std::string& searchPath,
|
||||
std::string& outRelative) const;
|
||||
};
|
||||
|
||||
inline std::string vfsResultToString(VFSResult result)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <format>
|
||||
|
||||
/*
|
||||
test implementation interface
|
||||
@@ -13,8 +15,38 @@ class ITest
|
||||
|
||||
protected:
|
||||
virtual void run() = 0;
|
||||
virtual const char* getName() = 0;
|
||||
|
||||
/*
|
||||
shamelessly yoinked from log lib
|
||||
*/
|
||||
template <typename... Args> void logVerbose(std::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
writeLog("\x1b[34;40m", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args> void logInfo(std::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
writeLog("\x1b[32;40m", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args> void logWarning(std::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
writeLog("\x1b[30;43m", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
template <typename... Args> void logError(std::format_string<Args...> fmt, Args&&... args)
|
||||
{
|
||||
writeLog("\x1b[97;41m", std::vformat(fmt.get(), std::make_format_args(args...)));
|
||||
}
|
||||
|
||||
friend class TestRunner;
|
||||
|
||||
private:
|
||||
void writeLog(const char* color, const std::string& message)
|
||||
{
|
||||
std::cout << color << "[" << getName() << "]" << "\x1b[0m " << message << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -26,7 +58,11 @@ class TestRunner
|
||||
void runTests()
|
||||
{
|
||||
for (auto& test : _tests)
|
||||
{
|
||||
test->logInfo("------- Init -------");
|
||||
test->run();
|
||||
test->logInfo("-------- End --------");
|
||||
}
|
||||
}
|
||||
|
||||
static TestRunner& get()
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#include "tests.h"
|
||||
|
||||
#include <seallib/events.h>
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
using namespace seallib;
|
||||
|
||||
class EventTest : ITest
|
||||
{
|
||||
public:
|
||||
EventTest() : ITest() {};
|
||||
|
||||
virtual void run() override
|
||||
{
|
||||
logInfo("Instantiating event");
|
||||
Event<std::string> testEvent;
|
||||
|
||||
logInfo("Registering listeners");
|
||||
auto listener1 = testEvent.addListener([](std::string str) {
|
||||
std::cout << "(Listener 1) Event has been fired with string: " << str << std::endl;
|
||||
});
|
||||
|
||||
auto listener2 = testEvent.addListener([](std::string str) {
|
||||
std::cout << "(Listener 2) Event has been fired, string: " << str << std::endl;
|
||||
});
|
||||
|
||||
logInfo("Running event with both listeners");
|
||||
const char* str1 = "somestring";
|
||||
testEvent.run(str1);
|
||||
|
||||
std::string str2("hello world");
|
||||
testEvent.run(str2);
|
||||
|
||||
logInfo("Removing listener 2");
|
||||
testEvent.removeListener(listener2);
|
||||
|
||||
logInfo("Running event with only 1 listener");
|
||||
testEvent.run("event without listener 2");
|
||||
};
|
||||
|
||||
virtual const char* getName() override { return "EventTest"; }
|
||||
};
|
||||
|
||||
static EventTest g_eventTest;
|
||||
@@ -55,14 +55,20 @@ class LogTest : ITest
|
||||
#ifdef _WIN32
|
||||
createAndSetupConsole();
|
||||
#endif
|
||||
logInfo("Instantiating logger");
|
||||
Logger log("TestLogger");
|
||||
|
||||
logInfo("Registering sink");
|
||||
log.addSink(std::make_shared<TestLogSink>());
|
||||
|
||||
logInfo("Calling log functions");
|
||||
log.verbose("verbose log");
|
||||
log.info("info log");
|
||||
log.warning("warning log");
|
||||
log.error("error log");
|
||||
}
|
||||
|
||||
virtual const char* getName() override { return "LogTest"; }
|
||||
};
|
||||
|
||||
static LogTest g_logTest;
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
#include "tests.h"
|
||||
|
||||
#include <seallib/vfs.h>
|
||||
|
||||
#include <string>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
using namespace seallib;
|
||||
|
||||
/*
|
||||
in-memory file provider
|
||||
*/
|
||||
class MemoryFileProvider : public IFileProvider
|
||||
{
|
||||
std::map<std::string, std::string> files;
|
||||
|
||||
public:
|
||||
bool exists(const std::string& path) const override { return files.count(path); }
|
||||
|
||||
VFSResult readFile(const std::string& path, FileBuffer& outBuffer) const override
|
||||
{
|
||||
if (!exists(path)) return VFSResult::FileNotFound;
|
||||
outBuffer = FileBuffer::fromString(files.at(path));
|
||||
return VFSResult::Success;
|
||||
}
|
||||
|
||||
VFSResult writeFile(const std::string& path, const FileBuffer& buffer) override
|
||||
{
|
||||
files[path] = buffer.toString();
|
||||
return VFSResult::Success;
|
||||
}
|
||||
|
||||
VFSResult deleteFile(const std::string& path) override
|
||||
{
|
||||
return files.erase(path) ? VFSResult::Success : VFSResult::FileNotFound;
|
||||
}
|
||||
|
||||
VFSResult createDirectory(const std::string& path) override { return VFSResult::Success; }
|
||||
|
||||
std::vector<std::string> listDirectory(const std::string& path) const override
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (auto const& [key, val] : files)
|
||||
result.push_back(key);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string getProviderName() const override { return "MemoryProvider"; }
|
||||
};
|
||||
|
||||
class VFSTest : ITest
|
||||
{
|
||||
public:
|
||||
VFSTest() : ITest() {};
|
||||
|
||||
virtual void run() override
|
||||
{
|
||||
VirtualFileSystem vfs;
|
||||
|
||||
auto prov1 = std::make_unique<MemoryFileProvider>();
|
||||
auto prov2 = std::make_unique<MemoryFileProvider>();
|
||||
|
||||
/*
|
||||
mnt test
|
||||
*/
|
||||
vfs.mount("scripts", std::move(prov1));
|
||||
vfs.mount("config/network", std::move(prov2));
|
||||
|
||||
/*
|
||||
write / read tests
|
||||
*/
|
||||
logInfo("Writing to different mount points");
|
||||
vfs.writeFile("scripts/main.lua", FileBuffer::fromString("print('hello')"));
|
||||
vfs.writeFile("config/network/settings.json", FileBuffer::fromString("{ \"ip\": \"127.0.0.1\" }"));
|
||||
|
||||
logInfo("Reading from mount points");
|
||||
FileBuffer b1, b2;
|
||||
if (vfs.readFile("scripts/main.lua", b1) == VFSResult::Success) logInfo("Read Scripts: {}", b1.toString());
|
||||
|
||||
/*
|
||||
existence ands crosstalk
|
||||
*/
|
||||
if (!vfs.exists("scripts/main.lua")) logError("scripts/main.lua is missing?");
|
||||
if (vfs.exists("scripts/settings.json")) logError("wrong provider, settings.json should be inaccessible");
|
||||
logInfo("Path isolation verified");
|
||||
|
||||
/*
|
||||
priority / overlapping mounts
|
||||
*/
|
||||
auto overrideProv = std::make_unique<MemoryFileProvider>();
|
||||
overrideProv->writeFile("patch.txt", FileBuffer::fromString("Hotfix Data"));
|
||||
|
||||
vfs.mount("scripts", std::move(overrideProv), 10); // higher priority
|
||||
|
||||
FileBuffer b3;
|
||||
if (vfs.readFile("scripts/patch.txt", b3) == VFSResult::Success)
|
||||
logInfo("Priority mount successful: {}", b3.toString());
|
||||
|
||||
/*
|
||||
directory listing
|
||||
*/
|
||||
logInfo("Listing 'scripts' folder");
|
||||
|
||||
auto list = vfs.listDirectory("scripts");
|
||||
for (const auto& file : list)
|
||||
logInfo("\t* {}", file);
|
||||
|
||||
/*
|
||||
deletion
|
||||
*/
|
||||
VFSResult delRes = vfs.deleteFile("scripts/main.lua");
|
||||
logInfo("Deletion result: {}", vfsResultToString(delRes));
|
||||
if (vfs.exists("scripts/main.lua")) logError("Failed to delete file, scrips/main.lua still accessible");
|
||||
|
||||
/*
|
||||
unmounting
|
||||
*/
|
||||
vfs.unmount("scripts");
|
||||
if (!vfs.exists("scripts/patch.txt")) logInfo("Unmount successful");
|
||||
}
|
||||
|
||||
virtual const char* getName() override { return "VFSTest"; }
|
||||
};
|
||||
|
||||
static VFSTest g_vfsTest;
|
||||
Reference in New Issue
Block a user