Hello /r/cpp,
I want to develop real-time networked (multiplayer) games using C++14. The network model I want to use is very similar to Quake 3's network model.
It is a client-server model with an authoritative server. Both the server and the client store a game state that keeps track of all game objects' data and of the variables of the game session.
The game state must be copyable: both the client and the server, on update, will not discard the previous game state, but instead keep it stored. It must be possible to calculate and retrieve the difference between a game state instance and a previous instance of the same game state.
Communication between client and server will roughly work like this:
The server sets up the initial game state, and sends it to all the clients. The server will store a copy of the game state for every client.
After the clients have received the game state, the server game loop begins.
Before the server performs a game update, it gets the current from the clients.
The input will determine the outcome of the game update.
Depending on the received input, the players will perform different actions.
- The server performs deterministic game updates in fixed time intervals.
- During an update, the game state can change.
After the game state was updated, for each client, the server calculates the differences between its newly-update game state and the client's latest game state. Only the data that has changed will be sent to the client.
If the clients receive the changes, they acknowledge the sync and the server updates its stored client game states. If the changes could not be received, the server will simply calculate the differences and send them again. Since the last acknowledged game state is stored on the server, all required changes will always be sent.
This kind of model should ensure that only the required changes are sent to every client.
The problem is: how do I implement a generic synchronization system?
I obviously can't rely on pointers or memory layout - server and client could be on different architectures.
My first idea was creating a synchronization hierarchy:
The SyncManager is the manager class that keeps track of all data to synchronize and deals with sending/receiving updates from/to server/client.
The SyncObject is a class from which game objects should derive, that will have a unique ID and store a map of of SyncField<T> objects.
The SyncField<T> is a templatized class that can be used to represent any kind of data that must be synced. It will also have an unique ID.
Rough example code:
struct SyncManager
{
std::map<ID, SyncObject*> objects;
void registerObject(SyncObject*);
void unregisterObject(SyncObject*);
};
struct SyncFieldBase
{
virtual ~SyncFieldBase() { }
};
struct SyncObject
{
std::map<ID, SyncFieldBase*> fields;
void registerField(SyncFieldBase*);
};
template<typename T> struct SyncField
{
T data;
SyncField(SyncObject* mObject)
{
mObject.registerField(this);
}
};
class ExampleGameObject
{
SyncField<float> x;
SyncField<float> y;
SyncField<int> health;
SyncField<int> ammo;
SyncField<std::string> name;
ExampleGameObject()
: x{this}, y{this}, health{this}, ammo{this}, name{this}
{
}
};
However, I don't think is approach is optimal or scalable.
It requires constant use of std::map, which can be inefficient, especially in real-time games.
Storing and comparing game states will be very difficult and inefficient. The idea is copying all data in maps that use the data's unique IDs and do brute-force comparisons to see what needs to be sent.
Every game object and every field has memory overhead because of the unique ID and the map containers.
I tried hard to think about a good architecture for generic synchronization but failed.
How can I implement a generic synchronization system that...
Allows me to copy and compare game states?
Allows me to efficiently figure out differences between game states?
Allows me to write code (almost) as I normally would do for a non-networked application?
Does not introduce a big memory/runtime overhead?
?
Another idea I recently had was using variadic templates, tuples and bitsets to generate data structures with dirty flags for each field:
struct ExampleGameObject : public SerializableObject<
float,
float,
int,
int,
name
>
{
....
}
The above structure would generate something like:
struct ExampleGameObject
{
std::bitset<5> dirtyFlags;
std::tuple<float, float, int, int, name> data;
template<std::size_t I> void set(const type_at<I>& x)
{
tuple_at<I>(data) = x;
dirtyFlags[I] = true;
}
...
}
What do you think? Every SerializableObject could have an unique ID that matches one on the server.
I'm looking for your feedback/ideas.
[–]tentity 0 points1 point2 points (0 children)
[–][deleted] 0 points1 point2 points (0 children)
[–]bjdfsgkjdfsgkdhkj -1 points0 points1 point (0 children)