diff --git a/CMakeLists.txt b/CMakeLists.txt index 88e55dc..613d981 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,10 +5,23 @@ set(PROJECT_NAME dinkyecs_sandbox) project(${PROJECT_NAME}) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -file(GLOB PROJECT_SRC *.cpp *.hpp *.h) - find_package(nlohmann_json) find_package(fmt) -add_executable(${PROJECT_NAME} ${PROJECT_SRC}) -target_link_libraries(${PROJECT_NAME} nlohmann_json::nlohmann_json fmt::fmt) +add_library(dinkyecs STATIC dbc.hpp dbc.cpp dinkyecs.hpp) +target_link_libraries(dinkyecs ${PROJECT_DEPS} nlohmann_json::nlohmann_json fmt::fmt) + +set(PROJECT_DEPS dinkyecs) + +add_executable(${PROJECT_NAME} main.cpp) +target_link_libraries(${PROJECT_NAME} ${PROJECT_DEPS}) + +####################################################### +find_package(Catch2 REQUIRED) +file(GLOB TEST_SRC ./tests/*.cpp ./tests/*.hpp ./tests/*.h) +add_executable(tests ${TEST_SRC}) +target_link_libraries(tests PRIVATE Catch2::Catch2WithMain ${PROJECT_DEPS}) + +include(CTest) +include(Catch) +catch_discover_tests(tests) diff --git a/conanfile.txt b/conanfile.txt index 1249efb..6c2ad01 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -1,5 +1,6 @@ [requires] nlohmann_json/3.11.3 +catch2/3.8.0 fmt/11.1.3 [generators] diff --git a/main.cpp b/main.cpp index c62f671..954eeb4 100644 --- a/main.cpp +++ b/main.cpp @@ -90,9 +90,13 @@ int main() configure(world, test); Position &pos = world.get(test); + Position &pos2 = world.get(test); REQUIRE(pos.location.x == 10); REQUIRE(pos.location.y == 20); + REQUIRE(pos2.location.x == 10); + REQUIRE(pos2.location.y == 20); + Velocity &vel = world.get(test); REQUIRE(vel.x == 1); REQUIRE(vel.y == 2); diff --git a/tests/dinkyecs.cpp b/tests/dinkyecs.cpp new file mode 100644 index 0000000..1813434 --- /dev/null +++ b/tests/dinkyecs.cpp @@ -0,0 +1,247 @@ +#define CATCH_CONFIG_MAIN +#include +#include +#include "../dinkyecs.hpp" +#include +#include + +using namespace fmt; +using DinkyECS::Entity; +using std::string; + +struct Point { + size_t x; + size_t y; +}; + +struct Player { + string name; + Entity eid; +}; + +struct Position { + Point location; +}; + +struct Motion { + int dx; + int dy; + bool random=false; +}; + +struct Velocity { + double x, y; +}; + +struct Gravity { + double level; +}; + +struct DaGUI { + int 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(component_map, world, ent1, data); + DinkyECS::configure(component_map, world, 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); + }); + */ +}