The Server Side

(Ben is doing this with Mel's help)

The server performs almost all the calculations for the game and keeps all the clients moving in lock-step. It is responsible for receiving user input messages from the clients and being able to safely recompute and broadcast the changes in the latest frame in a timely fashion. Synchronization was a heated topic in our group at first. We were worried about mutual exclusion since when the world is being recalculated and rebroadcast, a new packet could come in in the middle and change what the world looks like. We fixed that problem by agreeing to use a bounded buffer and sticking all the nasty DirectPlay code in to the network communications layer. Later we realized that the bounded buffer isn't necessary since DirectPlay should queue up network messages for us.

That is, the server will, upon start up, instantiate a commlayer server object (which runs a thread in the background) and register a call back function with it. That call back function, void receive(char *message), will be called every time a new packet comes in for the server off the network.

receive() is the most important function in the server. It is responsible for updating the world based upon input it receives from the clients. In order to avoid problems with the world being updated while it is being sent out to the clients (mutual exclusion), receive() is also in charge of broadcasting the world. After spawning the commlayer (which runs receive()) the "main" thread of the server has little to do but make sure that the world gets broadcast some minimum number of times per second (probably thirty). To do this, the thread will simply sleep for a quantum and then use the commlayer to send() a message to itself telling receive() that update time has come. We're assuming that Microsoft has done the DirectPlay networking code right and a message sent to localhost is very quick.

Server Pseudocode

// Pseudocode for the server process

// The main function doesn't do much. All the real work is being done
// in receive() which is being called by a thread started in main.
void winmain()
{
  double frame_rate=30;

  // Create a new comm object to receive messages using DirectPlay
  OXL_Comm_Server commobj;
  
  // Use it to create a new thread and register the receive() function 
  // as a callback when messages come in off the network.
  commobj.Create(winhandle, receive);

  // Now just make sure that an update message goes out to receive at
  // least frame_rate times per second.
  while(1)
    {
      millisleep(1000/frame_rate);
      commobj.SendSelf("update");

      // Could also place non-player updates here. (I.e., AI luxos
      // that the server controls. However, it might be better just to
      // leave all that in receive().
    }
}
	
// This is a callback function. Whenever a message comes in off the
// network for the server, we get called with a null-terminated string
// as the sole argument. We are in charge of updating the state of the
// world based upon what the clients tell us and occasionally
// broadcasting any delta information necessary to the clients.
//
// The possible messages receive can handle from a client are:
// "clientid: status"
// "clientid: action SOMEACTION"
// "clientid: keydown CHAR"   (where CHAR is a literal character)
// "clientid: keyup CHAR"   (where CHAR is a literal character)
// "clientid: joystick AXIS NUM"  
// (where AXIS is which axis (x or y) that is being sent and NUM is
// that's axis's coordinate normalized to be an int between 0 and 1000)

// Additionally receive must handle a message from the server telling
// it it is time to broadcast update information to all the clients:
// "serverid: update"

void receive(char *message)
{
  const char id[80], verb[80], adjective[80];

  sscanf(message, "%s: %s %s %s", id, verb, adjective);
  
  switch(verb)
    {
    case "new":
      // Adds a new player to the game.
      // (Client's are distinguished by clientid)
      break;

    case "delete":
      // Removes a player from the game
      break;

    case "status":
      // Sends out complete world information instead of just the delta.
      // This is used for a new client.
      break;

    case "action":
      // "clientid: action left"
      // "clientid: action right"
      // "clientid: action down"
      // "clientid: action up"
      // "clientid: action up-left"
      // "clientid: action down-left"
      // "clientid: action up-right"
      // "clientid: action down-right"
      break;

    case "keydown":
      // "clientid: keydown CHAR"   (where CHAR is a literal character)
      // "clientid: keydown button-1"
      // "clientid: keydown button-2"
      break;

    case "keyup":
      // "clientid: keyup CHAR"   (where CHAR is a literal character)
      // "clientid: keyup button-1"
      // "clientid: keyup button-2"
      break;

    case "joystick":	/* Optional */
      // "clientid: joystick x NUM"  
      // "clientid: joystick y NUM" 
      break;

    case "update":
      // "serverid: update"
      broadcast_delta();
      break;

    default:
      // Unknown verb
    }
}

Protocols

The server will send a small set of ASCII strings to the clients.

Current Todo List


ben@nospam.cs.washington.edu
Last modified: Sun May 17 21:47:41 PDT 1998