Room Persistence API
The Room Persistence API provides tools to store and retrieve the state of Rooms. This feature can be useful to store the state of a ongoing games, save personal Rooms customizations and provide persistence in a virtual world.
Overview
The Room Persistence API is outlined in the basic IRoomStorage interface provided under the com.smartfoxserver.persistence.room package:
IRoomStorage:
- saveRoom(Room theRoom)
- saveAllRooms(String groupId)
-
saveAllRooms()
-
loadRoom(String name)
- loadAllRooms(String groupId)
-
loadAllRooms()
-
removeRoom(String name)
- removeAllRooms(String groupId)
- removeAllRooms()
Each group of methods allow to work with a specific Room, a group of Rooms or the entire Room list inside the Zone. Rooms are stored by serializing all their properties (the CreateRoomSettings object) and, optionally, all their Room Variables.
NOTE
When storing Room Variables remember that only server-owned variables will be persisted. This is because variables owned by Users cannot be recreated at a later time without the presence of that Variable's owner in the Room.
Implementations
We provide two different implementations out of the box:
-
File system based storage: allows to store Room data in the local file system, by default under the data/roomData/ folder. The default path can be reconfigured to point to any other directory. Be aware that using network shared volumes might slow down the storage API significantly.
-
Database storage: uses the default Zone's DBManager to store Room data in an external database. It also allows to provide a custom DBManager and customize several other options.
Quick start
Each Server Zone runs a different instance of the Persistence API, so we can customize the persistence settings for each Application independently.
The following code shows how to initialize the Room Persistence API in our Zone Extension:
public void init()
{
// Initialize Persistence API
getParentZone().initRoomPersistence(RoomStorageMode.FILE_STORAGE, new FileRoomStorageConfig());
// Load all previously stored Rooms
try
{
List<CreateRoomSettings> allRooms = getParentZone().getRoomPersistenceApi().loadAllRooms();
// Recreate all Rooms
for (CreateRoomSettings settings : allRooms)
{
getApi().createRoom(getParentZone(), settings, null, false, null, false, false);
}
}
catch (SFSStorageException storageErr)
{
trace("Error loading rooms: " + storageErr);
}
catch (SFSCreateRoomException creationErr)
{
trace("Error creating room: " + creationErr);
}
}
We initialize the API by providing a Room Storage Mode (either FILE_STORAGE or DB_STORAGE) and the relative instance of the configuration class (FileRoomStorageConfig, DBRoomStorageConfig). Then we proceed with loading all the previously stored Rooms and re-create them in the system using the SFSApi.createRoom(...) method.
When initializing the RoomPersistence API you may also want to configure a number of settings:
- storeInactiveRooms: when true it will store even Rooms that are not active, i.e. Room.isActive() == false. Default setting is false.
- storeRoomVariables: when true it will store all server-owned Room Variables together with the other Room settings. Default setting is true.
- skipStaticRooms: when true it will make sure not to save static Rooms. Default setting is true.
NOTE
Static Rooms are those defined in the Admin Tool > Zone Configurator. These Rooms are already re-created every time the Server boots up. If you store them via the Persistence API and then attempt to re-create them, you will get an error because those Rooms already exist. Normally you don't need to persist static Rooms. Each persistence implementation provides also a number of custom settings, for example the database-driven implementation allows to specify the table name, a custom DBManager etc... You can read all the details in the Server Side API javadoc, under the com.smartfoxserver.persistence.room package.
Database persistence example
public void init()
{
DBRoomStorageConfig cfg = new DBRoomStorageConfig();
cfg.tableName = "my_game_room_data"
// Initialize Persistence API
getParentZone().initRoomPersistence(RoomStorageMode.DB_STORAGE, cfg);
// Load all previously stored Rooms
try
{
List<CreateRoomSettings> allRooms = getParentZone().getRoomPersistenceApi().loadAllRooms();
// Recreate all Rooms
for (CreateRoomSettings settings : allRooms)
{
getApi().createRoom(getParentZone(), settings, null, false, null, false, false);
}
}
catch (SFSStorageException storageErr)
{
trace("Error loading rooms: " + storageErr);
}
catch (SFSCreateRoomException creationErr)
{
trace("Error creating room: " + creationErr);
}
}
The only difference with the file-based example is the use of DBRoomStorageConfig to configure the API. Here we need to define at least the table name we want to use for the storage. As regards the database connection details, the API will use the Zone's Database Manager which is configured from the AdminTool's Zone Configurator.
If you prefer to define a separate connection for the API persistence you can also do so by specifying it in the DBRoomStorageConfig object:
public void init()
{
DBConfig dbCfg = new DBConfig();
dbCfg.active = true;
dbCfg.driverName = "com.mysql.jdbc.Driver";
dbCfg.connectionString = "jdbc:mysql://127.0.0.1/database_name";
dbCfg.userName = "db_user_name";
dbCfg.password = "db_user_pass";
dbCfg.testSql = "SELECT name FROM some_table LIMIT 1";
DBRoomStorageConfig cfg = new DBRoomStorageConfig();
cfg.tableName = "my_game_room_data"
cfg.dbManager = new SFSDBManager(dbCfg);
}
Suggested use
Below we discuss two different use cases for the Room Persistence API and provide some tips on how to obtain the best performance.
Save and load the game state for long running matches
The first case scenario is a game of chess where a match could last for days or longer. Each player can connect to the server, restore the game from the point they left last time and execute a new move. In order to accomplish this we will need:
- A unique Room name for each Chess game match. This is important to avoid that Room names collide thus producing an error when attempting to create a Room that already exists.
- The game state is going to be stored in a number of RoomVariables that are persisted when the players leave.
Creating a unique Room name can be tackled in different ways. A simple solution is to identify the Room after the two opponents' names. Since all user names are unique inside a Zone, combining two unique names gives us a unique Room name for each game. For example we could use a convention such as
Next we need a way for players to tell the system that they want to suspend the game. This can be done in several ways:
-
Simply allow the players to leave the game at any time, capture the event from Server side (ROOM_REMOVED event) and invoke the Persistence API to store the Room. This would work even when players leave the game unintentionally (i.e. got disconnected). NOTE: in this case you will need to make sure that inactive Rooms are saved. A Room obtained via event after its deletion will be inactive.
-
Allow players to express their intention to leave the game by adding a custom request in the game client. When both player's request is received by the server, the Extension code can store the Room state and remove the Room from the system.
In order to restore the game when the User comes back there are also multiple options. We recommend to store the ongoing game Room names in the User profile itself, so that when the player logs in again the system can immediately recreate the suspended game.
Save and load the Room state for personal Rooms
One common feature in multiplayer virtual worlds is to provide each User with a custom "home" that can be customized and configured with all kinds of decorations and trinkets.
The Room Persistence API can be useful to save and load these Rooms on demand when players log in the system. The basic requirements for this functionality are similar to the previous use case:
- Make sure we assign a unique Room name to each User's private home
- Use one or several Room Variables to store all the User's settings and customizations
Again we can leverage the uniqueness of the User's name to create a unique personal Room name. Something as simple as Home_
If the number of customizations is very high (let's say 50+ parameters) we should use SFSObjects instead of the one RoomVariable-per-setting approach. For example we can compact all settings for the Room's furniture in one SFSObject and all personal details in another SFSObjects etc., and finally store these SFSObjects as Room Variables.
In order to store and retrieve these personal Rooms on demand we can simply rely on the user login and disconnection events. Every time the client joins the system we load their personal Room data and create their Room. Similarly when the user leaves the system by disconnecting we can store the Room first and remove it from the Zone.
Excluding specific Room variables
RoomVariables expose a flag called storable that can be used to selectively exclude specific Variables from being saved by the Room Persistence API.
The flag is set to true by default in every RoomVariable, but it can be turned off at creation time to signal which Variables should be skipped.
Example: