From 3f87d199113a8f8cdbb7fe5932ba562a121b0250 Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Tue, 29 Oct 2024 17:27:12 -0400 Subject: [PATCH] Simple event system for entities in the world. --- dinkyecs.hpp | 34 ++++++++++++++--- status.txt | 1 + tests/dinkyecs.cpp | 91 +++++++++++++++++++++++++++++++--------------- 3 files changed, 92 insertions(+), 34 deletions(-) diff --git a/dinkyecs.hpp b/dinkyecs.hpp index 9e8011c..2690379 100644 --- a/dinkyecs.hpp +++ b/dinkyecs.hpp @@ -5,6 +5,9 @@ #include #include #include +#include +#include + namespace DinkyECS { @@ -12,10 +15,18 @@ namespace DinkyECS { typedef std::unordered_map EntityMap; + struct Event { + int event = 0; + Entity entity = 0; + }; + + typedef std::queue EventQueue; + struct World { unsigned long entity_count = 0; std::unordered_map $components; std::unordered_map $facts; + std::unordered_map $events; Entity entity() { return ++entity_count; @@ -26,6 +37,11 @@ namespace DinkyECS { return $components[std::type_index(typeid(Comp))]; } + template + EventQueue& queue_map_for() { + return $events[std::type_index(typeid(Comp))]; + } + template void remove(Entity ent) { EntityMap &map = entity_map_for(); @@ -81,16 +97,24 @@ namespace DinkyECS { } } - /* template - void send(int event, std::any data) { - + void send(int event, Entity entity) { + EventQueue &queue = queue_map_for(); + queue.push({event, entity}); } template - std::tuple recv() { + Event recv() { + EventQueue &queue = queue_map_for(); + Event evt = queue.front(); + queue.pop(); + return evt; + } + template + bool has_event() { + EventQueue &queue = queue_map_for(); + return !queue.empty(); } - */ }; } diff --git a/status.txt b/status.txt index 09f4767..226048f 100644 --- a/status.txt +++ b/status.txt @@ -7,6 +7,7 @@ NOTES: TODO: +* Rewrite collider to return a real struct not tuple. * Write a test that generates a ton of maps then confirms there's a path from one room to every other room? * Lua integration? diff --git a/tests/dinkyecs.cpp b/tests/dinkyecs.cpp index 3176e4a..363a29d 100644 --- a/tests/dinkyecs.cpp +++ b/tests/dinkyecs.cpp @@ -1,5 +1,5 @@ +#include #include "dinkyecs.hpp" - #include #include @@ -23,6 +23,10 @@ struct Gravity { double level; }; +struct DaGUI { + int event; +}; + /* * Using a function catches instances where I'm not copying * the data into the world. @@ -35,7 +39,7 @@ void configure(DinkyECS::World &world, Entity &test) { world.set(test, {1,2}); world.set(test2, {1,1}); - world.set(test2, {10,20}); + world.set(test2, {9,19}); println("---- Setting up the player as a fact in the system."); @@ -55,62 +59,91 @@ void configure(DinkyECS::World &world, Entity &test) { world.set_the({0.9}); } -int main() { +TEST_CASE("confirm ECS system works", "[ecs]") { DinkyECS::World world; Entity test = world.entity(); configure(world, test); Position &pos = world.get(test); - println("GOT POS x={}, y={}", pos.x, pos.y); + REQUIRE(pos.x == 10); + REQUIRE(pos.y == 20); Velocity &vel = world.get(test); - println("GOT VELOCITY x={}, y={}", vel.x, vel.y); + REQUIRE(vel.x == 1); + REQUIRE(vel.y == 2); - println("--- Position only system:"); world.query([](const auto &ent, auto &pos) { - println("entity={}, pos.x={}, pos.y={}", ent, pos.x, pos.y); + REQUIRE(ent > 0); + REQUIRE(pos.x >= 0); + REQUIRE(pos.y >= 0); }); - println("--- Velocity only system:"); - world.query([](const auto &, auto &vel) { - println("vel.x={}, vel.y={}", vel.x, vel.y); + 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); - println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y); + + REQUIRE(ent > 0); + REQUIRE(pos.x >= 0); + REQUIRE(pos.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(); - println("grav={}, entity={}, vel.x, vel.y, pos.x={}, pos.y={}", grav.level, ent, vel.x, vel.y, pos.x, pos.y); + REQUIRE(grav.level <= 1.0f); + REQUIRE(grav.level > 0.5f); + REQUIRE(ent > 0); + REQUIRE(pos.x >= 0); + REQUIRE(pos.y >= 0); + REQUIRE(ent > 0); + REQUIRE(vel.x >= 0); + REQUIRE(vel.y >= 0); }); // now remove Velocity world.remove(test); + REQUIRE_THROWS(world.get(test)); println("--- After remove test, should only result in test2:"); world.query([&](const auto &ent, auto &pos, auto &vel) { - println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y); + REQUIRE(pos.x >= 0); + REQUIRE(pos.y >= 0); }); +} + +enum FakeEvent { + HIT_EVENT, MISS_EVENT +}; + +TEST_CASE("confirm that the event system works", "[ecs]") { + DinkyECS::World world; + DinkyECS::Entity gui_ent = world.entity(); + DinkyECS::Entity player = world.entity(); + DaGUI gui{384}; + + world.set(gui_ent, gui); + DaGUI &gui_test = world.get(gui_ent); + REQUIRE(gui.event == gui_test.event); + + world.send(FakeEvent::HIT_EVENT, player); + + bool ready = world.has_event(); + REQUIRE(ready == true); + + auto [event, entity] = world.recv(); + REQUIRE(event == FakeEvent::HIT_EVENT); + REQUIRE(entity == player); - // to avoid repeatedly getting the player just make a closure with it - // QUESTION: could I just capture it and not have the double function wrapping? - auto playerVsEnemies = [&]() { - auto& player = world.get_the(); // grabbed it - world.query([&](const auto &ent, auto &pos) { - if(player.eid != ent) { - println("{} is enemy attacking player {}", ent, player.name); - } else { - println("{} is player", player.name); - } - }); - }; - - playerVsEnemies(); - - return 0; + ready = world.has_event(); + REQUIRE(ready == false); }