Server-side Cluster API
In the previous server-side development overview we have mentioned the new Cluster API and how it can replace some of the functionalities found in the standard SFS2X API.
There are several features provided by the new API that will help with cluster development. Among the most relevant there are:
- Match-making.
- Join Invitations to an existing game.
- Cluster Globals.
- Game Node events.
Also note that the Cluster API is mainly used from the Lobby side, which is the main controller of the cluster activities. From the Game Node side we will mostly use the standard SFS2X API.
Match-making
As we have already mentioned, match-making is the fundamental mechanism by which players can jump into existing games or start new ones. The following is an example of how this works in an Extension on the Lobby side:
var clusterApi = SFSCluster.getLobbyService().getApi(); var matchExp = new MatchExpression("type", StringMatch.EQUALS, "GinRummy").and("rank", NumberMatch.GREATER_THAN, 10); var groups = Arrays.asList("poker"); var settings = new CreateSFSGameSettings(); settings.setName("ClusterRoom-0123"); settings.setMaxUsers(12); settings.maxSpectators(8); settings.setDynamic(true); settings.setGamePublic(true); settings.setMinPlayersToStartGame(2); List<RoomVariable> roomVars = Arrays.asList((RoomVariable) new SFSRoomVariable("type" ,"GinRummy"), new SFSRoomVariable("rank", 12)); settings.setRoomVariables(roomVars); clusterApi.quickJoinOrCreateRoom(user, matchExp, groups, settings);
The Match Expression finds any Room tagged as "GinRummy" with a rank value greater than 10. It is also implied that any Room found must have at least one free player slot. If a matching Room is found the player is notified and joined in the game, otherwise a new Room is created with the provided settings and the User is auto-joined there, waiting for other players to join as well.
Join Invitations
The Cluster API provides a way to invite players in a existing game that has free player slots. If the recipients of an invitation agree to join the game they will be immediately sent to the specific game Room and start playing. See the following example snippet:
List<User> = Arrays.asList(userKermit, userFozzie); var clusterTarget = new ClusterTarget("serverId", 123); var params = new SFSObject(); var clusterApi = SFSCluster.getLobbyService().getApi(); clusterApi.sendJoinInvitation(inviter, userList, clusterTarget, 50, params);
ClusterTarget
class is a simple wrapper around the server id and room id that should be used.
The method also requires an invitation timeout expressed in seconds (i.e. the max amount of time allowed to reply) and an optional SFSObject with parameters relative to the Invitation itself.
Once the Invitations are sent, the system will take care of each player's responses and send them to the target Room.
Cluster globals
It is possible to set a number of global values from the Lobby side that will be accessibile to every Game Node in the cluster. These variables are read/write from the Lobby Extension side, and read-only from the Game Extension side.
The following is an example of how to set Cluster Globals from the Lobby side:
// Lobby Extension init method @Override public void init() { Map<String, Serializable> globals = new HashMap<>(); globals.put("gamaParam", "SomeData"); /* * ... Populate with more data ... */ SFSCluster.getLobbyService().getGlobals().set(globals); }
And access them from any Game Node:
Map<String, Serializable> globals; // Game Node Extension init method @Override public void init() { globals = SFSCluster.getGameService().getGlobals(). // Access as any Map String gameParam = (String) globals.get("gameParam"); }
Game Node events
The Cluster API provides a mechanism for generating events from a Game Node towards the Lobby Node. These custom events can be used to trigger specific actions on the Lobby side. For example a game finishes and the winner is proclaimed; if this event needs to be known by the Lobby, we can trigger it in this way:
public class ClusterGameExtension extends SFSExtension { @Override public void init() { Map<String, Object> eventParams = new HashMap<String, Object>(); eventParams.put("id","GameEnd"); eventParams.put("type", "GinRummy"); //... Populate with more data SFSCluster.getGameService().getApi().dispatchGameEventToLobby(eventParams); } }
Here we can pass an arbitrary Map with the data representing the event. On the Lobby side the event is handled like any other Extension event using the new type: SFSEventType.GAME_NODE_EVENT.
public class LobbyExtension extends SFSExtension { @Override public void init() { // ... // ... addEventHandler(SFSEventType.GAME_NODE_EVENT, new GameNodeEventHandler()); } } // ... // ... public class GameNodeEventHandler extends BaseServerEventHandler { @Override public void handleServerEvent(ISFSEvent event) throws SFSException { var eventParams = (Map<String, Object>) event.getParameter(SFSEventParam.CLUSTER_EVENT_PARAMS); String id = (String) eventParams.get("id"); String type = (String) eventParams.get("type"); if (id.equals("GameEnd")) { //... } } }
We recommend using an approach similar to this example where an "id" of some sort is included in the parameter's Map to identify each different event.