From a99b57a74252fd3de649d544d070b747f91fcf4d Mon Sep 17 00:00:00 2001 From: Mikko Rasa Date: Sun, 4 Dec 2022 15:29:57 +0200 Subject: [PATCH] Implement base support for buffered components They hold two copies of data, one being read from and the other written to. This makes it easier to write concurrent and order-independent code. Systems can declare dependencies to such components to have their prepare and commit functions called automatically. --- source/game/basicsystem.h | 10 +++++- source/game/component.h | 33 +++++++++++++++++++ source/game/stage.cpp | 3 ++ source/game/system.cpp | 25 ++++++++++++++ source/game/system.h | 69 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 137 insertions(+), 3 deletions(-) diff --git a/source/game/basicsystem.h b/source/game/basicsystem.h index aa5d942..37bd20a 100644 --- a/source/game/basicsystem.h +++ b/source/game/basicsystem.h @@ -14,11 +14,19 @@ template class BasicSystem: public System { public: - BasicSystem(Stage &s): System(s) { } + BasicSystem(Stage &); void tick(Time::TimeDelta) override; }; + +template +BasicSystem::BasicSystem(Stage &s): + System(s) +{ + declare_dependency(UPDATE); +} + template void BasicSystem::tick(Time::TimeDelta dt) { diff --git a/source/game/component.h b/source/game/component.h index 6b49dd1..44927ff 100644 --- a/source/game/component.h +++ b/source/game/component.h @@ -20,6 +20,39 @@ public: Handle get_entity() const { return entity; } }; +template +class BufferedComponent: public Component +{ +public: + using Data = T; + +protected: + T data[2]; + uint8_t read_index = 0; + uint8_t write_index = 0; + bool written = false; + + BufferedComponent(Handle e): Component(e) { } + + const T &read() const { return data[read_index]; } + T &write(); + +public: + virtual void prepare_tick() { write_index = 1-read_index; written = false; } + virtual void commit_tick() { if(written) read_index = write_index; } +}; + +template +T &BufferedComponent::write() +{ + if(!written && write_index!=read_index) + { + data[write_index] = data[read_index]; + written = true; + } + return data[write_index]; +} + } // namespace Msp::Game #endif diff --git a/source/game/stage.cpp b/source/game/stage.cpp index 5663a71..43446bd 100644 --- a/source/game/stage.cpp +++ b/source/game/stage.cpp @@ -60,7 +60,10 @@ void Stage::tick(Time::TimeDelta dt) AccessGuard::BlockForScope _block;; #endif for(const auto &s: systems) + { + System::Activator act(*s); s->tick(dt); + } } for(const auto &s: systems) diff --git a/source/game/system.cpp b/source/game/system.cpp index 87ba3b6..33157c3 100644 --- a/source/game/system.cpp +++ b/source/game/system.cpp @@ -1,7 +1,32 @@ #include "system.h" +using namespace std; + namespace Msp::Game { +thread_local System *System::active = nullptr; + +void System::begin_tick() +{ + if(active) + throw logic_error("System::active != nullptr"); + active = this; + + for(const Dependency &d: dependencies) + if(d.prepare) + d.prepare(stage); +} + +void System::end_tick() +{ + for(const Dependency &d: dependencies) + if(d.commit) + d.commit(stage); + + if(active==this) + active = nullptr; +} + void System::deferred_tick() { for(const auto &f: deferred_queue) diff --git a/source/game/system.h b/source/game/system.h index 3b37917..3d62c06 100644 --- a/source/game/system.h +++ b/source/game/system.h @@ -3,16 +3,51 @@ #include #include +#include "component.h" +#include "reflection.h" +#include "stage.h" namespace Msp::Game { -class Stage; - class System { +public: + enum DependencyFlags + { + NO_DEPENDENCY = 0, + READ_OLD = 1, + READ_FRESH = 3, + WRITE = 4, + UPDATE = READ_OLD | WRITE, + CHAINED_UPDATE = READ_FRESH | WRITE + }; + + struct Dependency + { + DependencyFlags flags = NO_DEPENDENCY; + const Reflection::ClassBase &type; + void (*prepare)(Stage &) = nullptr; + void (*commit)(Stage &) = nullptr; + + Dependency(const Reflection::ClassBase &t): type(t) { } + }; + + class Activator: public NonCopyable + { + private: + System &system; + + public: + Activator(System &s): system(s) { system.begin_tick(); } + ~Activator() { system.end_tick(); } + }; + protected: Stage &stage; std::vector> deferred_queue; + std::vector dependencies; + + static thread_local System *active; System(Stage &s): stage(s) { } public: @@ -20,7 +55,14 @@ public: Stage &get_stage() const { return stage; } +protected: + template + void declare_dependency(DependencyFlags); + +public: + void begin_tick(); virtual void tick(Time::TimeDelta) = 0; + void end_tick(); virtual void deferred_tick(); protected: @@ -28,6 +70,29 @@ protected: void defer(F &&f) { deferred_queue.emplace_back(std::forward(f)); } }; + +template +inline void System::declare_dependency(DependencyFlags flags) +{ + if(!std::is_base_of_v) + throw std::invalid_argument("System::declare_dependency"); + + const Reflection::ClassBase &type = stage.get_reflector().get_or_create_class(); + auto i = find_if(dependencies, [&type](const Dependency &d){ return &d.type==&type; }); + + if(i!=dependencies.end()) + flags = static_cast(flags|i->flags); + + Dependency &dep = (i!=dependencies.end() ? *i : dependencies.emplace_back(type)); + dep.flags = flags; + if constexpr(requires(T &c) { typename T::Data; c.prepare_tick(); c.commit_tick(); }) + if(flags&WRITE) + { + dep.prepare = +[](Stage &s){ s.iterate_objects([](T &c){ c.prepare_tick(); }); }; + dep.commit = +[](Stage &s){ s.iterate_objects([](T &c){ c.commit_tick(); }); }; + } +} + } // namespace Msp::Game #endif -- 2.43.0