Brought in nuke's idea for json serialize now to use it.

master
Zed A. Shaw 4 weeks ago
parent d798d154ae
commit 96efc990c1
  1. 11
      dinkyecs.cpp
  2. 27
      dinkyecs.hpp
  3. 4
      meson.build
  4. 250
      tests/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);
}
}
}

@ -7,10 +7,13 @@
#include <any>
#include <tuple>
#include <queue>
#include "tser.hpp"
#include <functional>
#include <nlohmann/json.hpp>
#include <nlohmann/json_fwd.hpp>
#include "dbc.hpp"
namespace DinkyECS {
using namespace nlohmann;
typedef unsigned long Entity;
@ -155,4 +158,26 @@ namespace DinkyECS {
return !queue.empty();
}
};
template <typename T> struct NameOf;
using ReflFuncSignature = std::function<void(World& world, Entity ent, nlohmann::json &j)>;
using ComponentMap = std::unordered_map<std::string, ReflFuncSignature>;
#define DINKY_HAS_COMPONENT(COMPONENT, ...) \
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(COMPONENT, __VA_ARGS__); \
template <> struct DinkyECS::NameOf<COMPONENT> { \
static constexpr const char *name = #COMPONENT; \
};
template <typename COMPONENT> void Component(ComponentMap &m) {
m[NameOf<COMPONENT>::name] = [](DinkyECS::World& world, DinkyECS::Entity ent, nlohmann::json &j) {
COMPONENT c;
from_json(j, c);
world.set<COMPONENT>(ent, c);
};
}
void configure(World& world, const ComponentMap& component_map, Entity ent, json& data);
}

@ -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])

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