Simple event system for entities in the world.
This commit is contained in:
parent
ea6cf1362b
commit
3f87d19911
3 changed files with 94 additions and 36 deletions
36
dinkyecs.hpp
36
dinkyecs.hpp
|
@ -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
|
|
||||||
// QUESTION: could I just capture it and not have the double function wrapping?
|
enum FakeEvent {
|
||||||
auto playerVsEnemies = [&]() {
|
HIT_EVENT, MISS_EVENT
|
||||||
auto& player = world.get_the<Player>(); // grabbed it
|
};
|
||||||
world.query<Position>([&](const auto &ent, auto &pos) {
|
|
||||||
if(player.eid != ent) {
|
TEST_CASE("confirm that the event system works", "[ecs]") {
|
||||||
println("{} is enemy attacking player {}", ent, player.name);
|
DinkyECS::World world;
|
||||||
} else {
|
DinkyECS::Entity gui_ent = world.entity();
|
||||||
println("{} is player", player.name);
|
DinkyECS::Entity player = world.entity();
|
||||||
}
|
DaGUI gui{384};
|
||||||
});
|
|
||||||
};
|
world.set<DaGUI>(gui_ent, gui);
|
||||||
|
DaGUI &gui_test = world.get<DaGUI>(gui_ent);
|
||||||
playerVsEnemies();
|
REQUIRE(gui.event == gui_test.event);
|
||||||
|
|
||||||
return 0;
|
world.send<DaGUI>(FakeEvent::HIT_EVENT, player);
|
||||||
|
|
||||||
|
bool ready = world.has_event<DaGUI>();
|
||||||
|
REQUIRE(ready == true);
|
||||||
|
|
||||||
|
auto [event, entity] = world.recv<DaGUI>();
|
||||||
|
REQUIRE(event == FakeEvent::HIT_EVENT);
|
||||||
|
REQUIRE(entity == player);
|
||||||
|
|
||||||
|
ready = world.has_event<DaGUI>();
|
||||||
|
REQUIRE(ready == false);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue