]> git.tdb.fi Git - libs/game.git/blobdiff - source/game/system.h
Schedule systems based on their declared dependencies
[libs/game.git] / source / game / system.h
index 3b379178c1b6e27972cd60f9bfa31441bafbebe2..09ee157a6953916522702cf0543ac75872f8ac78 100644 (file)
@@ -3,16 +3,65 @@
 
 #include <functional>
 #include <msp/time/timedelta.h>
+#include "accessguard.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,
+               DATA_MASK = 7,
+               RUN_BEFORE = 8,
+               RUN_AFTER = 16,
+               ORDER_MASK = 24
+       };
+
+       class Dependency
+       {
+               friend class System;
+
+       private:
+               DependencyFlags flags = NO_DEPENDENCY;
+               const Reflection::ClassBase &type;
+               void (*prepare)(Stage &) = nullptr;
+               void (*commit)(Stage &) = nullptr;
+               void (*unblock)(DependencyFlags) = nullptr;
+               void (*block)(DependencyFlags) = nullptr;
+
+       public:
+               Dependency(const Reflection::ClassBase &t): type(t) { }
+
+               DependencyFlags get_flags() const { return flags; }
+               const Reflection::ClassBase &get_type() const { return type; }
+       };
+
+       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 +69,16 @@ public:
 
        Stage &get_stage() const { return stage; }
 
+protected:
+       template<typename T>
+       void declare_dependency(DependencyFlags);
+
+public:
+       const std::vector<Dependency> &get_dependencies() const { return dependencies; }
+
+       void begin_tick();
        virtual void tick(Time::TimeDelta) = 0;
+       void end_tick();
        virtual void deferred_tick();
 
 protected:
@@ -28,6 +86,45 @@ protected:
        void defer(F &&f) { deferred_queue.emplace_back(std::forward<F>(f)); }
 };
 
+
+template<typename T>
+inline void System::declare_dependency(DependencyFlags flags)
+{
+       if((flags&DATA_MASK) && !std::is_base_of_v<Component, T>)
+               throw std::invalid_argument("System::declare_dependency");
+       if((flags&ORDER_MASK) && !std::is_base_of_v<System, 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);
+       if((flags&RUN_BEFORE) && (flags&RUN_AFTER))
+               throw std::logic_error("conflicting order 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(); })
+       {
+#ifdef DEBUG
+               dep.unblock = +[](DependencyFlags f){
+                       if(f&READ_OLD) AccessGuard::get_instance().unblock<AccessGuard::Read<typename T::Data>>();
+                       if(f&WRITE) AccessGuard::get_instance().unblock<AccessGuard::Write<typename T::Data>>();
+               };
+               dep.block = +[](DependencyFlags f){
+                       if(f&READ_OLD) AccessGuard::get_instance().block<AccessGuard::Read<typename T::Data>>();
+                       if(f&WRITE) AccessGuard::get_instance().block<AccessGuard::Write<typename T::Data>>();
+               };
+#endif
+               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