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)
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)
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)
#include "eventsource.h"
#include "handle.h"
#include "reflection.h"
+#include "systemscheduler.h"
namespace Msp::Game {
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 &);
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>
--- /dev/null
+#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
--- /dev/null
+#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