8 Commits
Author SHA1 Message Date
Dmitriy a6676997d4 Merge pull request 'Base implementation' (#1) from base-impl into master
Reviewed-on: #1
2026-09-29 14:25:03 +00:00
Samsonium 160e932f2e feat: add engine test 2026-09-29 15:19:18 +03:00
Samsonium 62f844a582 feat: implement db provider 2026-09-29 14:46:44 +03:00
Samsonium e756bb1d73 feat: implement module access in engine API
- Add implementation for `getModule`
- Add implementation for `getLoadedModules`
2026-09-29 13:25:49 +03:00
Samsonium ff0453e81d feat: add event bus to engine public API 2026-09-29 13:10:54 +03:00
Samsonium 045fe533d2 feat: add root makefile 2026-09-28 17:33:36 +03:00
Samsonium df7a8855d4 feat: add event bus tests 2026-09-28 17:33:17 +03:00
Samsonium 1978a24d1e upd: change event bus
- Make `.subscribeOnce` return listener id
- Make `.cancel` and `.isCanceled` methods in `MscEvent` implemented
2026-09-28 17:33:03 +03:00
13 changed files with 496 additions and 19 deletions
+2 -1
View File
@@ -4,8 +4,9 @@
!/docs !/docs
!/include !/include
!/src !/src
!/tests
# Include files # Include files
!/.gitignore !/.gitignore
!/CMakeLists.txt !/CMakeLists.txt
!/Makefile
+42
View File
@@ -3,10 +3,52 @@ project(magic_scrolls)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(SQLite3 REQUIRED)
file(GLOB_RECURSE sources file(GLOB_RECURSE sources
"${CMAKE_SOURCE_DIR}/src/*.cpp") "${CMAKE_SOURCE_DIR}/src/*.cpp")
add_executable(magic_scrolls ${sources}) add_executable(magic_scrolls ${sources})
target_include_directories(magic_scrolls PUBLIC "${CMAKE_SOURCE_DIR}/include") target_include_directories(magic_scrolls PUBLIC "${CMAKE_SOURCE_DIR}/include")
target_link_libraries(magic_scrolls PRIVATE SQLite::SQLite3)
# -------
enable_testing()
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
)
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)
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
)
add_test(
NAME "Database Provider"
COMMAND db_provider_test
)
add_test(
NAME "Engine"
COMMAND engine_test
)
+8
View File
@@ -0,0 +1,8 @@
.PHONY: build test
build:
cmake -B build
cmake --build build
test:
ctest --test-dir build --output-on-failure --progress
+9 -4
View File
@@ -1,8 +1,9 @@
#ifndef __MCS_ENGINE_HPP__ #ifndef __MCS_ENGINE_HPP__
#define __MCS_ENGINE_HPP__ #define __MCS_ENGINE_HPP__
#include "engine/core/engine_module.hpp" #include <engine/core/engine_module.hpp>
#include "engine/core/engine_module_api.hpp" #include <engine/core/engine_module_api.hpp>
#include <engine/core/engine_db_provider.hpp>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <vector> #include <vector>
@@ -13,6 +14,7 @@ class MscEngine : public IMscEngineModuleAPI
private: private:
/// Loaded modules list /// Loaded modules list
std::vector<std::shared_ptr<MscEngineModule>> _modules; std::vector<std::shared_ptr<MscEngineModule>> _modules;
MscEventBus _bus;
public: public:
/// Actual version /// Actual version
@@ -20,8 +22,11 @@ public:
MscEngine(std::vector<std::shared_ptr<MscEngineModule>> modules); MscEngine(std::vector<std::shared_ptr<MscEngineModule>> modules);
UNIMPL getBus() const override; MscEventBus &getBus() override;
UNIMPL getLoadedModules() const override; std::unordered_map<std::string, std::uint16_t> getLoadedModules() const override;
MscEngineModule &getModule(const std::string &name) override;
std::unique_ptr<MscEngineDBProvider> createDBProvider() override;
std::unique_ptr<MscEngineDBProvider> createDBProvider(const std::string &filepath) override;
}; };
#endif // __MCS_ENGINE_HPP__ #endif // __MCS_ENGINE_HPP__
+12 -3
View File
@@ -18,10 +18,19 @@ public:
virtual std::string getName() const = 0; virtual std::string getName() const = 0;
/// @brief Is current event is canceled /// @brief Is current event is canceled
virtual bool isCanceled() const = 0; bool isCanceled() const
{
return canceled;
}
/// @brief Cancel current event /// @brief Cancel current event
virtual void cancel() = 0; void cancel()
{
canceled = true;
}
private:
bool canceled = false;
}; };
/// @brief Engine event bus /// @brief Engine event bus
@@ -47,7 +56,7 @@ public:
/// @brief Subscribe to specified event and execute once on publish /// @brief Subscribe to specified event and execute once on publish
/// @param name event name /// @param name event name
/// @param listener event handler /// @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 /// @brief Unsubscribe listener by its own id
/// @param id subscription id /// @param id subscription id
@@ -0,0 +1,30 @@
#ifndef __MSC_ENGINE_DB_PROVIDER_HPP__
#define __MSC_ENGINE_DB_PROVIDER_HPP__
#include <sqlite3.h>
#include <string>
/// @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__
+21 -5
View File
@@ -1,18 +1,34 @@
#ifndef __MSC_ENGINE_MODULE_API_HPP__ #ifndef __MSC_ENGINE_MODULE_API_HPP__
#define __MSC_ENGINE_MODULE_API_HPP__ #define __MSC_ENGINE_MODULE_API_HPP__
typedef void UNIMPL; #include <engine/core/engine_bus.hpp>
#include <engine/core/engine_db_provider.hpp>
#include <cstdint>
#include <string>
#include <unordered_map>
#include <memory>
class MscEngineModule;
class IMscEngineModuleAPI class IMscEngineModuleAPI
{ {
public: public:
virtual ~IMscEngineModuleAPI() = default; virtual ~IMscEngineModuleAPI() = default;
/// Get access to engine event bus /// @brief Get access to engine event bus
virtual UNIMPL getBus() const = 0; virtual MscEventBus &getBus() = 0;
/// Get access to loaded module list /// @brief Get access to loaded module list
virtual UNIMPL getLoadedModules() const = 0; virtual std::unordered_map<std::string, std::uint16_t> getLoadedModules() const = 0;
/// @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<MscEngineDBProvider> createDBProvider() = 0;
/// @brief Create database provider with file database
virtual std::unique_ptr<MscEngineDBProvider> createDBProvider(const std::string &filepath) = 0;
}; };
#endif // __MSC_ENGINE_MODULE_API_HPP__ #endif // __MSC_ENGINE_MODULE_API_HPP__
+38 -4
View File
@@ -54,9 +54,43 @@ MscEngine::MscEngine(std::vector<std::shared_ptr<MscEngineModule>> modules)
_modules = std::move(modules); _modules = std::move(modules);
} }
UNIMPL MscEngine::getBus() const { throw std::runtime_error("Unimplemented"); } MscEventBus &MscEngine::getBus()
UNIMPL MscEngine::getLoadedModules() const
{ {
throw std::runtime_error("Unimplemented"); return _bus;
}
std::unordered_map<std::string, std::uint16_t> MscEngine::getLoadedModules() const
{
std::unordered_map<std::string, std::uint16_t> 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);
}
std::unique_ptr<MscEngineDBProvider> MscEngine::createDBProvider()
{
return std::make_unique<MscEngineDBProvider>();
}
std::unique_ptr<MscEngineDBProvider> MscEngine::createDBProvider(const std::string &filepath)
{
return std::make_unique<MscEngineDBProvider>(filepath);
} }
+45
View File
@@ -0,0 +1,45 @@
#include <engine/core/engine_db_provider.hpp>
#include <stdexcept>
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);
}
+2 -2
View File
@@ -48,9 +48,9 @@ MscEventBus::SubscriptionId MscEventBus::subscribe(const std::string &name, List
return _subscribe(name, std::move(listener), false); 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) bool MscEventBus::unsubscribe(SubscriptionId id)
+62
View File
@@ -0,0 +1,62 @@
#include <cassert>
#include <vector>
#include <stdexcept>
#include <engine/core/engine.hpp>
#include <engine/core/engine_db_provider.hpp>
/// @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;
}
+149
View File
@@ -0,0 +1,149 @@
#include <cassert>
#include <string>
#include <cstdint>
#include <memory>
#include <stdexcept>
#include <unordered_map>
#include <engine/utils/rev_range.hpp>
#include <engine/core/engine.hpp>
#include <engine/core/engine_module.hpp>
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<std::string, MscRevisionRange> &getDependencies() const override
{
static const std::unordered_map<std::string, MscRevisionRange> 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<std::string, MscRevisionRange> &getDependencies() const override
{
static const std::unordered_map<std::string, MscRevisionRange> 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<std::string, MscRevisionRange> &getDependencies() const override
{
static const std::unordered_map<std::string, MscRevisionRange> 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<std::string, MscRevisionRange> &getDependencies() const override
{
static const std::unordered_map<std::string, MscRevisionRange> deps{
{"first_tmod", MscRevisionRange{1, 2}}};
return deps;
}
void onInitialize(IMscEngineModuleAPI &engine) override {}
};
void testSatisfiedModules()
{
MscEngine engine({std::make_shared<FirstTestModuleR1>(),
std::make_shared<SecondTestModuleE1>()});
}
void testDuplicatedModules()
{
try
{
MscEngine engine({
std::make_shared<FirstTestModuleR1>(),
std::make_shared<FirstTestModuleR2>(),
});
assert(false);
}
catch (const std::runtime_error &error)
{
}
}
void testUnsatisfiedModules()
{
try
{
MscEngine engine({std::make_shared<FirstTestModuleR2>(),
std::make_shared<SecondTestModuleE1>()});
assert(false);
}
catch (const std::runtime_error &error)
{
}
}
void testSatisfiedMultiversionModule()
{
MscEngine engine({std::make_shared<FirstTestModuleR2>(),
std::make_shared<SecondTestModuleE2>()});
}
int main()
{
testSatisfiedModules();
testDuplicatedModules();
testUnsatisfiedModules();
testSatisfiedMultiversionModule();
return 0;
}
+76
View File
@@ -0,0 +1,76 @@
#include <cassert>
#include <engine/core/engine_bus.hpp>
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<TestEvent*>(&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;
}