Distributed objects

For a technical explanation of working with distributed objects, see com.threerings.presents documentation. This article discusses how to use distributed objects specifically with GameGardens programs.

= GameObject =

If you are using one of the sample GameGardens games as a template for your project, then you will have a GameObject class. Sagashi calls this SagashiObject, Mummichog calls it MummichogObject, etc. This class contains all information that must be shared by everybody playing the game. Each new object variable requires editing in three places. (Note: There is supposedly an Ant Task that can automate most of this, but it's simple enough to do by hand. If you figure out how to use Ant to automate it, please post a tutorial about that.) First add a line that provides the fieldname of the new variable, similar to SERVICE and BOARD. Second add a line declaring the variable itself. Finally, add a setter method for the variable, similar to the other setter methods in the class.

For any easily streamed field, adding to the GameObject works nicely. Simple data types and arrays of simple data types all work. Trying to add a Vector will cause streaming errors.

= Additional Objects =

It is awkward (and not elegant object-oriented coding!) to add multiple parallel arrays to the GameObject. The most common example needing an additional object is keeping track of scoring. The Zen Bilging game is a party style table that displays a row in the ScorePanel for each player that has earned points. This ScorePanel is updated at the end of each round, but it could just as easily have been updated after each individual player's score changes.

Here is how ZenBilging uses the additional distributed object.

ZenScore object class
In the ZenBilging data package, in addition to ZenObject, is a ZenScore class. Each ZenScore object contains all the scoring information for a single player.

package com.tcarr.zenbilging.data; import com.threerings.io.SimpleStreamableObject; import com.threerings.presents.dobj.DSet; /**  * A score record for a particular player. */ public class ZenScore extends SimpleStreamableObject implements DSet.Entry, Comparable {   /** The object id of the user associated with this record. */   public int userOid; /** This user's cumulative score. */   public int score; /** This user's score for the current round */ public int round; /** the current round number */ public int round_number; public ZenScore {   }    public ZenScore (int userOid) {       this.userOid = userOid; }   // documentation inherited from interface DSet.Entry public Comparable getKey {       if (_key == null) { _key = new Integer(userOid); }       return _key; }   // documentation inherited from interface Comparable public int compareTo (ZenScore other) {       return other.score - score; }   public void clearRound {       round = 0; }   public void nextRound {       round_number++; }   protected transient Integer _key; }

ZenObject changes to include ZenScores
In the ZenObject class, the following additional lines were needed.

/** The field name of the  field. */   public static final String SCORES = "scores";

/** Contains score records for every scoring occupant. */   public ZenScore[] scores;

public void setScores (ZenScore[] value) {       ZenScore[] ovalue = this.scores; requestAttributeChange(           SCORES, value, ovalue); this.scores = (value == null) ? null : (ZenScore[])value.clone; }

If the ability to update a single element of the scores array is needed, then this method would also be useful:

public void setScoresAt (ZenScore value, int index) {       ZenScore ovalue = this.scores[index]; requestElementUpdate(           SCORES, index, value, ovalue); this.scores[index] = value; }

ZenManager changes to include ZenScores
A protected variable _scores is added to ZenManager:

/** Contains score records for all participating players. */   protected HashMap _scores = new HashMap;

In the didStartUp method, a line is added:

_zbobj.setScores(new ZenScore[0]);

In the ZenBilging game, the ZenBoardView class determines the player's score and then submits it (as a string) to the ZenManager. That method in ZenManager is:

public void submitScore (ClientObject caller, String scoreS,                           ZenService.ResultListener rl) throws InvocationException {       BodyObject user = (BodyObject)caller; int score; score = Integer.parseInt(scoreS); ZenScore srec = (ZenScore)_scores.get(user.getOid); if (srec == null) { srec = new ZenScore(user.getOid); srec.round = score; srec.score = score; srec.round_number = _roundNumber + 1; _scores.put(srec.userOid, srec); } else { srec.round += score; srec.score += score; }       rl.requestProcessed(scoreS); }  // end submitScore

The gameWillStart method gets the following lines:

// clear the last round's scores from scoreboard ZenScore[] scores = new ZenScore[_scores.size]; _scores.values.toArray(scores); for (int ii = 0; ii < scores.length; ii++) { scores[ii].clearRound; scores[ii].nextRound; scores[ii].round_number = _roundNumber + 1; }       Arrays.sort(scores); _zbobj.setScores(scores);

We also need a method to update the scores:

/**    * Updates and broadcasts our scores array. */   protected void updateScores {  if (_bot > 0) { // bot's score for this round ZenScore srec = (ZenScore)_scores.get(-1); if (srec == null) { srec = new ZenScore(-1); srec.round = _zbobj.board.getBenchmark(_bot); srec.score = srec.round; srec.round_number = _roundNumber + 1; _scores.put(srec.userOid, srec); } else { int benchmark = _zbobj.board.getBenchmark(_bot); srec.round += benchmark; srec.score += benchmark; }       }        //  update the scoreboard ZenScore[] scores = new ZenScore[_scores.size]; _scores.values.toArray(scores); Arrays.sort(scores); _zbobj.setScores(scores); }   // end updateScores

When each round's time is up, the ticker calls a tick method that includes a line invoking updateScores. If we add the updateScores line to the submitScore method, then the scores would update every time any score changes instead of just after each round.

Since this is how we remember the scores, the scores array is also referenced in the gameDidEnd method, to determine the winner.

ScorePanel
The ScorePanel class is extremely similar to the ScorePanel class used by Sagashi. The most important section so far as dealing with distributed objects is concerned would be this one:

public void attributeChanged (AttributeChangedEvent event) { if (event.getName.equals(ZenObject.SCORES)) { fireTableDataChanged; }       }

= Links =
 * com.threerings.presents documentation
 * ZenBilging game