Three months ago I became the Red Hat intern for Errai. Shortly afterwards I embarked on a modest task: to make a demo showing the world how awesome Errai is. I am now happy to present
Block Drop, a simple multi-player, Tetris-like web game for the desktop or touch screen browsers.
If you're new to Errai, Block Drop is a great example of how Errai can be used to simplify the task of making rich and interactive web applications. In particular, Block Drop made use of the following elements of Errai:
- CDI Events
- Message Bus
- Errai UI
- Data Binding
In this post I will cover some basic use cases of CDI Events with examples from Block Drop. (Note that code snippets below may be slightly modified for readability and clarity. You can check out the full source
here.)
CDI Events
Making a Simple Game Lobby
In Block Drop players must log into a lobby page before they can begin playing. From this page we want players to be able to do the following tasks:
- See other idle players
- See current games being played
- Start new games, possibly inviting other players to them
- Join currently existing games
Furthermore, the lobby state will frequently be changing, and we want all clients to have up-to-date information.
CDI Events to the Rescue
In order to satisfy (1) and (2) above, we need a way to broadcast a list of players and a list of games to all connected clients. Thankfully this task is a piece of cake with Errai. First we need an object to store the information we need for transit. For the sake of simplicity, I have chosen to send the entire lobby state on each update, but in a real-world application one might prefer to send an incremental update.
/**
* A portable bean for transmitting lists of players in lobby and games in progress.
*/
@Portable
public class LobbyUpdate {
/** A map player ids to Player objects (of players in lobby). */
private Map<Integer, Player> playerMap = null;
/** A map of game ids to Game objects (of games in progress). */
private Map<Integer, GameRoom> gameMap = null;
/**
* Construct a LobbyUpdate instance.
*
* @param playerMap
* A map of player ids to players, for all the players in the lobby.
*
* @param gameMap
* A map of game ids to games, for all the currently in progress games.
*/
public LobbyUpdate(Map<Integer, Player> players, Map<Integer, GameRoom> games) {
this.playerMap = players;
this.gameMap = games;
}
// Getters and setters omitted
Like the name suggests, a LobbyUpdate object exists for the sole purpose of transmitting information about the state of the lobby. You'll notice that:
- playerMap contains the idle players
- gameMap contains the current games
- Both of these fields have getters and setters (omitted here for brevity)
- The class is annotated with @Portable
The Portable annotation is what will allow us to send and receive this object over the network. What we need next is way to send LobbyUpdates to clients. Since we want to broadcast this update to all clients indiscriminately, we'll use a CDI Event.
In it's simplest case, a CDI Event in Errai is a simple way for a server to send an object to all of the connected clients. By using an Event object, we will be able to fire LobbyUpdate objects to all clients with one simple command.
/** Used for sending lobby updates to clients. */
@Inject
private Event<LobbyUpdate> lobbyUpdate;
/**
* Fire an LobbyUpdate to connected clients. This should result in clients
* refreshing their lobby lists.
*/
public void sendLobbyList() {
lobbyUpdate.fire(new LobbyUpdate(lobbyPlayers, games));
}
The above is an excerpt from the game's Server class. The private field,
lobbyUpdate, is the Event object used in the method
sendLobbyList to transmit LobbyUpdates. The
@Inject annotation tells Errai to assign an event to the
lobbyUpdate field when the server is started.
So what happens when this event is sent to the clients? On the client side we need tell the clients to observe LobbyUpdate. So in the Lobby class we add the following method.
/**
* Update the lobby list model and display with newest lobby update from the server.
*/
public void updateLobby(@Observes LobbyUpdate update) {
// Updating logic goes here
}
The
@Observes tells the framework that this method should be called when an Event containing a LobbyUpdate is received. And that's all it takes to transmit the state of the lobby.
CDI Events from the Client
To go about task (3), we need to send an invitation from one client to a subset of the other connected clients. From the previous code examples, you might be expecting something like this:
@Override
public void onClick(ClickEvent event) {
Invitation invite = new Invitation();
// Invitation setup logic goes here...
gameInvitation.fire(invite);
}
This click handler is called in the lobby when a user presses the "New Game" button. The
Invitation object is a
@Portable class with setters and getters as its only methods, much like the LobbyUpdate class shown previously. On the last line, the event is fired using
gameInvitation, an Event
object, which was created using the @Inject annotation as in the previous example.
This is a good start, but the only hiccup is that a CDI Event fired from a client will only go to the server. So we'll need a method to catch and relay this event at the server.
/**
* Respond to an invitation by relaying it to the appropriate client.
*/
public void handleInvitation(@Observes Invitation invitation) {
// Relaying logic goes here
}
Limitations of CDI Events
But what exactly is the relaying logic that will go in the handleInvitation method above? Can we target specific clients with events? The answer is that we cannot. At least, not with events as we've seen them so far.
In my next post, I'll discuss the Errai Message Bus and how it can be used to relay messages to specific clients as well as lots of other neat things.