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, "/
Here is the code, from FRCWindow::MessageReceivedFromServer(), that handles an incoming
PR_RESULT_DATAITEMS Message:
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.
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;
[...]
}
}



