SFS2X Docs / AdvancedTopics / room-persistence-api
» Room Persistence API
In SmartFoxServer 2X 2.8.0 we have introduced a new Room Persistence API to help store and retrive the state of Rooms. This feature can be particularly useful to store the state of a ongoing games, personal Rooms in virtual worlds and more.
» Overview
The Room Persistence API is outlined in the basic IRoomStorage interface provided under the com.smartfoxserver.v2.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.
» 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:
// Init method of a 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.
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.v2.persistence.room package.
ยป Database persistence example
// Init method of a Zone Extension 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 tha 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 cfg = new DBConfig(); cfg.active = true; cfg.driverName = "com.mysql.jdbc.Driver"; cfg.connectionString = "jdbc:mysql://127.0.0.1/database_name"; cfg.userName = "db_user_name"; cfg.password = "db_user_pass"; cfg.testSql = "SELECT name FROM some_table LIMIT 1"; DBRoomStorageConfig cfg = new DBRoomStorageConfig(); cfg.tableName = "my_game_room_data" cfg.dbManager = new SFSDBManager(cfg); }
» 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 the match could last for days, weeks or even months. Opponents will be able to connect to the server and restore the game from where they left it since the last 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 will be stored in a number of RoomVariables that will be persisted when the players leave.
With this in mind we should be able to easily implement a game that allows players to freeze the match and return another time to continue from where they left.
Creating a unique Room name can be tackled in many different ways. The simplest ideas is to identify the Room after the two opponents' names. Since all user names are unique inside a Zone, combining two unique names will give us a unique Room name for each game. For example we could use a convention such as <Player1>_vs_<Player2> for the Room name.
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 passing the specific Room. This can be useful to make sure that the game is stored even when players have left the game unintentionlly (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 will to leave the game by adding a specific request in the client application. 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. The best idea is 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 of online virtual worlds is to provide each User with a custom "home" that they can customize and configure with all sorts of decorations and personal items.
Using the Room Persistence API can be the easiest way to save and load on demand these Rooms when players become active in the system. The basic requirements for this functionality are very similar to the previous use case:
- Make sure we assign a unique Room name to each User's private home
- User one or several Room Variables to store all the User's settings and customizations
We can again leverage the uniqueness of the User's name to create a distinctive personal Room name. Something as simple as Home_<PlayerName> will do the trick. Next we need to create the Room Variables that will keep track of the various User's decorations.
If the number of customizations is very high (let's say more than 30 parameters) we can leverage the convenience of SFSObjects instead of using 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. Each time the client joins the system we can load his/her personal Room data and create the Room in the system. Similarly when the user leaves the system by disconnecting we can store the Room and get rid of it until the next login.
» Excluding specific Room variables
Since SFS2X 2.14 RoomVariables expose a new 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 you can turn it off at creation time to signal which Variables should be skipped by the Persistence API.
Example:
RoomVariable rv = new SFSRoomVariable("TempName", "Sir No Name"); rv.setStorable(false);
» Custom Room Storage with SFS2X 2.18
Since the release of SmartFoxServer 2X 2.18.0 we have introduced a way to override the default implementations for the Room Persistence API so that they can be customized to your needs.
You can read all the details in this article.