]> git.tdb.fi Git - libs/game.git/commitdiff
Implement base support for buffered components
authorMikko Rasa <tdb@tdb.fi>
Sun, 4 Dec 2022 13:29:57 +0000 (15:29 +0200)
committerMikko Rasa <tdb@tdb.fi>
Sun, 4 Dec 2022 20:20:07 +0000 (22:20 +0200)
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
source/game/component.h
source/game/stage.cpp
source/game/system.cpp
source/game/system.h

index aa5d9426ea1053d2e4532a91b6b407d5c166c10b..37bd20ad2eb62ade7b40ca407a5c59d21b0ab646 100644 (file)
@@ -14,11 +14,19 @@ template<typename T>
 class BasicSystem: public System
 {
 public:
-       BasicSystem(Stage &s): System(s) { }
+       BasicSystem(Stage &);
 
        void tick(Time::TimeDelta) override;
 };
 
+
+template<typename T>
+BasicSystem::BasicSystem(Stage &s):
+       System(s)
+{
+       declare_dependency<T>(UPDATE);
+}
+
 template<typename T>
 void BasicSystem<T>::tick(Time::TimeDelta dt)
 {
index 6b49dd19e8c6e3f64da05ceb550616d1e2c31624..44927ffd2e73c640f9d8a609f4e4b66984633572 100644 (file)
@@ -20,6 +20,39 @@ public:
        Handle<Entity> get_entity() const { return entity; }
 };
 
+template<typename T>
+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<Entity> 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<typename T>
+T &BufferedComponent<T>::write()
+{
+       if(!written && write_index!=read_index)
+       {
+               data[write_index] = data[read_index];
+               written = true;
+       }
+       return data[write_index];
+}
+
 } // namespace Msp::Game
 
 #endif
index 5663a71fadf8b36e1db21267724cf3d27e9530c2..43446bd9d8e7af9ba6d03f5fd13d9a49d2e72d42 100644 (file)
@@ -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)
index 87ba3b691eb20f5aa7311440b82d85cfe41d39e7..33157c3946990c6c64b25b1b8b6f0ee8782f35da 100644 (file)
@@ -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)
index 3b379178c1b6e27972cd60f9bfa31441bafbebe2..3d62c06276553821694651c0c5f15020845cc083 100644 (file)
@@ -3,16 +3,51 @@
 
 #include <functional>
 #include <msp/time/timedelta.h>
+#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<std::function<void()>> deferred_queue;
+       std::vector<Dependency> 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<typename T>
+       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>(f)); }
 };
 
+
+template<typename T>
+inline void System::declare_dependency(DependencyFlags flags)
+{
+       if(!std::is_base_of_v<Component, T>)
+               throw std::invalid_argument("System::declare_dependency");
+
+       const Reflection::ClassBase &type = stage.get_reflector().get_or_create_class<T>();
+       auto i = find_if(dependencies, [&type](const Dependency &d){ return &d.type==&type; });
+
+       if(i!=dependencies.end())
+               flags = static_cast<DependencyFlags>(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>([](T &c){ c.prepare_tick(); }); };
+                       dep.commit = +[](Stage &s){ s.iterate_objects<T>([](T &c){ c.commit_tick(); }); };
+               }
+}
+
 } // namespace Msp::Game
 
 #endif