#include #include "dinkyecs.hpp" #include #include #include "point.hpp" using namespace fmt; using DinkyECS::Entity; using std::string; struct Player { string name; Entity eid; }; struct Position { Point location; }; DINKY_HAS_COMPONENT(Point, x, y); DINKY_HAS_COMPONENT(Position, location); struct Motion { int dx; int dy; bool random=false; }; DINKY_HAS_COMPONENT(Motion, dx, dy, random); struct Velocity { double x, y; }; DINKY_HAS_COMPONENT(Velocity, x, y); struct Gravity { double level; }; DINKY_HAS_COMPONENT(Gravity, level); struct DaGUI { int event; }; DINKY_HAS_COMPONENT(DaGUI, 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(test, {10,20}); world.set(test, {1,2}); world.set(test2, {1,1}); world.set(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_info); world.set(player_eid, {0,0}); world.set(player_eid, {0,0}); auto enemy = world.entity(); world.set(enemy, {0,0}); world.set(enemy, {0,0}); println("--- Creating facts (singletons)"); world.set_the({0.9}); } TEST_CASE("confirm ECS system works", "[ecs]") { DinkyECS::World world; Entity test = world.entity(); configure(world, test); Position &pos = world.get(test); REQUIRE(pos.location.x == 10); REQUIRE(pos.location.y == 20); Velocity &vel = world.get(test); REQUIRE(vel.x == 1); REQUIRE(vel.y == 2); world.query([](const auto &ent, auto &pos) { REQUIRE(ent > 0); REQUIRE(pos.location.x >= 0); REQUIRE(pos.location.y >= 0); }); world.query([](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([&](const auto &ent, auto &pos) { Velocity &vel = world.get(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([&](const auto &ent, auto &pos, auto &vel) { Gravity &grav = world.get_the(); 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(test)); world.remove(test); REQUIRE_THROWS(world.get(test)); REQUIRE(!world.has(test)); println("--- After remove test, should only result in test2:"); world.query([&](const auto &ent, auto &pos, auto &vel) { auto &in_position = world.get(ent); auto &in_velocity = world.get(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::HIT, player, string{"hello"}); bool ready = world.has_event(); REQUIRE(ready == true); auto [event, entity, data] = world.recv(); REQUIRE(event == GUIEvent::HIT); REQUIRE(entity == player); auto &str_data = std::any_cast(data); REQUIRE(string{"hello"} == str_data); ready = world.has_event(); REQUIRE(ready == false); } TEST_CASE("confirm copying and constants", "[ecs-constants]") { DinkyECS::World world1; Player player_info{"Zed", world1.entity()}; world1.set_the(player_info); world1.set(player_info.eid, {10,10}); world1.make_constant(player_info.eid); DinkyECS::World world2; world1.clone_into(world2); auto &test1 = world1.get(player_info.eid); auto &test2 = world2.get(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(); REQUIRE(player2.eid == player_info.eid); } TEST_CASE("test serialization with nlohmann::json", "[ecs-serialize]") { DinkyECS::ComponentMap component_map; DinkyECS::Component(component_map); DinkyECS::Component(component_map); DinkyECS::Component(component_map); DinkyECS::Component(component_map); DinkyECS::Component(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(world, component_map, ent1, data); DinkyECS::configure(world, component_map, ent2, data); world.query([&](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); }); }