mirror of
https://github.com/MadeOfJelly/MushMachine.git
synced 2025-07-06 01:26:45 +02:00
UpdateStrategy Refactor: 6. draft, now looks good, not fully tested
This commit is contained in:
225
framework/engine/src/mm/update_strategies/default_strategy.cpp
Normal file
225
framework/engine/src/mm/update_strategies/default_strategy.cpp
Normal 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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user