From: Mikko Rasa Date: Sun, 4 Dec 2022 13:29:57 +0000 (+0200) Subject: Implement base support for buffered components X-Git-Url: http://git.tdb.fi/?p=libs%2Fgame.git;a=commitdiff_plain;h=a99b57a74252fd3de649d544d070b747f91fcf4d 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. --- 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