mirror of
https://github.com/MadeOfJelly/MushMachine.git
synced 2025-06-18 18:56:36 +02:00
reworked the general update strategy interface
This commit is contained in:
@ -7,13 +7,12 @@ add_library(engine
|
||||
src/mm/engine.cpp
|
||||
|
||||
src/mm/update_strategies/update_strategy.hpp
|
||||
src/mm/update_strategies/dummy.hpp
|
||||
|
||||
src/mm/services/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/update_strategies/sequential_strategy.hpp
|
||||
src/mm/update_strategies/sequential_strategy.cpp
|
||||
|
||||
src/mm/services/scene_service_interface.hpp
|
||||
src/mm/services/scene_service_interface.cpp
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "./engine.hpp"
|
||||
|
||||
#include <mm/update_strategies/default_strategy.hpp>
|
||||
#include <mm/update_strategies/sequential_strategy.hpp>
|
||||
#include <mm/update_strategies/dummy.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
@ -29,8 +30,10 @@ void Engine::setup(void) {
|
||||
Engine::Engine(void) {
|
||||
setup();
|
||||
|
||||
_update_strategy = std::make_unique<MM::UpdateStrategies::SingleThreadedDefault>();
|
||||
LOG_INFO("defaulting to SingleThreadedDefault UpdateStrategy");
|
||||
_update_strategy = std::make_unique<MM::UpdateStrategies::Sequential>();
|
||||
LOG_INFO("defaulting to Sequential (single threaded) UpdateStrategy");
|
||||
//_update_strategy = std::make_unique<MM::UpdateStrategies::Dummy>();
|
||||
//LOG_WARN("Dummy UpdateStrategy [TESTING]");
|
||||
}
|
||||
|
||||
Engine::~Engine(void) {
|
||||
@ -59,48 +62,12 @@ void Engine::cleanup(void) {
|
||||
|
||||
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;
|
||||
//}
|
||||
|
||||
//{
|
||||
//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")
|
||||
|
||||
////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);
|
||||
////}
|
||||
|
||||
////if (!_fixed_defered.empty()) {
|
||||
////ZoneScopedN("MM::Engine::fixedUpdate::defered")
|
||||
////for (auto& fn : _fixed_defered) {
|
||||
////fn(*this);
|
||||
////}
|
||||
|
||||
////_fixed_defered.clear();
|
||||
////}
|
||||
|
||||
//FrameMarkEnd("fixedUpdate")
|
||||
//}
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
#include <emscripten.h>
|
||||
|
||||
@ -136,14 +103,20 @@ bool Engine::enableService(entt::id_type s_t) {
|
||||
|
||||
_service_enable_order.emplace_back(s_t); // TODO: make sure
|
||||
|
||||
ss_entry->first = ss_entry->second.get()->enable(*this);
|
||||
{ // tasking
|
||||
std::vector<UpdateStrategies::TaskInfo> task_array;
|
||||
|
||||
_update_strategy->enableService(s_t); // after service::enable()
|
||||
ss_entry->first = ss_entry->second.get()->enable(*this, task_array);
|
||||
if (ss_entry->first) {
|
||||
_update_strategy->enableService(s_t, std::move(task_array));
|
||||
}
|
||||
}
|
||||
|
||||
return ss_entry->first;
|
||||
}
|
||||
|
||||
// not found
|
||||
// TODO: improve this
|
||||
assert(false && "first add Service");
|
||||
return false;
|
||||
}
|
||||
@ -158,7 +131,6 @@ void Engine::disableService(entt::id_type s_t) {
|
||||
s_entry->first = false;
|
||||
|
||||
s_entry->second.get()->disable(*this);
|
||||
//_service_enable_order.emplace_back(service_family::type<T>);
|
||||
auto it = std::find(_service_enable_order.begin(), _service_enable_order.end(), s_t);
|
||||
if (it != _service_enable_order.end()) {
|
||||
_service_enable_order.erase(it);
|
||||
@ -170,6 +142,7 @@ void Engine::disableService(entt::id_type s_t) {
|
||||
bool Engine::provide(entt::id_type I, entt::id_type T) {
|
||||
if (!_services.count(T)) {
|
||||
// TODO: log error
|
||||
assert(false && "cant provide something that does not exist!");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -26,44 +26,21 @@ namespace Services {
|
||||
class Engine {
|
||||
friend Services::ImGuiEngineTools;
|
||||
|
||||
private:
|
||||
//using service_family = entt::family<struct internal_service_family>;
|
||||
|
||||
public:
|
||||
//using service_family_type = service_family::family_type;
|
||||
using service_id_type = entt::id_type; // alias, for future proof
|
||||
|
||||
// UpdateStrategy
|
||||
protected:
|
||||
std::unique_ptr<UpdateStrategies::UpdateStrategy> _update_strategy;
|
||||
|
||||
public:
|
||||
UpdateStrategies::UpdateStrategy& getUpdateStrategy(void) { return *_update_strategy; }
|
||||
|
||||
// state
|
||||
private:
|
||||
volatile bool _is_running = false;
|
||||
// ... just realisied: i never needed a getter ...
|
||||
|
||||
private:
|
||||
void setup(void);
|
||||
|
||||
public:
|
||||
Engine(void);
|
||||
|
||||
explicit Engine(std::unique_ptr<UpdateStrategies::UpdateStrategy> us) {
|
||||
setup();
|
||||
_update_strategy = std::move(us);
|
||||
}
|
||||
|
||||
public:
|
||||
~Engine(void);
|
||||
|
||||
// called from destructor or explicitly
|
||||
void cleanup(void);
|
||||
|
||||
void update(void);
|
||||
|
||||
void run(void); // calls update() until stopped
|
||||
void stop(void);
|
||||
|
||||
private:
|
||||
std::vector<entt::id_type> _service_add_order; // ?
|
||||
std::vector<entt::id_type> _service_enable_order; // ?
|
||||
|
||||
@ -75,6 +52,29 @@ class Engine {
|
||||
>>
|
||||
> _services;
|
||||
|
||||
|
||||
// private state helper
|
||||
private:
|
||||
void setup(void);
|
||||
|
||||
// ctr dtr ...
|
||||
public:
|
||||
Engine(void);
|
||||
~Engine(void);
|
||||
|
||||
explicit Engine(std::unique_ptr<UpdateStrategies::UpdateStrategy> us) {
|
||||
setup();
|
||||
_update_strategy = std::move(us);
|
||||
}
|
||||
|
||||
// called from destructor or explicitly (if eg "global", u need dis)
|
||||
void cleanup(void);
|
||||
|
||||
void update(void);
|
||||
|
||||
void run(void); // calls update() until stopped
|
||||
void stop(void);
|
||||
|
||||
public:
|
||||
template<typename T>
|
||||
constexpr static auto type(void) {
|
||||
@ -96,12 +96,6 @@ class Engine {
|
||||
|
||||
_service_add_order.emplace_back(type<T>());
|
||||
|
||||
// add updates to update strategy
|
||||
_update_strategy->registerService(
|
||||
type<T>(),
|
||||
ss_entry.get()->second->registerUpdates()
|
||||
);
|
||||
|
||||
return (T&)*ss_entry.get()->second.get();
|
||||
}
|
||||
|
||||
@ -154,13 +148,8 @@ class Engine {
|
||||
return provide(type<I>(), type<T>());
|
||||
}
|
||||
|
||||
// TODO: reimplement???
|
||||
//template<typename I>
|
||||
// TODO: remove service
|
||||
//void removeProvider(void) {
|
||||
//if (auto it = _implementation_provider.find(service_family::type<I>); it != _implementation_provider.end()) {
|
||||
//_implementation_provider.erase(it);
|
||||
//}
|
||||
//}
|
||||
};
|
||||
|
||||
} // MM
|
||||
|
@ -11,9 +11,11 @@ namespace MM {
|
||||
using System = std::function<void(::MM::Scene&, float)>;
|
||||
|
||||
// opaque way to add a System to a Scene
|
||||
[[deprecated("use organizer")]]
|
||||
void AddSystemToScene(::MM::Scene& scene, ::MM::System fn);
|
||||
|
||||
// opaque way to iterate over the Systems
|
||||
[[deprecated("use organizer")]]
|
||||
void EachSystemInScene(::MM::Scene& scene, std::function<void(::MM::Scene&, ::MM::System&)> fn);
|
||||
|
||||
} // MM
|
||||
@ -35,7 +37,7 @@ namespace MM::Services {
|
||||
|
||||
// adds a System to current Scene.
|
||||
// default impl. will use getScene() !
|
||||
inline virtual void addSystemToScene(::MM::System fn) {
|
||||
virtual void addSystemToScene(::MM::System fn) {
|
||||
::MM::AddSystemToScene(getScene(), std::move(fn));
|
||||
}
|
||||
};
|
||||
|
@ -6,7 +6,7 @@ namespace MM {
|
||||
|
||||
class Engine;
|
||||
namespace UpdateStrategies {
|
||||
struct UpdateCreationInfo;
|
||||
struct TaskInfo;
|
||||
}
|
||||
|
||||
namespace Services {
|
||||
@ -17,14 +17,12 @@ namespace MM {
|
||||
|
||||
virtual const char* name(void) = 0;
|
||||
|
||||
virtual bool enable(Engine& engine) = 0;
|
||||
// tasks are to be filled in by the service impl
|
||||
virtual bool enable(Engine& engine, std::vector<UpdateStrategies::TaskInfo>& task_array) = 0;
|
||||
virtual void disable(Engine& engine) = 0;
|
||||
|
||||
// optional, only if service actually needs to be part of the update loop
|
||||
virtual std::vector<UpdateStrategies::UpdateCreationInfo> registerUpdates(void) { return {}; }
|
||||
};
|
||||
|
||||
} // Services
|
||||
|
||||
} //MM
|
||||
} // MM
|
||||
|
||||
|
@ -1,236 +0,0 @@
|
||||
#include "./default_strategy.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
|
||||
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<update_key_t>& 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<UpdateCreationInfo>&& 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);
|
||||
|
||||
// simulate async
|
||||
for (size_t i = 0; !_async_queue.empty() && i < _max_async_per_tick; i++) {
|
||||
_async_queue.back()(engine);
|
||||
_async_queue.pop_back();
|
||||
}
|
||||
|
||||
if (!_deferred_queue.empty()) {
|
||||
for (auto&& fn : _deferred_queue) {
|
||||
fn(engine);
|
||||
}
|
||||
|
||||
_deferred_queue.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SingleThreadedDefault::addDeferred(std::function<void(Engine&)> function) {
|
||||
_deferred_queue.emplace_back(std::move(function));
|
||||
}
|
||||
|
||||
void SingleThreadedDefault::addAsync(std::function<void(Engine&)> function) {
|
||||
_async_queue.emplace_back(std::move(function));
|
||||
}
|
||||
|
||||
#undef __L_ASSERT
|
||||
|
||||
} // MM::UpdateStrategies
|
||||
|
@ -1,83 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "./update_strategy.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
// fwd
|
||||
namespace MM::Services {
|
||||
class ImGuiEngineTools;
|
||||
}
|
||||
|
||||
namespace MM::UpdateStrategies {
|
||||
|
||||
class SingleThreadedDefault : public MM::UpdateStrategies::UpdateStrategy {
|
||||
friend MM::Services::ImGuiEngineTools;
|
||||
|
||||
private:
|
||||
struct Task {
|
||||
std::string name;
|
||||
std::function<void(Engine&)> fn;
|
||||
update_phase_t phase;
|
||||
bool auto_enable;
|
||||
|
||||
bool enabled = false;
|
||||
};
|
||||
|
||||
std::unordered_map<update_key_t, Task> _tasks;
|
||||
|
||||
// the tasks a service has registered
|
||||
std::unordered_map<entt::id_type, std::vector<update_key_t>> _service_tasks;
|
||||
|
||||
// tasks dependencies
|
||||
using Graph = std::unordered_map<update_key_t, std::set<update_key_t>>;
|
||||
// TODO: do vector for perf?
|
||||
|
||||
Graph _pre_graph;
|
||||
Graph _main_graph;
|
||||
Graph _post_graph;
|
||||
|
||||
std::set<update_key_t> _pre_active;
|
||||
std::set<update_key_t> _main_active;
|
||||
std::set<update_key_t> _post_active;
|
||||
|
||||
std::vector<std::function<void(Engine&)>> _deferred_queue;
|
||||
std::vector<std::function<void(Engine&)>> _async_queue;
|
||||
const size_t _max_async_per_tick = 5; // prevent blocking, this should be finetuned
|
||||
|
||||
private:
|
||||
Graph& getGraph(update_phase_t phase);
|
||||
std::set<update_key_t>& getActiveSet(update_phase_t phase);
|
||||
|
||||
void runType(MM::Engine& engine, update_phase_t phase);
|
||||
|
||||
public:
|
||||
SingleThreadedDefault(void) = default;
|
||||
virtual ~SingleThreadedDefault(void);
|
||||
|
||||
const char* name(void) override { return "SingleThreadedDefault"; }
|
||||
|
||||
protected:
|
||||
bool registerService(const entt::id_type s_id, std::vector<UpdateCreationInfo>&& 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 addDeferred(std::function<void(Engine&)> function) override;
|
||||
|
||||
void addAsync(std::function<void(Engine&)> function) override;
|
||||
};
|
||||
|
||||
} // MM::UpdateStrategies
|
||||
|
@ -1,56 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "./update_strategy.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
namespace MM::UpdateStrategies {
|
||||
|
||||
// checks for cyles
|
||||
template<class T>
|
||||
class DependencyCheckDecorator : public T {
|
||||
static_assert(std::is_base_of_v<UpdateStrategy, T>);
|
||||
|
||||
public:
|
||||
//DependencyCheckDecorator(void) = default;
|
||||
|
||||
//template<typename ...Args>
|
||||
//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<UpdateCreationInfo>&& 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
|
||||
|
28
framework/engine/src/mm/update_strategies/dummy.hpp
Normal file
28
framework/engine/src/mm/update_strategies/dummy.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "./update_strategy.hpp"
|
||||
|
||||
// Rezz x Deadmau5 - hypnocurrency
|
||||
|
||||
namespace MM::UpdateStrategies {
|
||||
|
||||
// does nothing, even less then a mock class
|
||||
// for testing only!
|
||||
class Dummy : public UpdateStrategy {
|
||||
public:
|
||||
~Dummy(void) {}
|
||||
|
||||
const char* name(void) override { return "Dummy"; }
|
||||
|
||||
protected: // the engine facing interface
|
||||
bool enableService(const entt::id_type, std::vector<TaskInfo>&&) override { return true; }
|
||||
bool disableService(const entt::id_type) override { return true; }
|
||||
void doUpdate(MM::Engine&) override {}
|
||||
|
||||
public: // the user facing interface
|
||||
void addDeferred(std::function<void(Engine&)>) override {}
|
||||
void addAsync(std::function<void(Engine&)>) override {}
|
||||
};
|
||||
|
||||
} // MM::UpdateStrategies
|
||||
|
@ -0,0 +1,126 @@
|
||||
#include "./sequential_strategy.hpp"
|
||||
|
||||
#include "./tasking_utils.hpp"
|
||||
|
||||
#include <cassert>
|
||||
#include <functional>
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
namespace MM::UpdateStrategies {
|
||||
|
||||
//#define __L_ASSERT(cond) if (!(cond)) { return false; }
|
||||
void Sequential::doGraphSequential(MM::Engine& engine, const std::set<update_key_t>& tasks) {
|
||||
Graph graph = build_task_graph(tasks,
|
||||
[this](update_key_t key) -> const TaskInfo& {
|
||||
return _tasks.at(key);
|
||||
}
|
||||
);
|
||||
|
||||
walk_graph_sequential(graph, [this, &engine](update_key_t task_id ) {
|
||||
_tasks.at(task_id)._fn(engine);
|
||||
});
|
||||
}
|
||||
|
||||
Sequential::~Sequential(void) {
|
||||
}
|
||||
|
||||
bool Sequential::enableService(const entt::id_type service_id, std::vector<TaskInfo>&& task_array) {
|
||||
if (_service_tasks.count(service_id)) {
|
||||
// error
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& service = _service_tasks[service_id];
|
||||
|
||||
for (const auto& task : task_array) {
|
||||
_tasks.emplace(task._key, task);
|
||||
service.emplace(task._key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sequential::disableService(const entt::id_type service_id) {
|
||||
if (!_service_tasks.count(service_id)) {
|
||||
// error
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto task_id : _service_tasks[service_id]) {
|
||||
_tasks.erase(task_id);
|
||||
}
|
||||
|
||||
_service_tasks.erase(service_id);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Sequential::doUpdate(MM::Engine& engine) {
|
||||
ZoneScopedN("MM::UpdateStrategies::Sequential::doUpdate")
|
||||
// TODO: caching
|
||||
std::set<update_key_t> pre_tasks;
|
||||
std::set<update_key_t> main_tasks;
|
||||
std::set<update_key_t> post_tasks;
|
||||
|
||||
for (const auto& [task_id, task] : _tasks) {
|
||||
switch (task._phase) {
|
||||
case update_phase_t::PRE:
|
||||
pre_tasks.emplace(task_id);
|
||||
break;
|
||||
case update_phase_t::MAIN:
|
||||
main_tasks.emplace(task_id);
|
||||
break;
|
||||
case update_phase_t::POST:
|
||||
post_tasks.emplace(task_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
{ // pre
|
||||
ZoneScopedN("MM::UpdateStrategies::Sequential::doUpdate::pre")
|
||||
doGraphSequential(engine, pre_tasks);
|
||||
}
|
||||
|
||||
{ // main
|
||||
ZoneScopedN("MM::UpdateStrategies::Sequential::doUpdate::main")
|
||||
doGraphSequential(engine, main_tasks);
|
||||
}
|
||||
|
||||
{ // post
|
||||
ZoneScopedN("MM::UpdateStrategies::Sequential::doUpdate::post")
|
||||
doGraphSequential(engine, post_tasks);
|
||||
}
|
||||
|
||||
{ // simulate async
|
||||
ZoneScopedN("MM::UpdateStrategies::Sequential::doUpdate::async")
|
||||
for (size_t i = 0; !_async_queue.empty() && i < _max_async_per_tick; i++) {
|
||||
_async_queue.back()(engine);
|
||||
_async_queue.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ZoneScopedN("MM::UpdateStrategies::Sequential::doUpdate::deferred")
|
||||
if (!_deferred_queue.empty()) {
|
||||
for (auto&& fn : _deferred_queue) {
|
||||
fn(engine);
|
||||
}
|
||||
|
||||
_deferred_queue.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Sequential::addDeferred(std::function<void(Engine&)> function) {
|
||||
_deferred_queue.emplace_back(std::move(function));
|
||||
}
|
||||
|
||||
void Sequential::addAsync(std::function<void(Engine&)> function) {
|
||||
_async_queue.emplace_back(std::move(function));
|
||||
}
|
||||
|
||||
//#undef __L_ASSERT
|
||||
|
||||
} // MM::UpdateStrategies
|
||||
|
@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "./update_strategy.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
// HACK: welp
|
||||
// fwd
|
||||
namespace MM::Services {
|
||||
class ImGuiEngineTools;
|
||||
}
|
||||
|
||||
namespace MM::UpdateStrategies {
|
||||
|
||||
class Sequential : public MM::UpdateStrategies::UpdateStrategy {
|
||||
friend MM::Services::ImGuiEngineTools;
|
||||
|
||||
private:
|
||||
std::unordered_map<update_key_t, TaskInfo> _tasks;
|
||||
|
||||
// the tasks a service has registered
|
||||
std::unordered_map<entt::id_type, std::set<update_key_t>> _service_tasks;
|
||||
|
||||
std::vector<std::function<void(Engine&)>> _deferred_queue;
|
||||
std::vector<std::function<void(Engine&)>> _async_queue;
|
||||
|
||||
private: // utils
|
||||
void doGraphSequential(MM::Engine& engine, const std::set<update_key_t>& tasks);
|
||||
|
||||
public:
|
||||
Sequential(void) = default;
|
||||
virtual ~Sequential(void);
|
||||
|
||||
const char* name(void) override { return "Sequential"; }
|
||||
|
||||
protected: // engine facing interface
|
||||
bool enableService(const entt::id_type s_id, std::vector<TaskInfo>&& task_array) override;
|
||||
bool disableService(const entt::id_type s_id) override;
|
||||
|
||||
void doUpdate(MM::Engine& engine) override;
|
||||
|
||||
public: // user facing interface
|
||||
void addDeferred(std::function<void(Engine&)> function) override;
|
||||
|
||||
void addAsync(std::function<void(Engine&)> function) override;
|
||||
|
||||
size_t _max_async_per_tick = 5; // prevent blocking, this should be finetuned
|
||||
|
||||
protected: // engine tools interface
|
||||
void forEachTask(std::function<bool(TaskInfo&)> fn) override {
|
||||
for (auto&& t : _tasks) {
|
||||
if (!fn(t.second)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // MM::UpdateStrategies
|
||||
|
73
framework/engine/src/mm/update_strategies/tasking_utils.hpp
Normal file
73
framework/engine/src/mm/update_strategies/tasking_utils.hpp
Normal file
@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <mm/update_strategies/update_strategy.hpp>
|
||||
|
||||
namespace MM::UpdateStrategies {
|
||||
|
||||
// all tasks and it dependencies
|
||||
using Graph = std::unordered_map<update_key_t, std::set<update_key_t>>;
|
||||
|
||||
template<typename FN>
|
||||
void walk_graph_sequential(const Graph& graph, FN&& fn) {
|
||||
// set of all tasks, each completed task will be removed from this until none are left
|
||||
std::set<update_key_t> unworked_node_set{};
|
||||
for (auto it = graph.begin(); it != graph.end(); it++) {
|
||||
unworked_node_set.emplace(it->first);
|
||||
}
|
||||
|
||||
// TODO: check for inf-loops
|
||||
while (!unworked_node_set.empty()) {
|
||||
for (auto node_it = unworked_node_set.begin(); node_it != unworked_node_set.end();) {
|
||||
// check if deps are resolved
|
||||
bool resolved = true;
|
||||
for (const auto dep : graph.at(*node_it)) {
|
||||
if (unworked_node_set.count(dep)) {
|
||||
resolved = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (resolved) {
|
||||
// to task
|
||||
fn(*node_it);
|
||||
|
||||
node_it = unworked_node_set.erase(node_it);
|
||||
} else {
|
||||
node_it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename TaskGet>
|
||||
Graph build_task_graph(const std::set<update_key_t>& tasks, TaskGet&& task_get) {
|
||||
Graph graph;
|
||||
|
||||
// phase 1: create all tasks
|
||||
for (const auto task_id : tasks) {
|
||||
graph[task_id];
|
||||
}
|
||||
|
||||
// phase 2: do all dependencies (and dependents)
|
||||
for (const auto task_id : tasks) {
|
||||
auto& graph_node = graph[task_id];
|
||||
|
||||
{ // dependencies
|
||||
//graph_node.merge(task_get(task_id)._dependencies);
|
||||
const auto& dependencies = task_get(task_id)._dependencies;
|
||||
graph_node.insert(dependencies.cbegin(), dependencies.cend());
|
||||
}
|
||||
|
||||
// dependents
|
||||
for (const auto dependent : task_get(task_id)._dependents) {
|
||||
if (graph.count(dependent)) {
|
||||
graph[dependent].emplace(task_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
} // MM::UpdateStrategies
|
||||
|
@ -2,37 +2,89 @@
|
||||
|
||||
#include "../engine_fwd.hpp"
|
||||
|
||||
#include <entt/core/hashed_string.hpp>
|
||||
|
||||
#include <mm/services/service.hpp>
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
|
||||
// fwd / hack
|
||||
namespace MM::Services {
|
||||
class ImGuiEngineTools;
|
||||
}
|
||||
|
||||
namespace MM::UpdateStrategies {
|
||||
|
||||
using update_key_t = entt::id_type;
|
||||
|
||||
enum class update_phase_t {
|
||||
PRE, // for on-main-thread
|
||||
enum update_phase_t {
|
||||
PRE = 0, // for on-main-thread
|
||||
MAIN,
|
||||
POST // for on-main-thread
|
||||
};
|
||||
|
||||
struct UpdateCreationInfo {
|
||||
update_key_t key; // key for dependencies
|
||||
struct TaskInfo {
|
||||
update_key_t _key; // key for dependencies
|
||||
std::string _name; // unhashed key
|
||||
|
||||
std::string name; // for debugging
|
||||
update_phase_t _phase = update_phase_t::MAIN;
|
||||
|
||||
std::function<void(Engine&)> fn; // the actual payload
|
||||
|
||||
update_phase_t phase = update_phase_t::MAIN;
|
||||
|
||||
bool auto_enable = true; // whether this update is enabled with the service
|
||||
// updates we make dependents
|
||||
std::set<update_key_t> _dependents {};
|
||||
|
||||
// this update also depends on (in the same phase)
|
||||
std::vector<update_key_t> dependencies {};
|
||||
std::set<update_key_t> _dependencies {};
|
||||
|
||||
std::function<void(Engine&)> _fn; // the actual payload
|
||||
|
||||
public: // construction and assignment
|
||||
TaskInfo(void) = delete;
|
||||
TaskInfo(const TaskInfo&) = default;
|
||||
TaskInfo(TaskInfo&&) = default;
|
||||
|
||||
// TODO: is this right??
|
||||
TaskInfo& operator=(const TaskInfo&) = default;
|
||||
TaskInfo& operator=(TaskInfo&&) = default;
|
||||
|
||||
explicit TaskInfo(const std::string_view key_in) {
|
||||
key(key_in);
|
||||
}
|
||||
|
||||
public: // builder interface
|
||||
// TODO: does this need to be public?
|
||||
TaskInfo& key(const std::string_view key) {
|
||||
_name = key;
|
||||
_key = entt::hashed_string::value(key.data(), key.size());
|
||||
return *this;
|
||||
}
|
||||
|
||||
// default phase is main
|
||||
TaskInfo& phase(const update_phase_t phase) {
|
||||
_phase = phase;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template<typename Fn>
|
||||
TaskInfo& fn(Fn&& fn) {
|
||||
_fn = fn;
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskInfo& precede(const std::string_view key) {
|
||||
_dependents.emplace(entt::hashed_string::value(key.data(), key.size()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
TaskInfo& succeed(const std::string_view key) {
|
||||
_dependencies.emplace(entt::hashed_string::value(key.data(), key.size()));
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
// pure virtual interface for managing the update logic of the engine
|
||||
// pure virtual interface for managing the update(-task) logic of the engine
|
||||
class UpdateStrategy {
|
||||
public:
|
||||
virtual ~UpdateStrategy(void) {}
|
||||
@ -42,30 +94,17 @@ class UpdateStrategy {
|
||||
protected: // the engine facing interface
|
||||
friend ::MM::Engine;
|
||||
|
||||
// TODO: return something?
|
||||
virtual bool registerService(const entt::id_type s_id, std::vector<UpdateCreationInfo>&& 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;
|
||||
// - impossible dependencies?
|
||||
virtual bool enableService(const entt::id_type s_id, std::vector<TaskInfo>&& task_array) = 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.
|
||||
@ -77,6 +116,10 @@ class UpdateStrategy {
|
||||
// note: the US might decide to limit the amount of executed asyncs per tick (eg. when single-threaded)
|
||||
virtual void addAsync(std::function<void(Engine&)> function) = 0;
|
||||
//virtual std::future addAsync(std::function<void(Engine&)> function) = 0;
|
||||
protected: // engine tools inspector
|
||||
friend ::MM::Services::ImGuiEngineTools;
|
||||
|
||||
virtual void forEachTask(std::function<bool(TaskInfo&)> fn) = 0;
|
||||
};
|
||||
|
||||
} // MM::UpdateStrategies
|
||||
|
@ -1,7 +1,6 @@
|
||||
add_executable(engine_test
|
||||
update_strategy_test.cpp
|
||||
default_us_test.cpp
|
||||
#dependency_check_us_test.cpp
|
||||
#default_us_test.cpp
|
||||
|
||||
# old:
|
||||
#update_test.cpp
|
||||
|
@ -16,37 +16,25 @@ class MockUpdateStrategy : public MM::UpdateStrategies::UpdateStrategy {
|
||||
public:
|
||||
const char* name(void) override { return "MockUpdateStrategy"; }
|
||||
|
||||
MOCK_METHOD(
|
||||
bool,
|
||||
registerService,
|
||||
(const entt::id_type s_id, std::vector<MM::UpdateStrategies::UpdateCreationInfo>&& 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, enableService, (const entt::id_type s_id, std::vector<MM::UpdateStrategies::TaskInfo>&& task_array), (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, addDeferred, (std::function<void(MM::Engine&)> function), (override));
|
||||
MOCK_METHOD(void, addAsync, (std::function<void(MM::Engine&)> function), (override));
|
||||
|
||||
MOCK_METHOD(void, forEachTask, (std::function<bool(MM::UpdateStrategies::TaskInfo&)> 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(bool, enable, (MM::Engine& engine, std::vector<MM::UpdateStrategies::TaskInfo>& task_array), (override));
|
||||
MOCK_METHOD(void, disable, (MM::Engine& engine), (override));
|
||||
|
||||
MOCK_METHOD(std::vector<MM::UpdateStrategies::UpdateCreationInfo>, registerUpdates, (), (override));
|
||||
};
|
||||
|
||||
TEST(engine_mock, update_strategy_run) {
|
||||
@ -63,9 +51,7 @@ TEST(engine_mock, update_strategy_run) {
|
||||
TEST(engine_mock, service_update_strategy) {
|
||||
auto mock = std::make_unique<MockUpdateStrategy>();
|
||||
|
||||
EXPECT_CALL(*mock, registerService(_, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*mock, enableService(_))
|
||||
EXPECT_CALL(*mock, enableService(_, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*mock, disableService(_))
|
||||
.Times(1);
|
||||
@ -73,12 +59,9 @@ TEST(engine_mock, service_update_strategy) {
|
||||
class TmpMockService : public MockService {
|
||||
public:
|
||||
TmpMockService(void) {
|
||||
EXPECT_CALL(*this, registerUpdates())
|
||||
EXPECT_CALL(*this, enable(_, _))
|
||||
.Times(1);
|
||||
|
||||
EXPECT_CALL(*this, enable(_))
|
||||
.Times(1);
|
||||
ON_CALL(*this, enable(_))
|
||||
ON_CALL(*this, enable(_, _))
|
||||
.WillByDefault(Return(true));
|
||||
|
||||
EXPECT_CALL(*this, disable(_))
|
||||
@ -99,9 +82,7 @@ TEST(engine_mock, service_update_strategy) {
|
||||
TEST(engine_mock, service_update_strategy_run_1) {
|
||||
auto mock = std::make_unique<MockUpdateStrategy>();
|
||||
|
||||
EXPECT_CALL(*mock, registerService(_, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*mock, enableService(_))
|
||||
EXPECT_CALL(*mock, enableService(_, _))
|
||||
.Times(1);
|
||||
EXPECT_CALL(*mock, disableService(_))
|
||||
.Times(1);
|
||||
@ -109,23 +90,20 @@ TEST(engine_mock, service_update_strategy_run_1) {
|
||||
class TmpMockService : public MockService {
|
||||
public:
|
||||
explicit TmpMockService(void) {
|
||||
EXPECT_CALL(*this, registerUpdates())
|
||||
EXPECT_CALL(*this, enable)
|
||||
.Times(1);
|
||||
ON_CALL(*this, registerUpdates())
|
||||
.WillByDefault([]() -> std::vector<MM::UpdateStrategies::UpdateCreationInfo> {
|
||||
return {
|
||||
{
|
||||
"TmpMockService"_hs,
|
||||
"TmpMockService",
|
||||
[](MM::Engine&) {}
|
||||
}
|
||||
};
|
||||
});
|
||||
ON_CALL(*this, enable)
|
||||
.WillByDefault([](MM::Engine&, std::vector<MM::UpdateStrategies::TaskInfo>& task_array) -> bool {
|
||||
using MM::UpdateStrategies::TaskInfo;
|
||||
|
||||
EXPECT_CALL(*this, enable(_))
|
||||
.Times(1);
|
||||
ON_CALL(*this, enable(_))
|
||||
.WillByDefault(Return(true));
|
||||
task_array.push_back(
|
||||
TaskInfo{"TmpMockService"}
|
||||
//.precede("PreviousTask")
|
||||
.fn([](MM::Engine& engine) { (void)engine; })
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
EXPECT_CALL(*this, disable(_))
|
||||
.Times(1);
|
||||
|
Reference in New Issue
Block a user