Skip to content

Commit

Permalink
Add untested component replication code, quick&dirty
Browse files Browse the repository at this point in the history
  • Loading branch information
daid committed Oct 27, 2022
1 parent 88837dd commit 0563f42
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 3 deletions.
3 changes: 3 additions & 0 deletions src/ecs/component.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include "container/sparseset.h"

class GameServer;
template<typename T> class MultiplayerECSComponentReplication;
namespace sp::ecs {

class ComponentStorageBase {
Expand Down Expand Up @@ -29,6 +31,7 @@ template<typename T> class ComponentStorage : public ComponentStorageBase {

friend class Entity;
template<class, class...> friend class Query;
friend class ::MultiplayerECSComponentReplication<T>;
};

}
4 changes: 2 additions & 2 deletions src/ecs/entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

namespace sp::ecs {

static std::vector<uint32_t> entity_version;
static std::vector<uint32_t> free_list;
std::vector<uint32_t> Entity::entity_version;
std::vector<uint32_t> Entity::free_list;

Entity Entity::create()
{
Expand Down
12 changes: 11 additions & 1 deletion src/ecs/entity.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -41,12 +44,19 @@ class Entity final {
}

private:
static Entity fromIndex(uint32_t index);

uint32_t index = std::numeric_limits<uint32_t>::max();
uint32_t version = std::numeric_limits<uint32_t>::max();

static Entity fromIndex(uint32_t index);
static std::vector<uint32_t> entity_version;
static std::vector<uint32_t> free_list;

template<class, class...> 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.
};

}
60 changes: 60 additions & 0 deletions src/multiplayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <stdint.h>
#include "Updatable.h"
#include "stringImproved.h"
#include "ecs/entity.h"
#include "multiplayer_internal.h"

class MultiplayerObject;

Expand Down Expand Up @@ -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<memberReplicationInfo.size(); n++)
Expand Down Expand Up @@ -292,4 +300,56 @@ template<class T> 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<typename T> class MultiplayerECSComponentReplication : public MultiplayerECSComponentReplicationBase
{
public:
sp::SparseSet<T> component_copy;

void update(sp::io::DataBuffer& packet) override
{
for(auto [index, data] : sp::ecs::ComponentStorage<T>::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<T>::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<T>::storage.sparseset.set(index, data);
}
void remove(uint32_t index) override
{
sp::ecs::ComponentStorage<T>::storage.sparseset.remove(index);
}
};

#endif//MULTIPLAYER_H
43 changes: 43 additions & 0 deletions src/multiplayer_client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
5 changes: 5 additions & 0 deletions src/multiplayer_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 20 additions & 0 deletions src/multiplayer_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "multiplayer_internal.h"
#include "multiplayer.h"
#include "engine.h"
#include "ecs/entity.h"

#include "io/http/request.h"

Expand Down Expand Up @@ -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<uint32_t>::max());
for(uint32_t index; index<sp::ecs::Entity::entity_version.size(); index++) {
if (ecs_entity_version[index] != sp::ecs::Entity::entity_version[index]) {
ecs_entity_version[index] = sp::ecs::Entity::entity_version[index];
ecs_packet << CMD_ECS_ENTITY_VERSION << index << ecs_entity_version[index];
}
}
// For each component type, check which components are added/changed/deleted and send that over.
for(auto ecsrb = MultiplayerECSComponentReplicationBase::first; ecsrb; ecsrb=ecsrb->next) {
ecsrb->update(ecs_packet);
}
if (ecs_packet.getDataSize() > sizeof(CMD_ECS_UPDATE)) {
sendAll(ecs_packet);
}

