-
Notifications
You must be signed in to change notification settings - Fork 1
DataBlocks
As part of core functionality the Snipe server provides access to so-called data blocks.The data block is a basic building block of information that can be retrieved and modified with a standard API. When retrieved, the data block stays in cache for some time, speeding up access to it. The core has two types of blocks available: user parameters and user quests. The project developers can easily define more blocks for use in their projects.
Each data block has a unique ID (unique in the underlying database table) and contents object. There is an API available on both the cache and slave servers to get, lock/unlock, modify and save data blocks to database. The data blocks operations are partly automated and easy to setup by default but the "manual" control is also available.
If you're building classes on top of data blocks, take a look at the Generic Containers article.
The first thing you will need to do is create a new database table for storing block data. The table needs to contain at least a single field: ID
. The ID
field stores block ID and acts as a unique key. You can have other fields in the table named arbitrarily with the exception of Params
field. If the Params
field exists and the data class marks it as existing, it will contain serialized block data.
The API does not discern between row fields and Params
object fields. The only difference in operation is that you will need to change table definition each time you add new field definition to the table.
Using user quests storage as an example:
create table UserQuests
(
ID serial PRIMARY KEY,
Params text DEFAULT '' NOT NULL
);
The next thing to do is create a class for this block type and load it on the cache server start. Using user quests as an example:
package snipe.cache.data;
import snipe.cache.Cache;
class QuestDataCache extends Cache
{
public function new(mng: CacheManager)
{
super(mng);
name = 'questdata';
tableName = 'UserQuests';
hasParams = true;
paramsFormat = 'haxe';
}
}
The constructor sets up some parameters for the class. Let's take a closer look.
name = 'questdata';
This line sets up the name of the data block type. This name will be used in all API calls.
tableName = 'UserQuests';
This line stores the database table name.
hasParams = true;
Enabling hasParams
notifies the data cache manager that this block type has a Params
field that needs to be serialized and deserialized.
paramsFormat = 'haxe';
This line sets up the format of a Params
string. haxe
(Haxe serialization format) and json
formats are supported in the core, but you can setup a custom format using another string as an identifier and overriding load()
and save()
methods. Use the default methods as guidelines.
When your data block class is ready, put that line into initModules()
method for the class extending CacheServer
(again, user quests as an example):
cacheManager.loadModules([ QuestDataCache ]);
It will load the data block module into the cache server enabling it for API access.
Sometimes you need to read and modify data blocks directly on the cache server. For example, the core uses this during user registration. This section describes the API available and discusses what happens under the hood in some detail. All of the slave server data blocks API is based on these calls. Refer to API documentation for arguments description.
These are the various methods to get one of the block attributes of a specific type.
This method returns the raw Map
of block contents.
This method returns a copy of the raw Map
of block contents. It should be used in case when you intend to send block contents over to one of the slave servers manually in uniserver mode since using Block.getList()
will get you a link to the internal map.
Sets the block attribute to a given value and increases the version. You will need to call CacheManager.updated()
to mark the block for saving.
This method sets the contents of this block from database row. This should only be used in Cache.load()
in the case of custom block format.
This method marks this block as updated. It needs to be called if block contents were changed manually. For example, if you have changed some field in an object that you've received through one of "get" methods or if you've called Block.getList()
and changed values in the resulting Map
directly.
Note that you will also need to call CacheManager.updated()
to mark the block for saving into the database. That means that changing values in the most direct way will require you to call two different updated()
methods.
This API call will insert a new record into the database with a given block type and ID.
Will try to get the data block from the memory cache and lock it. If the data block is not currently in the cache, it will be loaded from database and put into cache. If the data block is currently locked, this method will fail. Only one server can lock the block for modifying at the same time.
Each successful get()
request to the data block will mark it as used. If the block is not used for some time and is not currently locked, it will be removed from memory. The time period before removing the block from cache is controlled by the cache.remove
configuration variable.
This method takes the current block version as an argument. In case when the block version is the same as the block stored currently in memory cache, this operation will return sameVersion
error code.
This call should be used when you want to receive the block contents without locking it. Note that this will not get you the most recent block version, only the latest that the cache server has. So if the block is currently locked by some slave server and has unsaved changes, this call will not be able to see them.
This call will update the block with the given changes, mark it for saving and unlock it if needed. Marking the block as unsaved will allow it to be saved into the database when enough time has passed since last change. This time period is controlled by the cache.save
configuration variable.
If you use the manual control for some types of blocks, don't forget to call update()
with the unlock flag set to true for each block when you finish working with it, or it will become a zombie block that is locked indefinitely.
This method will mark the block as updated and unsaved. Marking the block as unsaved will allow it to be saved into the database when enough time has passed since last change. This time period is controlled by the cache.save
configuration variable.
You should use this method if you've made some changes in the data block with Block.set()
on the cache server.
In general, the slave API server closely resembles the cache API server. You get the same CacheManager
class with the same create()
, get()
, getUnlocked()
and update()
functionality with small changes. There is a slight difference in workflow, though. All the blocks that the current slave server received and has locked are put into a local list. You don't need to call update()
after each operation, it will be called automatically if any block attributes were changed or marked as updated. Note that the automatic update()
does not unlock the block.
The ClientInfo
class also provides a free()
hook that is called on a client disconnect. You can put update()
calls in it in order to unlock additional user-specific blocks. Another place to do that is when using the core/user.freePost
game server method subscription.
These calls will return block attributes with a specific variable type.
Sets the block attribute to a given value. By default it will also mark the attribute as updated.
Separate call for marking the block attribute as updated. This is useful when you do some changes in bulk or modify some internal object property manually.
This API call will insert a new record into the database with a given block type and ID.
Will try to get the data block from the cache server memory cache and lock it. If the data block is not currently in the cache, it will be loaded from database and put into cache. If the data block is currently locked, this method will fail. Only one slave server can lock the block for modifying at the same time.
Each successful get()
request to the data block will mark it as used. If the block is not used for some time and is not currently locked, it will be removed from memory. The time period before removing the block from cache is controlled by the cache server cache.remove
configuration variable.
This method takes the current block version as an argument. In case when the block version is the same as the block stored currently in memory cache, this operation will return sameVersion
error code.
After the successful operation the block is also put into the local slave server block cache for automatic management by the core. The core will periodically check for local block changes (the delay is controlled by the cache.update
configuration variable) and if there are unsaved changes and enough time has passed since the last change (at least cache.update
seconds), the core will send an update message to the cache server.
This is a wrapper on top of its cache server counterpart. It returns the latest version of the block stored in the cache server.
Normally, all blocks that you get()
from the cache server are added to the local list and are updated automatically. However, you might want to free the block manually from the server. In this case you will need to call update()
directly.
The other time when you need to call this method is in a ClientInfo.free()
method or in a core/user.freePost
game server hook to unlock the block after the client disconnects.
This method will unlock the block without sending any local changes. It is mainly used to handle the situation when the client login fails after getting the user blocks.
This method checks the local block cache if it has the given block locked. Used when blocks are lazily initialized in projects.