Using MUSCLE to Implement a Multiplayer Networked Game

Prologue by the editor-in-chief: “A bit more than two years ago, while I was still serving at BeNews, I asked Jeremy Friesner to write an article and present his cross-platform client-server messaging system for dynamic distributed applications, MUSCLE. Two weeks later, he came back to me with a multi-page article, explaining MUSCLE. To demonstrate the power of MUSCLE, Jeremy created a demo application based on it. The result was BeShare.

BeShare quickly became one of the most favorite/influential and fun applications ever written for the BeOS. Years have passed, MUSCLE has become even more powerful and more feature-rich, and here I am again asking Jeremy to write another article about his creation. Jeremy created, exclusively for this article, a brand new application (a multiplayer networked game) to show the power of MUSCLE, and this time it can run on Unix, MacOSX and Windows. If you are a developer interested in any kind of networking programming under any major operating system, you should not pass this article without reading it! And if you are a simple user, you can always try out Jeremy’s game, FoxRabbitCarrot. Jeremy, thank you for all your wonderful work! –Eugenia”


By Jeremy Friesner

The Internet is useful for many things, but one of its most compelling applications is gathering people from many locations together into a shared virtual environment, where they are able to interact with each other in interesting and entertaining ways. A good multiplayer game can be a lot of fun!

But multiplayer games are also a major challenge to create – as a game programmer, you have to somehow coordinate the activities of dozens or hundreds of computers, so that all the players share in the experience of
a single well-ordered game universe. Implementing a communications architecture that can get the right information to the right computers at the right time is a major project in itself, requiring in-depth
knowledge of network programming and data management techniques — and this is all extra work that must be done in addition to the game itself. Because of this, many promising multiplayer game ideas never see the light of day.

But what if there were a way to outsource the networking headaches to someone else? Instead of groveling over which bytes need to go into which socket when, you could spend more of your time working on the game itself.

As it happens, there is such an option available — the MUSCLE library,
by Level Control Systems. MUSCLE is an open-source, general-purpose, client/server messaging system that has been developed and refined over the last several years, and is designed to quickly and conveniently handle many of the networking tasks that are required of multi-user network applications. In this article, I will present a simple multiplayer game implemented using MUSCLE, and discuss how it was implemented and how it works.

After reading this article, you should have a basic understanding of MUSCLE’s features and how to use MUSCLE to create a networked game or application of your own. Please note that this article assumes you have a basic knowledge of C++ and object-oriented design. MUSCLE also includes APIs for the Java and Python languages, but the FoxRabbitCarrot client and server code were both written in C++, so that is what we will be looking at here.

Before getting into the implementation details, a brief user-level description of
the game itself is in order. Our game will boast the following technical features:

  1. The game will consist of a server executable and a client executable.
    The server executable will be run on a central machine, and multiple
    clients may log in to the server at once in order to interact with
    each other.
  2. The game will support an arbitrary of players in a single game simultaneously.
    In addition to the players, there may be any number of spectators watching
    the gameplay unfold.
  3. The server will correctly handle players and spectators connecting to
    or disconnecting from the server at any time, with as little disruption
    of game play as possible. Clients that have crashed or otherwise become
    unresponsive will not interfere with the operation of the game.
  4. The server will take reasonable steps to disallow cheating — that is,
    it will not “trust” the clients to follow the rules of the game, but
    rather it will check the clients’ actions against the rules itself.
  5. All players and spectators will be able to see each other and talk to
    each other via a text-chat mechanism, during gameplay.
  6. The server will compile and run correctly with little or no effort
    on any OS with a modern TCP stack and C++ compiler. The client
    will compile and run on most modern OS’s as well, subject to the
    limitations of the GUI toolkit. Clients and servers running under
    different operating systems will be 100% compatible with each other.
  7. The code code written to implement the client and server will contain
    no direct networking calls whatsoever — instead, it will interact
    with the MUSCLE API, which will in turn handle the networking tasks.
  8. The game will be complex enough to be interesting (hopefully even fun!),
    but also simple enough to serve as a demo and source code example.

Click for a larger version This game is called “FoxRabbitCarrot”, and it is a hybrid game; it is part
board game, part strategy game, part cellular automata simulation. In this
game, each player starts with several game pieces placed on a chess-style rectangular
game board. The player’s objective is to take over as much of the board
as possible with his pieces, either through co-operation or competition
with the other players. Each player’s pieces are of three different types:
Fox, Rabbit, or Carrot (hence the name). Gameplay is divided into 30-second
long “turns”, and during each turn every player can instruct any or all of
their pieces to move to an adjacent square, if they wish to. At the end of
each turn, all pieces are moved simultaneously, and rules are applied to
any pieces that have come to share a square with another piece.

The rules for what happens to two pieces who occupy the same square are
largely what you might expect: If a Fox and a Rabbit end up together,
the Fox will eat the Rabbit, which improves the Fox’s health meter, but
destroys the Rabbit piece. Similarly, a Rabbit moved onto a Carrot will
eat the Carrot. However, a Fox that moves onto a Carrot will
be eaten by the Carrot! (this game features a truly nasty breed of
carnivorous Carrot)

If two pieces of the same species come to share a single square, they
will fight to the death, and the piece with the greater number of
health points will be the sole survivor (although his health will be
decreased by an amount equal to the loser’s health points).

Sex, on the other hand, is something that operates across adjacent squares.
If two pieces of the same species, but opposite gender, occupy adjacent
squares, there is a pretty good chance that the male piece will impregnate
the female piece during that turn. When this happens, the female piece
becomes pregnant, and several turns later, she will give birth to a new
game piece, which is added to the mother’s team. Note that mating your
own pieces together risks inbreeding, which shows up as a decreased
chance of pregancy, and an increased incidence of sterile (neuter-gendered)
children, which will not be able to mate with anyone. Because of this,
it may be too your advantage to cooperate with other players to produce
healthier offspring…

The last rule is this: every turn, every piece loses a small number of
health points. If a piece loses all its health points, it will die of
starvation. If all your pieces die (whether through starvation or being
killed by another piece), you have lost and are removed from the game.
However, you will remain as a spectator, and can re-enter the game on
the next turn (if there are any open game slots remaining).

If you would like to try out the game, you can find server and client
binaries for Windows, MacOS/X, and RedHat Linux 7.2, as well as source
code, here:

  1. Windows binaries – Runs under Windows 98 or higher
  2. MaxOS/X binaries – Runs under MaxOS/X 10.1 or higher
  3. RedHat7.2 binaries – Runs under Red Hat Linux 7.2 (you might have trouble if your system has GCC 3.x – better recompile it from scratch in that case)
  4. Source Code – Source code for client, server, and MUSCLE (doesn’t include Qt)

