#include <catch2/catch_test_macros.hpp> #include "dinkyecs.hpp" #include <iostream> #include <fmt/core.h> using namespace fmt; using DinkyECS::Entity; using std::string; struct Point { size_t x; size_t y; }; struct Player { string name; Entity eid; }; struct Position { Point location; }; struct Motion { int dx; int dy; bool random=false; }; struct Velocity { double x, y; }; struct Gravity { double level; }; struct DaGUI { int event; }; /* * Using a function catches instances where I'm not copying * the data into the world. */ void configure(DinkyECS::World &world, Entity &test) { println("---Configuring the base system."); Entity test2 = world.entity(); world.set<Position>(test, {10,20}); world.set<Velocity>(test, {1,2}); world.set<Position>(test2, {1,1}); world.set<Velocity>(test2, {9,19}); println("---- Setting up the player as a fact in the system."); auto player_eid = world.entity(); Player player_info{"Zed", player_eid}; // just set some player info as a fact with the entity id world.set_the<Player>(player_info); world.set<Velocity>(player_eid, {0,0}); world.set<Position>(player_eid, {0,0}); auto enemy = world.entity(); world.set<Velocity>(enemy, {0,0}); world.set<Position>(enemy, {0,0}); println("--- Creating facts (singletons)"); world.set_the<Gravity>({0.9}); } TEST_CASE("confirm ECS system works", "[ecs]") { DinkyECS::World world; Entity test = world.entity(); configure(world, test); Position &pos = world.get<Position>(test); REQUIRE(pos.location.x == 10); REQUIRE(pos.location.y == 20); Velocity &vel = world.get<Velocity>(test); REQUIRE(vel.x == 1); REQUIRE(vel.y == 2); world.query<Position>([](const auto &ent, auto &pos) { REQUIRE(ent > 0); REQUIRE(pos.location.x >= 0); REQUIRE(pos.location.y >= 0); }); world.query<Velocity>([](const auto &ent, auto &vel) { REQUIRE(ent > 0); REQUIRE(vel.x >= 0); REQUIRE(vel.y >= 0); }); println("--- Manually get the velocity in position system:"); world.query<Position>([&](const auto &ent, auto &pos) { Velocity &vel = world.get<Velocity>(ent); REQUIRE(ent > 0); REQUIRE(pos.location.x >= 0); REQUIRE(pos.location.y >= 0); REQUIRE(ent > 0); REQUIRE(vel.x >= 0); REQUIRE(vel.y >= 0); }); println("--- Query only entities with Position and Velocity:"); world.query<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) { Gravity &grav = world.get_the<Gravity>(); REQUIRE(grav.level <= 1.0f); REQUIRE(grav.level > 0.5f); REQUIRE(ent > 0); REQUIRE(pos.location.x >= 0); REQUIRE(pos.location.y >= 0); REQUIRE(ent > 0); REQUIRE(vel.x >= 0); REQUIRE(vel.y >= 0); }); // now remove Velocity REQUIRE(world.has<Velocity>(test)); world.remove<Velocity>(test); REQUIRE_THROWS(world.get<Velocity>(test)); REQUIRE(!world.has<Velocity>(test)); println("--- After remove test, should only result in test2:"); world.query<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) { auto &in_position = world.get<Position>(ent); auto &in_velocity = world.get<Velocity>(ent); REQUIRE(pos.location.x >= 0); REQUIRE(pos.location.y >= 0); REQUIRE(in_position.location.x == pos.location.x); REQUIRE(in_position.location.y == pos.location.y); REQUIRE(in_velocity.x == vel.x); REQUIRE(in_velocity.y == vel.y); }); } enum GUIEvent { HIT, MISS }; TEST_CASE("confirm that the event system works", "[ecs]") { DinkyECS::World world; DinkyECS::Entity player = world.entity(); world.send<GUIEvent>(GUIEvent::HIT, player, string{"hello"}); bool ready = world.has_event<GUIEvent>(); REQUIRE(ready == true); auto [event, entity, data] = world.recv<GUIEvent>(); REQUIRE(event == GUIEvent::HIT); REQUIRE(entity == player); auto &str_data = std::any_cast<string&>(data); REQUIRE(string{"hello"} == str_data); ready = world.has_event<GUIEvent>(); REQUIRE(ready == false); } TEST_CASE("confirm copying and constants", "[ecs-constants]") { DinkyECS::World world1; Player player_info{"Zed", world1.entity()}; world1.set_the<Player>(player_info); world1.set<Position>(player_info.eid, {10,10}); world1.make_constant(player_info.eid); DinkyECS::World world2; world1.clone_into(world2); auto &test1 = world1.get<Position>(player_info.eid); auto &test2 = world2.get<Position>(player_info.eid); REQUIRE(test2.location.x == test1.location.x); REQUIRE(test2.location.y == test1.location.y); // check for accidental reference test1.location.x = 100; REQUIRE(test2.location.x != test1.location.x); // test the facts copy over auto &player2 = world2.get_the<Player>(); REQUIRE(player2.eid == player_info.eid); } TEST_CASE("test serialization with nlohmann::json", "[ecs-serialize]") { /* DinkyECS::ComponentMap component_map; DinkyECS::Component<Position>(component_map); DinkyECS::Component<Velocity>(component_map); DinkyECS::Component<Motion>(component_map); DinkyECS::Component<Gravity>(component_map); DinkyECS::Component<DaGUI>(component_map); auto data = R"( [ { "_type": "Position", "location": { "x": 10, "y": 5 } }, { "_type": "Motion", "dx": 0, "dy": 1 }, { "_type": "Velocity", "x": 0.1, "y": 10.2 } ] )"_json; DinkyECS::World world; DinkyECS::Entity ent1 = world.entity(); DinkyECS::Entity ent2 = world.entity(); DinkyECS::configure(component_map, world, ent1, data); DinkyECS::configure(component_map, world, ent2, data); world.query<Position, Motion>([&](const auto ent, auto &pos, auto &motion) { fmt::println("entity: {}; position={},{} and motion={},{} motion.random={}", ent, pos.location.x, pos.location.y, motion.dx, motion.dy, motion.random); REQUIRE(pos.location.x == 10); REQUIRE(pos.location.y == 5); REQUIRE(motion.dx == 0); REQUIRE(motion.dy == 1); REQUIRE(motion.random == false); }); */ }