diff --git a/src/container/sparseset.h b/src/container/sparseset.h index 209efe81..e0d419d6 100644 --- a/src/container/sparseset.h +++ b/src/container/sparseset.h @@ -72,6 +72,8 @@ template class SparseSet final Iterator begin() { return Iterator(*this, dense.size() - 1); } Iterator end() { return Iterator(*this, std::numeric_limits::max()); } + + size_t size() { return data.size(); } private: std::vector sparse; std::vector dense; diff --git a/src/ecs/component.cpp b/src/ecs/component.cpp index 62e698db..5c0e6b21 100644 --- a/src/ecs/component.cpp +++ b/src/ecs/component.cpp @@ -16,4 +16,10 @@ void ComponentStorageBase::destroyAll(uint32_t index) storage->destroy(index); } +void ComponentStorageBase::dumpDebugInfo() +{ + for(auto storage = all_component_storage; storage; storage = storage->next) + storage->dumpDebugInfoImpl(); +} + } \ No newline at end of file diff --git a/src/ecs/component.h b/src/ecs/component.h index 0035be83..e3e28ebf 100644 --- a/src/ecs/component.h +++ b/src/ecs/component.h @@ -1,6 +1,7 @@ #pragma once #include "container/sparseset.h" +#include "logging.h" class GameServer; template class MultiplayerECSComponentReplication; @@ -10,11 +11,13 @@ class ComponentStorageBase { public: ComponentStorageBase(); + static void dumpDebugInfo(); private: ComponentStorageBase* next = nullptr; protected: static void destroyAll(uint32_t index); virtual void destroy(uint32_t index) = 0; + virtual void dumpDebugInfoImpl() = 0; friend class Entity; }; @@ -25,6 +28,11 @@ template class ComponentStorage : public ComponentStorageBase { sparseset.remove(index); } + virtual void dumpDebugInfoImpl() override + { + LOG(Debug, "Component:", typeid(T).name(), " Count: ", storage.sparseset.size()); + } + SparseSet sparseset; static inline ComponentStorage storage; diff --git a/src/ecs/entity.cpp b/src/ecs/entity.cpp index b138cbf4..d42dc3a6 100644 --- a/src/ecs/entity.cpp +++ b/src/ecs/entity.cpp @@ -1,5 +1,6 @@ #include "entity.h" #include "component.h" +#include "logging.h" #include namespace sp::ecs { @@ -33,7 +34,7 @@ Entity Entity::fromIndex(uint32_t index) Entity::operator bool() const { - if (index == std::numeric_limits::max()) + if (index == no_index) return false; return entity_version[index] == version; } @@ -69,4 +70,9 @@ void Entity::destroy() free_list.push_back(index); } +void Entity::dumpDebugInfo() +{ + LOG(Debug, "Entity count:", entity_version.size() - free_list.size(), " Free entities:", free_list.size()); +} + } diff --git a/src/ecs/entity.h b/src/ecs/entity.h index 6ff54a4b..0da1ca04 100644 --- a/src/ecs/entity.h +++ b/src/ecs/entity.h @@ -51,11 +51,15 @@ class Entity final { bool operator!=(const Entity& other) const; uint32_t getIndex() { return index; } // You should never need this, but the multiplayer code does need it. + + static void dumpDebugInfo(); + + static constexpr uint32_t no_index = std::numeric_limits::max(); private: - static constexpr uint32_t destroyed_flag = 1 << 31; + static constexpr uint32_t destroyed_flag = 1 << (std::numeric_limits::digits - 1); - uint32_t index = std::numeric_limits::max(); - uint32_t version = std::numeric_limits::max(); + uint32_t index = no_index; + uint32_t version = no_index; static Entity fromIndex(uint32_t index); static std::vector entity_version; diff --git a/src/engine.cpp b/src/engine.cpp index d55dead9..1e6467bd 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -8,6 +8,7 @@ #include "windowManager.h" #include "scriptInterface.h" #include "multiplayer_server.h" +#include "ecs/entity.h" #include #include @@ -218,6 +219,9 @@ void Engine::handleEvent(SDL_Event& event) } printf("%4d %s\n",grand_total,"All PObjects"); printf("------------------------\n"); + + sp::ecs::Entity::dumpDebugInfo(); + sp::ecs::ComponentStorageBase::dumpDebugInfo(); } #endif diff --git a/src/multiplayer.cpp b/src/multiplayer.cpp index c30cb76a..e33d44cf 100644 --- a/src/multiplayer.cpp +++ b/src/multiplayer.cpp @@ -68,12 +68,20 @@ template <> void multiplayerReplicationFunctions::sendData(void if (*e) { packet << e->getIndex(); } else { - packet << std::numeric_limits::max(); + packet << sp::ecs::Entity::no_index; } } template <> void multiplayerReplicationFunctions::receiveData(void* data, sp::io::DataBuffer& packet) { + auto e = (sp::ecs::Entity*)data; + + uint32_t index; + packet >> index; + if (index < game_client->entity_mapping.size()) + *e = game_client->entity_mapping[index]; + else + *e = sp::ecs::Entity{}; } static bool collisionable_isChanged(void* data, void* prev_data_ptr) diff --git a/src/multiplayer.h b/src/multiplayer.h index b82b7687..cd1f7b06 100644 --- a/src/multiplayer.h +++ b/src/multiplayer.h @@ -310,15 +310,35 @@ class MultiplayerECSComponentReplicationBase { component_index = next->component_index + 1; } + //Called on the server so reused component slots get properly send if an entity is destroyed and reused within the same tick. + // And that destroyed entities don't need to send "destroyed component" packets for each component. + virtual void onEntityDestroyed(uint32_t index) = 0; + + virtual void sendAll(sp::io::DataBuffer& packet) = 0; virtual void update(sp::io::DataBuffer& packet) = 0; - virtual void receive(sp::ecs::Entity entity, sp::io::DataBuffer& packet) = 0; - virtual void remove(sp::ecs::Entity entity) = 0; + virtual void receive(sp::ecs::Entity entity, sp::io::DataBuffer& packet) = 0; //Called from client + virtual void remove(sp::ecs::Entity entity) = 0; //Called from client }; + +//Simple replication setup that just sends over the whole component each time it is changed. template class MultiplayerECSComponentReplication : public MultiplayerECSComponentReplicationBase { public: sp::SparseSet component_copy; + void onEntityDestroyed(uint32_t index) override + { + component_copy.remove(index); + } + + void sendAll(sp::io::DataBuffer& packet) override + { + for(auto [index, data] : sp::ecs::ComponentStorage::storage.sparseset) + { + packet << CMD_ECS_SET_COMPONENT << component_index << index << data; + } + } + void update(sp::io::DataBuffer& packet) override { for(auto [index, data] : sp::ecs::ComponentStorage::storage.sparseset) @@ -343,6 +363,7 @@ template class MultiplayerECSComponentReplication : public Multiplay packet >> data; entity.addComponent(data); } + void remove(sp::ecs::Entity entity) override { entity.removeComponent(); diff --git a/src/multiplayer_client.cpp b/src/multiplayer_client.cpp index b5926a9e..3f363b85 100644 --- a/src/multiplayer_client.cpp +++ b/src/multiplayer_client.cpp @@ -277,8 +277,10 @@ void GameClient::update(float /*delta*/) uint32_t index; packet >> component_index >> index; for(auto ecsrb = MultiplayerECSComponentReplicationBase::first; ecsrb; ecsrb=ecsrb->next) { - if (ecsrb->component_index == component_index) - ecsrb->receive(entity_mapping[index], packet); + if (ecsrb->component_index == component_index) { + if (index < entity_mapping.size() && entity_mapping[index]) + ecsrb->receive(entity_mapping[index], packet); + } } } break; @@ -288,7 +290,7 @@ void GameClient::update(float /*delta*/) uint32_t index; packet >> component_index >> index; for(auto ecsrb = MultiplayerECSComponentReplicationBase::first; ecsrb; ecsrb=ecsrb->next) { - if (ecsrb->component_index == component_index) + if (ecsrb->component_index == component_index && index < entity_mapping.size()) ecsrb->remove(entity_mapping[index]); } } diff --git a/src/multiplayer_server.cpp b/src/multiplayer_server.cpp index bd4e5b6d..c4049214 100644 --- a/src/multiplayer_server.cpp +++ b/src/multiplayer_server.cpp @@ -149,10 +149,14 @@ void GameServer::update(float /*gameDelta*/) ecs_packet << CMD_ECS_UPDATE; // For each entity, check which version number we last transmitted and if it is changed, transmit creation/deletion of entities. ecs_entity_version.resize(sp::ecs::Entity::entity_version.size(), std::numeric_limits::max()); - for(uint32_t index; indexnext) { + ecsrb->onEntityDestroyed(index); + } + } ecs_entity_version[index] = sp::ecs::Entity::entity_version[index]; if (!(ecs_entity_version[index] & sp::ecs::Entity::destroyed_flag)) ecs_packet << CMD_ECS_ENTITY_CREATE << index; @@ -475,6 +479,19 @@ void GameServer::handleNewClient(ClientInfo& info) onNewClient(info.client_id); + //Replicate ECS data, we send this as one big packet so ECS state is always consistent on the client. + sp::io::DataBuffer ecs_packet; + ecs_packet << CMD_ECS_UPDATE; + // For each entity, check which version number we last transmitted and if it is changed, transmit creation/deletion of entities. + for(uint32_t index=0; indexnext) + ecsrb->sendAll(ecs_packet); + info.socket->queue(ecs_packet); + //On a new client, first create all the already existing objects. And update all the values. for(std::unordered_map >::iterator i=objectMap.begin(); i != objectMap.end(); i++) {