-
Notifications
You must be signed in to change notification settings - Fork 1
Containers
The Snipe server library provides two generic ways to group data inside of a block. The first one is a key-value map, the other one is a parametric list of items that have a given type. Both of these are only available on slave servers. The core uses these classes for accessing the data stored in user blocks. For example, user effects are built on top of the generic list and user attributes are stored in the generic map.
The map uses the snipe.slave.data.MapCore
class. The key is always string and the value can be one of the types supported by the serializer: Bool
, Float
, Int
, String
, anonymous structure or an array of those. Basically, anything that can be correctly serialized and deserialized in the data block is supported. If you will try to store something different (the API does not explicitly forbid that), you will get a deserialization error when the server tries to load the block.
The map API provides methods to get and set map values. It also gives two hooks, MapCore.setPre()
and MapCore.setPost()
. They will be called before and after setting of each value.
Let's take a look at the concrete example:
import snipe.slave.data.MapCore;
class UserSkills extends MapCore
{
var user: UserTest;
public function new(s: ServerTest, u: UserTest)
{
super(s, u.block, "skills");
user = u;
}
}
You can use this example with minimal changes as a template for grouping various data that is stored in the user data block. Note the third argument for the super call: "skills"
. It sets the name of the block attribute that the data will be stored in.
And here is the user class code:
import snipe.slave.data.*;
class UserTest extends UserCore<UserAttributesCore, UserVariablesCore,
UserQuestsCore, UserChainsCore, InventoryCore, EffectsCore, BadgesCore>
{
public var skills(default, null): UserSkills;
public function new(s: ServerTest, b: Block, l: String, classes: _UserClassesList)
{
super(s, b, l, classes);
skills = new UserSkills(s, this);
}
This code allows you to write something like this:
client.user.skills.set("swords", 100);
In the end the skills data will be stored in the user record in database with params
field that looks something like this (user blocks use JSON format): {"skills":{"swords":100}}
.
The list uses the snipe.slave.data.ListCore
class. It will accept the item type definition as a parameter. All of the methods that receive or return the items will then use that type to check for compilation errors.
The only field that has to exist in the type is the id
field. This ID should be unique for that item in that list. The list API provides an autoincrementing field called nextID
that is always calculated as the next one from the highest ID already in the list. If the item ID is not unique due to a programming mistake, the list API will work incorrectly.
Let's take a look at the example (to shorten the example the method bodies were removed):
import snipe.slave.data.ListCore;
import snipe.slave.Block;
class UserGifts extends ListCore<_Gift>
{
var user: UserClass;
public function new(s: ServerClass, b: Block, u: UserClass)
{
super(s, b, "gifts");
user = u;
}
// check and remove timed out gifts
public function check()
// ...
// dump gifts info
public function dump(lang: String): List<Dynamic>
// ...
}
typedef _Gift =
{
var id: Int; // unique id
var itemID: Int; // id in Items table
var fromID: Int; // gift sender ID
var from: String; // gift sender network ID
var note: String; // gift note
var timeStart: Float; // time when given
var time: Int; // time until expiration (minutes)
};
This example uses the generic list to hold ingame gifts received by the user. Note that the implementation does not have to use the user block for storage (and we do not do that actually). Since there can be a lot of gifts received by the user and they are accessed and modified fairly infrequently, you can store them in a separate block.
Keep in mind that if you change the item contents manually or remove the list item without using the removeItem()
method, you will have to notify the list about the changes by using the updated()
method. It accepts a single optional argument - the ID of an item. If this argument is not set, the whole list is marked as updated. In case when you remove an item from the list, always mark the whole list as updated.
Both generic map and list accept block as an argument. Though both examples used user blocks and sub-classes, this does not need to be so. You can use generic containers with any block type provided you do the block management yourself. You will need to get the block from the cache server at some point before using it and you will need to make the final block update with unlock after you're done with it. The intermediate updates will be handled automatically by the core.