--- /dev/null
+#include <msp/game/component.h>
+#include <msp/game/system.h>
+#include <msp/game/systemscheduler.h>
+#include <msp/test/test.h>
+
+using namespace Msp;
+
+class SchedulerTests: public Test::RegisteredTest<SchedulerTests>
+{
+public:
+ SchedulerTests();
+
+ static const char *get_name() { return "SystemScheduler"; }
+
+private:
+ void unrelated_components();
+ void chained_update();
+ void parallel_reads();
+ void ambiguous_data_order();
+};
+
+
+namespace {
+
+struct Env
+{
+ ThreadPool threads;
+ Game::Reflection::Reflector reflector;
+ DataFile::Collection res;
+ Game::Stage stage;
+
+ Env():
+ threads(1),
+ stage("stage", threads, reflector, res)
+ { }
+};
+
+struct A: Game::Component { };
+struct B: Game::Component { };
+struct C: Game::Component { };
+
+template<typename T, Game::System::DependencyFlags F>
+struct Dep
+{
+ using Type = T;
+ static constexpr Game::System::DependencyFlags FLAGS = F;
+};
+
+template<typename... Ds>
+struct Sys: Game::System
+{
+ template<typename D, typename... Rest>
+ void apply_deps()
+ {
+ declare_dependency<typename D::Type>(D::FLAGS);
+ if constexpr(sizeof...(Rest)>0)
+ apply_deps<Rest...>();
+ }
+
+ Sys(Game::Stage &s):
+ System(s)
+ {
+ apply_deps<Ds...>();
+ }
+
+ void tick(Time::TimeDelta) override { }
+};
+
+using enum Game::System::DependencyFlags;
+
+} // anonymous namespace
+
+
+SchedulerTests::SchedulerTests()
+{
+ add(&SchedulerTests::unrelated_components, "Unrelated components");
+ add(&SchedulerTests::chained_update, "Chained update");
+ add(&SchedulerTests::parallel_reads, "Parallel reads");
+ add(&SchedulerTests::ambiguous_data_order, "Ambiguous data order").expect_throw<Game::scheduling_error>();
+}
+
+void SchedulerTests::unrelated_components()
+{
+ Env env;
+
+ auto &sys1 = env.stage.add_system<Sys<Dep<A, READ_OLD>>>();
+ auto &sys2 = env.stage.add_system<Sys<Dep<B, READ_OLD>>>();
+
+ Game::SystemScheduler scheduler(env.reflector);
+ scheduler.add_system(sys1);
+ scheduler.add_system(sys2);
+
+ scheduler.schedule();
+ const auto &graph = scheduler.get_graph();
+
+ EXPECT_EQUAL(graph.size(), 2);
+ EXPECT_EQUAL(graph[0].prerequisites, 0);
+ EXPECT_EQUAL(graph[1].prerequisites, 0);
+}
+
+void SchedulerTests::chained_update()
+{
+ Env env;
+
+ auto &sys1 = env.stage.add_system<Sys<Dep<A, UPDATE>>>();
+ auto &sys2 = env.stage.add_system<Sys<Dep<A, CHAINED_UPDATE>>>();
+ auto &sys3 = env.stage.add_system<Sys<Dep<A, READ_FRESH>>>();
+
+ Game::SystemScheduler scheduler(env.reflector);
+ scheduler.add_system(sys1);
+ scheduler.add_system(sys2);
+ scheduler.add_system(sys3);
+
+ scheduler.schedule();
+ const auto &graph = scheduler.get_graph();
+
+ EXPECT_EQUAL(graph.size(), 3);
+ EXPECT_EQUAL(graph[0].prerequisites, 0);
+ EXPECT_EQUAL(graph[1].prerequisites, 1);
+ EXPECT_EQUAL(graph[2].prerequisites, 3);
+}
+
+void SchedulerTests::parallel_reads()
+{
+ Env env;
+
+ auto &sys1 = env.stage.add_system<Sys<Dep<A, READ_OLD>>>();
+ auto &sys2 = env.stage.add_system<Sys<Dep<A, READ_OLD>, Dep<B, UPDATE>>>();
+ auto &sys3 = env.stage.add_system<Sys<Dep<A, READ_OLD>, Dep<C, UPDATE>>>();
+ auto &sys4 = env.stage.add_system<Sys<Dep<B, READ_FRESH>, Dep<C, READ_FRESH>>>();
+
+ Game::SystemScheduler scheduler(env.reflector);
+ scheduler.add_system(sys1);
+ scheduler.add_system(sys2);
+ scheduler.add_system(sys3);
+ scheduler.add_system(sys4);
+
+ scheduler.schedule();
+ const auto &graph = scheduler.get_graph();
+
+ EXPECT_EQUAL(graph.size(), 4);
+ EXPECT_EQUAL(graph[0].prerequisites, 0);
+ EXPECT_EQUAL(graph[1].prerequisites, 0);
+ EXPECT_EQUAL(graph[2].prerequisites, 0);
+ EXPECT_EQUAL(graph[3].prerequisites, 6);
+}
+
+void SchedulerTests::ambiguous_data_order()
+{
+ Env env;
+
+ auto &sys1 = env.stage.add_system<Sys<Dep<A, UPDATE>>>();
+ auto &sys2 = env.stage.add_system<Sys<Dep<A, UPDATE>>>();
+
+ Game::SystemScheduler scheduler(env.reflector);
+ scheduler.add_system(sys1);
+ scheduler.add_system(sys2);
+
+ scheduler.schedule();
+}