diff --git a/ecs-serial/include/ecs-serial.h b/ecs-serial/include/ecs-serial.h index 1fe51d0..4f369bb 100644 --- a/ecs-serial/include/ecs-serial.h +++ b/ecs-serial/include/ecs-serial.h @@ -72,4 +72,5 @@ namespace ecs::serial { void deserialize(std::istream& is, Manager& manager); void serialize_ids(std::ostream& os); + void deserialize_ids(std::istream& is); } diff --git a/ecs-serial/src/ecs-serial.cpp b/ecs-serial/src/ecs-serial.cpp index 9d89342..123dc85 100644 --- a/ecs-serial/src/ecs-serial.cpp +++ b/ecs-serial/src/ecs-serial.cpp @@ -12,6 +12,8 @@ namespace ecs::serial { std::unordered_map, std::function, std::function>> internal::functions; + std::unordered_map conversion; + 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); @@ -47,54 +49,55 @@ namespace ecs::serial { void deserialize(std::istream& is, Manager& manager) { auto uuid_vector = iohelper::read_vector(is, 16); uuids::uuid uuid(uuid_vector.begin(), uuid_vector.end()); + + Entity* entity = nullptr; 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 new_id = iohelper::read_length(is); - bool tagged = iohelper::read(is); - size_t id = new_id; - if (tagged) { - 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[new_id])(is, component); - } else { - // Add new component - std::cout << "Adding component: " << id << '\n'; - Component* component = std::get<2>(internal::functions[new_id])(); - std::get<1>(internal::functions[new_id])(is, component); - entity->add_component(id, component); - } - } + entity = manager.get_entity(uuid); } 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 new_id = iohelper::read_length(is); - bool tagged = iohelper::read(is); - size_t id = new_id; - if (tagged) { - id = iohelper::read_length(is); - } + entity = manager.create_entity(uuid); + } - std::cout << "Adding component: " << id << '\n'; - Component* component = std::get<2>(internal::functions[new_id])(); + 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 new_id = conversion[iohelper::read_length(is)]; + bool runtime = iohelper::read(is); + size_t id = new_id; + if (runtime) { + id = conversion[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 << " [" << ComponentID::get_name(id) << "]"; + if (new_id != id) { + std::cout << " (base: " << new_id << " [" << ComponentID::get_name(new_id) << "])"; + } + std::cout << '\n'; + Component* component = entity->get_component(id); + // @note We do not have to check if this exists as the entity already has the component std::get<1>(internal::functions[new_id])(is, component); + } else { + // Add new component + std::cout << "Adding component: " << id << " [" << ComponentID::get_name(id) << "]"; + if (new_id != id) { + std::cout << " (base: " << new_id << " [" << ComponentID::get_name(new_id) << "])"; + } + std::cout << '\n'; + auto func = internal::functions.find(new_id); + if (func == internal::functions.end()) { + throw std::runtime_error("Unknown id"); + } + Component* component = std::get<2>(func->second)(); + std::get<1>(func->second)(is, component); entity->add_component(id, component); } } @@ -109,4 +112,18 @@ namespace ecs::serial { iohelper::write_length(os, id); } } + + void deserialize_ids(std::istream& is) { + size_t id_count = iohelper::read_length(is); + for (size_t i = 0; i < id_count; ++i) { + std::string name = iohelper::read(is); + size_t id = iohelper::read_length(is); + + conversion[id] = ComponentID::get_id({name})[0]; + } + + for (auto [remote_id, local_id] : conversion) { + std::cout << remote_id << " -> " << local_id << '\n'; + } + } } diff --git a/test/src/main.cpp b/test/src/main.cpp index ce3a8e5..fb8b9ed 100644 --- a/test/src/main.cpp +++ b/test/src/main.cpp @@ -24,7 +24,22 @@ inline void handle_error(sol::optional maybe_msg) { } } -int main() { +int main(int argc, const char** argv) { + bool save; + if (argc == 2) { + if (!std::string("save").compare(argv[1])) { + save = true; + } else if (!std::string("load").compare(argv[1])) { + save = false; + } else { + std::cerr << "Usage: " << argv[0] << " [save/load]\n"; + return -1; + } + } else { + std::cerr << "Usage: " << argv[0] << " [save/load]\n"; + return -1; + } + sol::state lua(sol::c_call); lua.open_libraries(sol::lib::base, sol::lib::package); @@ -33,8 +48,7 @@ int main() { generated::init(lua); generated::init(); - ecs::Manager manager; - + // @todo I am pretty sure that iohelper actually has an api that allows us to write a custom (de)serializer ecs::serial::register_component_custom( [](std::ostream& os, ecs::Component* component) { ecs::lua::Wrapper* wrapper = (ecs::lua::Wrapper*)component; @@ -107,13 +121,14 @@ int main() { break; case sol::type::number: - // @todo Handle floats wrapper->table[name] = iohelper::read(is); break; case sol::type::boolean: + { wrapper->table[name] = iohelper::read(is); break; + } default: break; @@ -122,117 +137,124 @@ int main() { } ); - // Testing interop with lua - { + if (save) { + ecs::Manager manager; - lua.set_function("get_manager", [&manager] () -> ecs::Manager& { - return manager; - }); + // Testing interop with lua + { - lua.safe_script_file("test.lua"); + lua.set_function("get_manager", [&manager] () -> ecs::Manager& { + return manager; + }); - std::cout << "Update position\n"; - manager.view().for_each([](ecs::Entity*, Position* pos, Velocity* vel) { - pos->x += vel->x; - pos->y += vel->y; - }); + lua.safe_script_file("test.lua"); - 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'; - }); + std::cout << "Update position\n"; + manager.view().for_each([](ecs::Entity*, Position* pos, Velocity* vel) { + pos->x += vel->x; + pos->y += vel->y; + }); - // 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::Wrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->table; + 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'; + }); - random["a"] = 21; - std::cout << random["a"].get() << '\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::Wrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->table; - for (auto [uuid, entity] : manager.view(ecs::ComponentID::get_id({"Random"}))) { - sol::table random = ((ecs::lua::Wrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->table; + random["a"] = 21; + std::cout << random["a"].get() << '\n'; + }; - std::cout << random["a"].get() << '\n'; - }; + for (auto [uuid, entity] : manager.view(ecs::ComponentID::get_id({"Random"}))) { + sol::table random = ((ecs::lua::Wrapper*)entity->get_component(ecs::ComponentID::get_id({"Random"})[0]))->table; + + std::cout << random["a"].get() << '\n'; + }; + } + + // Test serialization + { + std::cout << "STORE\n"; + + // 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); + ecs::serial::serialize_ids(file); + + 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'; + }); + } + } else { + ecs::Manager manager; + { + std::cout << "LOAD\n"; + + // Load entities from disk as a test + std::ifstream file("entities", std::ios::in); + + ecs::serial::deserialize_ids(file); + + size_t entity_count = iohelper::read_length(file); + size_t pos = file.tellg(); + for (size_t i = 0; i < entity_count; ++i) { + ecs::serial::deserialize(file, manager); + } + + // Load the first entity again (simulating an update) + file.seekg(pos, std::ios::beg); + 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'; + }); + + lua.set_function("get_manager", [&manager] () -> ecs::Manager& { + return manager; + }); + + lua.safe_script_file("test2.lua"); + } } - // Test serialization - { - std::cout << "STORE\n"; - - // 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'; - }); - - // @todo This should be used to constuct a conversion table - // This is needed to avoid id conflicts between clients - std::ofstream ids2("ids", std::ios::out | std::ios::trunc); - ecs::serial::serialize_ids(ids2); - ids2.close(); - } - - { - std::cout << "LOAD\n"; - ecs::Manager manager2; - - // 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, manager2); - } - - file.seekg(0, std::ios::beg); - iohelper::read_length(file); - ecs::serial::deserialize(file, manager2); - - if (false) { - auto ent = manager2.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; - } - } - - manager2.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'; - }); - - lua.set_function("get_manager", [&manager] () -> ecs::Manager& { - return manager; - }); - - lua.safe_script_file("test2.lua"); + for (auto [name, id] : ecs::ComponentID::_ids()) { + std::cout << name << ' ' << id << '\n'; } } diff --git a/test/test2.lua b/test/test2.lua index 444cee1..178c54c 100644 --- a/test/test2.lua +++ b/test/test2.lua @@ -1,16 +1,21 @@ +require "ecs" +require "ecs.Wrapper" + manager = get_manager() +manager:view("TestThing"):for_each(function(ent) + data = ent:get_component("TestThing") + print("test: " .. data.test) +end) + manager:view("LuaData"):for_each(function(ent) -- @todo It would be nice if this could somehow be passed in as function arg data = ent:get_component("LuaData") print("speed: " .. data.speed) print("something: " .. data.something) - -- print("alive: " .. data.alive) -end) - -manager:view("TestThing"):for_each(function(ent) - data = ent:get_component("TestThing") - print("test: " .. data.test) + -- @todo You cannot concatenate bool to string + print("alive: ") + print(data.alive) end) -- @todo Allow this @@ -18,4 +23,4 @@ end) -- print(i, v) -- end -print(manager:has_entity("6d58fdb5-6d8c-4e6f-89d4-f7d7b184f463")) +print(manager:has_entity("70bca3cf-33dd-40dc-8b0f-2e19aa0b4a17"))