posted by Jeremy Friesner on Mon 12th Aug 2002 20:52 UTC

"How the Client Handles Database Update Messages"
Now that we've covered some of the basic communication with the server, let's move up to a slightly higher level and look at how the client and server interact and make use of the MUSCLE server-side node-tree database.

In FRCWindow::ConnectedToServer(), the client sends a number of initialization Messages to the server to set things up. These include uploading our current user-name (so that other clients know who we are), and sending the database-live-query-subscription requests described previously in this document. One interesting Message that we send to the server is a Message that enables reflect-to-self mode for our client. Here is the code that does that:


   // Tell the muscle server we want updates on our own database nodes as well as everyone else's.
   // This way we'll see our self in the users list, which is important if we want to see our own stats.
   MessageRef turnOnSelfReflect = GetMessageFromPool(PR_COMMAND_SETPARAMETERS);
   if (turnOnSelfReflect())
   {
      turnOnSelfReflect()->AddBool(PR_NAME_REFLECT_TO_SELF, true);
      SendMessageToServer(turnOnSelfReflect);
   }     

Why do we do that? By default, MUSCLE database queries will not return results that represent nodes in your own session's subtree of the node tree. This is because your client has sole control over the contents of those nodes -- and if the only way for a node to appear in your subtree is because you (the client) uploaded it, then you should presumably already know what it is, so why bother telling you what you already know? That makes sense, but it helps to simplify the client if we can treat our own data the same as all the other data in the database, even if it does mean using up a little unnecessary bandwidth. So we send the above PR_COMMAND_SETPARAMETERS Message to the server, telling it to enable reflect-to-self mode... this causes the server to change its behaviour so that we get notifications regarding our own nodes as well as everyone else's. So, later on, when we subscribe to "/*/*/frc/uname" to find out the user names of all the FoxRabbitCarrot clients who are logged in to the server, our own name will be included in the results we get back -- which is important, because we want our own name included in the users list.

Probably the most complicated part of a MUSCLE client program is properly handling the database-update Messages that the server sends to your client. The database-update-Messages you get from the server never come unbidden -- they only come in response to your own subscription requests (or PR_COMMAND_GETDATA Messages). But whether you sent a one-time database query or a persistent, "live" subscription request, the result notifications come back in the same format: as one or more PR_RESULT_DATAITEMS Messages.

A PR_RESULT_DATAITEMS Message can contain either or both of two types of data: It may contain a list of Strings indicating the node-paths of matching nodes that have been deleted from the server-side database tree, and it may contain a list of Messages indicating the new Message payloads of matching nodes in the tree. Given the subscription requests passed to the server by our FoxRabbitCarrot client, an example PR_RESULT_DATAITEMS Message returned by the server might look like this:


