2012-03-02

firebase

Firebase from Scratch, pt VI: Activating Games

In a series of posts we’ll do “Firebase From Scratch”, an introduction to Firebase and its concepts and ideas. Hopefully, reading this series will give you a firm grasp of what Firebase is and what it can do.

In the last section we had a look at the game server code and its different components and we learned that “Tables” are the areas around which a number of players join and participate in games. Now we’ll look at the creation of tables, which is done via a game “Activator”.

What Is A Table Anyway?
Remember that although we speak of “tables”, a table is really an “area”: it is a server side representation of a space or a location which players join to play the game. As such, we need a way to create tables because without them, there won’t be anywhere to actually play; which would be a bit boring.

But here’s the catch: The Firebase API works the same way whether your running a single server on your development machine, or a full blow twenty machine cluster in a live environment. But in a cluster, where games are executed on many physical machines, who is going to control the tables and where does that component live?

The Game Activator
An Activator is a separate piece of code from the game, but is packaged in the same GAR. Firebase will create one, and only one, Activator per cluster, and it will be responsible for creating new tables. This might be illustrated with some examples.

Imagine a poker client. There’s a lobby (and yes, we’ll get to the Firebase lobby functionality later) with a list of available tables. The server needs to make sure that there are always tables available for players to join. So the Activator will, in a nutshell:

  • Run a background thread that regularly looks at all tables:
    • If there are too many empty tables, the Activator may destroy existing tables
    • If there aren’t enough tables of a certain type, say Texas Hold’em, the activator creates new tables
  • Normally, the Activator also checks for tables that have failed in some way and destroys them as well

Here’s what Firebase does in the background: When a table is created or destroyed, Firebase updates the lobby, and any client which subscribes to the lobby data will get an automatic update with the new or deleted information and adjusts its displays accordingly. A player chooses a table and sends a “join” request to it, and then we’re back into familiar territory (from the last episode).

Here’s how it would work in a board game, let’s say “Chess with Friends”. Normally there won’t be a “lobby” in the same sense as a poker server has a lobby with available tables. Rather you would challenge friends to a game, or participate in tournaments. So here’s how that would work:

  • A player, let’s call him Adam, sends a message to the Activator, saying the equivalent of “I, Adam, want a table, to play with my friend Tom”.
  • The Activator receives the message and creates a new Table, with reservations for Adam and Tom
  • When the table is created Firebase sends an invitation to Tom, and a confirmation to Adam that the table is created
  • The Activator also runs a background thread that regularly looks at all tables:
    • If a game at a table is finished, or timed-out, the Activator destroys it.

Note that Firebase still manages the tables in a Lobby but the clients probably won’t use it. Also, the “create table request” Adam sends to the Activator may contain a list of invitees, in this case Tom, who will get seats at the table reserved and also invites from Firebase to the new table; and all is done automatically! The Activator literally just creates the tables on demand.

The Simple Activator
Enough talk, let’s write some code instead! We’re going to create a very simple Activator for our little test game we started last episode, so if you don’t have one, go there to get it set-up.

Let’s start off with a new class for our Activator:

public class Activator implements GameActivator {

  @Override
  public void init(ActivatorContext con) throws SystemException { }

  @Override
  public void destroy() { }

  @Override
  public void start() { }

  @Override
  public void stop() { }

}

You’ll notice that we get the lifetime methods “init”, “start”, “stop” and “destroy” from the interface. The Activator in Firebase can be, and most probably will be, multi-threaded and hence it is important that Firebase gets to control when an Activator starts and stops.

We’re going to ignore any background threads for this demonstration, and instead go for the trivial route. First, the context of the Activator is important, so we’ll save that for later:

private ActivatorContext context;

@Override
public void init(ActivatorContext context) throws SystemException {
  this.context = context;
}

Then we’ll say that the Activator should create 10 tables on start-up:

@Override
public void start() {
  int noOfSeats = 10;
  TableFactory factory = context.getTableFactory();
  for (int i = 0; i < 10; i++) {
    final int tableNo = i + 1;
    factory.createTable(noOfSeats, new DefaultCreationParticipant() {

      @Override
      public String getTableName(GameDefinition def, Table table) {
        return "MyTable " + tableNo;
      }
    });
  }
}

This is pretty simple, eh? The only thing that looks complicated is that nested class: a “creation participant” helps Firebase create the table by supplying table name and lobby locations, and also gets an opportunity to change the table attributes in the lobby when the table is created. In this instance we’ve overridden the default Firebase implementation which takes care of the lobby location for us (we’ll get back to the lobby location of tables in a later installation of this series).

For the sake of cleaniness, let’s remove the tables when we shut down:

@Override
public void stop() {
  TableFactory factory = context.getTableFactory();
  for (LobbyTable table: factory.listTables()) {
    factory.destroyTable(table, true);
  }
}

Now the only thing we have left to do is to tell Firebase about our new shiny Activator. When the project was set up, Firebase created a magic file for you automatically (if you used the Maven archetype, that is), called “game.xml”. This game definition is located here in your Maven project:

src/main/resources/firebase/GAME-INF/game.xml

If you open it you’ll see that it contains the class name of your game, the ID of your game and also a name. This file is used by Firebase when the system starts up to figure out you main game class, and this is also where we’ll tell Firebase about the new activator, so we’ll add an “activator” element and point it to the right class:

<?xml version="1.0" encoding="UTF-8"?>
<game-definition id="666">
  <name>test-game</name>
  <classname>test.GameImpl</classname>
  <activator>test.Activator</activator>
  <version>1.0-SNAPSHOT</version>
</game-definition>

Note the new “activator” element. Now Firbease will pick up our Activator, call and “init” followed by “start” on it when the server starts up. If you’ve already compiled the client from the Hello World tutorial you can now connect and verify that you have indeed 10 tables, named “MyTable [1-10]”.

Diving Deeper
By now you probably realized that there’s a lot more to the Activator than we’ve seen so far, and this is true, the Activator is a surprisingly complicated beast and it is all too easy to mess it up (but don’t be ashamed, I’ve done it myself a few times). Fortunately Firebase comes with a “default Activator” that kicks in if you haven’t specified your own. This activator will make sure there are tables available, and can even be configured to a point if you need to change it, have a look at it’s source code, it is surprisingly enough named “DefaultActivator” and is included in the Firebase API.

Another complication is that tables are created by the Activator, but since we might execute on a cluster they may not actually appear until some undefined time later. As a result, you need to be careful how the table, clients and the activator interact. See Activation Best Practises on our Wiki for more information about that.

public class Activator implements GameActivator {

@Override
public void init(ActivatorContext con) throws SystemException { }

@Override
public void destroy() { }

@Override
public void start() { }

@Override
public void stop() { }

}