Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sequenced vectors with efficient replication #239

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions src/multiplayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <stdint.h>
#include "Updatable.h"
#include "stringImproved.h"
#include "seqvector.h"

class MultiplayerObject;

Expand Down Expand Up @@ -107,6 +108,55 @@ template <typename T> struct multiplayerReplicationFunctions
std::vector<T>* prev_data = *(std::vector<T>**)prev_data_ptr;
delete prev_data;
}

static bool isChangedSeqVector(void* data, void* prev_data_ptr)
{
sp::SeqVector<T>* ptr = (sp::SeqVector<T>*)data;
bool changed = false;
if (ptr->back().seq != ptr->last_seq)
changed = true;

return changed;
}

static void sendDataSeqVector(void* data, sp::io::DataBuffer& packet)
{
sp::SeqVector<T>* ptr = (sp::SeqVector<T>*)data;
uint16_t count = ptr->size();
unsigned int to_send = std::min(ptr->back().seq - ptr->last_seq, static_cast<unsigned int>(count));

ptr->last_seq = ptr->back().seq;
Copy link
Owner

Choose a reason for hiding this comment

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

The process of sending shouldn't modify the vector state. The concept of this code is that the isChanged call will check if it is changed and update data a the prev_data_ptr to know there is no change anymore.

Copy link
Author

Choose a reason for hiding this comment

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

I agree with you in principle ("The process of sending shouldn't modify the vector state"). We need somewhere to track what has been sent so that we don't resend the entire vector (say, of a sequence of log entries) every time we sync. A class that wraps the vector (whose state doesn't change) seemed like the right place. I experimented with just using a struct with a vector member and a last_seq member for clearer separation, but this was a cleaner fit for the templating. Maybe I've overlooked a simpler way.

I understand the prev_data approach of keeping a copy and comparing them, and started that way originally since that follows the pattern of the rest of the code. In addition to needlessly storing a second copy, I didn't come up with an easy solution to new client sync. Basically, instead of last_seq, I just used prev_data_ptr->back()->seq in it's place. The isChanged code was if (ptr->back().seq != prev_data_ptr->back().seq), and the send code was to_send = ... (ptr->back().seq - prev_data_ptr->back().seq)... and they worked fine, but of course would not sync the backlog to a new client connection. I agree that the last_seq-shuffle in sendWholeDataSeqVector is a bit hacky, but I think it's pretty clear at least?

This solution reduces memory footprint and makes for an easy way to sync new clients. The vector itself doesn't get modified by sending, just the super-class that is specifically FOR tracking. If there is a clearer way of expressing this within the framework we have here, I'm happy to learn it. :)

Copy link
Owner

Choose a reason for hiding this comment

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

Well, I don't think it has to be a huge issue to send the whole log each time it changes (high logs/sec would be unreadable no mater the log size, so not really a realistic scenario). The main issue I see with the master code is that it's CPU heavy if you increase the log size. Keeping track of the sequence number inside the prev_data would solve that part.


packet << count << to_send;
for(; to_send > 0; to_send--)
packet << (*ptr)[count - to_send];
}

static void sendWholeDataSeqVector(void* data, sp::io::DataBuffer& packet)
{
sp::SeqVector<T>* ptr = (sp::SeqVector<T>*)data;
auto last_seq = ptr->last_seq;
ptr->last_seq = ptr->front().seq - 1;
sendDataSeqVector(data, packet);
ptr->last_seq = last_seq;
}

static void receiveDataSeqVector(void* data, sp::io::DataBuffer& packet)
{
sp::SeqVector<T>* ptr = (sp::SeqVector<T>*)data;
uint16_t count;
unsigned int to_recv;
packet >> count >> to_recv;

// Remove oldest entries if local vector would grow larger than server's
while (ptr->size() + to_recv > count)
ptr->erase(ptr->begin());

ptr->resize(count);

for(unsigned int n=count - to_recv; n<count; n++)
packet >> (*ptr)[n];
}
};

template <typename T>
Expand Down Expand Up @@ -146,6 +196,7 @@ class MultiplayerObject : public virtual PObject

bool(*isChangedFunction)(void* data, void* prev_data_ptr);
void(*sendFunction)(void* data, sp::io::DataBuffer& packet);
void(*sendWholeFunction)(void* data, sp::io::DataBuffer& packet) = nullptr;
void(*receiveFunction)(void* data, sp::io::DataBuffer& packet);
void(*cleanupFunction)(void* prev_data_ptr);
};
Expand Down Expand Up @@ -217,6 +268,25 @@ class MultiplayerObject : public virtual PObject
memberReplicationInfo.push_back(info);
}

template <typename T> void registerMemberReplication_(F_PARAM sp::SeqVector<T>* member, float update_delay = 0.0f)
{
SDL_assert(!replicated);
SDL_assert(memberReplicationInfo.size() < 0xFFFF);
MemberReplicationInfo info;
#ifdef DEBUG
info.name = name;
#endif
info.ptr = member;
info.update_delay = update_delay;
info.update_timeout = 0.0f;
info.isChangedFunction = &multiplayerReplicationFunctions<T>::isChangedSeqVector;
info.sendFunction = &multiplayerReplicationFunctions<T>::sendDataSeqVector;
info.sendWholeFunction = &multiplayerReplicationFunctions<T>::sendWholeDataSeqVector;
info.receiveFunction = &multiplayerReplicationFunctions<T>::receiveDataSeqVector;
info.cleanupFunction = nullptr;
memberReplicationInfo.push_back(info);
}

void registerMemberReplication_(F_PARAM glm::vec3* member, float update_delay = 0.0f)
{
registerMemberReplication(&member->x, update_delay);
Expand Down
6 changes: 5 additions & 1 deletion src/multiplayer_server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,11 @@ void GameServer::generateCreatePacketFor(P<MultiplayerObject> obj, sp::io::DataB
for(unsigned int n=0; n<obj->memberReplicationInfo.size(); n++)
{
packet << int16_t(n);
(obj->memberReplicationInfo[n].sendFunction)(obj->memberReplicationInfo[n].ptr, packet);
if (obj->memberReplicationInfo[n].sendWholeFunction)
(obj->memberReplicationInfo[n].sendWholeFunction)(obj->memberReplicationInfo[n].ptr, packet);
else
(obj->memberReplicationInfo[n].sendFunction)(obj->memberReplicationInfo[n].ptr, packet);

}
}

Expand Down
1 change: 1 addition & 0 deletions src/multiplayer_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "stringImproved.h"
#include "networkAudioStream.h"
#include "timer.h"
#include "seqvector.h"

#include <stdint.h>
#include <unordered_map>
Expand Down
16 changes: 16 additions & 0 deletions src/seqvector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#ifndef SEQVECTOR_H
#define SEQVECTOR_H

#include <vector>

namespace sp {

// A vector that tracks sequenced elements
template<typename T> class SeqVector : public std::vector<T>{
public:
unsigned int last_seq = 0;
};

}//namespace sp

#endif//SEQVECTOR_H
Loading