MUSCLE’s design and features are really an article unto themselves, and
in fact there have been several articles describing MUSCLE. For a detailed
description of how MUSCLE works and how to use basic MUSCLE functionality,
please see any or all of the following:

  1. MUSCLE BeNews article
  2. Beginner’s Guide to MUSCLE
  3. MUSCLE home page
  4. MUSCLE API auto-docs

We will, however, pause here to give a brief high-level overview of MUSCLE’s
functionality.

MUSCLE’s basic unit of communication is the Message object. A MUSCLE Message
is a dictionary-style object very similar to BeOS’s BMessage class (on which it
was originally based). When using MUSCLE, you don’t send streams or packets
of bytes across the network; you send (and receive) Messages. Each Message
contains a 32-bit-integer ‘what’ code (which you may set to any value you wish), and
an arbitrary number of uniquely named data fields. Each data field may contain one or
more values of the same type. Here is an example of what a Message might look
like:


Message what=0x1234
  Field name="groceries to buy" type=B_STRING_TYPE
    Item 0. value="bread"
    Item 1. value="milk"
    Item 2. value="eggs"
  Field name="prices to pay" type=B_FLOAT_TYPE
    Item 0. value=2.99f
    Item 1. value=0.99f
  Field name="PIN Number" type=B_INT32_TYPE
    Item 0. value=8288945

As you can see, each field must have a unique name, but can have any number
of values in it. Possible field data types include integers (8, 16, 32, and
64-bit), floats, doubles, strings, points, rectangles, other Messages(!), and
raw byte arrays. Because type information is stored with the Message, MUSCLE
is able to handle all data translation and transmission issues automatically.
Also, because Messages can be nested inside other Messages, and because
a Message knows how to flatten itself into a standardized, architecture-neutral
flattened byte sequence, Messages provide a convenient way to store hierarchical data
to disk. Lastly, because you can add new fields to a Message without disturbing
the old ones, Messages make it trivial to maintain data-compatibility with old code
— the old code will safely ignore the new data fields, and new code
can easily detect when the new data fields are missing and supply appropriate
defaults.

MUSCLE network communication is asynchronous, and MUSCLE provides
both single-threaded APIs and multi-threaded APIs to queue up incoming and outgoing
messages and feed them out across the network (or in to the local app) at the
fastest possible speed, without ever blocking the execution of the main
program’s event loop. This is important, because it means that slow network
connections will never cause the client’s GUI to “freeze up” — and similarly,
a single poor-quality client connection will not keep a MUSCLE server from
servicing its other clients promptly.

The MUSCLE server, while included in the standard MUSCLE distribution,
is actually not a required component. It is quite possible and even
somewhat common to create a MUSCLE application that doesn’t make use of
the MUSCLE server, and uses only direct client-to-client connections.
However, as we will see in the next section, using a central server can provide
some distinct advantages for your app.

The MUSCLE server program (“muscled”) is somewhat complex internally, but its basic
function is quite simple: it accepts TCP connections from multiple clients
and forwards Messages from one client to another. This may not seem useful
at first glance — wouldn’t it be faster and simpler for the clients to send the Messages
to each other directly? As it turns out, not always. Forwarding the Messages
through a central server has the following advantages:

  1. Discovery. Clients can use the server to find out what other clients
    are currently available, and send the other clients Messages, without having
    to know the other clients’ IP addresses in advance.
  2. Broadcasting. If a client wishes to send the same Message to two or
    more other clients, he need only send one copy of the Message to the server,
    together with instructions telling the server who to forward it to. The
    server can then forward a copy of the Message out to each specified
    destination client. Given that many clients may be on slow modem
    connections, while the server is likely to be on a fast dedicated
    connection, this can be very efficient.
  3. Firewalls. Often, clients are hiding behind firewalls that do not
    allow incoming TCP connections. This means that there is no way for
    another client to connect to the firewalled client directly. However,
    the firewalled client is still able to connect out to the server
    machine, and thus the two clients may communicate indirectly (through the
    server) without any problems.
  4. Centralized data storage. For many apps, there is certain data (e.g.
    the user’s name, or the position of his pieces on the game board) that is
    useful to many other clients. The MUSCLE server allows each client to upload
    data of interest into a database held by the server, where other clients may
    access it without using up any of the original client’s bandwidth. In addition,
    the MUSCLE server has a subscription mechanism by which clients may be automatically
    notified when the server-hosted data they are interested gets updated.

Built upon and (integrated with) the MUSCLE server’s Message-forwarding
functionality is the server-side database. As mentioned above, the
server-side database lets any MUSCLE client upload data to the server,
where other clients can access it more quickly. The uploaded data is
not persistent, and will disappear when the connection to the client
is broken. The server-side database’s organization is very simple —
it is a single-rooted tree of nodes, and each node in the tree holds
a single “data payload” Message object. An example of what this node tree might look like is
shown below:

Whenever a client connects to the server, nodes representing his
IP address and unique session ID are added to the first and second levels of the tree
(i.e at Depths 1 and 2 in the diagram). Any data that client uploads to the server
appears as nodes underneath his second-level (“session”) node. A MUSCLE client may
upload a hierarchy of nodes as deep as it wishes, although in practice 1 to 5 levels of
hierarchy are common.