Message:  what=PR_RESULT_DATAITEMS
  Field:  name=PR_NAME_REMOVED_DATAITEMS type=B_STRING_TYPE
    Value 0:  "//0/frc/pieces/5/12"
    Value 1:  "//0/frc/pieces/6/57"
    Value 2:  "/132.239.50.8/23/frc/uname"
  Field:  name="//0/frc/pieces/3/15" type=B_MESSAGE_TYPE
    Value 0:  [A Message containing data describing piece #15's health, type, position, gender, etc]
  Field:  name="//0/frc/pieces/6/59" type=B_MESSAGE_TYPE
    Value 0:  [A Message containing data describing piece #59's health, type, position, gender, etc]

As you can see, there can be many different types of information communicated in a PR_RESULT_DATAITEMS Message, but they are all expressed as updates to nodes. In this particular Message, the first field, which is a String field with the name "!SnRd" (also known by its predefined constant, PR_NAME_REMOVED_DATAITEMS), represents nodes that have been deleted from the database. The first two values in that field belong to the FRCGameStateSession, which is always session 0 (because it is always the first session added to the server). The FRCGameStateSession is the one that holds the state of the game in its subtree of the server database, and here we can see that it has deleted two nodes from its subtree: "frc/pieces/5/12" is the node that represented the GamePiece with ID #12, and belonged to the client with session ID #5. "frc/pieces/6/57", on the other hand, is the node that represented the GamePiece with ID #57, and belonged to the client with session ID #6. This Message informs the client that both of these database nodes, and hence their corresponding GamePieces, have been deleted. The FoxRabbitCarrot client responds to this Message by deleting those pieces from its game board display. The third string in the PR_NAME_REMOVED_DATAITEMS field, on the other hand, did not come from the FRCGameStateSession's subtree at all -- as we can see from the first part of its node path, it belonged to to client session #23, who is logged in to the server from IP address 132.239.50.8. This client just deleted his user-name node. (Actually, it's more likely that the client just disconnected, in which case all his nodes are being automatically deleted for him by the server. But in either case, the appropriate response to this by our client is the same... it should remove session #23's user name from the user list in the GUI).

The other two fields in the Message hold Message objects instead of Strings, and their field names are not a hard-coded constant, but rather the field names are the node paths themselves. These fields indicate nodes that have been added or updated in the MUSCLE database. For example, the first field name, "//0/frc/pieces/3/15", indicates that the FRCGameStateSession now has a piece on the board with ID number 15, owned by session #3. The associated Message object contains all the information the clients need to know about that piece, including its position on the board, what species and gender it is, how many hit points it has remaining, and so on. Again, each client that receives this update responds by updating its own GUI to display this piece at its new position.

Here is the code, from FRCWindow::MessageReceivedFromServer(), that handles an incoming PR_RESULT_DATAITEMS Message:


void FRCWindow :: MessageReceivedFromServer(MessageRef ref)
{
   switch(ref()->what)
   {     
[...]
      // The MUSCLE server has sent us a message telling us that the server-side database has been updated.
      case PR_RESULT_DATAITEMS:
      {
         // Look for strings that indicate that a subscribed-to node has been removed from the server
         String nodepath;
         for (int i=0; (ref()->FindString(PR_NAME_REMOVED_DATAITEMS, i, nodepath) == B_NO_ERROR); i++) HandleDatabaseUpdateNotification(nodepath(), NULL);

         // Look for strings that indicate that a subscribed-to node has been added or updated on the server
         MessageFieldNameIterator iter = ref()->GetFieldNameIterator(B_MESSAGE_TYPE);
         while(iter.GetNextFieldName(nodepath) == B_NO_ERROR)
         {
            MessageRef subRef;
            for (int j=0; ref()->FindMessage(nodepath, j, subRef) == B_NO_ERROR; j++) HandleDatabaseUpdateNotification(nodepath(), subRef());
         }

         UpdateStatus();
      }
      break;   
[...]
   }
}

It's a two-step process: first, the above code searches the Message for Strings in the PR_NAME_REMOVED_DATAITEMS field that indicate removed nodes, then it searches the Message for subfields of type Message that contain updated data for still-existing nodes. In either case, it then calls a helper method, HandleDatabaseUpdateNotification(), that will parse out path string to determine which session' subtree each update is regarding, whether the updated node represented a piece on the game board, or a user name, or something else, and so on.

If this all seems rather involved to you, you're right -- it is. Rest assured, however, that the concepts it is based around are really quite simple and straightforward: the MUSCLE server maintains a tree of nodes, and the clients who subscribe to various portions of the tree are notified whenever those portions of the tree are modified. Seen this way, the whole system is really just an exercise in automated database caching -- the primary game database is maintained by the server, represented as a hierarchy of nodes, and each client contains a "local cache" of part or all of the server's database in its GUI, and the client-side caches are kept synchronized with the server's database by means of these PR_RESULT_DATAITEMS update Messages. In this way, the GUIs of an arbitrary number of machines scattered across the Internet are all kept synchronized so that they all display the same game board at the same time.

For more information on and examples of MUSCLE database mechanisms, please see the Beginner's Guide to MUSCLE, as well as the BeNews MUSCLE article.

Table of contents
  1. "Foxes, Rabbits, and Carrots"
  2. "The Game and How to Play it"
  3. "A Brief Review of the MUSCLE Networking Layer"
  4. "muscled, The basic MUSCLE server"
  5. "Customizing the MUSCLE server"
  6. "How to Set up the Custom Server Logic"
  7. "The FRCPlayerSession Class"
  8. "The FRCGameStateSession class"
  9. "Overview of the FRC server"
  10. "The FoxRabbitCarrot Client Program"
  11. "How the Client Handles Database Update Messages"
  12. "MUSCLE gaming Performance Issues"
e p (0)    8 Comment(s)

Technology White Papers

See More