Gillius's Programming

High-Level GNE API Server Design

Final Draft, October 19, 2002

Technical-Level Connection Process Flow

I've been working on the design and capabilities on the server-side of things. I've been stubbing some stuff out, and I decided to make a flow chart to help me design what I'm going to do. This is essentially the third revision of what work I've been doing internally, so I'm putting it up here for possible comments. Explaination of some concepts are below.

Back to High-Level Design

Original Visio Diagram

PlayerPacket

The player data is stored in a single packet. I decided on going this route to make things simpler, and for almost all games I can think of, this should be enough. This single packet will allow for approx 500 bytes of data to be attached to the player. You may think "well what if the player has all these units?" My answer to that is that the player data only contains data about the player themselves, for example the player name, player ID, ping time, score, player type (ie in an RPG, this might be a piece of data saying the client wants to join as a warrior or mage), and a few other small attributes. Objects (or avatars) that directly represent players in the game, or the player's units, are best dealt as objects to be handled by the user-side code. Then the packet that described an object in the game also contains the player ID of the person who owns that object. When the packet comes in, the client will create that object, and attach that object to the player using user-defined methods in their subclass of the PlayerData object. In this sense, a single player can have any number of packets "about" that player, but really there is only a single packet that's truly ABOUT the player themselves.

The PlayerData class has virtual methods that ask the user code to create a PlayerPacket describing the player, a method to create a new player based on an incoming packet, and a third method to update a player's information based on a PlayerPacket. The PlayerPacket has a unique ID already assigned by GNE, so the user must always return a single subclass of PlayerPacket and be designed to take that subclass any time GNE asks the user code to use or generate PlayerPackets. The methods here come from the fact that PlayerPacket is a NetworkObject.

Acceptance Process

The client starts out sending its PlayerPacket to the server. The server then decides if it likes that packet or not. If it does, it accepts the client. If it doesn't, it modifies the PlayerPacket and sends it back to the client. It may not like it because the name is not unique (i.e. there is already a player "Jason"), and makes a suggestion (for example to change the name to "Jason2"). If the client sends back the identical packet, then the server accepts the client. If it sends back a different packet, the process starts over. This negotiation is done in the user-side code. The server can choose either to allow the client, suggest a different PlayerPacket, or completely refuse the connection outright and refuse the client (the server also knows its ping times and IP address at this time, and it may decide the ping is too much, or the IP is banned, or something in the PlayerPacket it REALLY doesn't like). This process is done in user-code so that the programmer of the game has the maximum flexibility in handling player data.

Snapshots

A difficult issue in joining players into a game already in progress is snapshotting. When we start sending game state or player information to a new connection, this state will likely change between the time we send the packets (snapshot the current data) and the time the client actually connects. While not conceptually difficult to deal with, it can be a technical problem because from a coding standpoint, a connection transferring the snapshot is connecting, but not connected. You cannot treat it like all other connections, but many of the events for other connections must be queued up for the connecting client.

GNE will handle the issue of snapshotting player's data, but for user-data this is more complicated. GNE will provide a "queue up" method for sending packets after a connection has completed. The idea behind the queuing is this:

  1. The user-side code takes a snapshot of the game state, level, and all objects. It starts sending these to the client.
  2. The game state will be changing between the snapshot and when the client finishes the download.
  3. Changes that are sent to all players may not be relevant to a connecting client (such as chat messages).
  4. The user code queues up all changes to the snapshot while the game progresses and the client downloads, so these changes start to be sent after the snapshot download is complete.
  5. Eventually the client catches up, and then it can truly become part of the game.

The user will be responsible for ensuring the game logic synchronization. One possible user-code solution to the problem:

  1. Wrap the game logic code with a mutex call. This mutex will enable to the game logic to be paused/stopped if someone else acquires that mutex. There will be no significant effect on performance since the mutex will be locked/unlocked very rarely (once per frame), and contention will be extremely low.
  2. When a new player arrives, and the time for a snapshot comes, the network event thread (at the time running the user event for negotiations), will acquire the game logic mutex, effectively pausing the game.
  3. The snapshot code will quickly copy all relevant data for the player, and insert the packets needed into that player's queue.
  4. Then, the code will add the player to the list that gets important changes to game state (this is the part where GNE helps, by maintaining this list).
  5. The snapshot code then unpauses the game.
  6. When the snapshot download is complete, and the client has caught up (its queue will become empty or nearly empty), it will be considered to have completely joined the game, and is treated like a normal player thereafter. The waiting for this completion will be defined by the user-code. It will not return from the event until it wants this to happen. Some utility methods will likely be provided to aid in this.

Because the event thread remains open during the snapshot process, the user code can decide if and when the connecting client times out because it views it as too slow.