From 96ad0de01acf88efe239de363ca5771ba14944f9 Mon Sep 17 00:00:00 2001 From: Dreaded_X Date: Sat, 9 Feb 2019 21:41:43 +0000 Subject: [PATCH] Serializer now has a nice register function --- ecs-lua/include/ecs-lua.h | 5 +- ecs-lua/src/ecs-lua.cpp | 10 +- ecs-serial/include/ecs-serial.h | 62 +++++++ ecs-serial/src/ecs-serial.cpp | 194 ++++++--------------- ecs/include/ecs.h | 5 + flint.lua | 3 +- test/src/main.cpp | 289 +++++++++++++++++++++----------- 7 files changed, 323 insertions(+), 245 deletions(-) create mode 100644 ecs-serial/include/ecs-serial.h diff --git a/ecs-lua/include/ecs-lua.h b/ecs-lua/include/ecs-lua.h index 0916129..77c81ac 100644 --- a/ecs-lua/include/ecs-lua.h +++ b/ecs-lua/include/ecs-lua.h @@ -7,9 +7,10 @@ namespace ecs::lua { struct LuaWrapper : Component { - LuaWrapper(sol::object object) : _object(object) {} + LuaWrapper(sol::object _object) : object(_object) {} + LuaWrapper() {} - sol::object _object; + sol::object object; }; template diff --git a/ecs-lua/src/ecs-lua.cpp b/ecs-lua/src/ecs-lua.cpp index 01596c7..7026ce6 100644 --- a/ecs-lua/src/ecs-lua.cpp +++ b/ecs-lua/src/ecs-lua.cpp @@ -5,6 +5,9 @@ namespace ecs::lua { sol::table ecs = lua.create_table("ecs"); ecs.new_usertype("Entity", + "__tostring", [] (Entity* thiz) { + return uuids::to_string(thiz->uuid); + }, "add_component", [] (Entity* thiz, std::string name, Component* component) { return thiz->add_component(ComponentID::get_id({name})[0], component); }, @@ -33,7 +36,10 @@ namespace ecs::lua { ); ecs.new_usertype("Manager", - "create_entity", &Manager::create_entity, + "create_entity", [] (Manager* thiz) { + // @todo Allow to construct with given uuid + return thiz->create_entity(); + }, "view", [] (Manager* thiz, sol::variadic_args args) { std::vector names; for (std::string name : args) { @@ -44,7 +50,7 @@ namespace ecs::lua { ); register_component(lua, - "object", &LuaWrapper::_object + "object", &LuaWrapper::object ); } } diff --git a/ecs-serial/include/ecs-serial.h b/ecs-serial/include/ecs-serial.h new file mode 100644 index 0000000..fb2ffb2 --- /dev/null +++ b/ecs-serial/include/ecs-serial.h @@ -0,0 +1,62 @@ +#pragma once +#include "ecs.h" + +#include "iohelper/write.h" +#include "iohelper/read.h" + +#include + +namespace ecs::serial { + namespace internal { + extern std::unordered_map, std::function, std::function>> functions; + } + + template + void serialize_member(std::ostream&, T*) { + // We reached the end + } + + template + void serialize_member(std::ostream& os, T* t, M T::*m, Args... args) { + iohelper::write(os, t->*m); + + serialize_member(os, t, args...); + } + + template + void deserialize_member(std::istream&, T*) { + // We reached the end + } + + template + void deserialize_member(std::istream& is, T* t, M T::*m, Args... args) { + t->*m = iohelper::read(is); + + deserialize_member(is, t, args...); + } + + // @todo Allow for custom serializers and deserializers (maybe for this we do need a difference between update and new) + template + void register_component(Args... args) { + // Serialize component + auto func1 = [args...] (std::ostream& os, ecs::Component* component) { + T* t = (T*)component; + serialize_member(os, t, args...); + }; + + // Deserialize component + auto func2 = [args...] (std::istream& is, ecs::Component* component) { + T* t = (T*)component; + deserialize_member(is, t, args...); + }; + + auto func3 = [] () -> Component* { + return new T(); + }; + + internal::functions.insert({ComponentID::id, {func1, func2, func3}}); + } + + void serialize(std::ostream& os, Entity* entity); + void deserialize(std::istream& is, Manager& manager); +} diff --git a/ecs-serial/src/ecs-serial.cpp b/ecs-serial/src/ecs-serial.cpp index 494d250..728b351 100644 --- a/ecs-serial/src/ecs-serial.cpp +++ b/ecs-serial/src/ecs-serial.cpp @@ -1,147 +1,63 @@ -#include +#include "ecs-serial.h" -#include "ecs.h" -#include "iohelper/memstream.h" -#include "iohelper/write.h" -#include "iohelper/read.h" +namespace ecs::serial { + std::unordered_map, std::function, std::function>> internal::functions; -#include + void serialize(std::ostream& os, Entity* entity) { + auto uuid_data = reinterpret_cast(entity->uuid.as_bytes().data()); + iohelper::write_vector(os, std::vector(uuid_data, uuid_data + 16), false); -struct Position : ecs::Component { - Position(float x, float y) : _x(x), _y(y) {} - - float _x; - float _y; -}; - -struct Velocity : ecs::Component{ - Velocity(float vx, float vy) : _vx(vx), _vy(vy) {} - - float _vx; - float _vy; -}; - -std::unordered_map(ecs::Component*)>, std::function)>>> _functions; - -// @todo Add deserializer stuff -template -void register_component(std::function(ecs::Component*)> serializer, std::function)> deserializer) { - _functions.insert({ecs::ComponentID::id, {serializer, deserializer}}); -} - -enum class Marker : uint8_t { - ENTITY, - COMPONENT, - END -}; - -int main(int argc, const char* argv) { - ecs::Manager manager; - - register_component([] (ecs::Component* component) { - Position* pos = (Position*)component; - std::vector data; - iohelper::omemstream stream(data); - - iohelper::write(stream, pos->_x); - iohelper::write(stream, pos->_y); - - return data; - }, [] (std::vector data) { - iohelper::imemstream stream(data); - float x = iohelper::read(stream); - float y = iohelper::read(stream); - return new Position(x, y); - }); - - register_component([] (ecs::Component* component) { - Velocity* vel = (Velocity*)component; - std::vector data; - iohelper::omemstream stream(data); - - iohelper::write(stream, vel->_vx); - iohelper::write(stream, vel->_vy); - - return data; - }, [] (std::vector data) { - iohelper::imemstream stream(data); - float vx = iohelper::read(stream); - float vy = iohelper::read(stream); - return new Velocity(vx, vy); - }); - - if (argc == 1) { - // Create entities and store them - for (int i = 0; i < 10; ++i) { - // We can create entities - ecs::Entity* entity = manager.create_entity(); - // Then we can add components to them - entity->add_component(0.0f, 0.0f); - if (i % 2 == 0) { - entity->add_component(0.1f, 0.2f); - } - } - - manager.view().for_each([] (ecs::Entity*, Position* pos, Velocity* vel) { - pos->_x += vel->_vx; - pos->_y += vel->_vy; - }); - - std::ofstream file("entities", std::ios::out | std::ios::trunc); - for (auto [uuid, entity] : manager.view<>()) { - iohelper::write(file, (uint8_t)Marker::ENTITY); - // iohelper::write_length(file, uuid); - auto uuid_data = reinterpret_cast(uuid.as_bytes().data()); - iohelper::write_vector(file, std::vector(uuid_data, uuid_data + 16), false); - for (auto [id, component] : entity->get_components()) { - iohelper::write(file, (uint8_t)Marker::COMPONENT); - iohelper::write_length(file, id); - auto data = _functions[id].first(component); - iohelper::write_vector(file, data); - } - } - iohelper::write(file, (uint8_t)Marker::END); - file.close(); - } else { - // Load entities from disk as a test - std::ifstream file("entities", std::ios::in); - bool loop = true; - ecs::Entity* entity = nullptr; - while (loop) { - Marker marker = (Marker)iohelper::read(file); - switch (marker) { - case Marker::ENTITY: { - // size_t uuid = iohelper::read_length(file); - auto uuid_vector = iohelper::read_vector(file, 16); - entity = manager.create_entity(uuids::uuid(uuid_vector.begin(), uuid_vector.end())); - break; - } - case Marker::COMPONENT: { - size_t id = iohelper::read_length(file); - std::vector data = iohelper::read_vector(file); - entity->add_component(id, _functions[id].second(data)); - break; - } - case Marker::END: - loop = false; - break; - } - } - - if (false) { - auto ent = manager.get_entity(uuids::uuid::from_string("6d58fdb5-6d8c-4e6f-89d4-f7d7b184f463").value()); - if (ent->has_components()) { - auto pos = ent->get_component(); - pos->_x = 1.2f; - pos->_y = 3.4f; - } + auto components = entity->get_components(); + iohelper::write_length(os, components.size()); + for (auto [id, component] : components) { + iohelper::write_length(os, id); + // @todo What if the function does not exist? + std::get<0>(internal::functions[id])(os, component); } } - manager.view().for_each([] (ecs::Entity* entity, Position* pos) { - std::cout << "uuid: " << entity->uuid << '\n'; - std::cout << "x: " << pos->_x << '\n'; - std::cout << "y: " << pos->_y << '\n'; - }); + void deserialize(std::istream& is, Manager& manager) { + auto uuid_vector = iohelper::read_vector(is, 16); + uuids::uuid uuid(uuid_vector.begin(), uuid_vector.end()); + if (manager.has_entity(uuid)) { + // Update existing entity + Entity* entity = manager.get_entity(uuid); + size_t component_count = iohelper::read_length(is); + // std::cout << "Updating " << component_count << " components in entity: " << uuid << '\n'; + for (size_t i = 0; i < component_count; ++i) { + size_t id = iohelper::read_length(is); + // @todo We also need to be able to remove components + // Sending a component with length 0 -> remove component + // However we might have components that have no data and are just like tags + // So maybe instead if we send MAX_INT or something like that + // Or we send a flag in front + // @todo What if the function does not exist? + if (entity->has_components({id})) { + // Update the component + std::cout << "Updating component: " << id << '\n'; + Component* component = entity->get_component(id); + std::get<1>(internal::functions[id])(is, component); + } else { + // Add new component + std::cout << "Adding component: " << id << '\n'; + Component* component = std::get<2>(internal::functions[id])(); + std::get<1>(internal::functions[id])(is, component); + entity->add_component(id, component); + } + } + } else { + // Create new entity + Entity* entity = manager.create_entity(uuid); + size_t component_count = iohelper::read_length(is); + // std::cout << "Creating entity with " << component_count << " components: " << uuid << '\n'; + for (size_t i = 0; i < component_count; ++i) { + size_t id = iohelper::read_length(is); + std::cout << "Adding component: " << id << '\n'; + Component* component = std::get<2>(internal::functions[id])(); + std::get<1>(internal::functions[id])(is, component); + entity->add_component(id, component); + } + } + } } diff --git a/ecs/include/ecs.h b/ecs/include/ecs.h index d2c7fad..69a91da 100644 --- a/ecs/include/ecs.h +++ b/ecs/include/ecs.h @@ -179,6 +179,11 @@ namespace ecs { Entity* create_entity(uuids::uuid uuid); Entity* create_entity(); + bool has_entity(uuids::uuid uuid) { + // @todo c++20 has .contains() + return _entities.count(uuid); + } + Entity* get_entity(uuids::uuid uuid) { auto it = _entities.find(uuid); if (it == _entities.end()) { diff --git a/flint.lua b/flint.lua index 296f2eb..77d0f07 100644 --- a/flint.lua +++ b/flint.lua @@ -26,7 +26,7 @@ lib "ecs-lua" subfile("../iohelper/flint.lua", "iohelper") -executable "ecs-serial" +lib "ecs-serial" path "ecs-serial" dependency("ecs", "iohelper") @@ -35,5 +35,6 @@ executable "test" path "test" dependency "ecs-lua" + dependency "ecs-serial" run_target "test" diff --git a/test/src/main.cpp b/test/src/main.cpp index dce60a9..472ae53 100644 --- a/test/src/main.cpp +++ b/test/src/main.cpp @@ -1,19 +1,23 @@ #include "ecs-lua.h" +#include "ecs-serial.h" #include +#include struct Position : ecs::Component { - Position(float x, float y) : _x(x), _y(y) {} + Position(float _x, float _y) : x(_x), y(_y) {} + Position() {} - float _x; - float _y; + float x; + float y; }; -struct Velocity : ecs::Component{ - Velocity(float vx, float vy) : _vx(vx), _vy(vy) {} +struct Velocity : ecs::Component { + Velocity(float _x, float _y) : x(_x), y(_y) {} + Velocity() {} - float _vx; - float _vy; + float x; + float y; }; int main() { @@ -23,122 +27,205 @@ int main() { ecs::lua::init(lua); ecs::lua::register_component(lua, - "x", &Position::_x, - "y", &Position::_y + "x", &Position::x, + "y", &Position::y + ); + ecs::serial::register_component( + &Position::x, + &Position::y ); ecs::lua::register_component(lua, - "vx", &Velocity::_vx, - "vy", &Velocity::_vy + "x", &Velocity::x, + "y", &Velocity::y + ); + ecs::serial::register_component( + &Velocity::x, + &Velocity::y ); - // This manages all our entities - ecs::Manager manager; + ecs::serial::register_component( + // @todo We need to create a read and write function for sol::object + // &ecs::lua::LuaWrapper::object + ); - lua.set_function("get_manager", [&manager] () -> ecs::Manager& { - return manager; - }); + { + // This manages all our entities + ecs::Manager manager; - lua.script(R"lua( - manager = get_manager() - ent = manager:create_entity() - -- In the future we will be able to also write components in lua - -- @todo Figure out how get_component will work in this case - ent:add_component("Position", Position.new(1.9, 9.7)) - ent:add_component("Velocity", Velocity.new(0.2, 0.3)) - random = { - a = 10, - b = 11, - c = function(s) - print("Hello " .. s .. "!") - end - } - ent:add_component("Random", LuaWrapper.new(random)) + lua.set_function("get_manager", [&manager] () -> ecs::Manager& { + return manager; + }); - print(ent:has_components("Position", "Velocity")) - pos = ent:get_component("Position") - print("x: " .. pos.x) - print("y: " .. pos.y) - vel = ent:get_component("Velocity") - print("v_x: " .. vel.vx) - print("v_y: " .. vel.vy) + lua.script(R"lua( + manager = get_manager() + ent = manager:create_entity() + -- In the future we will be able to also write components in lua + -- @todo Figure out how get_component will work in this case + ent:add_component("Position", Position.new(1.9, 9.7)) + ent:add_component("Velocity", Velocity.new(0.2, 0.3)) + random = { + a = 10, + b = 11, + c = function(s) + print("Hello " .. s .. "!") + end + } + ent:add_component("Random", LuaWrapper.new(random)) - print("View test") - view = manager:view("Position", "Velocity") - view:for_each(function(ent) + print(ent:has_components("Position", "Velocity")) pos = ent:get_component("Position") - vel = ent:get_component("Velocity") - - pos.x = pos.x + vel.vx - pos.y = pos.y + vel.vy - print("x: " .. pos.x) print("y: " .. pos.y) + vel = ent:get_component("Velocity") + print("v_x: " .. vel.x) + print("v_y: " .. vel.y) - print("v_x: " .. vel.vx) - print("v_y: " .. vel.vy) - end) + print("View test") + view = manager:view("Position", "Velocity") + view:for_each(function(ent) + pos = ent:get_component("Position") + vel = ent:get_component("Velocity") - -- @todo Implement this - -- for uuid,entity in pairs(view) do - -- print "TEST" - -- end + pos.x = pos.x + vel.x + pos.y = pos.y + vel.y - manager:view("Random"):for_each(function(ent) - wrapped = ent:get_component("Random").object + print(ent) - print(wrapped.a) - wrapped.a = 11 - print(wrapped.a) - print(random.a) - random.a = 20 - print(wrapped.a) - print(random.a) + print("x: " .. pos.x) + print("y: " .. pos.y) - random.c("you") - end) - )lua"); + print("v_x: " .. vel.x) + print("v_y: " .. vel.y) + end) - // for (int i = 0; i < 10; ++i) { - // // We can create entities - // Entity* entity = manager.create_entity(); - // // Then we can add components to them - // entity->add_component(0.0f, 0.0f); - // if (i % 2 == 0) { - // entity->add_component(0.1f, 0.2f); - // } - // - // // We can check for components by name - // if (entity->has_components()) { - // std::cout << "YES\n"; - // } else { - // std::cout << "NO\n"; - // } - // } + -- @todo Implement this + -- for uuid,entity in pairs(view) do + -- print "TEST" + -- end - std::cout << "Update position\n"; - manager.view().for_each([](ecs::Entity*, Position* pos, Velocity* vel) { - pos->_x += vel->_vx; - pos->_y += vel->_vy; - }); + manager:view("Random"):for_each(function(ent) + wrapped = ent:get_component("Random").object - std::cout << "Show position!\n"; - manager.view().for_each([](ecs::Entity*, Position* pos) { - std::cout << "X: " << pos->_x << '\n'; - std::cout << "Y: " << pos->_y << '\n'; - }); + print(wrapped.a) + wrapped.a = 11 + print(wrapped.a) + print(random.a) + random.a = 20 + print(wrapped.a) + print(random.a) - // These are really just an internal api that should not be used - for (auto [uuid, entity] : manager.view(ecs::ComponentID::get_id({"Random"}))) { - sol::table random = ((ecs::lua::LuaWrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->_object; + random.c("you") + end) + )lua"); - random["a"] = 21; - std::cout << random["a"].get() << '\n'; - }; + // for (int i = 0; i < 10; ++i) { + // // We can create entities + // Entity* entity = manager.create_entity(); + // // Then we can add components to them + // entity->add_component(0.0f, 0.0f); + // if (i % 2 == 0) { + // entity->add_component(0.1f, 0.2f); + // } + // + // // We can check for components by name + // if (entity->has_components()) { + // std::cout << "YES\n"; + // } else { + // std::cout << "NO\n"; + // } + // } - for (auto [uuid, entity] : manager.view(ecs::ComponentID::get_id({"Random"}))) { - sol::table random = ((ecs::lua::LuaWrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->_object; + std::cout << "Update position\n"; + manager.view().for_each([](ecs::Entity*, Position* pos, Velocity* vel) { + pos->x += vel->x; + pos->y += vel->y; + }); - std::cout << random["a"].get() << '\n'; - }; + std::cout << "Show position!\n"; + manager.view().for_each([](ecs::Entity*, Position* pos) { + std::cout << "X: " << pos->x << '\n'; + std::cout << "Y: " << pos->y << '\n'; + }); + + // These are really just an internal api that should not be used + for (auto [uuid, entity] : manager.view(ecs::ComponentID::get_id({"Random"}))) { + sol::table random = ((ecs::lua::LuaWrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->object; + + random["a"] = 21; + std::cout << random["a"].get() << '\n'; + }; + + for (auto [uuid, entity] : manager.view(ecs::ComponentID::get_id({"Random"}))) { + sol::table random = ((ecs::lua::LuaWrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->object; + + std::cout << random["a"].get() << '\n'; + }; + } + + { + std::cout << "STORE\n"; + ecs::Manager manager; + + // Create entities and store them + for (int i = 0; i < 10; ++i) { + // We can create entities + ecs::Entity* entity = manager.create_entity(); + // Then we can add components to them + entity->add_component(0.0f, 0.0f); + if (i % 2 == 0) { + entity->add_component(0.1f, 0.2f); + } + } + + manager.view().for_each([] (ecs::Entity*, Position* pos, Velocity* vel) { + pos->x += vel->x; + pos->y += vel->y; + }); + + std::ofstream file("entities", std::ios::out | std::ios::trunc); + iohelper::write_length(file, manager.view<>().size()); + for (auto [uuid, entity] : manager.view<>()) { + ecs::serial::serialize(file, entity); + } + file.close(); + + manager.view().for_each([] (ecs::Entity* entity, Position* pos) { + std::cout << "uuid: " << entity->uuid << '\n'; + std::cout << "x: " << pos->x << '\n'; + std::cout << "y: " << pos->y << '\n'; + }); + } + + { + std::cout << "LOAD\n"; + ecs::Manager manager; + + // Load entities from disk as a test + std::ifstream file("entities", std::ios::in); + + size_t count = iohelper::read_length(file); + for (size_t i = 0; i < count; ++i) { + ecs::serial::deserialize(file, manager); + } + + file.seekg(0, std::ios::beg); + iohelper::read_length(file); + ecs::serial::deserialize(file, manager); + + if (false) { + auto ent = manager.get_entity(uuids::uuid::from_string("6d58fdb5-6d8c-4e6f-89d4-f7d7b184f463").value()); + if (ent->has_components()) { + auto pos = ent->get_component(); + pos->x = 1.2f; + pos->y = 3.4f; + } + } + + manager.view().for_each([] (ecs::Entity* entity, Position* pos) { + std::cout << "uuid: " << entity->uuid << '\n'; + std::cout << "x: " << pos->x << '\n'; + std::cout << "y: " << pos->y << '\n'; + }); + } }