Because each node in the tree has an ASCII label, many of the wildcarding
and set-specification tricks commonly applied when working with filesystems
are also available to MUSCLE clients. For example, if a MUSCLE client
wanted to be notified whenever another client connected to (or disconnected
from) the server, he could post a subscription to the path “/*/*”, and thus
would get notifications whenever a “session ID node” was added to or removed
from the node tree. For more information on how the MUSCLE database works, please see the MUSCLE Beginner’s Guide.

For some apps, the default MUSCLE server provides all the functionality
they need, right out of the box. For example, the BeShare chat and
file sharing system is implemented entirely using the default MUSCLE
server implementation.

However, other apps require some extra, custom server-side logic. This is particularly
true for games, because the code being executed on the clients can’t be
trusted to follow the rules of the game! (There’s always some wiseacre
who will ‘hack’ his client to give himself an unfair advantage over
the other players, if he can) Because of this, for our game, we’ll need to create
a specialized FoxRabbitCarrot server that knows the rules of our game,
and ensures that the players only make legal moves. Because the MUSCLE
server is designed to be customizable, we’ll be able to implement this
specialized server without modifying any of the MUSCLE code — we’ll do
it entirely by subclassing several of the C++ classes that make up
a standard MUSCLE server. We’ll call our customized server “frcd” (short
for “Fox Rabbit Carrot Daemon”).

In order to customize the MUSCLE server, we need to understand some of the
MUSCLE server’s parts and what they do. So, briefly, here is a description of some of the more
important C++ classes that together form the guts of a MUSCLE server:


class StorageReflectSession:

This class represents a single connection from the server to a client.
The server maintains a list of StorageReflectSession objects: whenever a
new client connects to the server, a StorageReflectSession object is created
to represent that client’s state; and whenever the TCP connection is broken,
the StorageReflectSession object associated with that connection
is deleted. It is the StorageReflectSession class that implements
most of the server-side database and subscription functionality.

For our app, we want to reuse some of the default StorageReflectSession
logic, but also customize it a bit, so we will create our own
subclass of StorageReflectSession, called FRCPlayerSession, and
use that instead of the vanilla StorageReflectSession class. So in
our server, every client who logs in will have an FRCPlayerSession
object that will represent him.


class ReflectServer:

This is the Big Class that holds everything. The ReflectServer class represents an entire
server, and contains the event loop that makes the server run. In
a typical MUSCLE server program, a single ReflectServer object is created
and set up, and then the ReflectServer’s ServerProcessLoop() method is
called. ServerProcessLoop() will typically never return unless/until
the server has decided for some reason that it wants to quit running.
Our app will subclass the ReflectServer class (with our own FRCServer
class), but doing so is largely a place-holder for future growth —
almost all the customization we need to do can be done by calling
methods on the basic ReflectServer class.


In our FoxRabbitCarrot server, we’d like to store important information
(like the positions of the pieces on game board) in the server-side
database, so that they are easily available to all clients. But this
raises a problem: where in the server-side database should we put
this information? If we place the game-state nodes underneath the
session node of a player’s session, they will be deleted when the
player disconnects from the server. And there is no ‘central’ area
in the database to speak of (except for the root of the tree, and we
aren’t allowed to put our own nodes there). The solution to this problem
is simple enough, however: we’ll create a session whose sole purpose
is to maintain and administer the state of the game. Unlike the
other sessions, this session will not have an associated TCP connection:
it will exist as an exclusively a server-side entity. Since the
logic necessary to maintain the game board is different from that
needed to represent a player, we will provide a separate StorageReflectSession
subclass for this session: the FRCGameStateSession. We’ll create a
single FRCGameStateSession object when the server starts up, and this object
will stick around the entire time.

Here is a diagram showing how the internal state of the FRCServer might
look if you could put it under an X-Ray while two people are logged in to the server:

Now that we have some idea of what our custom server’s sessions will
be, let’s look at how the server gets set up. Here is the interesting bit of code,
found at the end of main(), in frcd.cpp:


[...]
   FRCServer server;
   FRCPlayerSessionFactory playerFactory;

   AbstractReflectSessionRef gameStateSessionRef(new FRCGameStateSession(), NULL);
   ReflectSessionFactoryRef playerFactoryRef(&playerFactory, NULL, false);

   if ((server.AddNewSession(gameStateSessionRef, -1) == B_NO_ERROR)&&
       (server.PutAcceptFactory(4444, playerFactoryRef) == B_NO_ERROR))
   {
      server.ServerProcessLoop();
   }
   else LogTime(MUSCLE_LOG_ERROR, "Error setting up frcd (port 4444 already in use?)\n");

   server.Cleanup();

   return 0;
}

In the first two lines we declare an FRCPlayerSessionFactory object, and the FRCServer object.
These objects must both be present for the duration of the server’s execution, but because we
are in main(), we can nevertheless safely declare them on the stack.

The FRCServer object (named “server”) is our subclass of the MUSCLE ReflectServer class.
As mentioned on the previous page, it is the object that will be handling all of the networking
chores that the server needs to do. The first thing we do with our FRCServer is add a single
FRCGameStateSession to it, by calling server.AddNewSession(). The second argument to AddNewSession(), -1,
indicates that this session has no associated TCP connection, since “real” TCP sockets are non-negative
integers.

The FRCPlayerSessionFactory object (named “playerFactory”) is a subclass of MUSCLE’s
ReflectSessionFactory class. The FRCPlayerSessionFactory object’s job is to return a new FRCPlayerSession
whenever its CreateSession() method is called. The call to server.PutAcceptFactory() tells the
FRCServer object to start listening on port 4444: whenever a TCP connection is accepted on that
port, the factory’s CreateSession() method will be called, and the new FRCPlayerSession object that
FRCPlayerSessionFactory::CreateSession() returns will be responsible for handling all future
interactions with that TCP connection.

Once the the FRCGameStateSession has been added and the FRCPlayerSessionFactory has been installed,
we are ready to enter the server’s main event loop, by calling server.ServerProcessLoop(). Execution
will not return from the ServerProcessLoop() method until the server is ready to quit — and most
servers are designed to never quit! Nevertheless, in case the server does quit, we also include a call
to server.Cleanup(), which allows the server to tidy things up nicely before the process terminates.

And that’s pretty much all there is to setting up the server!

One additional bit of explanation is in order, however: you’ve probably noticed that both the
FRCPlayerSessionFactory object and the FRCGameStateSession object are handed to the FRCServer
object wrapped inside Ref objects… an AbstractReflectSessionRef and ReflectSessionFactoryRef, to be exact.
Ref objects are a common idiom in MUSCLE code — many objects are passed around using the (very lightweight)
Ref objects, which allows MUSCLE to use reference-counting as an efficient and easy form of garbage collection.
With reference-counting, there is no need to worry about when to delete an object that was previously allocated
with the new operator — instead, the object is automagically deleted by the destructor of the very
last Ref object that points to it. This avoids most memory-leak and dangling-pointer type bugs with
almost no extra effort required from the programmer.

The above code also illustrates another way in which references are handy — note that the FRCGameStateSession
object was allocated on the heap (using the new operator), while the FRCPlayerSessionFactory object was
allocated on the stack. But the FRCServer object doesn’t know that, and it doesn’t have to know that.
It merely holds copies of the Ref objects, and accesses the C++ objects that the Ref objects point to.
Note, however, that the Ref objects themselves need to know whether or not the object needs to be
eventually deleted — that is what the third argument to the ReflectSessionFactoryRef() constructor
does — by specifying (false), we tell the ReflectSessionFactoryRef that playerFactory was allocated
on the stack, and thus should not ever be deleted.

Now that the server is set up, let’s look at the FRCPlayerSession class:
Remember that one FRCPlayerSession object is created for each TCP connection
that the server receives, and its responsibility is to handle I/O to and from
the client on the other end of that TCP connection.

Here is the declaration of the FRCPlayerSession class, taken from frc/server/FRCPlayerSession.h:


class FRCPlayerSession : public StorageReflectSession
{
public:
   FRCPlayerSession();
   virtual ~FRCPlayerSession();

   /** Overridden in order to send out the MOTD, if one exists. */
   virtual status_t AttachedToServer();

   /** Overridden to inform the game-state session about our impending departure */
   virtual void AboutToDetachFromServer();

   virtual void MessageReceivedFromGateway(MessageRef msg);
   virtual void MessageReceivedFromSession(AbstractReflectSession & from, MessageRef msg, void * us
erData);

