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 16x16 board instead of an 8x8 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: "/
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:
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:
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.
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
[...]
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.
[...]
}



