Skip to content

DataBlocks

Max S edited this page Aug 10, 2015 · 3 revisions

Data Blocks

Overview

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 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 "manual" control is also available.

Defining new data block type

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 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 the data block type. This name will be used in all API calls.

      tableName = 'UserQuests';

This line stores database table name.

      hasParams = true;

Enabling hasParams notifies data cache manager that this block type has Params field that needs to be serialized and deserialized.

      paramsFormat = 'haxe';

This line sets up format of Params string. haxe (Haxe serialization format) and json formats are supported in the core but you can setup custom format using some other string as an identifier and overriding load() and save() methods. Use the default ones 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.

Cache server API

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.

CacheManager.create()

This API call will insert a new record into database with a given block type and ID.

CacheManager.get()

Will try to get the data block from memory cache and lock it. If the data block is not currently in cache, will load it 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 request to 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 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.

CacheManager.getUnlocked()

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.

CacheManager.update()

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 database when enough time has passed since last change. This time period is controlled by the cache.save configuration variable.

If you use manual control for some types of blocks, don't forget to call update() with 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.

Slave server API

In general, the slave server API closely resembles the cache server API. 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 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 onFree() hook that is called on client disconnect. You can put update() calls in it for unlocking additional user-specific blocks.

Block.get(), Block.getBool(), Block.getFloat(), Block.getInt(), Block.getString()

These calls will return block attributes with a specific variable type.

Block.set()

Sets the block attribute to a given value. By default it will also mark attribute as updated.

Block.updated()

Separate call for marking block attribute as updated. This is useful when you do some changes in bulk or modify some internal object property manually.