protected:
   virtual const char * GetTypeName() const {return "FRCPlayer Session";}
};

Besides the (not very interesting) constructor and destructor, there are several
virtual “hook” methods that we’ve overridden here in order to customize the session’s
behaviour. Let’s review them, in order:


virtual void AttachedToServer();

This method is called when the new FRCPlayerSession is first added to the FRCServer.
When this method is called, everything has been set up and it is safe to call
most of the methods in the StorageReflectSession API. The FRCPlayerSession
class overrides this method for two reasons: so that it can look for a motd.txt
file to read in and send as the server’s welcome text message to the new user,
and so it can tell the FRCGameStateSession that a new client has appeared.
Here is the body of the method:


status_t
FRCPlayerSession :: AttachedToServer()
{
   if (StorageReflectSession::AttachedToServer() != B_NO_ERROR) return B_ERROR;

   // Try to read the motd.txt and send it the client
   FILE * fpIn = fopen("motd.txt", "r");
   if (fpIn)
   {
      int bufSize = 2048;
      char * buf = newnothrow char[bufSize];
      if (buf)
      {
         MessageRef motdRef = GetMessageFromPool(FRC_COMMAND_CHAT_TEXT);
         if (motdRef())
         {
            motdRef()->AddString(PR_NAME_SESSION, "0");  // for FRC, session 0 means from motd
            while(fgets(buf, bufSize, fpIn)) motdRef()->AddString("text", buf);
            AddOutgoingMessage(motdRef);
         }
         delete [] buf;
      }
      fclose(fpIn);
   }

   // Notify the game-state session that we are here (other sessions will ignore this)
   BroadcastToAllSessions(GetMessageFromPool(FRC_INTERNAL_NOTIFY_NEW_CLIENT));

   return B_NO_ERROR;
}        

There are several important things things to note here:

First, it is critical that AttachedToServer() call the superclass’s AttachedToServer() implementation
before doing anything else; otherwise the session’s state will not be set up correctly
and Bad Things Will Happen.

Second, this method demonstrates how easy it is to send a Message from the server to the client’s
PC. All we need to do is allocate a new Message object (calling GetMessageFromPool()
is the most efficient way to do this), add our data to it, and then call
AddOutgoingMessage() with it. AddOutgoingMessage() will add the Message to the
outgoing Message queue, and it will be sent as soon as possible. Because the
Message is queued rather than sent synchronously, AddOutgoingMessage() is guaranteed
not to block, and thus you can add large amounts of outgoing data at once without
worrying about hurting the server’s responsiveness. (Of course, you might use
up lots of RAM that way!)

The last thing that AttachedToServer() does is send a Message to the FRCGameStateSession,
letting the FRCGameStateSession know that a new client has connected to the server.
Actually, that isn’t quite accurate — the Message is broadcast to all the sessions
in the server’s session list, not just the FRCGameStateSession — but all the FRCPlayerSessions
are programmed to ignore it. This isn’t as inefficient as it sounds, because all sessions are in
the server’s process space. Because of that, “sending a Message to a session” is
no more expensive than a virtual method call (okay, actually it IS a virtual method call ;^)).

We have AttachedToServer() return B_NO_ERROR to indicate that everything is okay,
and the session is ready to join the list of connected sessions. If we had returned
B_ERROR instead, that would indicate that the session had fatal problems initializing,
and the server would immediately close the TCP connection and delete the FRCPlayerSession
object.


virtual void AboutToDetachFromServer();

This method is the counterpart to AttachedToServer(). It is called just before
the session object is to be removed from the server — typically this happens
right after the TCP connection to the client has been closed. In this method,
you can do any cleanup that you need to do — the FRCPlayerSession class overrides
this method to send another Message to the FRCGameStateSession, this one notifying
the FRCGameStateSession that the session is leaving.


void
FRCPlayerSession :: AboutToDetachFromServer()
{
   // Make sure that the game controller removes our pieces from the board
   BroadcastToAllSessions(GetMessageFromPool(FRC_COMMAND_EXIT_GAME));
   StorageReflectSession::AboutToDetachFromServer();
}   

Again, it is critical that the AboutToDetachFromServer() call is passed on
to the superclass — only this time, it must be passed on as the last thing
the method does, rather than the first.


virtual void MessageReceivedFromGateway(MessageRef msg);

This method is a very important one — it is called whenever our client sends
us a Message. Our FRCPlayerSession class overrides this method to do a little
integrity checking — it makes sure that any Messages sent by the client contain
the correct PR_NAME_SESSION string (so that the client can’t pretend to be
another client), and that the Messages aren’t labelled with any command codes
that only the server is allowed to generate (so that the client can’t pretend
to be the server). Once it is is satisfied that the Message the client sent
to us is legitimate, it passes the MessageReceivedFromGateway() call on up
to the superclass, where it is handled in the usual MUSCLE fashion (which is
to say, any Messages that are MUSCLE commands are parsed and executed, and
any other Messages are broadcast to all the other sessions for them to look at)


void
FRCPlayerSession :: MessageReceivedFromGateway(MessageRef msgRef)
{
   // Don't allow clients to spoof any command codes that should only be generated internally.
   Message * msg = msgRef();
   if (msg)
   {
      // Make sure all messages received from the client contain only the client's
      // own session ID string, to thwart any attempts at spoofing the other clients...
      msg->ReplaceString(true, PR_NAME_SESSION, GetSessionIDString());

      // Also make sure that the Message's command code isn't a command code that only the server is allowed to generate
      if ((muscleInRange(msg->what, FRC_BEGIN_INTERNAL_COMMANDS, FRC_END_INTERNAL_COMMANDS) == false)&&
          (muscleInRange(msg->what, FRC_BEGIN_REPLIES, FRC_END_REPLIES) == false))
      {
         // If it passed all the tests, hand the Message over for default MUSCLE-style processing.
         StorageReflectSession::MessageReceivedFromGateway(msgRef);
      }
   }
}    


