From 433407cc9da8b2a13959705820bc218260a72535 Mon Sep 17 00:00:00 2001 From: Samsonium Date: Mon, 28 Sep 2026 16:48:42 +0300 Subject: [PATCH 1/9] ref: format --- include/engine/core/engine.hpp | 3 ++- include/engine/core/engine_module.hpp | 6 ++++-- include/engine/core/engine_module_api.hpp | 3 ++- include/engine/modules/test1/test1.hpp | 6 ++++-- include/engine/utils/rev_range.hpp | 6 ++++-- src/engine/core/engine.cpp | 24 +++++++++++++++-------- src/engine/modules/test1/test1.hpp | 9 +++++++-- src/main.cpp | 3 ++- 8 files changed, 41 insertions(+), 19 deletions(-) diff --git a/include/engine/core/engine.hpp b/include/engine/core/engine.hpp index 5f7b102..971d43c 100644 --- a/include/engine/core/engine.hpp +++ b/include/engine/core/engine.hpp @@ -8,7 +8,8 @@ #include /// Magic Srolls engine core -class MscEngine : public IMscEngineModuleAPI { +class MscEngine : public IMscEngineModuleAPI +{ private: /// Loaded modules list std::vector> _modules; diff --git a/include/engine/core/engine_module.hpp b/include/engine/core/engine_module.hpp index e421a58..19978a6 100644 --- a/include/engine/core/engine_module.hpp +++ b/include/engine/core/engine_module.hpp @@ -2,12 +2,14 @@ #define __MCS_ENGINE_MODULE_HPP__ #include "engine/utils/rev_range.hpp" +#include "engine/core/engine_module_api.hpp" #include #include #include /// Engine module base class -class MscEngineModule { +class MscEngineModule +{ public: virtual ~MscEngineModule() = default; @@ -22,7 +24,7 @@ public: getDependencies() const = 0; /// Module initialization phase - virtual void onInitialize(); + virtual void onInitialize(IMscEngineModuleAPI &engine) = 0; }; #endif // __MCS_ENGINE_MODULE_HPP__ diff --git a/include/engine/core/engine_module_api.hpp b/include/engine/core/engine_module_api.hpp index c5e9b39..10137b2 100644 --- a/include/engine/core/engine_module_api.hpp +++ b/include/engine/core/engine_module_api.hpp @@ -3,7 +3,8 @@ typedef void UNIMPL; -class IMscEngineModuleAPI { +class IMscEngineModuleAPI +{ public: virtual ~IMscEngineModuleAPI() = default; diff --git a/include/engine/modules/test1/test1.hpp b/include/engine/modules/test1/test1.hpp index 77411b8..444540f 100644 --- a/include/engine/modules/test1/test1.hpp +++ b/include/engine/modules/test1/test1.hpp @@ -2,9 +2,11 @@ #define __MSC_TEST_1_MODULE_HPP__ #include "engine/core/engine_module.hpp" +#include "engine/core/engine_module_api.hpp" #include -class Test1Module : public MscEngineModule { +class Test1Module : public MscEngineModule +{ public: ~Test1Module() = default; @@ -13,7 +15,7 @@ public: const std::unordered_map & getDependencies() const override; - void onInitialize() override; + void onInitialize(IMscEngineModuleAPI &engine) override; }; #endif // __MSC_TEST_1_MODULE_HPP__ diff --git a/include/engine/utils/rev_range.hpp b/include/engine/utils/rev_range.hpp index 8167f56..736ef09 100644 --- a/include/engine/utils/rev_range.hpp +++ b/include/engine/utils/rev_range.hpp @@ -2,12 +2,14 @@ #define __MSC_REV_RANGE_HPP__ #include -struct MscRevisionRange { +struct MscRevisionRange +{ uint16_t minRevision; uint16_t maxRevision; /// Check provided revision to be in range - bool isSatisfied(uint16_t rev) const { + bool isSatisfied(uint16_t rev) const + { return rev >= this->minRevision && rev <= this->maxRevision; } }; diff --git a/src/engine/core/engine.cpp b/src/engine/core/engine.cpp index 53ddee4..76765b7 100644 --- a/src/engine/core/engine.cpp +++ b/src/engine/core/engine.cpp @@ -8,33 +8,40 @@ uint16_t MscEngine::REV = 0; -MscEngine::MscEngine(std::vector> modules) { +MscEngine::MscEngine(std::vector> modules) +{ std::unordered_map registry; - for (const auto &module : modules) { + for (const auto &module : modules) + { if (!module) continue; auto [it, inserted] = registry.try_emplace(module->getName(), module->getRev()); - if (!inserted) { + if (!inserted) + { throw std::runtime_error("Duplicate module detected: " + module->getName()); } } - for (const auto &module : modules) { + for (const auto &module : modules) + { if (!module) continue; - for (const auto &[dep_name, range] : module->getDependencies()) { + for (const auto &[dep_name, range] : module->getDependencies()) + { auto it = registry.find(dep_name); - if (it == registry.end()) { + if (it == registry.end()) + { throw std::runtime_error("Module " + module->getName() + " requires missing module: " + dep_name); } int current_rev = it->second; - if (!range.isSatisfied(current_rev)) { + if (!range.isSatisfied(current_rev)) + { throw std::runtime_error( "Module " + module->getName() + " requires " + dep_name + " in range [" + std::to_string(range.minRevision) + " <-> " + @@ -49,6 +56,7 @@ MscEngine::MscEngine(std::vector> modules) { UNIMPL MscEngine::getBus() const { throw std::runtime_error("Unimplemented"); } -UNIMPL MscEngine::getLoadedModules() const { +UNIMPL MscEngine::getLoadedModules() const +{ throw std::runtime_error("Unimplemented"); } diff --git a/src/engine/modules/test1/test1.hpp b/src/engine/modules/test1/test1.hpp index e071cf3..cac5322 100644 --- a/src/engine/modules/test1/test1.hpp +++ b/src/engine/modules/test1/test1.hpp @@ -1,4 +1,5 @@ #include "engine/modules/test1/test1.hpp" +#include "engine/core/engine_module_api.hpp" #include "engine/utils/rev_range.hpp" #include #include @@ -9,9 +10,13 @@ std::string Test1Module::getName() const { return "test1"; } uint16_t Test1Module::getRev() const { return 1; } const std::unordered_map & -Test1Module::getDependencies() const { +Test1Module::getDependencies() const +{ static const std::unordered_map deps{}; return deps; } -void Test1Module::onInitialize() {} +void Test1Module::onInitialize(IMscEngineModuleAPI &engine) +{ + (void)(engine); +} diff --git a/src/main.cpp b/src/main.cpp index 3156943..d4a84cc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -5,7 +5,8 @@ #include #include -int main(int argc, char **argv) { +int main(int argc, char **argv) +{ auto engine = std::make_unique(std::vector>{ std::make_shared()}); From 8ea4bf45ba94cf32c33717a2d61b49b7366825fb Mon Sep 17 00:00:00 2001 From: Samsonium Date: Mon, 28 Sep 2026 16:48:52 +0300 Subject: [PATCH 2/9] feat: implement event bus --- include/engine/core/engine_bus.hpp | 73 +++++++++++++++++++++++++++ src/engine/core/event_bus.cpp | 80 ++++++++++++++++++++++++++++++ 2 files changed, 153 insertions(+) create mode 100644 include/engine/core/engine_bus.hpp create mode 100644 src/engine/core/event_bus.cpp diff --git a/include/engine/core/engine_bus.hpp b/include/engine/core/engine_bus.hpp new file mode 100644 index 0000000..2818789 --- /dev/null +++ b/include/engine/core/engine_bus.hpp @@ -0,0 +1,73 @@ +#ifndef __MSC_ENGINE_BUS_HPP__ +#define __MSC_ENGINE_BUS_HPP__ + +#include +#include +#include +#include +#include +#include + +/// @brief Event base class +class MscEvent +{ +public: + virtual ~MscEvent() = default; + + /// @brief Get event name + virtual std::string getName() const = 0; + + /// @brief Is current event is canceled + virtual bool isCanceled() const = 0; + + /// @brief Cancel current event + virtual void cancel() = 0; +}; + +/// @brief Engine event bus +class MscEventBus +{ +public: + /// @brief Event handler alias + using Listener = std::function; + + /// @brief Subscription identifier alias + using SubscriptionId = std::uint64_t; + + /// @brief Emit event + /// @param event event context + void publish(MscEvent &event); + + /// @brief Subscribe to specified event + /// @param name event name + /// @param listener event handler + /// @returns subscription identifier to make it possitble to remove listener + SubscriptionId subscribe(const std::string &name, Listener listener); + + /// @brief Subscribe to specified event and execute once on publish + /// @param name event name + /// @param listener event handler + void subscribeOnce(const std::string &name, Listener listener); + + /// @brief Unsubscribe listener by its own id + /// @param id subscription id + /// @return `true` if listener has been deleted, otherwise `false` + bool unsubscribe(SubscriptionId id); + +private: + using ListenerWithId = struct + { + SubscriptionId id; + Listener listener; + bool once; + }; + + std::unordered_map> _listeners; + std::unordered_map _listenersPaths; + std::uint64_t _lastId = 0; + std::mutex _listenersMutex; + + SubscriptionId _subscribe(const std::string &name, Listener listener, bool once); +}; + +#endif // __MSC_ENGINE_BUS_HPP__ diff --git a/src/engine/core/event_bus.cpp b/src/engine/core/event_bus.cpp new file mode 100644 index 0000000..7d6420a --- /dev/null +++ b/src/engine/core/event_bus.cpp @@ -0,0 +1,80 @@ +#include +#include +#include + +void MscEventBus::publish(MscEvent &event) +{ + std::vector listeners; + + { + std::lock_guard lock(_listenersMutex); + auto it = _listeners.find(event.getName()); + if (it == _listeners.end()) + return; + + for (const auto &entry : it->second) + listeners.push_back(entry); + } + + for (const auto &entry : listeners) + { + if (event.isCanceled()) + break; + + entry.listener(event); + if (entry.once) + unsubscribe(entry.id); + } +} + +MscEventBus::SubscriptionId MscEventBus::_subscribe(const std::string &name, MscEventBus::Listener listener, bool once) +{ + std::lock_guard lock(_listenersMutex); + + const SubscriptionId id = ++_lastId; + auto listenerWithId = ListenerWithId{ + .id = id, + .listener = std::move(listener), + .once = once}; + + _listeners[name].push_back(std::move(listenerWithId)); + _listenersPaths[id] = name; + + return id; +} + +MscEventBus::SubscriptionId MscEventBus::subscribe(const std::string &name, Listener listener) +{ + return _subscribe(name, std::move(listener), false); +} + +void MscEventBus::subscribeOnce(const std::string &name, Listener listener) +{ + _subscribe(name, std::move(listener), true); +} + +bool MscEventBus::unsubscribe(SubscriptionId id) +{ + std::lock_guard lock(_listenersMutex); + + auto it = _listenersPaths.find(id); + if (it == _listenersPaths.end()) + return false; + + auto &listeners = _listeners.at(it->second); + for (std::size_t i = 0; i < listeners.size(); i++) + { + if (listeners.at(i).id != id) + continue; + + listeners.erase(listeners.begin() + i); + _listenersPaths.erase(id); + + if (listeners.empty()) + _listeners.erase(it->second); + + return true; + } + + return false; +} \ No newline at end of file From 1978a24d1edd55e7092553c553e26a7ede3e78bb Mon Sep 17 00:00:00 2001 From: Samsonium Date: Mon, 28 Sep 2026 17:33:03 +0300 Subject: [PATCH 3/9] upd: change event bus - Make `.subscribeOnce` return listener id - Make `.cancel` and `.isCanceled` methods in `MscEvent` implemented --- include/engine/core/engine_bus.hpp | 15 ++++++++++++--- src/engine/core/event_bus.cpp | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/include/engine/core/engine_bus.hpp b/include/engine/core/engine_bus.hpp index 2818789..e3820bf 100644 --- a/include/engine/core/engine_bus.hpp +++ b/include/engine/core/engine_bus.hpp @@ -18,10 +18,19 @@ public: virtual std::string getName() const = 0; /// @brief Is current event is canceled - virtual bool isCanceled() const = 0; + bool isCanceled() const + { + return canceled; + } /// @brief Cancel current event - virtual void cancel() = 0; + void cancel() + { + canceled = true; + } + +private: + bool canceled = false; }; /// @brief Engine event bus @@ -47,7 +56,7 @@ public: /// @brief Subscribe to specified event and execute once on publish /// @param name event name /// @param listener event handler - void subscribeOnce(const std::string &name, Listener listener); + SubscriptionId subscribeOnce(const std::string &name, Listener listener); /// @brief Unsubscribe listener by its own id /// @param id subscription id diff --git a/src/engine/core/event_bus.cpp b/src/engine/core/event_bus.cpp index 7d6420a..a7a8a52 100644 --- a/src/engine/core/event_bus.cpp +++ b/src/engine/core/event_bus.cpp @@ -48,9 +48,9 @@ MscEventBus::SubscriptionId MscEventBus::subscribe(const std::string &name, List return _subscribe(name, std::move(listener), false); } -void MscEventBus::subscribeOnce(const std::string &name, Listener listener) +MscEventBus::SubscriptionId MscEventBus::subscribeOnce(const std::string &name, Listener listener) { - _subscribe(name, std::move(listener), true); + return _subscribe(name, std::move(listener), true); } bool MscEventBus::unsubscribe(SubscriptionId id) From df7a8855d4c6b72dc25ea9b88df6981dac061908 Mon Sep 17 00:00:00 2001 From: Samsonium Date: Mon, 28 Sep 2026 17:33:17 +0300 Subject: [PATCH 4/9] feat: add event bus tests --- .gitignore | 3 +- CMakeLists.txt | 14 ++++++ tests/engine/event_bus_test.cpp | 76 +++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 tests/engine/event_bus_test.cpp diff --git a/.gitignore b/.gitignore index b582844..0c6ec0e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,8 +4,9 @@ !/docs !/include !/src +!/tests # Include files !/.gitignore !/CMakeLists.txt - +!/Makefile diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a530e6..4f153ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,3 +10,17 @@ add_executable(magic_scrolls ${sources}) target_include_directories(magic_scrolls PUBLIC "${CMAKE_SOURCE_DIR}/include") +# ------- +enable_testing() + +add_executable(event_bus_test + tests/engine/event_bus_test.cpp + src/engine/core/event_bus.cpp +) + +target_include_directories(event_bus_test PUBLIC "${CMAKE_SOURCE_DIR}/include") + +add_test( + NAME "Event Bus" + COMMAND event_bus_test +) diff --git a/tests/engine/event_bus_test.cpp b/tests/engine/event_bus_test.cpp new file mode 100644 index 0000000..ec0fec0 --- /dev/null +++ b/tests/engine/event_bus_test.cpp @@ -0,0 +1,76 @@ +#include +#include + +const std::string testEventName = "TestEvent"; +int calls = 0; + +class TestEvent : public MscEvent +{ +public: + std::string getName() const override + { + return testEventName; + } +}; + +/// @brief Проверка на работу pub-sub +void testEventPubSub() +{ + calls = 0; + MscEventBus bus; + TestEvent event; + + bus.subscribe(testEventName, [&](MscEvent &ev) + { + calls++; + + auto *testEvent = dynamic_cast(&ev); + assert(testEvent != nullptr); }); + + bus.publish(event); + assert(calls == 1); +} + +/// @brief Проверка на работу +void testEventOnce() +{ + calls = 0; + MscEventBus bus; + TestEvent event; + + bus.subscribeOnce(testEventName, [&](MscEvent &) + { calls++; }); + + bus.publish(event); + bus.publish(event); + assert(calls == 1); +} + +/// @brief Проверка работы отписки +void testUnsubscribe() +{ + calls = 0; + MscEventBus bus; + TestEvent event; + + auto id = bus.subscribe(testEventName, [&](MscEvent &) + { calls++; }); + + bus.publish(event); + + assert(bus.unsubscribe(id) == true); + assert(bus.unsubscribe(id) == false); + + bus.publish(event); + + assert(calls == 1); +} + +int main() +{ + testEventPubSub(); + testEventOnce(); + testUnsubscribe(); + + return 0; +} From 045fe533d2c1808f0ed0d449daa112ff304aa4b4 Mon Sep 17 00:00:00 2001 From: Samsonium Date: Mon, 28 Sep 2026 17:33:36 +0300 Subject: [PATCH 5/9] feat: add root makefile --- Makefile | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..343456b --- /dev/null +++ b/Makefile @@ -0,0 +1,8 @@ +.PHONY: build test + +build: + cmake -B build + cmake --build build + +test: + ctest --test-dir build --output-on-failure --progress From ff0453e81dc377a8c73775d41487df7a4e6a786d Mon Sep 17 00:00:00 2001 From: Samsonium Date: Tue, 29 Sep 2026 13:10:54 +0300 Subject: [PATCH 6/9] feat: add event bus to engine public API --- include/engine/core/engine.hpp | 3 ++- include/engine/core/engine_module_api.hpp | 4 +++- src/engine/core/engine.cpp | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/include/engine/core/engine.hpp b/include/engine/core/engine.hpp index 971d43c..d171899 100644 --- a/include/engine/core/engine.hpp +++ b/include/engine/core/engine.hpp @@ -13,6 +13,7 @@ class MscEngine : public IMscEngineModuleAPI private: /// Loaded modules list std::vector> _modules; + MscEventBus _bus; public: /// Actual version @@ -20,7 +21,7 @@ public: MscEngine(std::vector> modules); - UNIMPL getBus() const override; + MscEventBus &getBus() override; UNIMPL getLoadedModules() const override; }; diff --git a/include/engine/core/engine_module_api.hpp b/include/engine/core/engine_module_api.hpp index 10137b2..d75b8ee 100644 --- a/include/engine/core/engine_module_api.hpp +++ b/include/engine/core/engine_module_api.hpp @@ -1,6 +1,8 @@ #ifndef __MSC_ENGINE_MODULE_API_HPP__ #define __MSC_ENGINE_MODULE_API_HPP__ +#include + typedef void UNIMPL; class IMscEngineModuleAPI @@ -9,7 +11,7 @@ public: virtual ~IMscEngineModuleAPI() = default; /// Get access to engine event bus - virtual UNIMPL getBus() const = 0; + virtual MscEventBus &getBus() = 0; /// Get access to loaded module list virtual UNIMPL getLoadedModules() const = 0; diff --git a/src/engine/core/engine.cpp b/src/engine/core/engine.cpp index 76765b7..ca9b170 100644 --- a/src/engine/core/engine.cpp +++ b/src/engine/core/engine.cpp @@ -54,7 +54,10 @@ MscEngine::MscEngine(std::vector> modules) _modules = std::move(modules); } -UNIMPL MscEngine::getBus() const { throw std::runtime_error("Unimplemented"); } +MscEventBus &MscEngine::getBus() +{ + return _bus; +} UNIMPL MscEngine::getLoadedModules() const { From e756bb1d7387433f0b644caafe513c175abce58b Mon Sep 17 00:00:00 2001 From: Samsonium Date: Tue, 29 Sep 2026 13:25:49 +0300 Subject: [PATCH 7/9] feat: implement module access in engine API - Add implementation for `getModule` - Add implementation for `getLoadedModules` --- include/engine/core/engine.hpp | 3 ++- include/engine/core/engine_module_api.hpp | 11 +++++++--- src/engine/core/engine.cpp | 25 +++++++++++++++++++++-- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/include/engine/core/engine.hpp b/include/engine/core/engine.hpp index d171899..bba9fae 100644 --- a/include/engine/core/engine.hpp +++ b/include/engine/core/engine.hpp @@ -22,7 +22,8 @@ public: MscEngine(std::vector> modules); MscEventBus &getBus() override; - UNIMPL getLoadedModules() const override; + std::unordered_map getLoadedModules() const override; + MscEngineModule &getModule(const std::string &name) override; }; #endif // __MCS_ENGINE_HPP__ diff --git a/include/engine/core/engine_module_api.hpp b/include/engine/core/engine_module_api.hpp index d75b8ee..f7adc7c 100644 --- a/include/engine/core/engine_module_api.hpp +++ b/include/engine/core/engine_module_api.hpp @@ -2,8 +2,10 @@ #define __MSC_ENGINE_MODULE_API_HPP__ #include - -typedef void UNIMPL; +#include +#include +#include +#include class IMscEngineModuleAPI { @@ -14,7 +16,10 @@ public: virtual MscEventBus &getBus() = 0; /// Get access to loaded module list - virtual UNIMPL getLoadedModules() const = 0; + virtual std::unordered_map getLoadedModules() const = 0; + + /// Access module by its name + virtual MscEngineModule &getModule(const std::string &name) = 0; }; #endif // __MSC_ENGINE_MODULE_API_HPP__ diff --git a/src/engine/core/engine.cpp b/src/engine/core/engine.cpp index ca9b170..011e8b4 100644 --- a/src/engine/core/engine.cpp +++ b/src/engine/core/engine.cpp @@ -59,7 +59,28 @@ MscEventBus &MscEngine::getBus() return _bus; } -UNIMPL MscEngine::getLoadedModules() const +std::unordered_map MscEngine::getLoadedModules() const { - throw std::runtime_error("Unimplemented"); + std::unordered_map result; + for (const auto &module : _modules) + { + if (!module) + continue; + + auto mod = module.get(); + result[mod->getName()] = mod->getRev(); + } + + return result; +} + +MscEngineModule &MscEngine::getModule(const std::string &name) +{ + for (auto &module : _modules) + { + if (module.get()->getName() == name) + return *module.get(); + } + + throw std::runtime_error("Module not found: " + name); } From 62f844a5826b232c77aecdbbb8c5974874c063ce Mon Sep 17 00:00:00 2001 From: Samsonium Date: Tue, 29 Sep 2026 14:46:44 +0300 Subject: [PATCH 8/9] feat: implement db provider --- CMakeLists.txt | 16 ++++++ include/engine/core/engine.hpp | 7 ++- include/engine/core/engine_db_provider.hpp | 30 +++++++++++ include/engine/core/engine_module_api.hpp | 17 ++++-- src/engine/core/engine.cpp | 10 ++++ src/engine/core/engine_db_provider.cpp | 45 ++++++++++++++++ tests/engine/db_provider_test.cpp | 62 ++++++++++++++++++++++ 7 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 include/engine/core/engine_db_provider.hpp create mode 100644 src/engine/core/engine_db_provider.cpp create mode 100644 tests/engine/db_provider_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4f153ec..1252706 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,12 +3,15 @@ project(magic_scrolls) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) +find_package(SQLite3 REQUIRED) + file(GLOB_RECURSE sources "${CMAKE_SOURCE_DIR}/src/*.cpp") add_executable(magic_scrolls ${sources}) target_include_directories(magic_scrolls PUBLIC "${CMAKE_SOURCE_DIR}/include") +target_link_libraries(magic_scrolls PRIVATE SQLite::SQLite3) # ------- enable_testing() @@ -17,10 +20,23 @@ add_executable(event_bus_test tests/engine/event_bus_test.cpp src/engine/core/event_bus.cpp ) +add_executable(db_provider_test + tests/engine/db_provider_test.cpp + src/engine/core/engine.cpp + src/engine/core/engine_db_provider.cpp +) target_include_directories(event_bus_test PUBLIC "${CMAKE_SOURCE_DIR}/include") +target_link_libraries(event_bus_test PRIVATE SQLite::SQLite3) + +target_include_directories(db_provider_test PUBLIC "${CMAKE_SOURCE_DIR}/include") +target_link_libraries(db_provider_test PRIVATE SQLite::SQLite3) add_test( NAME "Event Bus" COMMAND event_bus_test ) +add_test( + NAME "Database Provider" + COMMAND db_provider_test +) diff --git a/include/engine/core/engine.hpp b/include/engine/core/engine.hpp index bba9fae..e6b0284 100644 --- a/include/engine/core/engine.hpp +++ b/include/engine/core/engine.hpp @@ -1,8 +1,9 @@ #ifndef __MCS_ENGINE_HPP__ #define __MCS_ENGINE_HPP__ -#include "engine/core/engine_module.hpp" -#include "engine/core/engine_module_api.hpp" +#include +#include +#include #include #include #include @@ -24,6 +25,8 @@ public: MscEventBus &getBus() override; std::unordered_map getLoadedModules() const override; MscEngineModule &getModule(const std::string &name) override; + std::unique_ptr createDBProvider() override; + std::unique_ptr createDBProvider(const std::string &filepath) override; }; #endif // __MCS_ENGINE_HPP__ diff --git a/include/engine/core/engine_db_provider.hpp b/include/engine/core/engine_db_provider.hpp new file mode 100644 index 0000000..a3a936d --- /dev/null +++ b/include/engine/core/engine_db_provider.hpp @@ -0,0 +1,30 @@ +#ifndef __MSC_ENGINE_DB_PROVIDER_HPP__ +#define __MSC_ENGINE_DB_PROVIDER_HPP__ + +#include +#include + +/// @brief Engine database provider +class MscEngineDBProvider +{ +public: + ~MscEngineDBProvider(); + + /// @brief Create inmemory database + MscEngineDBProvider(); + + /// @brief Create file database + explicit MscEngineDBProvider(const std::string &filepath); + + /// @brief Execute SQL statement + /// @param sql statement + void execute(const std::string &sql); + + MscEngineDBProvider(const MscEngineDBProvider &) = delete; + MscEngineDBProvider &operator=(const MscEngineDBProvider &) = delete; + +private: + sqlite3 *_db = nullptr; +}; + +#endif // __MSC_ENGINE_DB_PROVIDER_HPP__ diff --git a/include/engine/core/engine_module_api.hpp b/include/engine/core/engine_module_api.hpp index f7adc7c..616bd9b 100644 --- a/include/engine/core/engine_module_api.hpp +++ b/include/engine/core/engine_module_api.hpp @@ -2,24 +2,33 @@ #define __MSC_ENGINE_MODULE_API_HPP__ #include -#include +#include #include #include #include +#include + +class MscEngineModule; class IMscEngineModuleAPI { public: virtual ~IMscEngineModuleAPI() = default; - /// Get access to engine event bus + /// @brief Get access to engine event bus virtual MscEventBus &getBus() = 0; - /// Get access to loaded module list + /// @brief Get access to loaded module list virtual std::unordered_map getLoadedModules() const = 0; - /// Access module by its name + /// @brief Access module by its name virtual MscEngineModule &getModule(const std::string &name) = 0; + + /// @brief Create database provider with in-memory database + virtual std::unique_ptr createDBProvider() = 0; + + /// @brief Create database provider with file database + virtual std::unique_ptr createDBProvider(const std::string &filepath) = 0; }; #endif // __MSC_ENGINE_MODULE_API_HPP__ diff --git a/src/engine/core/engine.cpp b/src/engine/core/engine.cpp index 011e8b4..95f8ff6 100644 --- a/src/engine/core/engine.cpp +++ b/src/engine/core/engine.cpp @@ -84,3 +84,13 @@ MscEngineModule &MscEngine::getModule(const std::string &name) throw std::runtime_error("Module not found: " + name); } + +std::unique_ptr MscEngine::createDBProvider() +{ + return std::make_unique(); +} + +std::unique_ptr MscEngine::createDBProvider(const std::string &filepath) +{ + return std::make_unique(filepath); +} diff --git a/src/engine/core/engine_db_provider.cpp b/src/engine/core/engine_db_provider.cpp new file mode 100644 index 0000000..e95d292 --- /dev/null +++ b/src/engine/core/engine_db_provider.cpp @@ -0,0 +1,45 @@ +#include +#include + +MscEngineDBProvider::MscEngineDBProvider() : MscEngineDBProvider(":memory:") +{ +} + +MscEngineDBProvider::MscEngineDBProvider(const std::string &filepath) +{ + const int result = sqlite3_open(filepath.c_str(), &_db); + if (result != SQLITE_OK) + { + const std::string message = _db != nullptr + ? sqlite3_errmsg(_db) + : "unknown SQLite error"; + + if (_db != nullptr) + { + sqlite3_close(_db); + _db = nullptr; + } + + throw std::runtime_error("Failed to open SQLite database: " + message); + } +} + +MscEngineDBProvider::~MscEngineDBProvider() +{ + if (_db != nullptr) + sqlite3_close(_db); +} + +void MscEngineDBProvider::execute(const std::string &sql) +{ + char *errorMessage = nullptr; + const int result = sqlite3_exec(_db, sql.c_str(), nullptr, nullptr, &errorMessage); + + if (result == SQLITE_OK) + return; + + std::string message(errorMessage); + sqlite3_free(errorMessage); + + throw std::runtime_error("Failed to execute SQL: " + message); +} diff --git a/tests/engine/db_provider_test.cpp b/tests/engine/db_provider_test.cpp new file mode 100644 index 0000000..c085da1 --- /dev/null +++ b/tests/engine/db_provider_test.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include + +/// @brief Проверка на создание экземпляра +void testProviderCreation() +{ + MscEngine engine({}); + + auto dbp1 = engine.createDBProvider(); + assert(dbp1.get() != nullptr); +} + +/// @brief Проверка на создание экземпляра с некорректным путём файла +void testIncorrectFilePath() +{ + MscEngine engine({}); + + try + { + engine.createDBProvider("/i/am/an/incorrect/path/to/file.txt"); + assert(false); + } + catch (const std::runtime_error &error) + { + } +} + +/// @brief Проверка на успешное выполнение корректного выражения +void testValidStatement() +{ + MscEngine engine({}); + auto dbp = engine.createDBProvider(); + dbp.get()->execute("SELECT 'hello, world' AS message;"); +} + +void testInvalidStatement() +{ + MscEngine engine({}); + auto dbp = engine.createDBProvider(); + + try + { + dbp.get()->execute("SELECT * FROM nonexistentdatabase ABC"); + assert(false); + } + catch (const std::runtime_error &error) + { + } +} + +int main() +{ + testProviderCreation(); + testIncorrectFilePath(); + testValidStatement(); + testInvalidStatement(); + + return 0; +} From 160e932f2e8b8fa4d89d50a44d501492b82fdb54 Mon Sep 17 00:00:00 2001 From: Samsonium Date: Tue, 29 Sep 2026 15:19:18 +0300 Subject: [PATCH 9/9] feat: add engine test --- CMakeLists.txt | 12 +++ tests/engine/engine_test.cpp | 149 +++++++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+) create mode 100644 tests/engine/engine_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 1252706..86dffa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,11 @@ add_executable(db_provider_test src/engine/core/engine.cpp src/engine/core/engine_db_provider.cpp ) +add_executable(engine_test + tests/engine/engine_test.cpp + src/engine/core/engine.cpp + src/engine/core/engine_db_provider.cpp +) target_include_directories(event_bus_test PUBLIC "${CMAKE_SOURCE_DIR}/include") target_link_libraries(event_bus_test PRIVATE SQLite::SQLite3) @@ -32,6 +37,9 @@ target_link_libraries(event_bus_test PRIVATE SQLite::SQLite3) target_include_directories(db_provider_test PUBLIC "${CMAKE_SOURCE_DIR}/include") target_link_libraries(db_provider_test PRIVATE SQLite::SQLite3) +target_include_directories(engine_test PUBLIC "${CMAKE_SOURCE_DIR}/include") +target_link_libraries(engine_test PRIVATE SQLite::SQLite3) + add_test( NAME "Event Bus" COMMAND event_bus_test @@ -40,3 +48,7 @@ add_test( NAME "Database Provider" COMMAND db_provider_test ) +add_test( + NAME "Engine" + COMMAND engine_test +) diff --git a/tests/engine/engine_test.cpp b/tests/engine/engine_test.cpp new file mode 100644 index 0000000..8aadfc0 --- /dev/null +++ b/tests/engine/engine_test.cpp @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class FirstTestModuleR1 : public MscEngineModule +{ +public: + std::string getName() const override + { + return "first_tmod"; + }; + + std::uint16_t getRev() const override + { + return 1; + } + + const std::unordered_map &getDependencies() const override + { + static const std::unordered_map deps{}; + return deps; + } + + void onInitialize(IMscEngineModuleAPI &engine) override {} +}; + +class FirstTestModuleR2 : public MscEngineModule +{ +public: + std::string getName() const override + { + return "first_tmod"; + }; + + std::uint16_t getRev() const override + { + return 2; + } + + const std::unordered_map &getDependencies() const override + { + static const std::unordered_map deps{}; + return deps; + } + + void onInitialize(IMscEngineModuleAPI &engine) override {} +}; + +class SecondTestModuleE1 : public MscEngineModule +{ +public: + std::string getName() const override + { + return "second_tmod"; + } + + std::uint16_t getRev() const override + { + return 1; + } + + const std::unordered_map &getDependencies() const override + { + static const std::unordered_map deps{ + {"first_tmod", MscRevisionRange{1, 1}}}; + return deps; + } + + void onInitialize(IMscEngineModuleAPI &engine) override {} +}; + +class SecondTestModuleE2 : public MscEngineModule +{ +public: + std::string getName() const override + { + return "second_tmod"; + } + + std::uint16_t getRev() const override + { + return 1; + } + + const std::unordered_map &getDependencies() const override + { + static const std::unordered_map deps{ + {"first_tmod", MscRevisionRange{1, 2}}}; + return deps; + } + + void onInitialize(IMscEngineModuleAPI &engine) override {} +}; + +void testSatisfiedModules() +{ + MscEngine engine({std::make_shared(), + std::make_shared()}); +} + +void testDuplicatedModules() +{ + try + { + MscEngine engine({ + std::make_shared(), + std::make_shared(), + }); + assert(false); + } + catch (const std::runtime_error &error) + { + } +} + +void testUnsatisfiedModules() +{ + try + { + MscEngine engine({std::make_shared(), + std::make_shared()}); + assert(false); + } + catch (const std::runtime_error &error) + { + } +} + +void testSatisfiedMultiversionModule() +{ + MscEngine engine({std::make_shared(), + std::make_shared()}); +} + +int main() +{ + testSatisfiedModules(); + testDuplicatedModules(); + testUnsatisfiedModules(); + testSatisfiedMultiversionModule(); + + return 0; +}