Simple event system for entities in the world.

main
Zed A. Shaw 3 weeks ago
parent ea6cf1362b
commit 3f87d19911
  1. 34
      dinkyecs.hpp
  2. 1
      status.txt
  3. 87
      tests/dinkyecs.cpp

@ -5,6 +5,9 @@
#include <typeinfo> #include <typeinfo>
#include <unordered_map> #include <unordered_map>
#include <any> #include <any>
#include <tuple>
#include <queue>
namespace DinkyECS { namespace DinkyECS {
@ -12,10 +15,18 @@ namespace DinkyECS {
typedef std::unordered_map<Entity, std::any> EntityMap; typedef std::unordered_map<Entity, std::any> EntityMap;
struct Event {
int event = 0;
Entity entity = 0;
};
typedef std::queue<Event> EventQueue;
struct World { struct World {
unsigned long entity_count = 0; unsigned long entity_count = 0;
std::unordered_map<std::type_index, EntityMap> $components; std::unordered_map<std::type_index, EntityMap> $components;
std::unordered_map<std::type_index, std::any> $facts; std::unordered_map<std::type_index, std::any> $facts;
std::unordered_map<std::type_index, EventQueue> $events;
Entity entity() { Entity entity() {
return ++entity_count; return ++entity_count;
@ -26,6 +37,11 @@ namespace DinkyECS {
return $components[std::type_index(typeid(Comp))]; return $components[std::type_index(typeid(Comp))];
} }
template <typename Comp>
EventQueue& queue_map_for() {
return $events[std::type_index(typeid(Comp))];
}
template <typename Comp> template <typename Comp>
void remove(Entity ent) { void remove(Entity ent) {
EntityMap &map = entity_map_for<Comp>(); EntityMap &map = entity_map_for<Comp>();
@ -81,16 +97,24 @@ namespace DinkyECS {
} }
} }
/*
template<typename Comp> template<typename Comp>
void send(int event, std::any data) { void send(int event, Entity entity) {
EventQueue &queue = queue_map_for<Comp>();
queue.push({event, entity});
} }
template<typename Comp> template<typename Comp>
std::tuple<int, std::any data> recv() { Event recv() {
EventQueue &queue = queue_map_for<Comp>();
Event evt = queue.front();
queue.pop();
return evt;
}
template<typename Comp>
bool has_event() {
EventQueue &queue = queue_map_for<Comp>();
return !queue.empty();
} }
*/
}; };
} }

@ -7,6 +7,7 @@ NOTES:
TODO: 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? * Write a test that generates a ton of maps then confirms there's a path from one room to every other room?
* Lua integration? * Lua integration?

@ -1,5 +1,5 @@
#include <catch2/catch_test_macros.hpp>
#include "dinkyecs.hpp" #include "dinkyecs.hpp"
#include <iostream> #include <iostream>
#include <fmt/core.h> #include <fmt/core.h>
@ -23,6 +23,10 @@ struct Gravity {
double level; double level;
}; };
struct DaGUI {
int event;
};
/* /*
* Using a function catches instances where I'm not copying * Using a function catches instances where I'm not copying
* the data into the world. * the data into the world.
@ -35,7 +39,7 @@ void configure(DinkyECS::World &world, Entity &test) {
world.set<Velocity>(test, {1,2}); world.set<Velocity>(test, {1,2});
world.set<Position>(test2, {1,1}); world.set<Position>(test2, {1,1});
world.set<Velocity>(test2, {10,20}); world.set<Velocity>(test2, {9,19});
println("---- Setting up the player as a fact in the system."); 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<Gravity>({0.9}); world.set_the<Gravity>({0.9});
} }
int main() { TEST_CASE("confirm ECS system works", "[ecs]") {
DinkyECS::World world; DinkyECS::World world;
Entity test = world.entity(); Entity test = world.entity();
configure(world, test); configure(world, test);
Position &pos = world.get<Position>(test); Position &pos = world.get<Position>(test);
println("GOT POS x={}, y={}", pos.x, pos.y); REQUIRE(pos.x == 10);
REQUIRE(pos.y == 20);
Velocity &vel = world.get<Velocity>(test); Velocity &vel = world.get<Velocity>(test);
println("GOT VELOCITY x={}, y={}", vel.x, vel.y); REQUIRE(vel.x == 1);
REQUIRE(vel.y == 2);
println("--- Position only system:");
world.query<Position>([](const auto &ent, auto &pos) { world.query<Position>([](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<Velocity>([](const auto &ent, auto &vel) {
world.query<Velocity>([](const auto &, auto &vel) { REQUIRE(ent > 0);
println("vel.x={}, vel.y={}", vel.x, vel.y); REQUIRE(vel.x >= 0);
REQUIRE(vel.y >= 0);
}); });
println("--- Manually get the velocity in position system:"); println("--- Manually get the velocity in position system:");
world.query<Position>([&](const auto &ent, auto &pos) { world.query<Position>([&](const auto &ent, auto &pos) {
Velocity &vel = world.get<Velocity>(ent); Velocity &vel = world.get<Velocity>(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:"); println("--- Query only entities with Position and Velocity:");
world.query<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) { world.query<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
Gravity &grav = world.get_the<Gravity>(); Gravity &grav = world.get_the<Gravity>();
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 // now remove Velocity
world.remove<Velocity>(test); world.remove<Velocity>(test);
REQUIRE_THROWS(world.get<Velocity>(test));
println("--- After remove test, should only result in test2:"); println("--- After remove test, should only result in test2:");
world.query<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) { world.query<Position, Velocity>([&](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);
}); });
}
// to avoid repeatedly getting the player just make a closure with it enum FakeEvent {
// QUESTION: could I just capture it and not have the double function wrapping? HIT_EVENT, MISS_EVENT
auto playerVsEnemies = [&]() { };
auto& player = world.get_the<Player>(); // grabbed it
world.query<Position>([&](const auto &ent, auto &pos) { TEST_CASE("confirm that the event system works", "[ecs]") {
if(player.eid != ent) { DinkyECS::World world;
println("{} is enemy attacking player {}", ent, player.name); DinkyECS::Entity gui_ent = world.entity();
} else { DinkyECS::Entity player = world.entity();
println("{} is player", player.name); DaGUI gui{384};
}
}); world.set<DaGUI>(gui_ent, gui);
}; DaGUI &gui_test = world.get<DaGUI>(gui_ent);
REQUIRE(gui.event == gui_test.event);
world.send<DaGUI>(FakeEvent::HIT_EVENT, player);
bool ready = world.has_event<DaGUI>();
REQUIRE(ready == true);
playerVsEnemies(); auto [event, entity] = world.recv<DaGUI>();
REQUIRE(event == FakeEvent::HIT_EVENT);
REQUIRE(entity == player);
return 0; ready = world.has_event<DaGUI>();
REQUIRE(ready == false);
} }

Loading…
Cancel
Save