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);
The User objects representing the players to invite are typically obtained via the player's Buddy List while the 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");
			}
NOTE: Cluster Globals are a mean to share data across the cluster such as game settings, configuration and similar static values. We don't recommend to use them for large/dynamic data sets such as high score tables, leaderboards, etc. For these purposes a database is always recommended.

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.

NOTE: all values in the event Map must be Serializable.