std::vector<int32_t> delList;
for(std::unordered_map<int32_t, P<MultiplayerObject> >::iterator i=objectMap.begin(); i != objectMap.end(); i++)
{
Expand Down
2 changes: 2 additions & 0 deletions src/multiplayer_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ class GameServer : public Updatable
int32_t nextObjectId;
std::unordered_map<int32_t, P<MultiplayerObject> > objectMap;

std::vector<uint32_t> ecs_entity_version;

string master_server_url;
std::thread master_server_update_thread;
MasterServerState master_server_state = MasterServerState::Disabled;
Expand Down

1 comment on commit 0563f42

@daid-tinyci
Copy link

@daid-tinyci daid-tinyci bot commented on 0563f42 Dec 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TinyCI build failure:

[/home/tinyci/builds/daid/SeriousProton/_build_native:make -j 2] returned [2]:


[ 15%] Built target spfreetype2

[ 16%] Built target glad

[ 29%] Built target box2d

[ 37%] Built target lua

[ 43%] Built target basisu-encoder

[ 83%] Built target opus

[ 83%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/engine.cpp.o

[ 83%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/multiplayer.cpp.o

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/multiplayer_client.cpp.o

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/multiplayer_proxy.cpp.o

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/multiplayer_server.cpp.o

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/multiplayer_server_scanner.cpp.o

[ 84%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/networkRecorder.cpp.o

[ 85%] Building CXX object CMakeFiles/seriousproton_objects.dir/src/ecs/entity.cpp.o

In file included from /data/tinyci_builds/daid/SeriousProton/src/ecs/component.h:3,

                 from /data/tinyci_builds/daid/SeriousProton/src/ecs/entity.h:6,

                 from /data/tinyci_builds/daid/SeriousProton/src/ecs/entity.cpp:1:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:60:35: error: ‘size_t’ has not been declared

   60 |         Iterator(SparseSet& _set, size_t _dense_index) : set(_set), dense_index(_dense_index) {}

      |                                   ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:70:9: error: ‘size_t’ does not name a type

   70 |         size_t dense_index;

      |         ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:5:1: note: ‘size_t’ is defined in header ‘<cstddef>’; did you forget to ‘#include <cstddef>’?

    4 | #include <vector>

  +++ |+#include <cstddef>

    5 | #include <limits>

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In constructor ‘sp::SparseSet<T>::Iterator::Iterator(sp::SparseSet<T>&, int)’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:60:69: error: class ‘sp::SparseSet<T>::Iterator’ does not have any field named ‘dense_index’

   60 |         Iterator(SparseSet& _set, size_t _dense_index) : set(_set), dense_index(_dense_index) {}

      |                                                                     ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘bool sp::SparseSet<T>::Iterator::operator!=(const sp::SparseSet<T>::Iterator&) const’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:62:63: error: ‘dense_index’ was not declared in this scope

   62 |         bool operator!=(const Iterator& other) const { return dense_index != other.dense_index; }

      |                                                               ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘void sp::SparseSet<T>::Iterator::operator++()’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:63:29: error: ‘dense_index’ was not declared in this scope

   63 |         void operator++() { dense_index--; }

      |                             ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘std::pair<unsigned int, T&> sp::SparseSet<T>::Iterator::operator*()’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:64:65: error: ‘dense_index’ was not declared in this scope

   64 |         std::pair<uint32_t, T&> operator*() { return {set.dense[dense_index], set.data[dense_index]}; }

      |                                                                 ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘std::pair<unsigned int, const T&> sp::SparseSet<T>::Iterator::operator*() const’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:65:77: error: ‘dense_index’ was not declared in this scope

   65 |         std::pair<uint32_t, const T&> operator*() const { return {set.dense[dense_index], set.data[dense_index]}; }

      |                                                                             ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘bool sp::SparseSet<T>::Iterator::atEnd()’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:67:31: error: ‘dense_index’ was not declared in this scope

   67 |         bool atEnd() { return dense_index == std::numeric_limits<size_t>::max(); }

      |                               ^~~~~~~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:67:66: error: ‘size_t’ was not declared in this scope; did you mean ‘std::size_t’?

   67 |         bool atEnd() { return dense_index == std::numeric_limits<size_t>::max(); }

      |                                                                  ^~~~~~

      |                                                                  std::size_t

In file included from /usr/include/c++/12/limits:42,

                 from /data/tinyci_builds/daid/SeriousProton/src/ecs/entity.h:4:

/usr/include/x86_64-linux-gnu/c++/12/bits/c++config.h:298:33: note: ‘std::size_t’ declared here

  298 |   typedef __SIZE_TYPE__         size_t;

      |                                 ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:67:72: error: template argument 1 is invalid

   67 |         bool atEnd() { return dense_index == std::numeric_limits<size_t>::max(); }

      |                                                                        ^

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h: In member function ‘sp::SparseSet<T>::Iterator sp::SparseSet<T>::end()’:

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:74:65: error: ‘size_t’ was not declared in this scope; did you mean ‘std::size_t’?

   74 |     Iterator end() { return Iterator(*this, std::numeric_limits<size_t>::max()); }

      |                                                                 ^~~~~~

      |                                                                 std::size_t

/usr/include/x86_64-linux-gnu/c++/12/bits/c++config.h:298:33: note: ‘std::size_t’ declared here

  298 |   typedef __SIZE_TYPE__         size_t;

      |                                 ^~~~~~

/data/tinyci_builds/daid/SeriousProton/src/container/sparseset.h:74:71: error: template argument 1 is invalid

   74 |     Iterator end() { return Iterator(*this, std::numeric_limits<size_t>::max()); }

      |                                                                       ^

make[2]: *** [CMakeFiles/seriousproton_objects.dir/build.make:866: CMakeFiles/seriousproton_objects.dir/src/ecs/entity.cpp.o] Error 1

make[2]: *** Waiting for unfinished jobs....

make[1]: *** [CMakeFiles/Makefile2:238: CMakeFiles/seriousproton_objects.dir/all] Error 2

make: *** [Makefile:136: all] Error 2

Please sign in to comment.