virtual void MessageReceivedFromSession(AbstractReflectSession & from, MessageRef msgRef, void * userData);

This method is the “local equivalent” of MessageReceivedFromGateway() — whenever a session object passes a Message
to a neighbor session (either by calling BroadcastMessageToAllSessions(), or through one of several alternate
methods), the target session’s MessageReceivedFromSession() is called. Inside MessageReceivedFromSession(),
the target session can decide what to do with the Message — it can examine the Message and take some
action based on the Message’s contents, or pass the Message on to its client by calling AddOutgoingMessage()
with it, or simply ignore it. A reference to the sending session is passed in, so the target session can
“reply” to the Message, if it likes, simply by calling from.MessageReceivedFromSession() with another Message
object. In our FRCPlayerSession class, however, the implementation is very simple: we check the Message
to make sure it’s not one of the “internal” Messages that are never supposed to leave the server, and if
it isn’t, we pass it on up to the superclass for the usual default MUSCLE processing (which is usually
to send the Message on out to the client via AddOutgoingMessage()).


void
FRCPlayerSession :: MessageReceivedFromSession(AbstractReflectSession & from, MessageRef msgRef, void * userData)
{
   const Message * msg = msgRef();
   if (msg)
   {
      if (muscleInRange(msg->what,FRC_BEGIN_INTERNAL_COMMANDS,FRC_END_INTERNAL_COMMANDS))
      {
         // No internal commands to process yet.... but don't send them back to the client anyway.
      }
      else StorageReflectSession::MessageReceivedFromSession(from, msgRef, userData);
   }
}

And that’s all there is to the FRCPlayerSession class — as you can see, it’s a very thin veneer over
the standard MUSCLE StorageReflectSession class. If it wasn’t for a bit of cheater-detection and the
Message Of the Day, we wouldn’t need to have a custom subclass at all! All the game-specific FoxRabbitCarrot
logic is contained in the FRCGameStateSession object, which will we take a look at next.

If FRCPlayerSession objects server as the interface between the server and each client,
then the FRCGameStateSession object is the interface between the server and the game
itself. All the actual game logic takes place inside the FRCGameStateSession; the
FRCPlayerSessions, the TCP connections to the client computers, and the FoxRabbitCarrot
programs running on the client computers can all be thought of as nothing more than
elaborate I/O devices for this session. This is truly the nerve center of the game.

That said, the FRCGameStateSession is structured very similarly to the FRCPlayerSession
(they both derive from the same base class, after all). Here is the declaration
of the FRCGameStateSession class, from frc/server/FRCGameStateSession.h:


class FRCGameStateSession : public StorageReflectSession
{
public:
   FRCGameStateSession(const Message & prefs);
   virtual ~FRCGameStateSession();

   virtual status_t AttachedToServer();

   virtual void MessageReceivedFromSession(AbstractReflectSession & from, MessageRef msgRef, v
oid * userData);

   virtual void Pulse(uint64 now, uint64 schedTime);
   virtual uint64 GetPulseTime(uint64 now, uint64 prevResult);
[...]
};             

In the AttachedToServer method(), the FRCGameStateSession does some basic setup.
One of the MUSCLE database nodes it’s going to publish is the node representing
the Game Board itself (not the pieces, mind you, just the board). The FoxRabbitCarrot
client programs will all use the MUSCLE database-subscription facility to “watch” this
node, so that if it ever changes (i.e. the person running the server decides they
want a 16×16 board instead of an 8×8 board), they can all update their GUIs to match it.


status_t FRCGameStateSession :: AttachedToServer()
{
   if (StorageReflectSession::AttachedToServer() != B_NO_ERROR) return B_ERROR;

   MessageRef gameBoardRef = GetMessageFromPool();
   if (_gameState.GetBoard().SaveToArchive(*gameBoardRef()) == B_NO_ERROR) 
   {
      return SetDataNode("frc/board", gameBoardRef);
   }
   else return B_ERROR;
}

As you can see, the publishing of the GameBoard’s state into the MUSCLE database
takes several steps. First, a Message is allocated from the Message pool.
Then, the GameBoard object’s SaveToArchive() method is called, which dumps
the state of the GameBoard object into the Message. Then, SetDataNode() is
called. SetDataNode places the Message into the MUSCLE database tree, as
a grandchild of the FRCGameStateSession’s “home node” in the tree. I like to
use pre-defined constants to build my database paths up, because it eliminates
the chances of typos — that’s why the first argument to SetDataNode() looks so
strange. Once it’s gone through the C preprocessor, however, that argument
is equivalent to “frc/board”. So the fully qualified path name of our GameBoard
node in the MUSCLE database (assuming that the FRCGameStateSession is session 0)
will be: “//0/frc/board”. (the first node name in the path is “
instead of an IP address, because the FRCGameStateSession has no associated
client).

There is, of course, no MessageReceivedFromGateway() method defined for the
FRCGameStateSession, for the simple reason that the FRCGameStateSession has
no client associated with it, and thus will never receive Messages from its
client. So overriding that method would be pointless.

The MessageReceivedFromSession() method is where most of the action occurs.
You’ll recall that this method is called whenever another session passes
a Message to the FRCGameStateSession. Since this method is the only way
that FRCPlayerSessions can interact with the FRCGameStateSession, this method
handles all the game players’ move requests and other input. Here is
an abridged listing of this method’s contents:


