Sign in to follow this  
Timbatron

Feedback on network framework

Recommended Posts

So I've been thinking about a few multiplayer games I want to write, one of them being sort of a multiplayer roguelike game. Thinking about how to keep state synchronized between clients seemed like a monumental task. Every possible way that players can interact with the environment would need a custom message if I was doing things in the same paradigm I've used in previous projects. Since I'm writing this in C#, I figured there was probably a better way to do this, in a more generalized way. Looking online, I can't seem to find any libraries that do quite what I want. Using a bit of C# reflection, however, I think it wouldn't be horribly difficult to create a generalized library that would work with simple games. Here's a summary of how the library would work: 1. All state classes must be serializable. 2. All state classes must be clonable. 3. All state transitions must be deterministic (that is, you can always calculate state k + 1 from state k without any randomness). 4. All state classes must have a unique ID, and are stored in a dictionary of all objects (indexed by ID). The dictionary of all objects in the global game state is called GameState 5. All references to other objects within the game state must be encapsulated within another object, call it GameRef. This GameRef serializes as an ID, maintaining a weak reference to the object, without serializing the referenced object within the parent object. (This lets you serialize stuff with circular references, etc.) 6. On initially connecting to a server, the GameState is serialized and sent to the new client. 7. Game state is updated at a set time interval. This interval will be called updateTime. 8. At each interval of updateTime, the state is transitioned to the next state (this is deterministic) on every client. 9. Every so often, the server will send a packet indicating what state it is currently at, called K. The client must save a copy of state K until the next server sync packet. This sync packet would also try to sync the updates so that all clients are updating at approximately the same time (maybe measuring latency...) 10. If the client wishes to interact with the state, it sends a client delta request. The delta request is formatted as follows: To modify a state class (call it SC), the client creates a copy of SC, makes the changes it desires, serializes it, and sends it to the server as a delta request packet. 11. If a delta change is approved by the server, the server will broadcast a delta state packet with the same information as the delta request packet, and the timestamp that it is applied to, call it J. If the client has already progressed past state J, it must recalculate the state J from state K (saved from the last sync). This is possible because all state changes are deterministic. The delta is applied to state J, and this becomes the new state K. I don't know how well this would work for games that are very latency-sensitive, but I think this could make it easier to create some types of games, since most of the network stuff would be hidden from the programmer. Anyone have any comments or criticism?

Share this post


Link to post
Share on other sites
That seems complicated, I couldn't follow everything :)

If you take the Quake3 model, it's like this (you probably know it already, but I'll run a quick recap).

For each client, and each frame, the server builds a game state (a 'view' of the game from the client perspective) and pushes it into a queue. That state gets assigned a sequence number.

The client, upon reception of the state, stores it into its own queue (I think it only has to be two state deep, but no harm running it deeper), and returns a ack of the state sequence number. Obviously, out-of-order or incomplete states are discarded.

The server receives the ack.

The server then builds a delta of the state acked by the client, and it's latest state. The delta is sent. If the ack number is too old (the state can't be found in the queue), then obvisouly a ful state (snapshot) is sent.

When a client receives a delta, he should already have the reference state stored into its reception queue, and therefore, can un-delta the packet and construct the new server state.

This is the transmission bit.

As for the game state and serialisation, you gotta have to use entities guids and class guids, to identify each entity. Each entity should also be fully serialisable, and deserialisable. It should also be possible to instanciate an entity from a serialisation packet (basically, a default constructor, and call the deserialise()).

Also, there must be some sort of mechanism to signal a destroyed entity to a client.

The first snapshot is likely to be big, much bigger than the MTU, so your engine should provide a way to chop and re-assemble network packets efficiently and reliably. Also, the server should wait until the client aknowledges the first snapshot, before sending further states, to avoid indefinite wait on the client side, if the packet loss is too important and choking the server output. If the client fails to return a ack in due time (that is, the snapshot being sent is pushed out of the queue), the client fails to connect.

Each entity should is unique, and the game state is therefore made of a list of unique entity serialisations. This is cool, since now, the delta compression is on a per-entity basis.

Personnaly, for the delta, I use a RLE encoder. If the data is packed correctly (first, data members that rarely change, then at the end, the data members that are likely to change), then the data will be minimal.

Quake3 uses a bitfield to signal which data is inside the delta. So is Counterstrike and the likes. Quake3 does nothing really clever, while CS registers class members for serialisation at the constructor automatically.

This delta-compression is up to you, and can be implemented on a per-entity basis.

Now, this is a server-client architecture.

for a more Peer to Peer approach, the same can be done too. There is no reason why peers cannot create entities of their own (like player grenades, and their player himself), and send the part of the game state in their control to the server and other peers, as delta-encoded.

The only problem I've found so far, is entity guid clashes. So you need to reserve a guid range for each clients. Pretty easy to fix.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this