Compare commits

...

12 Commits

Author SHA1 Message Date
neru 9d841358ee docs: remove - 2026-05-11 08:37:58 -03:00
neru e48076a452 docs: wording, table of contents, and misc changes 2026-05-11 08:37:30 -03:00
neru b17f84cb40 docs: add documentation for event lib 2026-05-11 08:29:43 -03:00
neru 7f979dacd4 feat: add VFS test 2026-05-11 08:24:36 -03:00
neru b51f253705 feat: add log message for log test 2026-05-11 08:17:30 -03:00
neru 61983f5903 feat: add log messages for event test 2026-05-11 08:17:24 -03:00
neru fb259b019b feat: add logging functions for tests 2026-05-11 08:17:13 -03:00
neru 4d63fe9b71 fix: consider all mounts for listDirectory 2026-05-11 08:07:56 -03:00
neru 4a0d387328 feat: add getRelativePath 2026-05-11 08:07:12 -03:00
neru 9d3b5d25b2 fix: iterate through mounts instead of getting best mount 2026-05-11 08:07:05 -03:00
neru 0fbf8c715c feat: add event test 2026-05-11 07:59:28 -03:00
neru 430cb5bc5b feat: add event library 2026-05-11 07:59:19 -03:00
9 changed files with 409 additions and 26 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ if (SEALLIB_ASSERT)
endif() endif()
if (SEALLIB_EVENTS) 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() endif()
if (SEALLIB_LOG) if (SEALLIB_LOG)
+49 -10
View File
@@ -1,11 +1,21 @@
<h1 align="center"> <h1 align="center">
<b>seallib</b> <b>🦭seallib🦭</b>
</h1> </h1>
<p align="center"> <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> </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 ## Installation & Integration
### 1. Requirements ### 1. Requirements
@@ -49,6 +59,7 @@ target_link_libraries(your_project PRIVATE seallib)
--- ---
## Build Options ## Build Options
Toggle features by setting these variables to ``ON`` or ``OFF`` in your CMake configuration.
| Option | Description | Default | | Option | Description | Default |
| :--- | :--- | :--- | | :--- | :--- | :--- |
| SEALLIB_ASSERT | Enable Assertion utility | OFF | | 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 ### 1. Logging
The Log module uses a Sink architecture. You create a Logger, attach an ILogSink, and log messages using std::format syntax. 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. Assert
### 2. Assertion
(Documentation pending implementation) (Documentation pending implementation)
<a name="module-events"></a>
### 3. Events ### 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 ### 4. VFS
The VFS (Virtual File System) module provides a unified interface for file operations by mapping virtual paths to user defined providers. 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) ### Windows (PowerShell + Visual Studio)
A script is provided to automate generation and open the solution in Visual Studio: A script is provided to automate generation and open the solution in Visual Studio:
+76
View File
@@ -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
View File
@@ -3,6 +3,7 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include <cctype> #include <cctype>
#include <set>
using namespace seallib; using namespace seallib;
@@ -87,20 +88,32 @@ void VirtualFileSystem::unmountAll()
bool VirtualFileSystem::exists(const std::string& path) const bool VirtualFileSystem::exists(const std::string& path) const
{ {
std::shared_lock lock(_mutex); std::shared_lock lock(_mutex);
std::string relativePath; std::string searchPath = trimPathSeparators(normalizePath(path));
const MountPoint* mount = findBestMount(path, relativePath);
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 VFSResult VirtualFileSystem::readFile(const std::string& path, FileBuffer& outBuffer) const
{ {
std::shared_lock lock(_mutex); std::shared_lock lock(_mutex);
std::string relativePath; std::string searchPath = trimPathSeparators(normalizePath(path));
const MountPoint* mount = findBestMount(path, relativePath);
if (!mount) return VFSResult::FileNotFound; for (const auto& mount : _mounts)
return mount->provider->readFile(relativePath, outBuffer); {
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) 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) VFSResult VirtualFileSystem::deleteFile(const std::string& path)
{ {
std::shared_lock lock(_mutex); std::shared_lock lock(_mutex);
std::string relativePath; std::string searchPath = trimPathSeparators(normalizePath(path));
MountPoint* mount = findBestMount(path, relativePath);
if (!mount) return VFSResult::FileNotFound; for (auto& mount : _mounts)
return mount->provider->deleteFile(relativePath); {
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) 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::vector<std::string> VirtualFileSystem::listDirectory(const std::string& path) const
{ {
std::shared_lock lock(_mutex); std::shared_lock lock(_mutex);
std::string relativePath; std::string searchPath = trimPathSeparators(normalizePath(path));
const MountPoint* mount = findBestMount(path, relativePath);
if (!mount) return {}; std::set<std::string> mergedFiles;
return mount->provider->listDirectory(relativePath);
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 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); 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 mountpoint
*/ */
+3
View File
@@ -99,6 +99,9 @@ namespace seallib
static bool isPathSeparator(char c); static bool isPathSeparator(char c);
static std::string trimPathSeparators(const std::string& path); 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) inline std::string vfsResultToString(VFSResult result)
+36
View File
@@ -1,6 +1,8 @@
#pragma once #pragma once
#include <vector> #include <vector>
#include <iostream>
#include <format>
/* /*
test implementation interface test implementation interface
@@ -13,8 +15,38 @@ class ITest
protected: protected:
virtual void run() = 0; 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; 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() void runTests()
{ {
for (auto& test : _tests) for (auto& test : _tests)
{
test->logInfo("------- Init -------");
test->run(); test->run();
test->logInfo("-------- End --------");
}
} }
static TestRunner& get() static TestRunner& get()
+46
View File
@@ -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;
+6
View File
@@ -55,14 +55,20 @@ class LogTest : ITest
#ifdef _WIN32 #ifdef _WIN32
createAndSetupConsole(); createAndSetupConsole();
#endif #endif
logInfo("Instantiating logger");
Logger log("TestLogger"); Logger log("TestLogger");
logInfo("Registering sink");
log.addSink(std::make_shared<TestLogSink>()); log.addSink(std::make_shared<TestLogSink>());
logInfo("Calling log functions");
log.verbose("verbose log"); log.verbose("verbose log");
log.info("info log"); log.info("info log");
log.warning("warning log"); log.warning("warning log");
log.error("error log"); log.error("error log");
} }
virtual const char* getName() override { return "LogTest"; }
}; };
static LogTest g_logTest; static LogTest g_logTest;
+126
View File
@@ -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;