]> git.tdb.fi Git - libs/game.git/commitdiff
Schedule systems based on their declared dependencies
authorMikko Rasa <tdb@tdb.fi>
Wed, 7 Dec 2022 10:02:59 +0000 (12:02 +0200)
committerMikko Rasa <tdb@tdb.fi>
Wed, 7 Dec 2022 10:02:59 +0000 (12:02 +0200)
source/game/stage.cpp
source/game/stage.h
source/game/system.h
source/game/systemscheduler.cpp [new file with mode: 0644]
source/game/systemscheduler.h [new file with mode: 0644]

index 7cad5bc250873b9e8c265ce3aacb617a9c8abcdb..3888410a43c8bc976034b753693b56ab35f16d72 100644 (file)
@@ -15,7 +15,8 @@ Stage::Stage(Reflection::Reflector &f, DataFile::Collection &r):
        resources(r),
        event_source(event_bus),
        event_observer(event_bus),
-       root(std::make_unique<Root>(*this))
+       root(std::make_unique<Root>(*this)),
+       scheduler(reflector)
 {
        event_observer.observe<Events::ComponentCreated>([this](auto &e){
                if(!active_camera)
@@ -30,7 +31,9 @@ Stage::~Stage()
 
 void Stage::remove_system(System &s)
 {
+       scheduler.remove_system(s);
        erase_if(systems, [&s](auto &p){ return p.get()==&s; });
+       pending_reschedule = true;
 }
 
 void Stage::set_active_camera(Handle<Camera> c)
@@ -57,22 +60,29 @@ void Stage::synthesize_initial_events(Handle<Entity> entity, EventObserver &targ
 
 void Stage::tick(Time::TimeDelta dt)
 {
+       if(pending_reschedule)
+       {
+               scheduler.schedule();
+               pending_reschedule = false;
+       }
+
        {
 #ifdef DEBUG
                AccessGuard::BlockForScope _block;
 #endif
-               for(const auto &s: systems)
-               {
-                       System::Activator act(*s);
-                       try
-                       {
-                               s->tick(dt);
-                       }
-                       catch(const invalid_access &exc)
+               for(const SystemScheduler::Group &g: scheduler.get_groups())
+                       for(System *s: g.systems)
                        {
-                               throw invalid_access(format("%s by %s", exc.what(), Debug::demangle(typeid(*s).name())));
+                               System::Activator act(*s);
+                               try
+                               {
+                                       s->tick(dt);
+                               }
+                               catch(const invalid_access &exc)
+                               {
+                                       throw invalid_access(format("%s by %s", exc.what(), Debug::demangle(typeid(*s).name())));
+                               }
                        }
-               }
        }
 
        for(const auto &s: systems)
index 283dc81c0747776875cfed9d0048aa720b1bfbba..0c2219bdc18b1d893851ed91467677c5a35fc4b1 100644 (file)
@@ -9,6 +9,7 @@
 #include "eventsource.h"
 #include "handle.h"
 #include "reflection.h"
+#include "systemscheduler.h"
 
 namespace Msp::Game {
 
@@ -33,7 +34,9 @@ private:
        to put it in a pool. */
        std::unique_ptr<Root> root;
        std::vector<std::unique_ptr<System>> systems;
+       SystemScheduler scheduler;
        Handle<Camera> active_camera;
+       bool pending_reschedule = false;
 
 public:
        Stage(Reflection::Reflector &, DataFile::Collection &);
@@ -78,8 +81,13 @@ inline void Stage::iterate_objects(const F &func)
 template<typename T, typename... Args>
 inline T &Stage::add_system(Args &&... args)
 {
-       systems.emplace_back(std::make_unique<T>(*this, std::forward<Args>(args)...));
-       return static_cast<T &>(*systems.back());
+       // Ensure that a reflected class exists for scheduling
+       reflector.get_or_create_class<T>();
+
+       auto &sys = systems.emplace_back(std::make_unique<T>(*this, std::forward<Args>(args)...));
+       scheduler.add_system(*sys);
+       pending_reschedule = true;
+       return static_cast<T &>(*sys);
 }
 
 template<typename T>
index 8c9c8b1316428258c2c4e3f84858386a75f73a20..09ee157a6953916522702cf0543ac75872f8ac78 100644 (file)
@@ -27,8 +27,11 @@ public:
                ORDER_MASK = 24
        };
 
-       struct Dependency
+       class Dependency
        {
+               friend class System;
+
+       private:
                DependencyFlags flags = NO_DEPENDENCY;
                const Reflection::ClassBase &type;
                void (*prepare)(Stage &) = nullptr;
@@ -36,7 +39,11 @@ public:
                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
@@ -67,6 +74,8 @@ protected:
        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();
diff --git a/source/game/systemscheduler.cpp b/source/game/systemscheduler.cpp
new file mode 100644 (file)
index 0000000..d6fb9a5
--- /dev/null
@@ -0,0 +1,154 @@
+#include "systemscheduler.h"
+#include <limits>
+#include <msp/core/algorithm.h>
+#include <msp/strings/format.h>
+#include "system.h"
+
+using namespace std;
+
+namespace Msp::Game {
+
+SystemScheduler::SystemScheduler(Reflection::Reflector &r):
+       reflector(r)
+{ }
+
+void SystemScheduler::add_system(System &s)
+{
+       nodes.emplace_back(&s, reflector.find_class(typeid(s)));
+}
+
+void SystemScheduler::remove_system(System &s)
+{
+       erase_if(nodes, [&s](const GraphNode &n){ return n.system==&s; });
+       for(auto i=groups.begin(); i!=groups.end(); )
+       {
+               erase_if(i->systems, [&s](System *gs){ return gs==&s; });
+               if(i->systems.empty())
+                       i = groups.erase(i);
+               else
+                       ++i;
+       }
+}
+
+void SystemScheduler::schedule()
+{
+       for(GraphNode &n: nodes)
+       {
+               n.scheduled_order = ~0U;
+               n.predecessors.clear();
+       }
+
+       for(auto i=nodes.begin(); i!=nodes.end(); ++i)
+               for(auto j=i; ++j!=nodes.end(); )
+               {
+                       int order = get_order(*i, *j);
+                       if(order<0)
+                               j->predecessors.push_back(&*i);
+                       else if(order>0)
+                               i->predecessors.push_back(&*j);
+               }
+
+       groups.clear();
+       unsigned unscheduled_count = nodes.size();
+       while(unscheduled_count)
+       {
+               unsigned order = groups.size();
+               Group &group = groups.emplace_back();
+               for(GraphNode &n: nodes)
+                       if(!~n.scheduled_order && ranges::all_of(n.predecessors, [order](GraphNode *p){ return p->scheduled_order<order; }))
+                       {
+                               group.systems.push_back(n.system);
+                               n.scheduled_order = order;
+                               --unscheduled_count;
+                       }
+       }
+}
+
+int SystemScheduler::get_order(const GraphNode &node1, const GraphNode &node2)
+{
+       if(int order = get_explicit_order(node1, node2))
+               return order;
+       return get_data_order(node1, node2);
+}
+
+int SystemScheduler::get_explicit_order(const GraphNode &node1, const GraphNode &node2)
+{
+       const vector<System::Dependency> &deps1 = node1.system->get_dependencies();
+       const vector<System::Dependency> &deps2 = node2.system->get_dependencies();
+
+       auto get_dep_type = [](const System::Dependency &d){ return &d.get_type(); };
+       auto i = ranges::find(deps1, node2.type, get_dep_type);
+       int flags1 = (i!=deps1.end() ? i->get_flags()&System::ORDER_MASK : 0);
+       i = ranges::find(deps2, node1.type, get_dep_type);
+       int flags2 = (i!=deps2.end() ? i->get_flags()&System::ORDER_MASK : 0);
+       if(flags1&flags2)
+               throw scheduling_error(format("Conflicting ordering between %s and %s", node1.type->get_name(), node2.type->get_name()));
+       else if(flags1==System::RUN_BEFORE || flags2==System::RUN_AFTER)
+               return -1;
+       else if(flags1==System::RUN_AFTER || flags2==System::RUN_BEFORE)
+               return 1;
+       else
+               return 0;
+}
+
+int SystemScheduler::get_data_order(const GraphNode &node1, const GraphNode &node2)
+{
+       int data_order = 0;
+       const Reflection::ClassBase *ordered_data = nullptr;
+       const Reflection::ClassBase *unordered_data = nullptr;
+       for(const System::Dependency &d1: node1.system->get_dependencies())
+       {
+               int flags1 = d1.get_flags()&System::DATA_MASK;
+               if(!flags1)
+                       continue;
+
+               for(const System::Dependency &d2: node2.system->get_dependencies())
+               {
+                       int flags2 = d2.get_flags()&System::DATA_MASK;
+                       if(!flags2)
+                               continue;
+
+                       const Reflection::ClassBase *common_type = nullptr;
+                       if(&d1.get_type()==&d2.get_type() || d1.get_type().is_base_of(d2.get_type()))
+                               common_type = &d1.get_type();
+                       else if(d2.get_type().is_base_of(d1.get_type()))
+                               common_type = &d2.get_type();
+                       else
+                               continue;
+
+                       int order = 0;
+                       // Read-only access to old data always comes first
+                       if(flags1==System::READ_OLD)
+                               order = -1;
+                       else if(flags2==System::READ_OLD)
+                               order = 1;
+                       // Read-only access to fresh data always comes last
+                       else if(flags1==System::READ_FRESH)
+                               order = 1;
+                       else if(flags2==System::READ_FRESH)
+                               order = -1;
+                       // Chained updates always come after non-chained writes
+                       else if(flags1==System::CHAINED_UPDATE && (flags2==System::WRITE || flags2==System::UPDATE))
+                               order = 1;
+                       else if(flags2==System::CHAINED_UPDATE && (flags1==System::WRITE || flags1==System::UPDATE))
+                               order = -1;
+                       else
+                               unordered_data = common_type;
+
+                       if(order && data_order && order!=data_order)
+                               throw scheduling_error(format("Ambiguous data ordering between %s and %s, accessing %s and %s",
+                                       node1.type->get_name(), node2.type->get_name(), ordered_data->get_name(), common_type->get_name()));
+
+                       data_order = order;
+                       ordered_data = common_type;
+               }
+       }
+
+       if(!data_order && unordered_data)
+               throw scheduling_error(format("Ambiguous data ordering between %s and %s, accessing %s",
+                       node1.type->get_name(), node2.type->get_name(), unordered_data->get_name()));
+
+       return data_order;
+}
+
+} // namespace Msp::Game
diff --git a/source/game/systemscheduler.h b/source/game/systemscheduler.h
new file mode 100644 (file)
index 0000000..2ab4804
--- /dev/null
@@ -0,0 +1,54 @@
+#ifndef MSP_GAME_SYSTEMSCHEDULER_H_
+#define MSP_GAME_SYSTEMSCHEDULER_H_
+
+#include <vector>
+#include "reflection.h"
+
+namespace Msp::Game {
+
+class System;
+
+class scheduling_error: public std::logic_error
+{
+public:
+       scheduling_error(const std::string &w): logic_error(w) { }
+};
+
+class SystemScheduler
+{
+public:
+       struct Group
+       {
+               std::vector<System *> systems;
+       };
+
+private:
+       struct GraphNode
+       {
+               System *system = nullptr;
+               Reflection::ClassBase *type = nullptr;
+               std::vector<GraphNode *> predecessors;
+               unsigned scheduled_order = 0;
+       };
+
+       Reflection::Reflector &reflector;
+       std::vector<GraphNode> nodes;
+       std::vector<Group> groups;
+
+public:
+       SystemScheduler(Reflection::Reflector &);
+
+       void add_system(System &);
+       void remove_system(System &);
+       void schedule();
+private:
+       static int get_order(const GraphNode &, const GraphNode &);
+       static int get_explicit_order(const GraphNode &, const GraphNode &);
+       static int get_data_order(const GraphNode &, const GraphNode &);
+public:
+       const std::vector<Group> &get_groups() const { return groups; }
+};
+
+} // namespace Msp::Game
+
+#endif