From c1ae30c89c55cec22dc999e0ec88e7fa3a838ad0 Mon Sep 17 00:00:00 2001 From: Green Sky Date: Sat, 12 Dec 2020 16:55:27 +0100 Subject: [PATCH] UpdateStrategy Refactor: 6. draft, now looks good, not fully tested --- framework/engine/CMakeLists.txt | 8 +- framework/engine/src/mm/engine.cpp | 185 ++++---------- framework/engine/src/mm/engine.hpp | 73 +++--- framework/engine/src/mm/engine_fwd.hpp | 2 +- .../src/mm/services/default_service.cpp | 52 ---- .../src/mm/services/default_service.hpp | 26 -- framework/engine/src/mm/services/service.hpp | 12 +- .../mm/update_strategies/default_strategy.cpp | 225 ++++++++++++++++++ .../mm/update_strategies/default_strategy.hpp | 70 ++++++ .../dependency_check_decorater.hpp | 56 +++++ .../mm/update_strategies/update_strategy.hpp | 78 ++++++ framework/engine/test/CMakeLists.txt | 14 +- framework/engine/test/default_us_test.cpp | 108 +++++++++ .../engine/test/dependency_check_us_test.cpp | 144 +++++++++++ framework/engine/test/run_test.cpp | 3 +- ...rvice_system_test.cpp => service_test.cpp} | 0 .../engine/test/update_strategy_test.cpp | 140 +++++++++++ framework/engine/test/update_test.cpp | 84 +++++-- 18 files changed, 990 insertions(+), 290 deletions(-) delete mode 100644 framework/engine/src/mm/services/default_service.cpp delete mode 100644 framework/engine/src/mm/services/default_service.hpp create mode 100644 framework/engine/src/mm/update_strategies/default_strategy.cpp create mode 100644 framework/engine/src/mm/update_strategies/default_strategy.hpp create mode 100644 framework/engine/src/mm/update_strategies/dependency_check_decorater.hpp create mode 100644 framework/engine/src/mm/update_strategies/update_strategy.hpp create mode 100644 framework/engine/test/default_us_test.cpp create mode 100644 framework/engine/test/dependency_check_us_test.cpp rename framework/engine/test/{service_system_test.cpp => service_test.cpp} (100%) create mode 100644 framework/engine/test/update_strategy_test.cpp diff --git a/framework/engine/CMakeLists.txt b/framework/engine/CMakeLists.txt index b2c7fdf..73a82c5 100644 --- a/framework/engine/CMakeLists.txt +++ b/framework/engine/CMakeLists.txt @@ -6,10 +6,14 @@ add_library(engine src/mm/engine.hpp src/mm/engine.cpp + src/mm/update_strategies/update_strategy.hpp + src/mm/services/service.hpp - src/mm/services/default_service.cpp - src/mm/services/default_service.hpp + src/mm/update_strategies/dependency_check_decorater.hpp + + src/mm/update_strategies/default_strategy.hpp + src/mm/update_strategies/default_strategy.cpp src/mm/services/scene_service_interface.hpp src/mm/services/scene_service_interface.cpp diff --git a/framework/engine/src/mm/engine.cpp b/framework/engine/src/mm/engine.cpp index 2ea3251..87f570b 100644 --- a/framework/engine/src/mm/engine.cpp +++ b/framework/engine/src/mm/engine.cpp @@ -1,5 +1,7 @@ #include "./engine.hpp" +#include + #include #include @@ -16,71 +18,7 @@ namespace MM { -Engine::FunctionDataHandle Engine::addUpdate(std::function function) { - if (!function) { - LOG_ERROR("could not add Update, empty function!"); - return {}; - } - - FunctionDataHandle r = _update_functions.emplace_back(std::make_shared(function)); - _update_functions_modified = true; - - return r; -} - -Engine::FunctionDataHandle Engine::addFixedUpdate(std::function function) { - if (!function) { - LOG_ERROR("could not add fixedUpdate, empty function!"); - return {}; - } - - FunctionDataHandle r = _fixed_update_functions.emplace_back(std::make_shared(function)); - _fixed_update_functions_modified = true; - - return r; -} - -void Engine::removeUpdate(FunctionDataHandle handle) { - if (handle.expired()) { - LOG_ERROR("could not remove Update, invalid handle!"); - return; - } - - auto lock = handle.lock(); - auto it = std::find(_update_functions.begin(), _update_functions.end(), lock); - if (it != _update_functions.end()) { - _update_functions.erase(it); - } else { - LOG_ERROR("could not remove Update, unknown handle!"); - } -} - -void Engine::removeFixedUpdate(FunctionDataHandle handle) { - if (handle.expired()) { - LOG_ERROR("could not remove fixedUpdate, invalid handle!"); - return; - } - - auto lock = handle.lock(); - auto it = std::find(_fixed_update_functions.begin(), _fixed_update_functions.end(), lock); - if (it != _fixed_update_functions.end()) { - _fixed_update_functions.erase(it); - } else { - LOG_ERROR("could not remove fixedUpdate, unknown handle!"); - } -} - -void Engine::addFixedDefer(std::function function) { - _fixed_defered.emplace_back(function); -} - -void Engine::traverseUpdateFunctions(std::vector>& list) { - for (auto& entry : list) { - entry->f(*this); - } -} - -Engine::Engine(float f_delta_time) : _fixed_delta_time(f_delta_time) { +void Engine::setup(void) { if (!MM::Logger::initialized) { MM::Logger::init(); } @@ -88,6 +26,13 @@ Engine::Engine(float f_delta_time) : _fixed_delta_time(f_delta_time) { MM::Logger::initSectionLogger("Engine"); } +Engine::Engine(void) { + setup(); + + _update_strategy = std::make_unique(); + LOG_INFO("defaulting to SingleThreadedDefault UpdateStrategy"); +} + Engine::~Engine(void) { cleanup(); } @@ -107,74 +52,61 @@ void Engine::cleanup(void) { _service_enable_order.clear(); - _update_functions.clear(); - _fixed_update_functions.clear(); + _update_strategy.reset(); spdlog::get("Engine")->flush(); } void Engine::update(void) { FrameMarkStart("update") - if (_update_functions_modified) { - ZoneScopedN("MM::Engine::update::sort_update_functions") - std::sort(_update_functions.begin(), _update_functions.end(), [](const auto& a, const auto& b) { return a->priority > b->priority; }); - _update_functions_modified = false; - } + //if (_update_functions_modified) { + //ZoneScopedN("MM::Engine::update::sort_update_functions") + //std::sort(_update_functions.begin(), _update_functions.end(), [](const auto& a, const auto& b) { return a->priority > b->priority; }); + //_update_functions_modified = false; + //} - { - ZoneScopedN("MM::Engine::update::traverseUpdateFunctions") - traverseUpdateFunctions(_update_functions); - } + //{ + //ZoneScopedN("MM::Engine::update::traverseUpdateFunctions") + //traverseUpdateFunctions(_update_functions); + //} + + _update_strategy->doUpdate(*this); FrameMarkEnd("update") } -void Engine::fixedUpdate(void) { - FrameMarkStart("fixedUpdate") - if (_fixed_update_functions_modified) { - ZoneScopedN("MM::Engine::fixedUpdate::sort_update_functions") +//void Engine::fixedUpdate(void) { + //FrameMarkStart("fixedUpdate") + ////if (_fixed_update_functions_modified) { + ////ZoneScopedN("MM::Engine::fixedUpdate::sort_update_functions") - std::sort(_fixed_update_functions.begin(), _fixed_update_functions.end(), [](const auto& a, const auto& b) { return a->priority > b->priority; }); - _fixed_update_functions_modified = false; - } + ////std::sort(_fixed_update_functions.begin(), _fixed_update_functions.end(), [](const auto& a, const auto& b) { return a->priority > b->priority; }); + ////_fixed_update_functions_modified = false; + ////} - { - ZoneScopedN("MM::Engine::fixedUpdate::traverseUpdateFunctions") - traverseUpdateFunctions(_fixed_update_functions); - } + ////{ + ////ZoneScopedN("MM::Engine::fixedUpdate::traverseUpdateFunctions") + ////traverseUpdateFunctions(_fixed_update_functions); + ////} - if (!_fixed_defered.empty()) { - ZoneScopedN("MM::Engine::fixedUpdate::defered") - for (auto& fn : _fixed_defered) { - fn(*this); - } + ////if (!_fixed_defered.empty()) { + ////ZoneScopedN("MM::Engine::fixedUpdate::defered") + ////for (auto& fn : _fixed_defered) { + ////fn(*this); + ////} - _fixed_defered.clear(); - } + ////_fixed_defered.clear(); + ////} - FrameMarkEnd("fixedUpdate") -} + //FrameMarkEnd("fixedUpdate") +//} #ifdef __EMSCRIPTEN__ #include static void emscripten_update(void* arg) { - using clock = std::chrono::high_resolution_clock; - static long long int accumulator = 0; - static auto now = clock::now(); - auto* e = (MM::Engine*)arg; - auto newNow = clock::now(); - auto deltaTime = std::chrono::duration_cast(newNow - now); - now = newNow; - accumulator += deltaTime.count(); - auto dt = e->getFixedDeltaTime() * 1'000'000'000.0f; - while (accumulator >= dt) { - accumulator -= dt; - e->fixedUpdate(); - } - e->update(); } #endif @@ -183,30 +115,9 @@ void Engine::run(void) { #ifdef __EMSCRIPTEN__ emscripten_set_main_loop_arg(emscripten_update, this, 0, 1); #else - using clock = std::chrono::high_resolution_clock; _is_running = true; - long long int accumulator = 0; - auto now = clock::now(); while (_is_running) { - auto newNow = clock::now(); - auto deltaTime = std::chrono::duration_cast(newNow - now); - now = newNow; - accumulator += deltaTime.count(); - auto dt = _fixed_delta_time * 1'000'000'000.0f; - - - size_t continuous_counter = 0; - while (accumulator >= dt) { - continuous_counter++; - accumulator -= static_cast(dt); - fixedUpdate(); - } - - if (continuous_counter > 2) { - LOG_WARN("had {} contiguous fixedUpdates!", std::to_string(continuous_counter)); - } - update(); } #endif @@ -224,7 +135,12 @@ bool Engine::enableService(service_family::family_type s_t) { } _service_enable_order.emplace_back(s_t); // TODO: make sure - return ss_entry->first = ss_entry->second.get()->enable(*this); + + ss_entry->first = ss_entry->second.get()->enable(*this); + + _update_strategy->enableService(s_t); // after service::enable() + + return ss_entry->first; } // not found @@ -236,7 +152,11 @@ void Engine::disableService(service_family::family_type s_t) { if (_services.count(s_t)) { auto* s_entry = _services[s_t].get(); if (s_entry->first) { + + _update_strategy->disableService(s_t); + s_entry->first = false; + s_entry->second.get()->disable(*this); //_service_enable_order.emplace_back(service_family::type); auto it = std::find(_service_enable_order.begin(), _service_enable_order.end(), s_t); @@ -258,6 +178,5 @@ bool Engine::provide(service_family::family_type I, service_family::family_type return true; } - } // MM diff --git a/framework/engine/src/mm/engine.hpp b/framework/engine/src/mm/engine.hpp index 42dbe42..45f63d2 100644 --- a/framework/engine/src/mm/engine.hpp +++ b/framework/engine/src/mm/engine.hpp @@ -1,5 +1,7 @@ #pragma once +#include "./engine_fwd.hpp" + #include #include @@ -12,6 +14,7 @@ #include #include +#include namespace MM { @@ -22,63 +25,38 @@ class Engine { public: using service_family_type = service_family::family_type; - // the services "internal" interface - //private: + protected: + std::unique_ptr _update_strategy; + public: - struct FunctionPriorityDataStructure { - std::function f; - int16_t priority = 0; // 0 is equal to scene update, the higher the prio the earlier - std::string name; - - explicit FunctionPriorityDataStructure(std::function fun) : f(fun) {} - FunctionPriorityDataStructure(const FunctionPriorityDataStructure& rhs) - : f(rhs.f), priority(rhs.priority), name(rhs.name) {} - }; - - using FunctionDataType = FunctionPriorityDataStructure; - //using FunctionDataHandle = FunctionDataType*; // important: its a pointer - using FunctionDataHandle = std::weak_ptr; // important: its a pointer - - // return nullptr on error - [[nodiscard]] FunctionDataHandle addUpdate(std::function function); - [[nodiscard]] FunctionDataHandle addFixedUpdate(std::function function); - - void removeUpdate(FunctionDataHandle handle); - void removeFixedUpdate(FunctionDataHandle handle); - - // dont use, if you are not using it to modify the engine. - // you usualy dont need to use this, if you think you need to use this, you probably dont. - void addFixedDefer(std::function function); - - private: - std::vector> _update_functions; - bool _update_functions_modified; - - std::vector> _fixed_update_functions; - bool _fixed_update_functions_modified; - - std::vector> _fixed_defered; - - private: - void traverseUpdateFunctions(std::vector>& list); // traverses an update list, gets called by update()/fixedUpdate() + UpdateStrategies::UpdateStrategy& getUpdateStrategy(void) { return *_update_strategy; } private: volatile bool _is_running = false; - const float _fixed_delta_time; + + private: + void setup(void); + + public: + Engine(void); + + explicit Engine(std::unique_ptr us) { + setup(); + _update_strategy = std::move(us); + } public: - explicit Engine(float f_delta_time = 1.f/60.f); ~Engine(void); // called from destructor or explicitly void cleanup(void); - [[nodiscard]] float getFixedDeltaTime(void) const { return _fixed_delta_time; }; + //[[nodiscard]] float getFixedDeltaTime(void) const { return _fixed_delta_time; }; void update(void); - void fixedUpdate(void); + //void fixedUpdate(void); - void run(void); // calls update()/fixedUpdate() until stopped + void run(void); // calls update() until stopped void stop(void); private: @@ -94,9 +72,6 @@ class Engine { >> > _services; - // maps I to T - //std::unordered_map _implementation_provider; - public: template constexpr auto type(void) { @@ -117,6 +92,12 @@ class Engine { _service_add_order.emplace_back(service_family::type); + // add updates to update strategy + _update_strategy->registerService( + service_family::type, + ss_entry.get()->second->registerUpdates() + ); + return (T&)*ss_entry.get()->second.get(); } diff --git a/framework/engine/src/mm/engine_fwd.hpp b/framework/engine/src/mm/engine_fwd.hpp index b181e2b..99eb4d3 100644 --- a/framework/engine/src/mm/engine_fwd.hpp +++ b/framework/engine/src/mm/engine_fwd.hpp @@ -1,10 +1,10 @@ #pragma once - // this is a forwarding header #include namespace MM { + namespace UpdateStrategies { class UpdateStrategy; } class Engine; using Entity = entt::entity; using Scene = entt::basic_registry; diff --git a/framework/engine/src/mm/services/default_service.cpp b/framework/engine/src/mm/services/default_service.cpp deleted file mode 100644 index 2a0509e..0000000 --- a/framework/engine/src/mm/services/default_service.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include "./default_service.hpp" - -namespace MM::Services { - -// TODO: error handling -bool DefaultService::enable(Engine& engine) { - { - _func_handles[0] = engine.addUpdate([this](Engine& e) { this->preSceneUpdate(e); }); - auto tmp_lock = _func_handles[0].lock(); - tmp_lock->priority = 1; - tmp_lock->name = "DefaultService::preSceneUpdate"; - } - - { - _func_handles[1] = engine.addUpdate([this](Engine& e) { this->postSceneUpdate(e); }); - auto tmp_lock = _func_handles[1].lock(); - tmp_lock->priority = -1; - tmp_lock->name = "DefaultService::postSceneUpdate"; - } - - { - _func_handles[2] = engine.addFixedUpdate([this](Engine& e) { this->preSceneFixedUpdate(e); }); - auto tmp_lock = _func_handles[2].lock(); - tmp_lock->priority = 1; - tmp_lock->name = "DefaultService::preSceneFixedUpdate"; - } - - { - _func_handles[3] = engine.addFixedUpdate([this](Engine& e) { this->postSceneFixedUpdate(e); }); - auto tmp_lock = _func_handles[3].lock(); - tmp_lock->priority = -1; - tmp_lock->name = "DefaultService::postSceneFixedUpdate"; - } - - return true; -} - -void DefaultService::disable(Engine& engine) { - engine.removeUpdate(_func_handles[0]); - engine.removeUpdate(_func_handles[1]); - - engine.removeFixedUpdate(_func_handles[2]); - engine.removeFixedUpdate(_func_handles[3]); - - _func_handles[0].reset(); - _func_handles[1].reset(); - _func_handles[2].reset(); - _func_handles[3].reset(); -} - -} // MM::Services - diff --git a/framework/engine/src/mm/services/default_service.hpp b/framework/engine/src/mm/services/default_service.hpp deleted file mode 100644 index fd0645a..0000000 --- a/framework/engine/src/mm/services/default_service.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once - -#include "./service.hpp" -#include - -namespace MM::Services { - -class DefaultService : public Service { - private: - Engine::FunctionDataHandle _func_handles[4]; - - public: - virtual const char* name(void) override { return "DefaultService"; } - - virtual bool enable(Engine& engine) override; - virtual void disable(Engine& engine) override; - - virtual void preSceneUpdate(Engine&) {} - virtual void postSceneUpdate(Engine&) {} - virtual void preSceneFixedUpdate(Engine&) {} - virtual void postSceneFixedUpdate(Engine&) {} -}; - -} //MM::Services - - diff --git a/framework/engine/src/mm/services/service.hpp b/framework/engine/src/mm/services/service.hpp index e067c4c..0dc2204 100644 --- a/framework/engine/src/mm/services/service.hpp +++ b/framework/engine/src/mm/services/service.hpp @@ -1,8 +1,13 @@ #pragma once +#include + namespace MM { class Engine; + namespace UpdateStrategies { + struct UpdateCreationInfo; + } namespace Services { @@ -10,12 +15,13 @@ namespace MM { public: virtual ~Service(void) {} - virtual const char* name(void) { return "UnNamedService"; } - //virtual const char* name(void) = 0; // use this to find unnamed services + virtual const char* name(void) = 0; - // required virtual bool enable(Engine& engine) = 0; virtual void disable(Engine& engine) = 0; + + // optional, only if service actually needs to be part of the update loop + virtual std::vector registerUpdates(void) { return {}; } }; } // Services diff --git a/framework/engine/src/mm/update_strategies/default_strategy.cpp b/framework/engine/src/mm/update_strategies/default_strategy.cpp new file mode 100644 index 0000000..ad43855 --- /dev/null +++ b/framework/engine/src/mm/update_strategies/default_strategy.cpp @@ -0,0 +1,225 @@ +#include "./default_strategy.hpp" + +#include + +namespace MM::UpdateStrategies { + +#define __L_ASSERT(cond) if (!(cond)) { return false; } + +SingleThreadedDefault::Graph& SingleThreadedDefault::getGraph(update_phase_t type) { + using type_t = update_phase_t; + + switch (type) { + case type_t::PRE: + return _pre_graph; + case type_t::MAIN: + return _main_graph; + case type_t::POST: + return _post_graph; + } + + return _main_graph; // unreachable +} + +std::set& SingleThreadedDefault::getActiveSet(update_phase_t type) { + using type_t = update_phase_t; + + switch (type) { + case type_t::PRE: + return _pre_active; + case type_t::MAIN: + return _main_active; + case type_t::POST: + return _post_active; + } + + return _main_active; // unreachable +} + +void SingleThreadedDefault::runType(MM::Engine& engine, update_phase_t type) { + auto aset = getActiveSet(type); // copy + const auto& graph = getGraph(type); + + while (!aset.empty()) { + for (const auto key : aset) { + // check if dependencies are resolved (or dont exist) + bool resolved = true; + for (const auto deps : graph.at(key)) { + if (aset.count(deps)) { + resolved = false; + break; + } + } + + if (resolved) { + // do task + _tasks[key].fn(engine); + + // delete task from set + aset.erase(key); + + break; // this might be optional but makes the delete easy + } else { + // continue the for / dont do anything with this task yet + } + } + } +} + +SingleThreadedDefault::~SingleThreadedDefault(void) { +} + +bool SingleThreadedDefault::registerService(const entt::id_type s_id, std::vector&& info_array) { + __L_ASSERT(_service_tasks.count(s_id) == 0); + + // early out + if (info_array.empty()) { + return true; + } + + auto& service_tasks = _service_tasks[s_id]; + + for (const UpdateCreationInfo& reg_e : info_array) { + // test if already exists + __L_ASSERT(_tasks.count(reg_e.key) == 0); + // also the graphs + __L_ASSERT(_pre_graph.count(reg_e.key) == 0); + __L_ASSERT(_main_graph.count(reg_e.key) == 0); + __L_ASSERT(_post_graph.count(reg_e.key) == 0); + + // also the enabled taks + __L_ASSERT(_pre_active.count(reg_e.key) == 0); + __L_ASSERT(_main_active.count(reg_e.key) == 0); + __L_ASSERT(_post_active.count(reg_e.key) == 0); + + // potentially check for cicles (can be done by provided decorator) + } + + for (const UpdateCreationInfo& reg_e : info_array) { + // add to tasks + { + auto& new_task = _tasks[reg_e.key]; + new_task.name = reg_e.name; + new_task.fn = reg_e.fn; + new_task.phase = reg_e.phase; + new_task.auto_enable = reg_e.auto_enable; + + new_task.enabled = false; + } + + // add relation to service + service_tasks.emplace_back(reg_e.key); + + // fill in dependencies + auto& graph = getGraph(reg_e.phase); + graph[reg_e.key].insert(reg_e.dependencies.begin(), reg_e.dependencies.end()); + } + + return true; +} + +bool SingleThreadedDefault::enable(const update_key_t key) { + __L_ASSERT(_tasks.count(key) == 1); + __L_ASSERT(_tasks[key].enabled == false); + + auto ret = getActiveSet(_tasks[key].phase).emplace(key); + _tasks[key].enabled = true; + + return ret.second; +} + +bool SingleThreadedDefault::disable(const update_key_t key) { + __L_ASSERT(_tasks.count(key) == 1); + __L_ASSERT(_tasks[key].enabled == true); + + auto& aset = getActiveSet(_tasks[key].phase); + + __L_ASSERT(aset.count(key) == 1); + + aset.erase(key); + + _tasks[key].enabled = false; + + return true; +} + +bool SingleThreadedDefault::enableService(const entt::id_type s_id) { + bool succ = true; + for (const auto id : _service_tasks[s_id]) { + auto& task = _tasks[id]; + + // there should be no task running, if the service is no enabled!! + assert(!task.enabled); + + if (task.auto_enable) { + succ &= enable(id); + } + } + + return succ; +} + +bool SingleThreadedDefault::disableService(const entt::id_type s_id) { + bool succ = true; + for (const auto id : _service_tasks[s_id]) { + auto& task = _tasks[id]; + + if (task.auto_enable) { + assert(task.enabled); // this should never happen + } + + if (task.enabled) { + succ &= disable(id); + } + } + + return succ; +} + +bool SingleThreadedDefault::depend(const update_key_t A, const update_key_t B) { + // TODO: error checking lol + + if (_tasks.count(A) == 0) { + return false; // can not add a dependecy of a non existing task + // TODO: or do we? + } + + if (_tasks.count(B) == 1) { + if (_tasks[A].phase != _tasks[B].phase) { + // cross graph tasks are not allowed + return false; + } + } + + auto& graph = getGraph(_tasks[A].phase); + auto ret = graph[A].emplace(B); + return ret.second; // returns whether it was inserted (or already existed) +} + +void SingleThreadedDefault::doUpdate(MM::Engine& engine) { + // pre + runType(engine, update_phase_t::PRE); + + // main + runType(engine, update_phase_t::MAIN); + + // post + runType(engine, update_phase_t::POST); + + if (!_defered_queue.empty()) { + for (auto&& fn : _defered_queue) { + fn(engine); + } + + _defered_queue.clear(); + } +} + +void SingleThreadedDefault::addDefered(std::function function) { + _defered_queue.emplace_back(std::move(function)); +} + +#undef __L_ASSERT + +} // MM::UpdateStrategies + diff --git a/framework/engine/src/mm/update_strategies/default_strategy.hpp b/framework/engine/src/mm/update_strategies/default_strategy.hpp new file mode 100644 index 0000000..bf2d1c9 --- /dev/null +++ b/framework/engine/src/mm/update_strategies/default_strategy.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include "./update_strategy.hpp" + +#include +#include +#include +#include + +namespace MM::UpdateStrategies { + +class SingleThreadedDefault : public MM::UpdateStrategies::UpdateStrategy { + private: + struct Task { + std::string name; + std::function fn; + update_phase_t phase; + bool auto_enable; + + bool enabled = false; + }; + + std::unordered_map _tasks; + + // the tasks a service has registered + std::unordered_map> _service_tasks; + + // tasks dependencies + using Graph = std::unordered_map>; + // TODO: do vector for perf? + + Graph _pre_graph; + Graph _main_graph; + Graph _post_graph; + + std::set _pre_active; + std::set _main_active; + std::set _post_active; + + std::vector> _defered_queue; + + private: + Graph& getGraph(update_phase_t phase); + std::set& getActiveSet(update_phase_t phase); + + void runType(MM::Engine& engine, update_phase_t phase); + + public: + SingleThreadedDefault(void) = default; + virtual ~SingleThreadedDefault(void); + + protected: + bool registerService(const entt::id_type s_id, std::vector&& info_array) override; + + bool enableService(const entt::id_type s_id) override; + bool disableService(const entt::id_type s_id) override; + + void doUpdate(MM::Engine& engine) override; + + public: + bool enable(const update_key_t key) override; + bool disable(const update_key_t key) override; + + bool depend(const update_key_t A, const update_key_t B) override; + + void addDefered(std::function function) override; +}; + +} // MM::UpdateStrategies + diff --git a/framework/engine/src/mm/update_strategies/dependency_check_decorater.hpp b/framework/engine/src/mm/update_strategies/dependency_check_decorater.hpp new file mode 100644 index 0000000..3075436 --- /dev/null +++ b/framework/engine/src/mm/update_strategies/dependency_check_decorater.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include "./update_strategy.hpp" + +#include +#include + +namespace MM::UpdateStrategies { + +// checks for cyles +template +class DependencyCheckDecorator : public T { + static_assert(std::is_base_of_v); + + public: + //DependencyCheckDecorator(void) = default; + + //template + //DependencyCheckDecorator(Args... args) : T(args) { + //} + + virtual ~DependencyCheckDecorator(void) {} + + private: + void doUpdate(MM::Engine& engine) override { + T::doUpdate(engine); + } + + bool registerService(const entt::id_type s_id, std::vector&& info_array) override { + return T::registerService(s_id, std::move(info_array)); + } + + bool enableService(const entt::id_type s_id) override { + return T::enableService(s_id); + } + + bool disableService(const entt::id_type s_id) override { + return T::disableService(s_id); + } + + public: + bool enable(const update_key_t key) override { + return T::enable(key); + } + + bool disable(const update_key_t key) override { + return T::disable(key); + } + + bool depend(const update_key_t A, const update_key_t B) override { + return T::depend(A, B); + } +}; + +} // MM::UpdateStrategies + diff --git a/framework/engine/src/mm/update_strategies/update_strategy.hpp b/framework/engine/src/mm/update_strategies/update_strategy.hpp new file mode 100644 index 0000000..5e564a4 --- /dev/null +++ b/framework/engine/src/mm/update_strategies/update_strategy.hpp @@ -0,0 +1,78 @@ +#pragma once + +#include "../engine_fwd.hpp" + +#include +#include +#include +#include + +namespace MM::UpdateStrategies { + +using update_key_t = entt::id_type; + +enum class update_phase_t { + PRE, // for on-main-thread + MAIN, + POST // for on-main-thread +}; + +struct UpdateCreationInfo { + update_key_t key; // key for dependencies + + std::string name; // for debugging + + std::function fn; // the actual payload + + update_phase_t phase = update_phase_t::MAIN; + + bool auto_enable = true; // whether this update is enabled with the service + + // this update also depends on (in the same phase) + std::vector dependencies {}; +}; + +// pure virtual interface for managing the update logic of the engine +class UpdateStrategy { + public: + virtual ~UpdateStrategy(void) {} + + protected: // the engine facing interface + friend ::MM::Engine; + + // TODO: return something? + virtual bool registerService(const entt::id_type s_id, std::vector&& info_array) = 0; + + // returns true on success + // failure conditions may include: + // - already en/dis-abled + // - is auto_enable + // - impossible dependencies + virtual bool enableService(const entt::id_type s_id) = 0; + virtual bool disableService(const entt::id_type s_id) = 0; + + // runs one update + virtual void doUpdate(MM::Engine& engine) = 0; + + public: // the user facing interface + // similar to *ableService, can only be used for non-auto_enable-updates + virtual bool enable(const update_key_t key) = 0; + virtual bool disable(const update_key_t key) = 0; + + // add extra dependencies into the tree, the user has the most knowlage about + // the order the services should execute in. + // A -> B (make A depend on B) + virtual bool depend(const update_key_t A, const update_key_t B) = 0; + + // WIP: + + // dont use, if you are not using it to modify the engine. + // you usualy dont need to use this, if you think you need to use this, you probably dont. + //virtual void addFixedDefered(std::function function) = 0; + virtual void addDefered(std::function function) = 0; // called after everything + + //virtual std::future addAsync(std::function function) = 0; +}; + +} // MM::UpdateStrategies + diff --git a/framework/engine/test/CMakeLists.txt b/framework/engine/test/CMakeLists.txt index ff28b72..b6b2862 100644 --- a/framework/engine/test/CMakeLists.txt +++ b/framework/engine/test/CMakeLists.txt @@ -1,8 +1,13 @@ add_executable(engine_test - update_test.cpp - run_test.cpp - service_system_test.cpp - default_service_test.cpp + update_strategy_test.cpp + default_us_test.cpp + #dependency_check_us_test.cpp + + # old: + #update_test.cpp + #run_test.cpp + #service_test.cpp + #default_service_test.cpp ) target_include_directories(engine_test PRIVATE ".") @@ -10,6 +15,7 @@ target_include_directories(engine_test PRIVATE ".") target_link_libraries(engine_test engine gtest_main + gmock ) add_test(NAME engine_test COMMAND engine_test) diff --git a/framework/engine/test/default_us_test.cpp b/framework/engine/test/default_us_test.cpp new file mode 100644 index 0000000..4c26e9b --- /dev/null +++ b/framework/engine/test/default_us_test.cpp @@ -0,0 +1,108 @@ +#include "mm/update_strategies/update_strategy.hpp" +#include +#include + +#include +#include +#include + +#include + +using ::testing::Return; +using ::testing::_; + +class MockService : public MM::Services::Service { + public: + const char* name(void) override { return "MockService"; } + + MOCK_METHOD(bool, enable, (MM::Engine& engine), (override)); + MOCK_METHOD(void, disable, (MM::Engine& engine), (override)); + + MOCK_METHOD(std::vector, registerUpdates, (), (override)); +}; + +TEST(default_update_strategy, simple_update) { + MM::Engine engine(std::make_unique()); + + engine.update(); +} + +TEST(default_update_strategy, service) { + class TmpMockService : public MockService { + public: + TmpMockService(void) { + EXPECT_CALL(*this, registerUpdates()) + .Times(1); + + EXPECT_CALL(*this, enable(_)) + .Times(1); + ON_CALL(*this, enable(_)) + .WillByDefault(Return(true)); + + EXPECT_CALL(*this, disable(_)) + .Times(1); + } + }; + + { + MM::Engine engine(std::make_unique()); + + engine.addService(); + + ASSERT_TRUE(engine.enableService()); + engine.disableService(); + } +} + + +TEST(default_update_strategy, run_1) { + class TmpMockService : public MockService { + int& _counter; + public: + explicit TmpMockService(int& counter) : _counter(counter) { + EXPECT_CALL(*this, registerUpdates()) + .Times(1); + ON_CALL(*this, registerUpdates()) + .WillByDefault([this]() -> std::vector { + return { + { + "TmpMockService"_hs, + "TmpMockService", + [this](MM::Engine&) { _counter++; } + } + }; + }); + + EXPECT_CALL(*this, enable(_)) + .Times(1); + ON_CALL(*this, enable(_)) + .WillByDefault(Return(true)); + + EXPECT_CALL(*this, disable(_)) + .Times(1); + } + }; + + { + int counter = 1; + MM::Engine engine(std::make_unique()); + ASSERT_EQ(counter, 1); + + engine.addService(counter); + ASSERT_EQ(counter, 1); + + ASSERT_TRUE(engine.enableService()); + ASSERT_EQ(counter, 1); + + engine.getUpdateStrategy().addDefered([](MM::Engine& e) { e.stop(); }); + + engine.run(); + + ASSERT_EQ(counter, 2); + + engine.disableService(); + + ASSERT_EQ(counter, 2); + } +} + diff --git a/framework/engine/test/dependency_check_us_test.cpp b/framework/engine/test/dependency_check_us_test.cpp new file mode 100644 index 0000000..bc03960 --- /dev/null +++ b/framework/engine/test/dependency_check_us_test.cpp @@ -0,0 +1,144 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +using ::testing::Return; +using ::testing::_; + +#include +//using namespace ::entt::literals; + +class MockUpdateStrategy : public MM::UpdateStrategies::UpdateStrategy { + public: + MOCK_METHOD( + bool, + registerService, + (const entt::id_type s_id, std::vector&& info), + (override) + ); + + // protected: + MOCK_METHOD(void, doUpdate, (MM::Engine& engine), (override)); + + MOCK_METHOD(bool, enableService, (const entt::id_type s_id), (override)); + MOCK_METHOD(bool, disableService, (const entt::id_type s_id), (override)); + + // public: + MOCK_METHOD(bool, enable, (const MM::UpdateStrategies::update_key_t key), (override)); + MOCK_METHOD(bool, disable, (const MM::UpdateStrategies::update_key_t key), (override)); + + MOCK_METHOD(bool, depend, (const MM::UpdateStrategies::update_key_t A, const MM::UpdateStrategies::update_key_t B), (override)); + + MOCK_METHOD(void, addDefered, (std::function function), (override)); +}; + +class MockService : public MM::Services::Service { + public: + const char* name(void) override { return "MockService"; } + + MOCK_METHOD(bool, enable, (MM::Engine& engine), (override)); + MOCK_METHOD(void, disable, (MM::Engine& engine), (override)); + + MOCK_METHOD(std::vector, registerUpdates, (), (override)); +}; + +TEST(dependency_check_update_strategy, decoration_mock) { + auto dep = std::make_unique>(); + auto* mock = static_cast(dep.get()); + + EXPECT_CALL(*mock, registerService(_, _)) + .Times(1); + EXPECT_CALL(*mock, enableService(_)) + .Times(1); + EXPECT_CALL(*mock, disableService(_)) + .Times(1); + + class TmpMockService : public MockService { + public: + TmpMockService(void) { + EXPECT_CALL(*this, registerUpdates()) + .Times(1); + + EXPECT_CALL(*this, enable(_)) + .Times(1); + ON_CALL(*this, enable(_)) + .WillByDefault(Return(true)); + + EXPECT_CALL(*this, disable(_)) + .Times(1); + } + }; + + { + MM::Engine engine(std::move(dep)); + + engine.addService(); + + ASSERT_TRUE(engine.enableService()); + engine.disableService(); + } +} + +TEST(dependency_check_update_strategy, simple_loop) { + auto dep = std::make_unique>(); + auto* mock = static_cast(dep.get()); + + EXPECT_CALL(*mock, registerService(_, _)) + .Times(1); + EXPECT_CALL(*mock, enableService(_)) + .Times(1); + //EXPECT_CALL(*mock, disableService(_)) + //.Times(1); + EXPECT_CALL(*mock, depend(_, _)) + .Times(1); // the layer should catch the error before forwarding it + + class BonkersService : public MM::Services::Service { + public: + virtual ~BonkersService(void) {} + + const char* name(void) override { return "BonkersService"; } + + bool enable(MM::Engine& engine) override { + engine.getUpdateStrategy().depend("BonkerA"_hs, "BonkerB"_hs); + + // maleformed: direct cycle + engine.getUpdateStrategy().depend("BonkerB"_hs, "BonkerA"_hs); + + return true; + } + + void disable(MM::Engine&) override {} + + std::vector registerUpdates(void) override { + return { + MM::UpdateStrategies::UpdateCreationInfo{ + "BonkerA"_hs, + "BonkerA", + [](MM::Engine&) {} + }, + MM::UpdateStrategies::UpdateCreationInfo{ + "BonkerB"_hs, + "BonkerB", + [](MM::Engine&) {} + } + }; + } + }; + + { + MM::Engine engine(std::move(dep)); + + engine.addService(); + + ASSERT_THROW(engine.enableService(), std::logic_error); + + //engine.disableService(); + } +} + diff --git a/framework/engine/test/run_test.cpp b/framework/engine/test/run_test.cpp index ee37429..70c876f 100644 --- a/framework/engine/test/run_test.cpp +++ b/framework/engine/test/run_test.cpp @@ -1,9 +1,10 @@ #include +#include #include TEST(engine_run, test_run) { - MM::Engine engine; + MM::Engine engine{std::make_unique}; bool run = false; diff --git a/framework/engine/test/service_system_test.cpp b/framework/engine/test/service_test.cpp similarity index 100% rename from framework/engine/test/service_system_test.cpp rename to framework/engine/test/service_test.cpp diff --git a/framework/engine/test/update_strategy_test.cpp b/framework/engine/test/update_strategy_test.cpp new file mode 100644 index 0000000..d112140 --- /dev/null +++ b/framework/engine/test/update_strategy_test.cpp @@ -0,0 +1,140 @@ +#include "mm/services/service.hpp" +#include +#include + +#include +#include +#include + +#include + +using ::testing::Return; +using ::testing::_; + +class MockUpdateStrategy : public MM::UpdateStrategies::UpdateStrategy { + public: + MOCK_METHOD( + bool, + registerService, + (const entt::id_type s_id, std::vector&& info), + (override) + ); + + // protected: + MOCK_METHOD(void, doUpdate, (MM::Engine& engine), (override)); + + MOCK_METHOD(bool, enableService, (const entt::id_type s_id), (override)); + MOCK_METHOD(bool, disableService, (const entt::id_type s_id), (override)); + + // public: + MOCK_METHOD(bool, enable, (const MM::UpdateStrategies::update_key_t key), (override)); + MOCK_METHOD(bool, disable, (const MM::UpdateStrategies::update_key_t key), (override)); + + MOCK_METHOD(bool, depend, (const MM::UpdateStrategies::update_key_t A, const MM::UpdateStrategies::update_key_t B), (override)); + + MOCK_METHOD(void, addDefered, (std::function function), (override)); +}; + +class MockService : public MM::Services::Service { + public: + const char* name(void) override { return "MockService"; } + + MOCK_METHOD(bool, enable, (MM::Engine& engine), (override)); + MOCK_METHOD(void, disable, (MM::Engine& engine), (override)); + + MOCK_METHOD(std::vector, registerUpdates, (), (override)); +}; + +TEST(engine_mock, update_strategy_run) { + auto mock = std::make_unique(); + + EXPECT_CALL(*mock, doUpdate(_)) + .Times(1); + + MM::Engine engine(std::move(mock)); + + engine.update(); +} + +TEST(engine_mock, service_update_strategy) { + auto mock = std::make_unique(); + + EXPECT_CALL(*mock, registerService(_, _)) + .Times(1); + EXPECT_CALL(*mock, enableService(_)) + .Times(1); + EXPECT_CALL(*mock, disableService(_)) + .Times(1); + + class TmpMockService : public MockService { + public: + TmpMockService(void) { + EXPECT_CALL(*this, registerUpdates()) + .Times(1); + + EXPECT_CALL(*this, enable(_)) + .Times(1); + ON_CALL(*this, enable(_)) + .WillByDefault(Return(true)); + + EXPECT_CALL(*this, disable(_)) + .Times(1); + } + }; + + { + MM::Engine engine(std::move(mock)); + + engine.addService(); + + ASSERT_TRUE(engine.enableService()); + engine.disableService(); + } +} + +TEST(engine_mock, service_update_strategy_run_1) { + auto mock = std::make_unique(); + + EXPECT_CALL(*mock, registerService(_, _)) + .Times(1); + EXPECT_CALL(*mock, enableService(_)) + .Times(1); + EXPECT_CALL(*mock, disableService(_)) + .Times(1); + + class TmpMockService : public MockService { + public: + explicit TmpMockService(void) { + EXPECT_CALL(*this, registerUpdates()) + .Times(1); + ON_CALL(*this, registerUpdates()) + .WillByDefault([]() -> std::vector { + return { + { + "TmpMockService"_hs, + "TmpMockService", + [](MM::Engine&) {} + } + }; + }); + + EXPECT_CALL(*this, enable(_)) + .Times(1); + ON_CALL(*this, enable(_)) + .WillByDefault(Return(true)); + + EXPECT_CALL(*this, disable(_)) + .Times(1); + } + }; + + { + MM::Engine engine(std::move(mock)); + + engine.addService(); + + ASSERT_TRUE(engine.enableService()); + engine.disableService(); + } +} + diff --git a/framework/engine/test/update_test.cpp b/framework/engine/test/update_test.cpp index a5ced28..3bbc254 100644 --- a/framework/engine/test/update_test.cpp +++ b/framework/engine/test/update_test.cpp @@ -1,84 +1,123 @@ #include +#include #include +//class MyUpdateStrategy : public MM::UpdateStrategy::UpdateStrategy { + //public: + //virtual ~MyUpdateStrategy(void) {} + + //// return nullptr on error + //[[nodiscard]] MM::UpdateStrategy::FunctionDataHandle addUpdate(std::function fn) override { + //} + + //[[nodiscard]] MM::UpdateStrategy::FunctionDataHandle addFixedUpdate(std::function fn) override { + //} + + //// knows which one + //void removeUpdate(MM::UpdateStrategy::FunctionDataHandle handle) override { + //} + + //// dont use, if you are not using it to modify the engine. + //// you usualy dont need to use this, if you think you need to use this, you probably dont. + //void addFixedDefered(std::function function) override { + //} + + ////virtual std::future addAsync(std::function function) = 0; + + //void doUpdate(MM::Engine& engine) override { + //} + + //void doFixedUpdate(MM::Engine& engine) override { + //} + +//}; + +class MyEngine : public MM::Engine { + public: + MyEngine(void) : MM::Engine(std::make_unique()) { + } +}; + TEST(engine_fixed_update, empty_add_rm) { - MM::Engine engine; + MyEngine engine; auto test_fun = [](auto&) {}; - auto handle = engine.addFixedUpdate(test_fun); + auto handle = engine.getUpdateStrategy().addFixedUpdate(test_fun); ASSERT_NE(handle.lock(), nullptr); - handle.lock()->priority = 1; + //handle.lock()->priority = 1; - engine.removeFixedUpdate(handle); + engine.getUpdateStrategy().removeUpdate(handle); } TEST(engine_update, empty_add_rm) { - MM::Engine engine; + MyEngine engine; auto test_fun = [](auto&) {}; - auto handle = engine.addUpdate(test_fun); + auto handle = engine.getUpdateStrategy().addUpdate(test_fun); ASSERT_NE(handle.lock(), nullptr); - handle.lock()->priority = 1; + //handle.lock()->priority = 1; - engine.removeUpdate(handle); + engine.getUpdateStrategy().removeUpdate(handle); } TEST(engine_fixed_update, empty_run) { - MM::Engine engine; + MyEngine engine; auto test_fun = [](auto&) {}; - auto handle = engine.addFixedUpdate(test_fun); + auto handle = engine.getUpdateStrategy().addFixedUpdate(test_fun); ASSERT_NE(handle.lock(), nullptr); - handle.lock()->priority = 1; + //handle.lock()->priority = 1; engine.fixedUpdate(); // single update - engine.removeFixedUpdate(handle); + engine.getUpdateStrategy().removeUpdate(handle); } TEST(engine_update, empty_run) { - MM::Engine engine; + MyEngine engine; auto test_fun = [](auto&) {}; - auto handle = engine.addUpdate(test_fun); + auto handle = engine.getUpdateStrategy().addUpdate(test_fun); ASSERT_NE(handle.lock(), nullptr); - handle.lock()->priority = 1; + //handle.lock()->priority = 1; engine.update(); - engine.removeUpdate(handle); + engine.getUpdateStrategy().removeUpdate(handle); } TEST(engine_fixed_update, test_run) { - MM::Engine engine; + MyEngine engine; bool run = false; auto test_fun = [&run](auto&) { run = true; }; - auto handle = engine.addFixedUpdate(test_fun); + auto handle = engine.getUpdateStrategy().addFixedUpdate(test_fun); ASSERT_NE(handle.lock(), nullptr); - handle.lock()->priority = 1; + //handle.lock()->priority = 1; ASSERT_FALSE(run); engine.fixedUpdate(); // single update ASSERT_TRUE(run); - engine.removeFixedUpdate(handle); + engine.getUpdateStrategy().removeUpdate(handle); } +#if 0 + TEST(engine_update, test_run) { - MM::Engine engine; + MyEngine engine; bool run = false; @@ -97,7 +136,7 @@ TEST(engine_update, test_run) { } TEST(engine_fixed_update, test_order_run) { - MM::Engine engine; + MyEngine engine; bool run1 = false; bool run2 = false; @@ -228,3 +267,4 @@ TEST(engine_update, test_order_rev_run) { engine.removeUpdate(handle2); } +#endif