UpdateStrategy Refactor: 6. draft, now looks good, not fully tested

This commit is contained in:
2020-12-12 16:55:27 +01:00
parent bab5552e6f
commit c1ae30c89c
18 changed files with 990 additions and 290 deletions

View File

@ -1,5 +1,7 @@
#include "./engine.hpp"
#include <mm/update_strategies/default_strategy.hpp>
#include <chrono>
#include <algorithm>
@ -16,71 +18,7 @@
namespace MM {
Engine::FunctionDataHandle Engine::addUpdate(std::function<void(Engine&)> function) {
if (!function) {
LOG_ERROR("could not add Update, empty function!");
return {};
}
FunctionDataHandle r = _update_functions.emplace_back(std::make_shared<FunctionDataType>(function));
_update_functions_modified = true;
return r;
}
Engine::FunctionDataHandle Engine::addFixedUpdate(std::function<void(Engine&)> function) {
if (!function) {
LOG_ERROR("could not add fixedUpdate, empty function!");
return {};
}
FunctionDataHandle r = _fixed_update_functions.emplace_back(std::make_shared<FunctionDataType>(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<void(Engine&)> function) {
_fixed_defered.emplace_back(function);
}
void Engine::traverseUpdateFunctions(std::vector<std::shared_ptr<FunctionDataType>>& 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<MM::UpdateStrategies::SingleThreadedDefault>();
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 <emscripten.h>
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<std::chrono::nanoseconds>(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<std::chrono::nanoseconds>(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<long long int>(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<T>);
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

View File

@ -1,5 +1,7 @@
#pragma once
#include "./engine_fwd.hpp"
#include <entt/core/family.hpp>
#include <functional>
@ -12,6 +14,7 @@
#include <cassert>
#include <mm/services/service.hpp>
#include <mm/update_strategies/update_strategy.hpp>
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<UpdateStrategies::UpdateStrategy> _update_strategy;
public:
struct FunctionPriorityDataStructure {
std::function<void(Engine&)> f;
int16_t priority = 0; // 0 is equal to scene update, the higher the prio the earlier
std::string name;
explicit FunctionPriorityDataStructure(std::function<void(Engine&)> 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<FunctionDataType>; // important: its a pointer
// return nullptr on error
[[nodiscard]] FunctionDataHandle addUpdate(std::function<void(Engine&)> function);
[[nodiscard]] FunctionDataHandle addFixedUpdate(std::function<void(Engine&)> 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<void(Engine&)> function);
private:
std::vector<std::shared_ptr<FunctionDataType>> _update_functions;
bool _update_functions_modified;
std::vector<std::shared_ptr<FunctionDataType>> _fixed_update_functions;
bool _fixed_update_functions_modified;
std::vector<std::function<void(Engine&)>> _fixed_defered;
private:
void traverseUpdateFunctions(std::vector<std::shared_ptr<FunctionDataType>>& 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<UpdateStrategies::UpdateStrategy> 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<service_family::family_type, service_family::family_type> _implementation_provider;
public:
template<typename T>
constexpr auto type(void) {
@ -117,6 +92,12 @@ class Engine {
_service_add_order.emplace_back(service_family::type<T>);
// add updates to update strategy
_update_strategy->registerService(
service_family::type<T>,
ss_entry.get()->second->registerUpdates()
);
return (T&)*ss_entry.get()->second.get();
}

View File

@ -1,10 +1,10 @@
#pragma once
// this is a forwarding header
#include <entt/fwd.hpp>
namespace MM {
namespace UpdateStrategies { class UpdateStrategy; }
class Engine;
using Entity = entt::entity;
using Scene = entt::basic_registry<Entity>;

View File

@ -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

View File

@ -1,26 +0,0 @@
#pragma once
#include "./service.hpp"
#include <mm/engine.hpp>
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

View File

@ -1,8 +1,13 @@
#pragma once
#include <vector>
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<UpdateStrategies::UpdateCreationInfo> registerUpdates(void) { return {}; }
};
} // Services

View File

@ -0,0 +1,225 @@
#include "./default_strategy.hpp"
#include <cassert>
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);
if (!_defered_queue.empty()) {
for (auto&& fn : _defered_queue) {
fn(engine);
}
_defered_queue.clear();
}
}
void SingleThreadedDefault::addDefered(std::function<void(Engine&)> function) {
_defered_queue.emplace_back(std::move(function));
}
#undef __L_ASSERT
} // MM::UpdateStrategies

View File

@ -0,0 +1,70 @@
#pragma once
#include "./update_strategy.hpp"
#include <memory>
#include <unordered_map>
#include <vector>
#include <set>
namespace MM::UpdateStrategies {
class SingleThreadedDefault : public MM::UpdateStrategies::UpdateStrategy {
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&)>> _defered_queue;
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);
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 addDefered(std::function<void(Engine&)> function) override;
};
} // MM::UpdateStrategies

View File

@ -0,0 +1,56 @@
#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

View File

@ -0,0 +1,78 @@
#pragma once
#include "../engine_fwd.hpp"
#include <mm/services/service.hpp>
#include <functional>
#include <string>
#include <vector>
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<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
// this update also depends on (in the same phase)
std::vector<update_key_t> 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<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;
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<void(Engine&)> function) = 0;
virtual void addDefered(std::function<void(Engine&)> function) = 0; // called after everything
//virtual std::future addAsync(std::function<void(Engine&)> function) = 0;
};
} // MM::UpdateStrategies