diff --git a/src/ecs/component.h b/src/ecs/component.h index 06b8dbed..0035be83 100644 --- a/src/ecs/component.h +++ b/src/ecs/component.h @@ -2,6 +2,8 @@ #include "container/sparseset.h" +class GameServer; +template class MultiplayerECSComponentReplication; namespace sp::ecs { class ComponentStorageBase { @@ -29,6 +31,7 @@ template class ComponentStorage : public ComponentStorageBase { friend class Entity; template friend class Query; + friend class ::MultiplayerECSComponentReplication; }; } \ No newline at end of file diff --git a/src/ecs/entity.cpp b/src/ecs/entity.cpp index 114cdce6..941a7d41 100644 --- a/src/ecs/entity.cpp +++ b/src/ecs/entity.cpp @@ -4,8 +4,8 @@ namespace sp::ecs { -static std::vector entity_version; -static std::vector free_list; +std::vector Entity::entity_version; +std::vector Entity::free_list; Entity Entity::create() { diff --git a/src/ecs/entity.h b/src/ecs/entity.h index 84107754..150e80e0 100644 --- a/src/ecs/entity.h +++ b/src/ecs/entity.h @@ -6,6 +6,9 @@ #include "component.h" +class MultiplayerObject; +class GameServer; +class GameClient; namespace sp::ecs { // An entity is a lightweight, copyable reference to entity components. @@ -41,12 +44,19 @@ class Entity final { } private: - static Entity fromIndex(uint32_t index); uint32_t index = std::numeric_limits::max(); uint32_t version = std::numeric_limits::max(); + static Entity fromIndex(uint32_t index); + static std::vector entity_version; + static std::vector free_list; + template friend class Query; + + friend class ::MultiplayerObject; // We need to be a friend for network replication. + friend class ::GameServer; // We need to be a friend for network replication. + friend class ::GameClient; // We need to be a friend for network replication. }; } \ No newline at end of file diff --git a/src/multiplayer.h b/src/multiplayer.h index 42111bed..0c8f125e 100644 --- a/src/multiplayer.h +++ b/src/multiplayer.h @@ -5,6 +5,8 @@ #include #include "Updatable.h" #include "stringImproved.h" +#include "ecs/entity.h" +#include "multiplayer_internal.h" class MultiplayerObject; @@ -224,6 +226,12 @@ class MultiplayerObject : public virtual PObject registerMemberReplication(&member->z, update_delay); } + void registerMemberReplication_(F_PARAM sp::ecs::Entity* member, float update_delay = 0.0f) + { + registerMemberReplication(&member->index, update_delay); + registerMemberReplication(&member->version, update_delay); + } + void updateMemberReplicationUpdateDelay(void* data, float update_delay) { for(unsigned int n=0; n MultiplayerObject* createMultiplayerObject() return new T(); } +class MultiplayerECSComponentReplicationBase { +public: + static inline MultiplayerECSComponentReplicationBase* first = nullptr; + MultiplayerECSComponentReplicationBase* next; + uint16_t component_index = 0; + + MultiplayerECSComponentReplicationBase() { + next = first; + first = this; + if (next) + component_index = next->component_index + 1; + } + + virtual void update(sp::io::DataBuffer& packet) = 0; + virtual void receive(uint32_t index, sp::io::DataBuffer& packet) = 0; + virtual void remove(uint32_t index) = 0; +}; +template class MultiplayerECSComponentReplication : public MultiplayerECSComponentReplicationBase +{ +public: + sp::SparseSet component_copy; + + void update(sp::io::DataBuffer& packet) override + { + for(auto [index, data] : sp::ecs::ComponentStorage::storage.sparseset) + { + if (!component_copy.has(index) || component_copy.get(index) != data) { + component_copy.set(index, data); + packet << CMD_ECS_SET_COMPONENT << component_index << index << data; + } + } + for(auto [index, data] : component_copy) + { + if (!sp::ecs::ComponentStorage::storage.sparseset.has(index)) { + component_copy.remove(index); + packet << CMD_ECS_DEL_COMPONENT << component_index << index; + } + } + } + + void receive(uint32_t index, sp::io::DataBuffer& packet) override + { + T data; + packet >> data; + sp::ecs::ComponentStorage::storage.sparseset.set(index, data); + } + void remove(uint32_t index) override + { + sp::ecs::ComponentStorage::storage.sparseset.remove(index); + } +}; + #endif//MULTIPLAYER_H diff --git a/src/multiplayer_client.cpp b/src/multiplayer_client.cpp index 5bbae0b0..7e8d453b 100644 --- a/src/multiplayer_client.cpp +++ b/src/multiplayer_client.cpp @@ -247,6 +247,49 @@ void GameClient::update(float /*delta*/) reply << CMD_ALIVE_RESP; socket->send(reply); break; + case CMD_ECS_UPDATE: + while(packet.available()) + { + uint8_t ecs_cmd; + packet >> ecs_cmd; + switch(ecs_cmd) + { + case CMD_ECS_ENTITY_VERSION: + { + uint32_t index, version; + packet >> index >> version; + if (index >= sp::ecs::Entity::entity_version.size()) { + sp::ecs::Entity::entity_version.push_back(version); + } else if (sp::ecs::Entity::entity_version[index] != version) { + sp::ecs::Entity::entity_version[index] = version; + } + } + break; + case CMD_ECS_SET_COMPONENT: + { + uint16_t component_index; + uint32_t index; + packet >> component_index >> index; + for(auto ecsrb = MultiplayerECSComponentReplicationBase::first; ecsrb; ecsrb=ecsrb->next) { + if (ecsrb->component_index == component_index) + ecsrb->receive(index, packet); + } + } + break; + case CMD_ECS_DEL_COMPONENT: + { + uint16_t component_index; + uint32_t index; + packet >> component_index >> index; + for(auto ecsrb = MultiplayerECSComponentReplicationBase::first; ecsrb; ecsrb=ecsrb->next) { + if (ecsrb->component_index == component_index) + ecsrb->remove(index); + } + } + break; + } + } + break; default: LOG(ERROR) << "Unknown command from server: " << command; } diff --git a/src/multiplayer_internal.h b/src/multiplayer_internal.h index b935fb10..b0ae5c2f 100644 --- a/src/multiplayer_internal.h +++ b/src/multiplayer_internal.h @@ -25,4 +25,9 @@ static const command_t CMD_AUDIO_COMM_START = 0x0020; static const command_t CMD_AUDIO_COMM_DATA = 0x0021; static const command_t CMD_AUDIO_COMM_STOP = 0x0022; +static constexpr command_t CMD_ECS_UPDATE = 0x0030; +static constexpr uint8_t CMD_ECS_ENTITY_VERSION = 0x00; +static constexpr uint8_t CMD_ECS_SET_COMPONENT = 0x01; +static constexpr uint8_t CMD_ECS_DEL_COMPONENT = 0x02; + #endif//MULTIPLAYER_INTERNAL_H diff --git a/src/multiplayer_server.cpp b/src/multiplayer_server.cpp index 3c9245ee..407499e0 100644 --- a/src/multiplayer_server.cpp +++ b/src/multiplayer_server.cpp @@ -3,6 +3,7 @@ #include "multiplayer_internal.h" #include "multiplayer.h" #include "engine.h" +#include "ecs/entity.h" #include "io/http/request.h" @@ -143,6 +144,25 @@ void GameServer::update(float /*gameDelta*/) sendAll(packet); } + //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 the new version number. + ecs_entity_version.resize(sp::ecs::Entity::entity_version.size(), std::numeric_limits::max()); + for(uint32_t index; indexnext) { + ecsrb->update(ecs_packet); + } + if (ecs_packet.getDataSize() > sizeof(CMD_ECS_UPDATE)) { + sendAll(ecs_packet); + } + std::vector delList; for(std::unordered_map >::iterator i=objectMap.begin(); i != objectMap.end(); i++) { diff --git a/src/multiplayer_server.h b/src/multiplayer_server.h index 988db30b..aa96c728 100644 --- a/src/multiplayer_server.h +++ b/src/multiplayer_server.h @@ -88,6 +88,8 @@ class GameServer : public Updatable int32_t nextObjectId; std::unordered_map > objectMap; + std::vector ecs_entity_version; + string master_server_url; std::thread master_server_update_thread; MasterServerState master_server_state = MasterServerState::Disabled;