From 96efc990c12fcbfe4d06bd86c48567247bbf0ef4 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Sat, 8 Feb 2025 12:04:53 -0500 Subject: [PATCH] Brought in nuke's idea for json serialize now to use it. --- dinkyecs.cpp | 11 ++ dinkyecs.hpp | 27 ++++- meson.build | 4 +- tests/dinkyecs.cpp | 250 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 290 insertions(+), 2 deletions(-) create mode 100644 dinkyecs.cpp create mode 100644 tests/dinkyecs.cpp diff --git a/dinkyecs.cpp b/dinkyecs.cpp new file mode 100644 index 0000000..0bfae37 --- /dev/null +++ b/dinkyecs.cpp @@ -0,0 +1,11 @@ +#include "dinkyecs.hpp" + +namespace DinkyECS { + void configure(World& world, const ComponentMap& component_map, Entity ent, json& data) { + for (auto &i : data) { + assert(i.contains("_type") && i["_type"].is_string()); + assert(component_map.contains(i["_type"])); + component_map.at(i["_type"])(world, ent, i); + } + } +} diff --git a/dinkyecs.hpp b/dinkyecs.hpp index 0d77ccc..18353d4 100644 --- a/dinkyecs.hpp +++ b/dinkyecs.hpp @@ -7,10 +7,13 @@ #include #include #include -#include "tser.hpp" +#include +#include +#include #include "dbc.hpp" namespace DinkyECS { + using namespace nlohmann; typedef unsigned long Entity; @@ -155,4 +158,26 @@ namespace DinkyECS { return !queue.empty(); } }; + + template struct NameOf; + + using ReflFuncSignature = std::function; + using ComponentMap = std::unordered_map; + +#define DINKY_HAS_COMPONENT(COMPONENT, ...) \ + NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(COMPONENT, __VA_ARGS__); \ + template <> struct DinkyECS::NameOf { \ + static constexpr const char *name = #COMPONENT; \ + }; + + template void Component(ComponentMap &m) { + m[NameOf::name] = [](DinkyECS::World& world, DinkyECS::Entity ent, nlohmann::json &j) { + COMPONENT c; + from_json(j, c); + world.set(ent, c); + }; + } + + void configure(World& world, const ComponentMap& component_map, Entity ent, json& data); + } diff --git a/meson.build b/meson.build index c331998..a42c4cb 100644 --- a/meson.build +++ b/meson.build @@ -51,6 +51,7 @@ sources = [ 'config.cpp', 'dbc.cpp', 'devices.cpp', + 'dinkyecs.cpp', 'gui.cpp', 'inventory.cpp', 'levelmanager.cpp', @@ -76,6 +77,8 @@ sources = [ executable('runtests', sources + [ 'tests/base.cpp', 'tests/dbc.cpp', + 'tests/dinkyecs.cpp', + 'tests/fsm.cpp', 'tests/inventory.cpp', 'tests/levelmanager.cpp', 'tests/lighting.cpp', @@ -84,7 +87,6 @@ executable('runtests', sources + [ 'tests/pathing.cpp', 'tests/spatialmap.cpp', 'tests/tilemap.cpp', - 'tests/fsm.cpp', 'tests/worldbuilder.cpp', ], override_options: exe_defaults, dependencies: dependencies + [catch2]) diff --git a/tests/dinkyecs.cpp b/tests/dinkyecs.cpp new file mode 100644 index 0000000..847034c --- /dev/null +++ b/tests/dinkyecs.cpp @@ -0,0 +1,250 @@ +#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); + }); +}