Skip to content
maxless edited this page Dec 14, 2015 · 4 revisions

Cache Server Modules

Overview

Parts of game logic that require server to server communication, or have to use synchronized data, can be put into cache server modules. The code in these modules is running in the cache server threads and can be called from slave servers with a standard API. The core provides some modules, but their functionality will be described in the sections for proper game modules.

Two additional APIs are not described on this page: module subscriptions and editor notifications. They are described in detail on Subscriptions and Editor Notifications pages.

Creating a module

The first thing you need to do is create a module class. It should extend the parametric ModuleCache class supplying your cache client and cache server classes as parameters. Nothing is required beyond that. Here is the minimal example:

package modules;

import snipe.cache.SlaveClient;
import snipe.cache.CacheServer;

class TestModuleCache extends ModuleCache<TestSlaveClient, CacheServerTest>
{
  public function new(s: CacheServerTest)
    {
      super(s);
      name = 'test';
    }
}

The most important thing here is the module name. All external module requests are routed using the module name, more on that below in the call() section.

Now we need to add a call to load the module into the server during initialization. This is done in initModules() method with this call:

  public override function initModules()
    {
      loadModules([ modules.TestModuleCache ]);
    }

Note that project modules are initialized after the core modules so all functionality from core modules is available to them.

Loading static data during initialization with loadTables()

If your module requires the loading of some external data like database tables, you can do it in loadTables() hook like this (just as an example):

  var _list: Map<String, TestType>;

  override function loadTables()
    {
      _list = new Map<String, TestType>();

      var res = server.query("SELECT * FROM TestTable ORDER BY ID");
      for (row in res)
        {
          var obj = {
            id: row.id,
            data: row.data,
            };
          _list.set('' + row.id, obj);
        }
    }

Additional initialization with initPost()

The loadTables() method is called in the constructor for ModuleCache class. If you want to have a module that does some initialization that depends on the data loaded in another module, you cannot do this in loadTables() since the other modules may not be loaded at that point. For that you can use the initPost() hook.

  public override function initPost()
    {
      // some additional initialization
    }

The internal call order of cache server initialization is this:

  initCoreModules();
  initModules();

  for (m in _modules)
    m.initPost();

Handling external requests with call()

Handling external requests by slave servers or the editor will require using the call() hook. Each time the slave server sends a request to cache server, the request handler decides what module to pass the message to by the module name and calls the call() method of this module with message parameters as an argument. The request type format is "<module>.<name>".

Example of the slave server code:

  var ret = server.cacheRequest({
    _type: 'test.call1',
    var1: 10,
    var2: 20
    });

Example of the cache server code to handle requests:

  public override function call(client: TestSlaveClient, type: String, params: Params): Dynamic
    {
      var response = null;
      if (type == "test.call1")
        response = call1(client, params);
      else if (type == "test.call2")
        response = call2(client, params);

      return response;
    }


  function call1(client, params)
    {
      var var1 = params.getInt('var1');
      var var2 = params.getInt('var2');

      // request handling

      return { errorCode: 'ok' };
    }


  function call2(client, params)
    {
      // request handling

      return { errorCode: 'ok' };
    }

Public module methods

Sometimes you need to make some of the module methods a part of your internal project API. There are two ways to get access to module as a variable. The first method is to simply use server.getModule() call each time you need to make an API call, which is not that convenient:

  var testModule: modules.TestModuleCache = server.getModule('test');
  var ret = testModule.apiCall();

The second method involves setting up global module links in the server class itself in the initModules() method:

import snipe.cache.CacheServer;

class CacheServerTest extends CacheServer
{
  // ...

  public var testModule(default, null): modules.TestModuleCache;

  public override function initModules()
    {
      loadModules([ modules.TestModuleCache ]);

      testModule = getModule('test');)
    }

  // ...
}

Now that the link is set up, you can call public methods like this:

  var ret = server.testModule.apiCall();

You can note that CacheServer class has a couple of links to core modules that provide API.

Thread safety

Since all cache server modules code can be ran concurrently by different slave server threads, you need to account for that in cases with shared module data and the like. To do that, the ModuleCache class provides two methods: _acquire() and _release(). These methods acquire and release module-wide mutex. If the method handler generates an exception, the lock will be released automatically.

Usage example:

  public override function call(client: TestSlaveClient, type: String, params: Params): Dynamic
    {
      _acquire();
      var response = null;
      if (type == "test.call1")
        response = call1(client, params);
      else if (type == "test.call2")
        response = call2(client, params);

      _release();
      return response;
    }