void
FRCGameStateSession :: MessageReceivedFromSession(AbstractReflectSession & from, MessageRef msgRef, void *)
{
   const Message * msg = msgRef();
   if (msg)
   {
      uint32 fromSessionID = (uint32) atol(from.GetSessionIDString());  // get the session ID of the session who passed us this Message
      switch(msg->what)
      {
[...]
         case FRC_COMMAND_ENTER_GAME:
            _gameState.PlayerWantsToEnter(fromSessionID);  // note that this client wants to start playing
 
            // If there weren't already any players already playing, force a call to GetPulseTime()
            // so that the new turn will start immediately.  It's silly to wait for (no players)!
            if (_gameState.GetPlayers().GetNumItems() == 0) InvalidatePulseTime(true);
         break;

         case FRC_COMMAND_EXIT_GAME:
            _gameState.PlayerWantsToExit(fromSessionID);  // note that this client wants out of the game
         break;

         case FRC_COMMAND_PROPOSE_MOVES:
         {
            // User specified a move he would like to do.  Check to make sure it's a legal
            // move, and if it is, put it into our list of moves-to-be-done-when-the-turn-is-over
[...]

As you can see from the above code snippet, this method first identifies which other Session
passed it the Message, and then looks at the Message to see which ‘what’ code it has. If it
has one of the ‘what’ codes that were pre-defined to have a special meaning in our game, it
switches to the proper block of code, where it takes the appropriate action, such as adding
a player to the game, or updating the game’s state to reflect the player’s move. The actual
game logic is contained inside the _gameState member variable, and since it isn’t really
MUSCLE-specific, I won’t go into it here.

The FRCGameStateSession also overrides two “hook” methods that the FRCPlayerSession didn’t
need to: these are the Pulse() and GetPulseTime() methods. If you remember the FoxRabbitCarrot
game description back on page two, you’ll recall that in FoxRabbitCarrot, each turn lasts
a certain number of seconds, at which point all the pieces move at once. Because there is
a time-dependent element in the game, the server can’t be purely “reactive” — that is, it
can’t be implemented entirely based on responding to the inputs it gets from its various
clients’ TCP streams. Instead, once every thirty seconds or so, it has to initiate an action
“by itself” — it has to move all the pieces and start the next turn. It has to do this
whether or not it receives any Messages during that period, and so we need access to some sort
of timer that will wake up the server to do it. Pulse() and GetPulseTime() provide just such
a timer. They are fairly easy to use: you override GetPulseTime() to indicate when you
would like Pulse() to be called… and then, at time you specified (or shortly thereafter),
MUSCLE makes sure that Pulse() is called. Let’s look at how FRCGameStateSession implements
these two methods:


uint64 FRCGameStateSession :: GetPulseTime(uint64 now, uint64 prevResult)
{
   return now + ((prevResult == MUSCLE_TIME_NEVER) ? 0 : (_gameState.GetMaxSecondsPerTurn()*1000000));
}
    
void FRCGameStateSession :: Pulse(uint64, uint64)
{
   _readyToGo.Clear();

   // Run the sim for one turn.  This call also populates the two hashtables.
[...]
}

When dealing with time, MUSCLE foresakes such awkward structures as “struct timevals”, in
favor of unsigned 64-bit integers. These 64-bit integers represent time in microseconds
(there are one million microseconds in a second). Specifying time this way gives us
the accuracy we need for fine-grained timing (assuming the underlying OS can provide it,
of course), as well as the convenience of being able to use standard arithmetic operators
on the time values.

There are two arguments passed in to GetPulseTime() — the first is the approximate current
time, and the second argument is the last value our GetPulseTime() returned. If this call
is the very first call that GetPulseTime() has had, then the second argument will be set
to the special value MUSCLE_TIME_NEVER (which is equal to the largest uint64 there is).

Since we want Pulse() to be called at even 30-second intervals, we add 30 seconds
(actually 30 million microseconds) to the current time and return that result. Since
GetPulseTime() is called only when necessary (once at startup, and then once again
after each call to Pulse()), this gives us the behaviour we want. There is one exception,
though — when the very first player joins the game, we want his pieces added to the
board immediately; he shouldn’t have to wait 30 seconds for the current turn (which nobody
is playing in anyway) to finish. So in that case, we return (now), which causes Pulse()
to be called immediately.

The Pulse() method implementation is straightforward — it is called at the time specified in
GetPulseTime(), and it handles the updating of the internal game state, then the updating of the relevant
MUSCLE database nodes that represent the various pieces in the game. The MUSCLE database
nodes are updated through calls to the standard SetDataNode() method, or, in the case
of a node being removed from the database, by calling RemoveDataNodes(). Because all
of the FoxRabbitCarrot clients have made MUSCLE subscriptions to these nodes, all
the clients are automagically notified of the new state of the database (and hence, of
the game) whenever these nodes are changed. When they get the database-update notification
Messages, they all update their board displays accordingly, and all the players and
spectators thus see the new state of the game.

In the last few pages, we’ve seen how the various components
of the server-side architecture are implemented, and how they
relate to one another. “That’s nice”, you are probably saying,
“But how does it actually work? What’s the big picture?” So
let’s take a few minutes now to go over that. We’ll start with
the easy bit: the on-line chat.

Like any modern multiplayer networked game, FoxRabbitCarrot has
a text-chat facility which players can use to talk with (or
threaten) each other, and spectators can use to kibitz. To
implement this, we don’t need any custom server-side logic;
the basic MUSCLE implementation does just fine. Specifically,
whenever any user types a text chat string into his FoxRabbitCarrot
client’s chat box, the client sends a FRC_COMMAND_CHAT_TEXT
Message to the server:


   MessageRef chatMsg = GetMessageFromPool(FRC_COMMAND_CHAT_TEXT);
   if (chatMsg())
   {
      chatMsg()->AddString(PR_NAME_KEYS, "frc/uname");  // send only to other FRC clients
      chatMsg()->AddString("text", chatText);
      SendMessageToServer(chatMsg);
   }

In the above code (excerpted from the client’s FRCWindow class) we simply allocate a Message
from the Message pool, add the user’s chat text to it, and send it to the server. The
only non-obvious thing about it is the PR_NAME_KEYS field that is added to the Message.
The PR_NAME_KEYS field is there to tell the server that only clients who have a database
node whose node path matches wildcard pattern “/*/*/frc/uname” should have this Message
forwarded to them. This is done to ensure that the Message goes only to other FoxRabbitCarrot
clients, and not to any unrelated clients who just happen to be logged in to the same server (not
that there will probably be any, but just in case).

Once this Message is sent to the server, it is received by the appropriate FRCPlayerSession’s
MessageReceivedFromGateway() method, which promptly passes it up the superclass’s
MessageReceivedFromGateway() method, which then uses the standard MUSCLE pattern-matching procedure to
distribute it to the other FRCPlayerSessions. These sessions then send the Message
to their clients, who receive it and display the chat text on screen.
And thus the chat text is spread around to all participants.

In addition to the chat text itself, it is traditional for a multiplayer game to
show a list of the “handles” (or usernames) of all the connected participants, so
that people can easily see who they are chatting with. Again, the vanilla MUSCLE
logic provides for this. To accomplish this, we use the MUSCLE database node
subscription mechanism. Each FoxRabbitCarrot uploads a single database node
to the server, containing the client’s username:


void FRCWindow :: UploadUserName()
{
   MessageRef uploadMsg = GetMessageFromPool(PR_COMMAND_SETDATA);
   MessageRef nameMsg   = GetMessageFromPool();
   if ((uploadMsg())&&(nameMsg()))
   {
      nameMsg()->AddString("uname", (const char *) _userName->text());
      uploadMsg()->AddMessage("frc/uname", nameMsg);
      SendMessageToServer(uploadMsg);
   }
}     

In addition to doing that, each client tells the server it wants to subscribe
to the node path “/*/*/frc/uname”. That means that whenever a database node
whose node path matches that pattern appears on the server, the client will
be notified. The client will also be notified if a matching node disappears,
or if its Message payload is changed. In this way, each client makes sure that
is always notified of any changes to the set of logged in users, and can
immediately update its users list to reflect the changes. Here is the code
where the FoxRabbitCarrot client generates its database subscription requests
and sends them to the server:


   MessageRef subscribeRef = GetMessageFromPool(PR_COMMAND_SETPARAMETERS);
   if (subscribeRef())
   {
      // We want to know what other users are logged in (for chatting, etc)
      subscribeRef()->AddBool("SUBSCRIBE:frc/uname", false);

      // We also want to know the state of the game board
      subscribeRef()->AddBool("SUBSCRIBE:/*/0/frc/board", false);

      // And the current turn number
      subscribeRef()->AddBool("SUBSCRIBE:/*/0/frc/turn", false);

      // And the player statistics (color, score, etc)
      subscribeRef()->AddBool("SUBSCRIBE:/*/0/frc/pieces/*", false);

      // And which pieces are on the board
      subscribeRef()->AddBool("SUBSCRIBE:/*/0/frc/pieces/*/*", false);

      SendMessageToServer(subscribeRef);
   }                        

As you can see, the first AddBool() line specifies the subscription to the user list…
but there are four other subscriptions being added as well! Well, as you probably
guessed, these other subscriptions tell the server to notify the client whenever
the nodes representing the current state of the game change. The FRCGameStateSession
places one node into the database for each piece on the board, as well as a node
for the board itself. Underneath the “pieces” node, the FRCGameStateSession places
nodes whose names represent which sessions are currently playing — and the children
of those nodes represent the pieces themselves. This way, all the other clients
can keep informed of the state of the board simply by subscribing to “/*/0/frc/pieces/*/*”,
And the owner of any given piece can be easily determined by looking at the parent of the
piece’s node. So the state of the MUSCLE node tree during a FoxRabbitCarrot game with two
players and one spectator might look like this:

So by merely adding the server subscription requests with the appropriate wildcarded
path patterns, the clients tell the server which parts of the database they need to
have “live updates” of. In this way, they can get instant notification of important
changes to the database, without wasting bandwidth getting notifications about
parts of the database that they are not interested in. In the next section, we will
look more at the FoxRabbitCarrot client program, and how it interacts with the server.

So far we’ve discussed the server side of the FoxRabbitCarrot game
system; now it is time to take a look at the client. Unlike the
server program, which can run quietly in the background or in a
shell window, the client program requires a GUI in order for most
people to enjoy using it. For FoxRabbitCarrot, I chose to use
TrollTech’s Qt GUI Toolkit to create my client GUI. I chose
Qt for several reasons: First, because I am familiar with Qt
and have used in previous projects. Second, because MUSCLE
already contains support classes that make it easy to integrate
MUSCLE and Qt together into a single program. And third, because
Qt allows me to generate executables for Windows, MacOS/X and
Linux from a single set of source code, so that I don’t have
to spend time writing separate code for each operating system.
Of course there is nothing in MUSCLE that requires you to use any
particular GUI toolkit (or any GUI at all, for that matter). But
if you are looking for a cross-platform GUI toolkit to use, I
highly recommend Qt.

To start off, let’s look at the class MUSCLE provides to interface
with Qt. This class, called QMessageTransceiverThread, handles
connecting to the server, sends and receives Messages in a separate
thread (so that the Qt GUI won’t lock up), and uses Qt’s
signals-and-slots notification mechanism to signal the GUI whenever
a Message has been received from the server.

Since you may not be familiar with signals-and-slots, I’ll describe
them briefly here. Qt uses some special preprocessor magic to implement
a new language feature on top of C++: by adding special tags to the
class declaration of any class that subclasses QObject, you can outfit
that class with “signals”, “slots”, or both. Signals and slots are
similar to normal C++ methods, but with an added feature: you can use
the QObject::connect() function at run time to connect any “signal” method to
any “slot” method you choose. The owner of a signal can then “emit” the
signal at any time, causing all the “slot” methods that are connected to
that signal to be called. Qt handles the mechanics of the calls internally,
and is flexible enough to do things like dropping extra arguments from the
signal method if they are not required in the slot method. Because any
signal can be connected to any slot, and because multiple signals can
be connected to a single slot (or, alternatively, a single signal can
be connected to multiple slots at once), this provides a high degree of
flexibility and convenience in structuring Qt GUI code.

The QMessageTransceiverThread features over a dozen signals that are emitted
to signal that occurrence of various network events, but there are three
signals that are the most commonly used:


   void SessionConnected(const String & sessionID);  
   void SessionDisconnected(const String & sessionID);
   void MessageReceived(MessageRef msg, const String & sessionID);

As you might have guessed, the first signal (SessionConnected()) is emitted
when the TCP connection to the server has been successfully established. The
second signal (SessionDisconnected()) is emitted when the TCP connection has
been closed, or if the connection attempt failed. The third signal (MessageReceived())
is emitted whenever a Message is received from the server. These three signals
are enough to let our client do everything it needs to do — below is the code
(from the FRCWindow constructor) that sets up the QMessageTransceiverThread object’s
connections:


FRCWindow :: FRCWindow()
{
[...]
   connect(&_mtt, SIGNAL(SessionConnected(const String &)), SLOT(ConnectedToServer()));
   connect(&_mtt, SIGNAL(SessionDisconnected(const String &)), SLOT(DisconnectedFromServer()));
   connect(&_mtt, SIGNAL(MessageReceived(MessageRef, const String &)), SLOT(MessageReceivedFromServer(MessageRef)));

As you can see, our QMessageTransceiverThread object’s name is _mtt. _mtt is a member variable of
the FRCWindow class. We use the Qt connect() command to connect _mtt’s three signals mentioned above
to three slots that are declared in the FRCWindow class. In this way, whenever the TCP connection
is created or broken, and whenever a Message is received from the server, the appropriate FRCWindow
method is automatically called to handle the event.

But, just connecting signals to slots isn’t enough to get the connection going — we need to
tell the QMessageTransceiver where to connect to. So later on, in FRCWindow::ConnectToServer(),
we have the following code execute:


   if (_mtt.StartInternalThread() == B_NO_ERROR)
   {
      if (_mtt.AddNewConnectSession(hostname, atoi(port)) == B_NO_ERROR)
      {
         _currentServerName = s;
         _isConnectingToServer = true;
         UpdateStatus();
         return;  // success!
      }
      QMessageBox::critical(this, "FRC", "Couldn't add connect session!");
   }
   QMessageBox::critical(this, "FRC", "Couldn't start networking thread!");   

The StartInternalThread() call spawns the internal networking thread that
the QMessageTransceiverThread object uses to handle all the asynchronous
networking tasks for us. Once that thread has been successfully started,
we then call AddNewConnection() with the appropriate server hostname and
port; this call instructs the QMessageTransceiverThread to begin connecting
to the server. Since a TCP connection can take a significant amount of
time to complete, AddNewConnectSession() does not wait for TCP connection
to finish — instead, it always returns immediately. Later on, when the
TCP connection is complete, the SessionConnected() signal will be emitted,
and therefore our FRCServer::ConnectedToServer() slot will be called. At that
point, we will update the GUI to indicate that the client has connected to
the server. If, on the other hand, the TCP connection fails (which could
happen if the server is down, or the user’s modem is turned off, or etc), then
the SessionDisconnected() signal will be emitted, and so the
FRCServer::DisconnectedFromServer() slot will be called, which will print
out an appropriate error message for the user to see.

So now we have covered the basics of how the FoxRabbitCarrot client handles
connecting and disconnecting from the server. Once connected, sending a
Message to the server is trivial: All we have to do is call the
QMessageTransceiverThread::SendMessageToSessions() method, and pass it
a MessageRef containing the Message to send. The Message is then automatically
queued up in an outgoing-messages-queue and sent to the server as quickly as
possible. This is done, for example, whenever the user clicks and drags
on one of his pieces to indicate that he wants that piece to move to an
adjacent square:


   // From frc/client/FRCCanvas.cpp, in the CanvasMouseEvent() method
   MessageRef propMoveMsg = GetMessageFromPool(FRC_COMMAND_PROPOSE_MOVES);
   if (propMoveMsg())
   {
      propMoveMsg()->AddInt32("turn", _currentTurnNumber);
      MessageRef movePieceMsg = GetMessageFromPool();
      if (movePieceMsg())
      {
         movePieceMsg()->AddInt32("pos", _curMousePos[0]);
         movePieceMsg()->AddInt32("pos", _curMousePos[1]);

         char buf[64]; sprintf(buf, "%lu", key.GetPieceID());
         propMoveMsg()->AddMessage(buf, movePieceMsg);
         _mtt.SendMessageToSessions(propMoveMsg);

         gpc->SetProposedPosition(_curMousePos);
         PlaySound(_scheduleMoveSound);
      }
   }        

As you can see here, in order to tell the server that the user wants to
move a piece, we allocate a FRC_COMMAND_PROPOSE_MOVES Message from the
Message Pool, and then we add the various necessary data to the Message
to describe the move: Which piece the user wants to move, which square
he wants to move it to, etc. When all the necessary data is in the
Message, we simply call SendMessageToSessions() on our QMessageTransceiverThread
object, and off it goes. Note that we don’t actually move the piece on the
game board — that won’t happen until the end of the current turn, and even
then it will only happen if the server allows the move and updates the
MUSCLE node database to reflect the piece’s new position.

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.

In many networked video games, the playability of the game is very much dependent
on the responsiveness of the network under load. A slow connection and lost packets
can mean an unpleasant gaming experience. This is especially the case for fast-twitch
arcade-style games such as Quake or Doom, but is less of a problem for slower-paced
strategy-oriented games such as Civilization or The Sims.

As a gaming support platform, MUSCLE is more directly suited to the slower-paced
type of game, primary because it does all its network communication over TCP. TCP
is based on the concept of “reliable streaming”, where if a packet is lost (due to,
say, an overloaded router running out of memory), the TCP stack will re-transmit
the packet again until it finally gets through. That is just what you want when you
are transmitting a file, but often worse than useless if the re-transmitted packet is
obsolete by the time it gets to its destination. In these cases, an unreliable
datagram protocol like UDP is often a more appropriate method of data trasmission
to use.

That said, MUSCLE can be useful even for action/arcade games, because it can
be used for everything except the real-time-update packets that can’t wait for
retransmission. In other words, you could use MUSCLE for peer discovery, chat,
data transfer, and other non-real-time updates, and at the same time, use UDP for
player and projectile position updates. And of course, if your game is only
meant to be played on a LAN, where packet loss is not a major problem, then both
UDP and TCP will work fine.

Aside from the compromises inherent in using TCP as a base protocol, MUSCLE has
been designed to be as responsive as possible. All client side and server side mechanisms
are event-based; there are never any wasteful polling or delay loops at any part of
the process. Outgoing TCP buffers are force-flushed whenever the outgoing Message queue
runs dry, so that Nagle’s algorithm does not reduce responsiveness. The MUSCLE
server’s node database subscriptions are implemented using per-node callback tables, so
that the CPU cost of notifying subscribers about node changes remains constant
no matter how many nodes are in the database.

The MUSCLE system was developed as a platform for distributing real-time audio
metering data over a LAN for a mixing console application. As such, it has been
tuned to deliver forwarded Messages and database updates quickly and efficiently
over a reliable LAN (throughput is typically on the order of hundreds of Messages
per second). On the open Internet, the packet-loss problems inherent with TCP
can cause some delay, but it should still be satisfactory for slower-paced
games like FoxRabbitCarrot; and of course, combining MUSCLE with UDP will
yield the best of both worlds.


Concluding Remarks

In this article I have tried to give a good idea of what MUSCLE is, and
show by example how it can be used as the basis for a multiplayer, multiplatform,
networked strategy game. Unfortunately, a multiplayer game is an inherently
complex system, and even a lengthy article can do little more than scratch the
surface of all the issues and design decisions that creating one involves. Hopefully,
however, the combination of this article and the source code archive that is included
with it will be helpful to anyone who would like to write a multiplayer Internet game,
but didn’t know where to begin. If it isn’t enough, and you have questions or comments
about FoxRabbitCarrot, MUSCLE, C++, or networking in general, feel free to email me;
my email address is [email protected].


Thanks for your time, and if you are reading this, congratulations on getting to the end of the article. ;^)

8 Comments

  1. 2002-08-12 9:41 pm
  2. 2002-08-12 9:57 pm
  3. 2002-08-12 10:11 pm
  4. 2002-08-12 10:20 pm
  5. 2002-08-12 11:18 pm
  6. 2002-08-13 1:06 am
  7. 2002-08-13 1:43 am
  8. 2002-08-13 3:56 pm