Installation

Note

If you need to install Anthill Platform on Windows or Mac OS X for development purposes, see Development instead. This page describes installation steps on Linux (Debian 8 or 9) for production purposes.

Along with actual services, Anthill Platform consists of a lot of external components. Configuring them might be quite challenging, so using Puppet in recommended.

What will be installed

  1. Nginx as a reverse proxy and a load balancer
  2. MySQL 5.7 database for primary content storage
  3. Redis for fast key/value
  4. RabbitMQ for internal communication across services
  5. Supervisor to roll actual services
  6. Python 3.6 with bunch of packages
  7. Bunch of Debian packages themselves

Puppet can handle all of these dependencies for you. If you don’t know what Puppet is, please follow here. For mode detailed steps (if would be necessary) please see this article.

Installation Steps

1. Choose a domain name

In order you clients to reach your servers, a public domain name (like example.com) should be bound to machine’s IP address(ses). Simplest way would be to create A record for *.<domain>.com, for example *.example.com. That would make go any of subdomains requests to this machine (like foo.example.com/test or bar.example.com/test).

2. Install Debian

Setup Debian operating system on the target machine with SSH server running. Currently, Puppet configuration supports only Debian 8 and 9. This tutorial assumes that you have apt updated and working with root privileges.

Please pay attention to define correct hostname to the machine you’re installing Debian on. As described in previous step, having correct hostname might save your time.

Good practice example See

<region>-<number>-<environment>.example.com
region
ISO 3166 shortcut for the country the server is located in
number
Incremented number to discriminate several servers in same country
environment
production, dev, beta, etc, depending on the application

3. (Optional) Add SSH keys

Upload your public SSH key to target machine so login would go faster:

ssh-copy-id <username>@<hostname>

If you haven’t generated your keys yet, do ssh-keygen.

4. Install Puppet

First of all, add the puppet’s deb package to the apt:

cd ~ && wget http://apt.puppet.com/puppet5-release-stretch.deb
dpkg -i puppet5-release-stretch.deb
apt update
cd ~ && wget http://apt.puppet.com/puppet5-release-jessie.deb
dpkg -i puppet5-release-jessie.deb
apt update

Puppet is primarily made of two components: Puppet Server and Puppet Agent. Puppet Server used to hold configurations (like “we need database and nginx”). Puppet Agent actually applies these configurations (like “install database or nginx using Puppet Server configuration”).

Practically, a system have one Puppet Server node, and many Puppet Agent nodes, so once applied on the Server, all Agents will install those configurations on machines they running. A minimal setup is to have both Puppet Server and Puppet Agent on a same machine.

Hint

Depending on your current requirements, you can either have all of the services on a single node, or split them among multiple instances. On a multi-node environment you would need to install Puppet Master on some node, and just Puppet Agents on the rest of them.

apt -y install puppetserver

To install the Puppet Server on the Master node

apt -y install puppet-agent

To install the Puppet Agent on the Agent nodes

apt -y install puppetserver

To install both Puppet Server and Agent on the same node

5. Configure the Puppet Server

This step is required on the Master node only (and for single-node environment node too).

/etc/init.d/puppetserver start

Then make sure it’s running using this:

/etc/init.d/puppetserver status

If it’s running, run this to make sure puppet starts when the systems boots:

/opt/puppetlabs/bin/puppet resource service puppetserver ensure=running enable=true

6. Configure your environment

Fork this repository: https://github.com/anthill-platform/puppet-anthill-dev

The repository above has a minimal configuration required for a dev environment. It consists of a two main parts:

The environments/ folder

This folder contains all of your environments you need. For example, you may need two environments: dev for a development and early-testing of new features and production for actual production releases.

Every environment folder should have such structure:

environments/
    dev/
        manifests/
            init.pp
        modules/
            keys/
                anthill.pem
                anthill.pub
                * other keys *

File manifests/init.pp is the main configuration file for the environment. According to the Puppet language, it tells which service belongs to each node. Please see Puppet Configuration for details.

The submodule modules/keys is a special module for your private keys. Anthill Platform uses asymmetric cryptography to authenticate users. To do so, an encrypted private/public key pair should be generated (anthill.pem and anthill.pub from the example above).

Please see Authentication Keys for a simple instruction on how to generate your keys.

The modules/ folder

This folder contains all modules Puppet needs, including modules for Anthill Platform itself, and some external modules from open-source developers.

7. Deploy your Puppet Configuration repository onto the Master node

The configuration repository need to be placed at /etc/puppet/code folder:

cd /etc/puppetlabs
rm -rf code
git clone https://<your fork>.git code
cd /etc/puppetlabs/code
git submodule update --init --recursive

8. Configure the Puppet Agent on each Agent node

Once Puppet Server is configured, Puppet Agents can be used to install your environment on the actual machines.

If you’re installing the Puppet Agent on a different machine than Puppet Server, do this:

apt -y install puppet-agent

Once you have Puppet Agent package installed, the Puppet Agent will need to know where puppet Server is located, and what environment to work on:

nano /etc/puppetlabs/puppet/puppet.conf

Set these options:

[main]
environment = <environment>
server = <hostname>

The <hostname> option is the Puppet Server location. In a minimal installations, it’s a current machine hostname.

The <environment> option dictates what environment this Puppet Agent belongs to.

9. Fire the Puppet Agent

To install actual software, run the following command

/opt/puppetlabs/puppet/bin/puppet agent --test --certname=<domain name of the machine> --environment=<environment>

Note

If you’re trying to run Puppet Agent on a different machine from Puppet Server, the first run of the Agent might just return with Exiting; no certificate found and waitforcert is disabled error due to Agent certificate not being signed. To fix this, sign the Puppet Agent’s certificate on a Puppet Server node: /opt/puppetlabs/puppet/bin/puppet cert sign <domain name of the puppet agent's machine> and re-run.

Attention

If you’re experience the problem related to SSL, please see SSL: Regenerating all Certificates in a Puppet deployment.

From now on, if you need something changed, just commit these changes into your Puppet Configuration repository, pull it on place, and apply with /opt/puppetlabs/puppet/bin/puppet agent --test again like described above.

The actual installation will take a while (up to several hours), and if everything goes fine, you will have such things configured:

  • mysql-server 5.7 installed and configured
  • databases created for each service along with database accounts
  • nginx installed and configured to reverse-proxy each service at a different location
  • nginx vhost record is created for each service
  • rabbitmq installed and configured
  • redis installed and configured
  • supervisor installed and configured
  • each service is registered in supervisor as a program that can be turned on or off

10. Done

  • Reboot the system. From that point you should have a fully configured service running on one machine.

  • Open http://admin-<environment>.<external domain>/ in your browser.

    _images/admin_page.png
  • Login using username root and password anthill.


Authentication Keys

Anthill Platform uses Public-key cryptography to authenticate users. The idea is goes as follows:

  1. User authenticates himself in the system, giving credentials
  2. The short-lived access token is issued with Private key

To validate the access token, the Public key is used. The public key is indeed public and can be stored at any service. Unlike the public key, the private key is stored securely (using passphrase) on the Login service only. To do so, an encrypted private/public key pair should be generated.

Pick some strong passwords

The private key is a very sensitive piece of information, so it should encrypted with a strong password. Please generate a complex password, that will be used to encrypt the actual private key.

Edit the environments/<environment>/manifests/init.pp file and change this section:

class { anthill::keys:
  authentication_private_key_passphrase => "<password A>",
  authentication_public_key => "puppet:///modules/keys/anthill.pub",
  authentication_private_key => "puppet:///modules/keys/anthill.pem",
}

This class will take care on actual installation of these keys.

Generate the key pair

Then generate the actual keys:

cd <repository location>
cd environments/<environment>/modules/keys/files
openssl genrsa -des3 -out anthill.pem 2048

Warning

The key length depends on your situation, but at least 2048-bit key is recommended.

You will be asked for a password, copy/paste the password A here. Then extract the public key:

openssl rsa -in anthill.pem -outform PEM -pubout -out anthill.pub

Using the same password.

Push the keys into the git repository

git add anthill.pem
git add anthill.pub
git commit -m "Nothing to see here"
git push

Puppet Configuration

Note

This document provides documentation on Puppet classes and defines for ease use in order to deploy Anthill Platform on any Linux system. Please refer to Installation for the actual installation instructions.

All the examples below are related to init.pp document inside of your Puppet environment. Please refer to The environments/ folder for additional information about that file.

Components

The Puppet Configuration consists of a different components you may use and may not (that depends on your use case):

component Anthill Core

This code will install the core of the Anthill Platform, including required packages, setup necessary directories and will install ‘core’ python packages required for every service:

class { anthill:
  debug => true,
  external_domain_name => "example.com",
  internal_domain_name => "example.internal"
}

component PuppetDB

PuppetDB is required to run “exported resources” the Puppet language offers.

include anthill::puppetdb

This will allow the resources on one node (for example, internal IP of the node A) to appear on another node (for example, as an entry in /etc/hosts file on the node B, so node B would know how to communicate node A).

Note

This component is only required on the Puppet Master node, therefore, if you do not plan to deploy the Anthill Platform on the same machine with the Puppet Master, separate the PuppetDB instead:

node 'puppet.example.com' {
  include anthill::puppetdb
}

node 'therest.example.com' {
  class { anthill: }
}

component Authentication Keys

Please see Authentication Keys documentation for more information about this component.

To deploy both public and private keys to the node, use this:

class { anthill::keys:
  authentication_public_key => "puppet:///modules/keys/anthill.pub",
  authentication_private_key => "puppet:///modules/keys/anthill.pem",
  authentication_private_key_passphrase => "anthill"
}

To deploy just the public key to the node, use this instead:

class { anthill::keys:
  authentication_public_key => "puppet:///modules/keys/anthill.pub"
}

Note: The submodule of your environment modules/keys is required to hold the actual keys. See puppet-anthill-dev for the example.

component External Services

These Puppet classes will install, configure and setup the external services you would need

# reverse proxy to handle users requests
class { anthill::nginx: }

# primary storage database
class { anthill::mysql: }

# run, monitor and control of the Anthill services
class { anthill::supervisor: }

# messaging system
class { anthill::rabbitmq: }

# fast key/value storage (primarily for caching)
class { anthill::redis: }

component DNS Management

This will make sure you services cat reach each other out, in convenient way

class { anthill::dns: }

PuppetDB component is required for this feature to work.

component Commons Library

This will pull the source code of the Anthill Commons library, required by every Anthill Service.

class { anthill::common:
  repository_remote_url => "https://github.com/anthill-platform/anthill-common.git"
}

Aside from the source code, you wold also need to checkout a certain commit of the source code as a certain version of the library, so other services would be able to use the right version of the library:

anthill::common::version { "<version of the library>":
  source_commit => "<certain commit hash from the repository>"
}

For example,

anthill::common::version { "0.2":
  source_commit => "87d139808837db6bd5ec6e888b4e75ea2ce2be03"
}

component Anthill Services

Like the Anthill Commons library, you can checkout the source code of some service first:

class { anthill_<service name>:
  repository_remote_url => "repository url",
  default_version => "<default version>"
}

And then setup certain version of that service for user’s use:

anthill_<service name>::version { "<version of the service>":
  source_commit => "<certain commit hash from the repository>"
}

For example, the Discovery Service might be installed like so:

# pull the source code first
class { anthill_discovery:
  default_version => "0.2",
  repository_remote_url => "https://github.com/anthill-platform/anthill-discovery.git"
}

# and then checkout a certain version (0.2) of the service
anthill_discovery::version { "0.2":
  source_commit => "630b7526d1619c76150cd2107edf8d7a2b16bacd"
}

SSH Keys

If you would like to pull the source code through SSH (git@github.com:...), you would need to configure your SSH keys:

  1. Generate SSH key pair: ssh-keygen
  2. Upload your public key (id_rsa.pub) to your repository provider.
  3. Add your private key (id_rsa) to your puppet configuration (environment/<environment>/modules/keys/files/ssh.pem)
  4. Update anthill::keys class:
class { anthill::keys:
  ssh_private_key => "puppet:///modules/keys/ssh.pem"
}

Multiple Versions At The Same TIme

You can have multiple versions of services to be installed at the same time:

# pull the source code first
class { anthill_login:
  default_version => "0.2",
  repository_remote_url => "https://github.com/anthill-platform/anthill-login.git"
}

# version 0.2 (default)
anthill_discovery::version { "0.2":
  source_commit => "1020132daf294ec306db8a46425e8cb5e04e34f0"
}

# also checkout "0.3" version of the Anthill Commons library
anthill::common::version { "0.3":
  source_commit => "<some hash pointing to version 0.3 on the Anthill Commons>"
}

# checkout version 0.3
anthill_discovery::version { "0.3":
  source_commit => "<some hash pointing to version 0.3 on the commit history>"
}

The users will decide which version to use (or simply prefer to have the default one)

component VPN

Once you will have multiple nodes, you would need to install Virtual Private Network to have secure connection between them.

Simply add anthill::vpn with mode=server to the node that is going to be the Server:

# node Server
class { anthill::vpn:
  mode => 'server',
  vpn_tag => 'main',
  server_country => 'US',
  server_province => 'TX',
  server_city => 'Austin',
  server_organization => 'example.com',
  server_email => 'admin@example.com'
}

And anthill::vpn with mode=clinet to the nodes that are going to be the Clients:

# node ClientA
class { anthill::vpn:
  mode => 'client',
  vpn_tag => 'main',
  client_index => 0,
  client_server_fqdn => "vpn-dev.example.com"
}
# node ClientB
class { anthill::vpn:
  mode => 'client',
  vpn_tag => 'main',
  client_index => 1,
  client_server_fqdn => "vpn-dev.example.com"
}

Notes:

  1. PuppetDB component is required for this feature to work.
  2. You should ensure there is no two client nodes with same client_index
  3. To successfully setup the VPN connection you would need to run puppet agent several times: first, ont the client node, then on the server node, then on the client not again several times. These limitations are related to the way exported resources work.

component HTTPS

If you would like to serve your services through https:

  1. Generate the SSL Bundle file and the Private Key file for your domain (for example, example_com.ssl-bunlde and example_com.key)
  2. Add these files to your puppet configuration:
environments/
    <environment>/
        modules/
            keys/
                files/
                    example_com.key
                    example_com.ssl-bunlde
  1. Update your anthill::keys class like follows:
class { anthill::keys:
  https_keys_bundle_contents => "puppet:///modules/keys/example_com.ssl-bundle",
  https_keys_private_key_contents => "puppet:///modules/keys/example_com.key"
}
  1. Enable HTTPS, with anthill class:
class { anthill:
  enable_https => true,
  protocol => 'https'
}

component Monitoring

If you need to keep track on what’s happening on the services, you might want to enable monitoring:

# Make services to report stats
class { anthill:
  services_enable_monitoring => true
}

# Install necessary monitoring components
class { anthill::monitoring::grafana: }
class { anthill::monitoring::influxdb: }
class { anthill::monitoring::collectd: }
  • The InfluxDB component will store your monitoring time series;
  • The Collectd component will collect important statictics of a particular node its insalled on, and send them to InfluxDB;
  • The Grafana component will display all collected information from InfluxDB in beautiful charts, with alerts, if neccessary.
  • If anthill::services_enable_monitoring is enabled, the services themselves will also report some informations, like requests rate, etc.

These components are independent, and you can place them on difference machines, if you want to. In that case, you would need to define:

  • collectd::influxdb_location so Collectd would know where to send the stats;
  • influxdb::grafana_location so Grafana would know where to pull the stats from;
  • anthill::monitoring_location like with Collectd, so each service would know in which InfluxDB push the stats into

A good practive would be to place Grafana and InfluxDB on the same node so it would be dedicated to the monitoring only, and then put Collectd on another nodes (and enable anthill::services_enable_monitoring there).

Note: Due to dependency limitations, if you’re deploying all three components on a same machine, the grafana component should be mentioned first.

Examples

All Anthill Services onto a single machine:

Example 1 Show/Hide Code

node 'vm.anthillplatform.org' {

  include anthill::puppetdb

  class { anthill:
    debug => true
  }

  class { anthill::keys:
    authentication_public_key => "puppet:///modules/keys/anthill.pub",
    authentication_private_key => "puppet:///modules/keys/anthill.pem",
    authentication_private_key_passphrase => "anthill"
  }

  # core libraries/services
  class { anthill::nginx: }
  class { anthill::mysql: }
  class { anthill::supervisor: }
  class { anthill::rabbitmq: }
  class { anthill::redis: }

  # internal dns management
  class { anthill::dns: }

  # Anthill Commons library
  class { anthill::common: }

  # Anthill Services themselves
  class { anthill_admin: default_version => "0.2" }
  class { anthill_config: default_version => "0.2" }
  class { anthill_discovery: default_version => "0.2" }
  class { anthill_dlc: default_version => "0.2" }
  class { anthill_environment: default_version => "0.2" }
  class { anthill_event: default_version => "0.2" }
  class { anthill_exec: default_version => "0.2" }
  class { anthill_game_master: default_version => "0.2" }
  class { anthill_game_controller: default_version => "0.2" }
  class { anthill_leaderboard: default_version => "0.2" }
  class { anthill_login: default_version => "0.2" }
  class { anthill_message: default_version => "0.2" }
  class { anthill_profile: default_version => "0.2" }
  class { anthill_promo: default_version => "0.2" }
  class { anthill_report: default_version => "0.2" }
  class { anthill_social: default_version => "0.2" }
  class { anthill_static: default_version => "0.2" }
  class { anthill_store: default_version => "0.2" }

  # Anthill Commonts library versions
  anthill::common::version { "0.2": source_commit => "87d139808837db6bd5ec6e888b4e75ea2ce2be03" }

  # Anthill Services versions assigned to appropriate commits
  anthill_admin::version { "0.2": source_commit => "ccb5b47432d9b040212d940823b2da0cef8c5a03" }
  anthill_config::version { "0.2": source_commit => "def49544f7db7cf422e4b23f054f3ec713ac59c7" }
  anthill_discovery::version { "0.2": source_commit => "630b7526d1619c76150cd2107edf8d7a2b16bacd" }
  anthill_dlc::version { "0.2": source_commit => "95041ad4cfa037318704a01cefe640a52aa346e3" }
  anthill_environment::version { "0.2": source_commit => "773401a968317469c85e4f8efdf3068ce4c9dde8" }
  anthill_event::version { "0.2": source_commit => "cf99af35d5835e44f884ba82180154e20bdcad9a" }
  anthill_exec::version { "0.2": source_commit => "5510b1fb9fb81f318c2030549674c7c3d26be585" }
  anthill_game_master::version { "0.2": source_commit => "9acfe29d6bf9f59c2baa3d0438c4296a01f8dc89" }
  anthill_game_controller::version { "0.2": source_commit => "f1fa1f166e2e4a19bf00dee72137e282f46f4af0" }
  anthill_leaderboard::version { "0.2": source_commit => "339dacba3d47179c2e26f1c5e0622ad95d2aa5fb" }
  anthill_login::version { "0.2": source_commit => "1020132daf294ec306db8a46425e8cb5e04e34f0" }
  anthill_message::version { "0.2": source_commit => "0378351628d2ceffc9796b9a255a74181f1fb325" }
  anthill_profile::version { "0.2": source_commit => "c193846dd866f22efe0e6edaee17c5f1561cc838" }
  anthill_promo::version { "0.2": source_commit => "17dcb5493c09f06c9abfc602fc3344cbbe3e72e7" }
  anthill_report::version { "0.2": source_commit => "0cab78eaeefc7a8740c0f2f321724812a754cc1d" }
  anthill_social::version { "0.2": source_commit => "e1fc582396315bd764909990f009908de4fd7b46" }
  anthill_static::version { "0.2": source_commit => "14a2de5f2c3f44d02b9733a4a845a0e86dcbd709" }
  anthill_store::version { "0.2": source_commit => "9e6afd0fb8b4fd5e944765a8646177ce02561475" }

}

Game Controller Service On A Separate Machine From The Rest Services:

All Services Show/Hide Code

node 'vm.anthillplatform.org' {

  include anthill::puppetdb

  class { anthill:
    debug => true
  }

  class { anthill::keys:
    authentication_public_key => "puppet:///modules/keys/anthill.pub",
    authentication_private_key => "puppet:///modules/keys/anthill.pem",
    authentication_private_key_passphrase => "anthill"
  }

  class { anthill::vpn:
    mode => 'server',
    vpn_tag => 'main',
    server_country => 'US',
    server_province => 'TX',
    server_city => 'Austin',
    server_organization => 'anthillplatform.org',
    server_email => 'admin@anthillplatform.org'
  }

  # core libraries/services
  class { anthill::nginx: }
  class { anthill::mysql: }
  class { anthill::supervisor: }
  class { anthill::rabbitmq: }
  class { anthill::redis: }

  # internal dns management
  class { anthill::dns: }

  # Anthill Commons library
  class { anthill::common: }

  # Anthill Services themselves
  class { anthill_admin: default_version => "0.2" }
  class { anthill_config: default_version => "0.2" }
  class { anthill_discovery: default_version => "0.2" }
  class { anthill_dlc: default_version => "0.2" }
  class { anthill_environment: default_version => "0.2" }
  class { anthill_event: default_version => "0.2" }
  class { anthill_exec: default_version => "0.2" }
  class { anthill_game_master: default_version => "0.2" }
  class { anthill_leaderboard: default_version => "0.2" }
  class { anthill_login: default_version => "0.2" }
  class { anthill_message: default_version => "0.2" }
  class { anthill_profile: default_version => "0.2" }
  class { anthill_promo: default_version => "0.2" }
  class { anthill_report: default_version => "0.2" }
  class { anthill_social: default_version => "0.2" }
  class { anthill_static: default_version => "0.2" }
  class { anthill_store: default_version => "0.2" }

  # Anthill Commonts library versions
  anthill::common::version { "0.2": source_commit => "87d139808837db6bd5ec6e888b4e75ea2ce2be03" }

  # Anthill Services versions assigned to appropriate commits
  anthill_admin::version { "0.2": source_commit => "ccb5b47432d9b040212d940823b2da0cef8c5a03" }
  anthill_config::version { "0.2": source_commit => "def49544f7db7cf422e4b23f054f3ec713ac59c7" }
  anthill_discovery::version { "0.2": source_commit => "630b7526d1619c76150cd2107edf8d7a2b16bacd" }
  anthill_dlc::version { "0.2": source_commit => "95041ad4cfa037318704a01cefe640a52aa346e3" }
  anthill_environment::version { "0.2": source_commit => "773401a968317469c85e4f8efdf3068ce4c9dde8" }
  anthill_event::version { "0.2": source_commit => "cf99af35d5835e44f884ba82180154e20bdcad9a" }
  anthill_exec::version { "0.2": source_commit => "5510b1fb9fb81f318c2030549674c7c3d26be585" }
  anthill_game_master::version { "0.2": source_commit => "9acfe29d6bf9f59c2baa3d0438c4296a01f8dc89" }
  anthill_leaderboard::version { "0.2": source_commit => "339dacba3d47179c2e26f1c5e0622ad95d2aa5fb" }
  anthill_login::version { "0.2": source_commit => "1020132daf294ec306db8a46425e8cb5e04e34f0" }
  anthill_message::version { "0.2": source_commit => "0378351628d2ceffc9796b9a255a74181f1fb325" }
  anthill_profile::version { "0.2": source_commit => "c193846dd866f22efe0e6edaee17c5f1561cc838" }
  anthill_promo::version { "0.2": source_commit => "17dcb5493c09f06c9abfc602fc3344cbbe3e72e7" }
  anthill_report::version { "0.2": source_commit => "0cab78eaeefc7a8740c0f2f321724812a754cc1d" }
  anthill_social::version { "0.2": source_commit => "e1fc582396315bd764909990f009908de4fd7b46" }
  anthill_static::version { "0.2": source_commit => "14a2de5f2c3f44d02b9733a4a845a0e86dcbd709" }
  anthill_store::version { "0.2": source_commit => "9e6afd0fb8b4fd5e944765a8646177ce02561475" }

}

The Game Controller Node Code Show/Hide Code

node 'game-ctl-1-vm.anthillplatform.org' {

  class { anthill:
    debug => true
  }

  class { anthill::keys:
    authentication_public_key => "puppet:///modules/keys/anthill.pub"
  }

  class { anthill::vpn:
    mode => 'client',
    vpn_tag => 'main',
    client_index => 0,
    client_server_fqdn => "vpn-vm.anthillplatform.org"
  }

  # core libraries/services
  class { anthill::nginx: }
  class { anthill::supervisor: }
  class { anthill::redis: }

  # internal dns management
  class { anthill::dns: }

  # Anthill Commons library
  class { anthill::common: }

  class { anthill_game_controller:
    default_version => "0.2",
    domain => "game-ctl-1-vm",
    gs_host => "game-ctl-1-vm.example.com",
    internal_broker => "amqp://anthill:anthill@rabbitmq-vm.example.internal:5672/${environment}",
    pubsub => "amqp://anthill:anthill@rabbitmq-vm.example.internal:5672/${environment}"
  }

  # Anthill Commonts library versions
  anthill::common::version { "0.2":
    source_commit => "87d139808837db6bd5ec6e888b4e75ea2ce2be03"
  }

  # Anthill Services versions assigned to appropriate commits
  anthill_game_controller::version { "0.2":
    source_commit => "f1fa1f166e2e4a19bf00dee72137e282f46f4af0"
  }
}

See also

Source Code for these Puppet modules

Services

Discovery Service

Some things never change. Most of them do: as the game grows, you often need to move servers around. This service allows to discover each service dynamically, at runtime.

Each service is represented by few things: service ID and service locations.

Multiple networks

Not only players use discovery service, but other services do. If some service needs some other service location, it uses discovery API to find out.

Yet often services need to use internal routes to communicate with other services for security reasons. That’s why discovery service has multiple networks defined: external and internal. The external network is used by end users, and available online from the web.

On the other hand, the internal network is only accessible from inside of the Anthill Platform. Often, services give more privileges for internal requests. Some requests can only be called from the inside. Even the internal location of a service can be discovered only from the inside.

Each service has a list of IP addresses, request from who is considered to be internal, usually it’s a local subnet, like 10.0.0.0/24.

Disclaimer

Warning

This service does not acts as a load balancer. Services in question need to be behind a load balancer themselves, for example behind nginx.

REST API

Discover a service

Returns the service’s external location.

← Request
GET /service/<service-id>
Argument Description
<service-id> ID of the required service
→ Response

In case of success, a complete URL of the service is returned:

https://login-dev.anthillplatfrom.org

This URL should be used as a base. For example, if service provides api called auth, the request would be:

POST https://login-dev.anthillplatfrom.org/auth
Response Description
200 OK Everything went OK, service location follows.
404 Not Found No such Service found

Discover multiple services

Returns a location for multiple services at a same time. Same as request above, but for multiple services.

← Request
GET /services/<services>
Argument Description
<services> Comma-separated list of service ID’s to return location about
→ Response

In case of success, a JSON object with complete URLs of the services is returned:

{
    "login": "https://login-dev.anthillplatfrom.org/v0.1",
    "profile": "https://profile-dev.anthillplatfrom.org/v0.1"
}

These URLs should be used as a base, same as in request above.

Response Description
200 OK Everything went OK, locations of services follows.
404 Not Found One of the requested services cannot be found.

Discover service’s location for a network

Returns the service’s location for a give network.

Only available from the ``internal`` network.

← Request
GET /service/<service-id>/<network>
Argument Description
<service-id> ID of the required service
<network> A network to return location from. For example, internal.
→ Response

Please note that internal locations are not accessable from the outside.

Response Description
200 OK Everything went OK, service location follows.
404 Not Found No such Service found

Discover multiple services, for a network

Returns a location for multiple services at a same time, for a given network. Same as request above, but for multiple services.

← Request
GET /services/<services>/<network>
Argument Description
<services> Comma-separated list of service ID’s to return location about
<network> A network to return locations from. For example, internal.
→ Response

In case of success, a JSON object with complete URLs of the services is returned. Please note that internal locations are not accessible from the outside.

Environment Service

When the game starts, it should know where the actual services located, to communicate with. That’s why each game has a well-known address of the Environment Service hardcoded in it. So other services can move without side effects.

This service solves two problems:

  1. There should be one well-known location to start communication from;
  2. Production environment should be divided from development with no side effects

See also

Source Code

A Sandbox

While the game is being developed, usually it runs on special development environment to ensure the real players are not affected. And eventually, should be switched to production. However, usually the “environment switch” would require a new game build with new environment information. Yet every new build should be tested before shipping … in development environment.

That’s why the game hardcodes only these things:

  1. Environment Location (e.g. https://environment.example.com)
  2. Application Name (e.g. thegame) and Application Version (e.g. 1.0)

At the end of the day, the Environment Service points client application to the right Discovery Service location (based on the environment).

Application Name

Application Name is an unique ID of the game to identify the game in question. Used practically on every service.

Application Version

Application Version is designed to separate different versions of the application across different environments:

_images/environment_example.png

In the example above, certain versions of the application will be completely divided from other ones. That’s where the service kicks in: the actual environment is based on the version information, server side.

Note

Environments or Game Versions could be configured at the admin-tool.

API Versions

Games tend to be outdated once in a while, yet the online services may progress constantly. This service allows to bind specific version of game to a specific version of API. Once new version of the the API released, older versions of games may still use old API.

Customization

Each environment could have custom variables defined, completely game-specific.

For example an ID of some ad provider, or analytics account. Or URL of the website to open when player hits “help topics”.

REST API

Get the Environment information

Returns the environment information based on the application version. Please note, the environment service location, along with game name and version should be hardcoded inside the game.

← Request
GET /<game-name>/<game-version>
Argument Description
<game-name> Name of the current application, see Application Name
<game-version> Current version of the application, see Application Version
→ Response

In case of success, a JSON object with environment information returned:

{
    "discovery": "https://dicovery-test.example.com",
    "<custom attribute>": "<custom attribute value defined for the environment>"
}

Exec Service

Every multiplayer project needs to run the game code on the backend side. Game Master Service perfectly does that task. But sometimes you don’t need that kind of complicity and just to want a simple script that players can connect to and do things here and there. In Javascript!

The service uses Git to host your code on, so you can setup a repository, put your javascript there, configure it on the service, and you’re all set.

See also

Source Code

Javascript support

EcmaScript 8 is supported.

Hello, world

function hello_world(args) {
    return "Hello, world!";
}

hello_world.allow_call = true;

As simple at it looks like. Then you can call it:

POST /call/test/1.0/hello_world
> Hello, world!

Note

Only the functions that defined allow_call = true can be called by the client, thus declaring some functios “API ready”, others private.

Sessions

Calling simple function is not always enough. Often the state between the calls has to be saved, wrapping the whole call sequence in a “session”. That’s what the Sessions are for.

function SessionHelloWorld(args)
{
    // this function is called upon session initialization
    this.name = "unknown";
}

SessionHelloWorld.allow_session = true;

SessionHelloWorld.prototype.my_name_is = function(args)
{
    this.name = args["name"];
    return "Gotcha!"
};

SessionHelloWorld.prototype.hello = function(args)
{
    return "Hello, " + this.name + "!";
};

Sessions are run by WebSockets, with JSON-RPC as transport protocol.

WebSocket /session/test/1.0/SessionHelloWorld

call("hello")
> Hello, unknown!
call("my_name_is", {"name": "world"})
> Gotcha!
call("hello")
> Hello, world!

Note

Only constructor functions (SessionHelloWorld from example above) that define allow_session = true will be allowed to open session on. Also, methods that start with underscore are not allowed to call:

SessionHelloWorld.prototype._internal_method = function(args)
{
    return "I am so internal";
};

API

Concepts

Plain Function definition

Plain functions can be defined as follows:

function function_name(args)
{
    return "I am the result";
}
Argument Description
args JSON dictionary of arguments, than might be passed to the function with call (se below)
Throwing an error

To notify the client that error is happened during function/method call, this construction must be called:

throw new Error(error_code, error_message)

// for example
throw new Error(404, "No such hero!")
Asynchronous Functions

EcmaScript 8 states that functions can be defined as asynchronous.

The API allows that with the async / await keywords:

async function test(args) {
    // wait for one second
    await sleep(1.0)
    // get the user profile
    var my_profile = await profile.get();
    return "Hello," + my_profile.name;
}

test.prototype.allow_call = true;
POST /call/test/1.0/test
# waits for one, second, gets the profile
> Hello, %username%

REST API

Call the plain function

Calls the given function server-side, returning its result.

The function must have allow_call = true to be defined, otherwise the 404 Not Found will be returned.

← Request
POST /call/<application_name>/<application_version>/<function_name>
Argument Description
application_name A name of the application to call the function about, see Application Name
application_version A version of the application to call the function about, see Application Version
function_name Name of the function to call

Access scope exec_func_call is required for this request.

→ Response

Function response is returned as is.

Response Description
200 OK Everything went OK, result follows.
404 Not Found No such function
custom errors Function may throw errors with custom codes for the client application to process

WebSocket API

Open a new Session

To open a new session, a new WebSocket connection on this location should be established:

WebSocket connection
WEBSOCKET /session/<application_name>/<application_version>/<class_name>
Argument Description
application_name A name of the application to call the function about, see Application Name
application_version A version of the application to call the function about, see Application Version
class_name Name of the Construction Function to be used on this session
args JSON dictionary of arguments that will be passed as args argument to the Construction Function

Access scope exec_func_call is required for this request.

Note

For the session to be successfully be opened, a Constructor Function with name class_name should exists, and should have allow_session = true to be defined:

function Test(args) {}
Test.allow_session = true;
Communication protocol

JSON-RPC is used as transport protocol to call the methods of the session, and get the responses.

Call a method

To call a method, a request named call should be sent.

Argument Description
method_name Name of method to be called.
arguments JSON dictionary of arguments that will be passed as args argument to method

Example of calling a method:

-> {"jsonrpc": "2.0", "method": "call", "params": {"method_name": "test", "arguments": {}}, "id": 1}
<- {"jsonrpc": "2.0", "result": "Testing!", "id": 1}
function Test(args) {}

Test.prototype.test = function(args) {
    return "Testing!";
};

Test.allow_session = true;

Note

Methods don’t need allow_call since all public method of the Constructor function are allowed to call. To make the method private, start its name with underscore.

“released” method

If the session needs to run some code once the connections is lost, a method released could be defined:

Test.prototype.released = function(args) {
    log("I am being released");
};

It will be called automatically upon session being closed. This method cannot be called manually, and should return no result, as it will be ignored. Also, this method allowed to be asynchronous.

Standard API

Along with standard Javascript functions, several are added by the API.

Note

Functions, marked star with are asynchronous. They return Promise that required to be await’ ed.

  • Error(code, message)

    See Throwing an error.

    Argument Description
    code The code indicating the problem.
    message Error description
  • log(message)

    To issue a log message, use log(message)

    Argument Description
    message Log message
  • star sleep(delay)

    Delays the execution for some time.

    Argument Description
    delay Time for delay in seconds
web

An object to access to the internet

  • star web.get(url, [headers])

    Downloads the file at the url and returns its contents.

    Argument Description
    url An URI to download the contents from
    headers (Optional). JSON object of HTTP headers to send
config

An object to access to the ../config

  • star config.get()

    Returns the Configuration Info for the game name / game version.

store

An object to access to the ../store

  • star store.get(name)

    Returns the configuration of the given Store

    Argument Description
    name Store name
  • star store.new_order(store, item, currency, amount, component)

    Places a new order in the Store. Returns the new order id.

    Argument Description
    store Store name
    item Item name
    currency Currency name
    amount Items amount
    component Component
  • star store.update_order(order_id)

    Updates the given order. No additional documentation so far.

    Argument Description
    order_id Order id to update
  • star store.update_orders()

    Updates all unfinished orders of the user. No additional documentation so far.

profile

An object to access to the Profile Service

  • star profile.get([path])

    Returns the user’s profile.

    Argument Description
    path (Optional). Path of the profile to get. If not defined, the whole profile is returned
  • star profile.update(profile, [path], [merge])

    Updates the user’s profile.

    Argument Description
    profile A JSON object for the profile to update
    path (Optional). Path of the profile to update. If not defined, the whole profile is updated
    merge (Optional). If true (default), the JSON objects of existing profile and updated one are mixed, otherwise the old object is replaces
  • star profile.query(query, [limit])

    Search for user profiles with JSON Database Query.

    Argument Description
    profile JSON Database Query
    limit (Optional) Limit number of resulting profiles, default is 1000.

    A result is a JSON object:

    {
        "results": {
            "1": {
                "profile": { ... profile object ... }
            },
            "12": {
                "profile": { ... profile object ... }
            },
            ...
        },
        "total_count": <total amount of profiles found>
    }
    
admin

An object for the administrative purposes. Can be accessed only from a server context, and clients have no ways to access it.

  • star admin.delete_accounts(accounts, [gamespace_only])

    Warning

    This actions is destructive and should be proceed with caution. Regular database backups are required before using this.

    Triggers PERMANENT deletion of certain accounts from all and every service.

    Argument Description
    accounts A JSON list of accounts to delete
    gamespace_only (Optional) Keep the account ID and credentials, so the user can still access data from other gamespaces. Default is true. If true, data will be deleted only from current gamespace. If false, ALL USER DATA FROM ALL GAMESPACES WILL BE PERMANENTLY DELETED.

Game Controller Service

Game Controller is a second part of Game Master Service. It is responsible for managing a single Host machine, while Game Master Service manages the whole matchmaking system.

Please refer the Game Master Service for a documentation for this service.

See also

Source Code

Game Master Service

When it comes to hosting a server for a multiplayer game, major problems appear:

  1. Game servers actually need to be put somewhere;
  2. The life cycle of the game server need to be maintained (as game servers may crash or become outdated);
  3. The players should be “matchmaked” either by region (to ensure low latency) or by custom set or/and rules (completely game-specific, for example, a user level, map, or game mode);
  4. The system overall must be scalable to add new servers into the pool when the concurrent players number explode;
  5. Binary files of the game server need to be deployed to all host machines (the more machines are, harder it is).

This service solves them all, the rest is completely up to the game.

Note

Game Master Service covers one part of the matchmaking services only. See Game Controller Service for another part.

See also

Source Code

Concepts

Game Service is actually made out of two services: a Master Service and a Controller Service.

Master Service
Master Service is a part of Game Service that holds the information about all rooms and balances Players across multiple Hosts. Also it heartbeats health status of each Host and routes Players to a healthy one if some Host dies.
Controller Service
Controller Service is a second part of Game Service. It runs on a certain Host and spawns Game Server instance on it when requested by Master Service. Also it heartbeats health status of each Game Server running on Host and stops it if it stops responding or crashes. See Controller Service documentation.
Room
Room represents a single game instance the player may join. Each room has a limit of maximum players on it and may contain custom setting to search upon.
Game Server
Game Server (GS) is a completely game-specific piece of software that runs server side, holds a single game instance and a may be addressed by according room.
Game Server Configuration

Game Server Configuration is a way to launch same piece of software (particularly Game Servers) in different “modes”, each of whom could serve totally different purpose.

For example, you can have a lobby configuration to gather players together before actual games, and main configuration for actual games. These two configurations have different options tuned, but ran by the same software.

Host
Host is actually a single hardware machine that can run multiple Game Servers on itself. Since only limited number of Game Servers can be spawned on each host due to hardware limitations, multiple hosts may be grouped by a Region.
Region
Region is a group of Hosts (even with a single one) that physically located on a same geographical region (or Data Center). Players may search rooms only on certain Region to ensure low latency.
Party

In certain cases, partying players together is required before (or without) the actual Game Server being instantiated:

  • You want to start the Game Server only when full room of people is matched;
  • Players want to discuss the future map / game mode before starting the game;
  • Players want to join a random game with their friends.

Overall Architecture

_images/game_service_architecture.png

Flow charts

Game Server Spawn Flow

In order to spawn a game server right, few steps is done.

1. Creating a room

The Player decides to create a room, or just joins to first room that matches required criteria. In case of second option, if no rooms found that match the criteria, a new one is created.

2. Resolving a Region

Using the IP address of the Player, the closest Region is resolved (the one with closest geo distance to the Player). If no geo information is known upon Player’s IP address, the default Region is chosen instead.

3. Resolving a Host

On the Region from previous step, the least loaded Host it resolved (using Memory / CPU information provided using heartbeats). If a healthy Host cannot be found (either all of them are overloaded or lost connection on heartbeats), and error is returned to the Player.

4. Spawn Request

One the Host is resolved, an HTTP spawn request is sent to that Host. At that point, any result returned is redirected to the Player.

Yet, the Player’s request is not responded until the Game Server instance is spawned and completely initialized. If any error occurs during spawning process, the Player is responded with that error.

5. Spawning Game Server instance

At this point, all communication is happens on the Host the spawning process is happen on.

On the Host machine, depending of which Game Server Configuration is being spawned, a new process is instantiated. That process is completely game-specific. Upon starting a process, multiple command line arguments are passed:

<binary> <zeromq endpoint> <ports> <other> ...

  • <binary> A binary file that is actually being instantiated.
  • <zeromq endpoint> A path to ZeroMQ Endpoint that Controller will communicate with a Game Server upon, depending on the operation system, could have format tcp://127.0.0.1:<port> (tcp) or ipc://<unix domain socket> (Unix Domain Socket)
  • <ports> A comma separated list of ports us made available for that particular Game Server instance. For example, 32765,32766. Game Server instance may listen on that ports as Player may connect to them.
  • <other> … Additional command line arguments, that may appear as defined in Additional Command Line Arguments section of the Game Server Configuration.

Warning

If the Host machine has the firewall enabled, A port’s pool range (by default 38000..40000) must be explicitly enabled.

Alongside with those arguments, depending of Game Server Configuration, a bunch of Environment variables can be defined:

Environment Variable Description
login_access_token A complete and working Access token instance of server-side use.
discovery_services A JSON Object with predefined key/value list of service locations for server-side use. See Discover Services section of the Game Server Configuration.
game_max_players Maximum players the Room can take (on which this Game Server is spawned upon).
room_settings A JSON Object with custom room settings as defined by player.
server_settings A JSON Object with Custom Server Configuration Settings (see according section of the Game Server Configuration).
Environment Variables Those defined in Environment Variables section of the Game Server Configuration.

6. Communication between Game Server and Controller Service

After being spawned, the Game Server instance is required to communicate with Controller Service using JSON-RPC protocol.

In short, a JSON-RPC protocol allows two nodes to send each other requests, end receive responses (in form of JSON objects):

Node A -> { request JSON object } -> Node B
Node A <- { response JSON object } <- Node B

JSON-RPC is a high-level protocol, so the ZeroMQ library is used to proceed transport-level communication:

  • The Game Server instance must create ØMQ Pair socket instance
  • Then, the Game Server instance must bind that socket with Inter-Process Communication transport of the ZeroMQ to listen on that Unix Domain Socket
  • On top of that, each ZeroMQ message should be a complete JSON-RPC object (either request or response).

Examples of opening a ZeroMQ channel for communication:

context = zmq.Context()
socket = context.socket(zmq.PAIR)
socket.connect("<path to zeromq endpoint>")
context = new ZContext();
socket = context.createSocket(ZMQ.PAIR);
socket.connect("<path to zeromq endpoint>")
m_context = std::shared_ptr<zmqpp::context>(new zmqpp::context());
zmqpp::socket_type type = zmqpp::socket_type::pair;
m_socket = std::shared_ptr<zmqpp::socket>(new zmqpp::socket(*m_context, type));
zmqpp::endpoint_t endpoint = "<path to zeromq endpoint>";
m_socket->set(zmqpp::socket_option::linger, 1);
m_socket->connect(endpoint);
7. Game Server initialization

Once the Game Server instance is completely initialized and ready to receive connections, the inited request should be sent to the Controller.

Example of the JSON-RPC Request object Show/Hide Code

{
    "jsonrpc": "2.0",
    "method": "inited",
    "params": {
        "settings": {
            "test": 5
        }
    },
    "id": 1
}
  • If the argument settings passed along the request, the Rooms settings is updated with that argument. For example, if player requested to create a room with {"map": "badone"} and the Game Server instance realized there is no such map, in can choose the other map instead, and pass {"map": "goodone"} as the settings argument to the inited call. That would lead to the Room have correct map setting no matter what setting the Player have passed.
  • The Controller will respond {"status": "OK"} to that request if everything went fine. If the error is returned instead, the Game Server instance should exit the process (and will be forced to at some point).

The Game Server instance has around 30 seconds (as defined in SPAWN_TIMEOUT) to send the inited request to the Controller that the Game Server is completely initialized.

Warning

If the Game Server would not manage to initialize within that time, the Game Server instance will be killed, and the error is returned to the Player.

8. The Game Server instance details

Once the inited request is called, the Master Service will return the Game Server instance details to the player (as described in step 4):

  • The host location of the Game Server instance
  • The ports made available for that particular Game Server instance
  • the Room Registration Key
  • the Room Settings (original or as Game Server instance modified them)

That information is need to be used by Player to perform a connection to the Game Server Instance.

9. The Game Server instance status

After complete initialization, Game Controller service with periodically check (or heartbeat) the Game Server instance status using status request.

Please note that this request comes from the Game Controller side, to the Game Server instance:

Controller Service -> { request 'status' } -> Game Server instance

The Game Server instance is required to respond to that request with {"status": "ok"} object. If other response is received, or no response received in certain time, the Game Server instance will be shot down as “hang”.

Join Room Flow

The Player is required to be joined into the Room in order to connect to the Game Server. The join process ensures that no extra player can join the Game Server due to concurrency issues (as hundreds of Players are constantly join to different Game Servers).

The flow goes like this:

  1. The Player successfully “joins” into the Room, gets room location and key in return.
  2. Using the location information, the Player connects to the Game Server using any protocol, that’s up to the game
  3. The Player sends the key to the Game Server. The Game Server checks the key, registering the Player in the Room at the same time. If the key has been rejected, the Player gets disconnected.

Also, the join process makes the Access token of the Player to be available on the Game Server, yet with no Access token being sent directly to the Game Server (for server-side use) as Access token is a sensitive piece of information and communication between the Game Server instance and the Player if often unencrypted.

1. Room Registration

After the join call, no matter if the Game Server instance have just spawned, or it’s an old room, a registration process on that room is performed. Registration process ensures that:

  • Player has a valid access token for a join
  • Player has not exceeded the join rate limits
  • There is enough space for that Player in the Room
  • Player has not been banned from Matchmaking

Due to concurrency, multiple Players can perform a join request on the same room at the same time, yet it may has only one free slot left. Is that case, only the first one will succeed.

As a response to a successful registration the Master Service will respond to the Player with some information:

  • The host location of the Game Server instance for that room
  • The ports made available for that particular Game Server instance
  • the Room registration Key
  • the Room Settings (original or as Game Server instance modified them)

the Room registration Key is important and acts as a proof that the Player has the right to join that room.

Note

At that point, the registration is temporary and will be released automatically within 30 seconds (as described in AUTO_REMOVE_TIME). To ensure the registration is permanent, the Player need to do the next steps.

2. Connecting

Then, the Player connects to the Game Sever instance, using the information in the previous step (such as a host location, or ports). The connection protocol (either UDP or TCP or even both) is completely up to the game.

After the successful connection, the Player sends the Room registration Key to the Game Server instance (again, the way it is sent is completely up to the game). If no registration Key is sent within some time, the Game Server instance must drop that connection.

Then, the Game Server instance should try to exchange the registration Key using a JSON-RPC request joined.

Arguments for that command are:

Argument Description
key The registration Key
extend_token, extend_scopes (Optional) See step 2a for more information.

Example of the JSON-RPC Request object

{
    "jsonrpc": "2.0",
    "method": "joined",
    "params": {
        "key": "<Player's registration key>",
        "extend_token": "<see step 2a>",
        "extend_scopes": "<see step 2a>"
    },
    "id": 2
}

If the request is successful, the Controller will respond:

{
    "access_token": "<Player's access token>",
    "account": "<Player's account id>",
    "info": { ... custom player's info },
    "scopes": ["<A list of Player's access token scopes>"]
}

That token then should be used by the Game Server Instance to communicate with any service in behalf ot the Player (for example, update the Player’s profile, or post a score to a leaderboard etc). The scopes field may be used to give the Player certain admin rights inside the game.

Also, a successful request will make room registration permanent (until the Player leaves the server).

2a. Token Extension

If both extend_token and extend_scopes are passed diring the joined request, the Access token of the player will be extended (see Extend Access Token) using extend_token as master token and extend_scopes as a list of scopes the Player’s Access token should be extended with.

Token extention is used to do strict actions server side in behalf of the Player while the Player itself cannot. For example,

  1. User Authenticates asking for profile scope. This scope allows only to read user profile, but not to write;
  2. The Game Server instance Authenticates itself with profile_write scope access (allows to modify the profile);
  3. The Game Server extends this token to the more powerful one, so server can write the profile in behalf of the Player;
  4. At the same time, user still have perfectly working access token, without such possibility;
  5. So player can only read Player’s profile, but the Game Server can also write it.
3. Disconnecting

Once player left the Game Server instance (intentionally or due to connection error), the Controller needs to be notified about it using the left request.

Arguments for that command are:

Argument Description
key The registration Key

Example of the JSON-RPC Request object

{
    "jsonrpc": "2.0",
    "method": "left",
    "params": {
        "key": "<Player's registration key>"
    },
    "id": 3
}

After a successful response, a slot it room is freed for future joins.

REST API

Matchmaking

Search Rooms

Returns a list of rooms.

← Request
GET /rooms/<application_name>/<application_version>/<game_server_name>
Argument Description
application_name Name of the game, see Application Name
application_version Version of the game, see Application Version
game_server_name Game Server Configuration name, see Concepts
settings Optional. A JSON Database Query, filtering room’s settings.
show_full Optional, default is true. To return rooms with maximum players capacity reached, or not.
my_region_only Optional, default is false. Return only rooms from my Region. See Concepts
region Optional. Return only rooms from a specific region. Contradicts with my_region_only

Access scope game is required for this request.

→ Response

In case of success, a JSON object with rooms is returned:

{
   "rooms": [<room>, <room>, <room>, ...]
}

A room object would be:

{
   "id": <room id>,
   "settings": <room settings>,
   "players": <number of players>,
   "max_players": <maximum number of players>,
   "game_name": <application_name for the room>,
   "game_version": <application_version for the room>,
   "location": <location>
}
Response Description
200 OK Everything went OK, rooms follow.
400 Bad Arguments Some arguments are missing or wrong.
404 Not Found No such application_name / application_version / game_server_name combination found
Join to a Room

“Joins” a player to specific Room by its ID.

← Request
POST /rooms/<application_name>/<room_id>/join
Argument Description
application_name Name of the game, see Application Name
room_id A Room ID to join into

Access scope game is required for this request.

→ Response

In case of success, a JSON object with room is returned:

{
   "id": <room id>,
   "settings": <room settings>,
   "players": <number of players>,
   "max_players": <maximum number of players>,
   "game_name": <application_name for the room>,
   "game_version": <application_version for the room>,

   "key": <key>,
   "location": <location>
}

At that point, the player has very little time window to actually to connect to the Game Server. The last two arguments in the example above should be used to proceed with connecting to the Game Server. See Join Room Flow for more information.

Response Description
200 OK Everything went OK, join info follows.
400 Bad Arguments Some arguments are missing or wrong.
404 Not Found No such room
423 Banned The player has been banned from participating in the Matchmaking. See X-Ban-Until, X-Ban-Id and X-Ban-Reason HTTP return headers for additional information.
Find a Room and Join into it

Performs a search for appropriate Room and does automatic join into it. Useful for “quick play” type of joins, where you don’t see any rooms before joining.

← Request
POST /join/<application_name>/<application_version>/<game_server_name>
Argument Description
application_name Name of the game, see Application Name
application_version Version of the game, see Application Version
game_server_name Game Server Configuration name, see Concepts
settings Optional. A JSON Database Query, filtering room’s settings.
auto_create Optional, default is true. Create a new Room, if there is no suitable one. This will instantiate a new Game Server instance. If false, and there is no suitable room, a 404 Not Found will be returned.
create_settings Optional. If auto_create is true, and new room is being created, these settings will be used for a new room.
my_region_only Optional, default is false. Join only in rooms from my Region. See Concepts
region Optional. Join only rooms from a specific region. Contradicts with my_region_only

Access scope game is required for this request.

→ Response

In case of success, a JSON object with room is returned:

{
   "id": <room id>,
   "settings": <room settings>,
   "players": <number of players>,
   "max_players": <maximum number of players>,
   "game_name": <application_name for the room>,
   "game_version": <application_version for the room>,

   "key": <key>,
   "location": <location>
}

At that point, the player has very little time window to actually to connect to the Game Server. The last two arguments in the example above should be used to proceed with connecting to the Game Server. See Join Room Flow for more information.

Response Description
200 OK Everything went OK, join info follows.
400 Bad Arguments Some arguments are missing or wrong.
404 Not Found No suitable rooms has been found
423 Banned The player has been banned from participating in the Matchmaking. See HTTP headers X-Ban-Until, X-Ban-Id and X-Ban-Reason returned for additional information.
Find a Room and Join into it for several players

Performs a search for appropriate Room and does automatic join into it, in behalf of several players. Usually done by authoritative party since game_multi scope is required. May be useful to perform “quick play” with friends, but somehow difficult due to the fact that you need authoritative party for it (for example, a Game Server instance itself).

← Request
POST /join/multi/<application_name>/<application_version>/<game_server_name>
Argument Description
application_name Name of the game, see Application Name
application_version Version of the game, see Application Version
game_server_name Game Server Configuration name, see Concepts
accounts A JSON list of accounts [1, 20, 444, 888] the search fill be performed for. The more accounts the more room in the destination room is required.
settings Optional. A JSON Database Query, filtering room’s settings.
auto_create Optional, default is true. Create a new Room, if there is no suitable one. This will instantiate a new Game Server instance. If false, and there is no suitable room, a 404 Not Found will be returned.
create_settings Optional. If auto_create is true, and new room is being created, these settings will be used for a new room.
my_region_only Optional, default is false. Join only in rooms from my Region. See Concepts. Please note, that “my” context is determined from the caller’s IP.
region Optional. Join only rooms from a specific region. Contradicts with my_region_only

Access scopes game and game_multi are required for this request.

→ Response

In case of success, a JSON object with room information is returned, along with keys for individual accounts:

{
   "id": <room id>,
   "settings": <room settings>,

   "slots": <slots>,
   "location": <location>
}

The slots object is made from keys as account ID’s being requested:

{
   <account-id>: {
      "slot": <slot-id>,
      "key": <key for account>
   },

   1: { "slot": <slot-id>, "key": <a key for account 1> },
   20: { ... },
   444: { ... },
   888: { ... }
}

The entity that has requested the join needs to pass the information to the appropriate members for them to to proceed with connecting to the Game Server. See Join Room Flow for more information.

Response Description
200 OK Everything went OK, join info follows.
400 Bad Arguments Some arguments are missing or wrong.
404 Not Found No suitable rooms has been found
Create Room

Spawns a new Room (and new Game Server instance) and does automatic join into it. Useful for cases when existing rooms should be ignored and there should be always a new one.

← Request
POST /create/<application_name>/<application_version>/<game_server_name>
Argument Description
application_name Name of the game, see Application Name
application_version Version of the game, see Application Version
game_server_name Game Server Configuration name, see Concepts
settings These settings will be used for a new room.

Warning

Creating new Room does not allow to pick a region, as it always automatically chosen by caller’s geo location.

Note

The caller gets automatically “joined” into the Room, meaning there is no way to create an empty room.

Access scope game is required for this request.

→ Response

In case of success, a JSON object with a new room info is returned:

{
   "id": <room id>,
   "settings": <room settings>,
   "players": <number of players>,
   "max_players": <maximum number of players>,
   "game_name": <application_name for the room>,
   "game_version": <application_version for the room>,

   "key": <key>,
   "location": <location>
}

At that point, the player has very little time window to actually to connect to the Game Server. The last two arguments in the example above should be used to proceed with connecting to the Game Server. See Join Room Flow for more information.

Response Description
200 OK Everything went OK, join info follows.
400 Bad Arguments Some arguments are missing or wrong.
404 Not Found No suitable rooms has been found
423 Banned The player has been banned from participating in the Matchmaking. See HTTP headers X-Ban-Until, X-Ban-Id and X-Ban-Reason returned for additional information.
Create Room for several players

Spawns a new Room (and new Game Server instance) and does automatic join for several players into it. Useful for cases when a player’s list is known beforehand. Usually done by authoritative party since game_multi scope is required.

← Request
POST /create/multi/<application_name>/<application_version>/<game_server_name>
Argument Description
application_name Name of the game, see Application Name
application_version Version of the game, see Application Version
game_server_name Game Server Configuration name, see Concepts
accounts A JSON list of accounts [1, 20, 444, 888] the creation fill be performed for.
settings These settings will be used for a new room.

Warning

Creating new Room does not allow to pick a region, as it always automatically chosen by caller’s geo location.

Note

The caller gets automatically “joined” into the Room, meaning there is no way to create an empty room.

Access scopes game and game_multi are required for this request.

→ Response

In case of success, a JSON object with room information is returned, along with keys for individual accounts:

{
   "id": <room id>,
   "settings": <room settings>,

   "slots": <slots>,
   "location": <location>
}

The slots object is made from keys as account ID’s being requested:

{
   <account-id>: {
      "slot": <slot-id>,
      "key": <key for account>
   },

   1: { "slot": <slot-id>, "key": <a key for account 1> },
   20: { ... },
   444: { ... },
   888: { ... }
}

The entity that has requested the creation of the Room needs to pass the information to the appropriate members for them to to proceed with connecting to the Game Server. See Join Room Flow for more information.

Response Description
200 OK Everything went OK, join info follows.
400 Bad Arguments Some arguments are missing or wrong.
404 Not Found No such Game Server Configuration found
Get Region List

Returns a current list of regions. Useful as way for the Player to pick the region to look into.

← Request
GET /regions

Access scope game is required for this request.

→ Response

In case of success, a JSON object with regions is returned:

{
   "regions": {
      "<region-id>": <region>,
      "<region-id>": <region>,
      "<region-id>": <region>,
      "<region-id>": <region>
   },
   "my_region": "<region-id>"
}

my_region is an automatically detected region of the caller.

A region object would be:

{
   "settings": <custom settings>,
   "location": {
      "x": <Longitude location>,
      "y": <Latitude geo location>
   }
}

settings are custom JSON object defined in admin-tool and can be used to display additional information (like title, icon, etc). location can be used to display that region on a map.

Get Player Game Status

Returns Player’s “playing” information. If a Player is playing, a record with information would be returned. Useful to display Player’s status, like “playing”, “offline”, etc.

← Request
GET /player/<account_id>
Argument Description
account_id Account ID of the Player in question

Access scope game is required for this request.

→ Response

In case of success, a JSON object with associated records is returned:

{
   "records": [<record>, <record>]
}

It is possible for a fraction of a time, for a Player, to have multiple records (for example, player is joining into some game while being in “lobby” server).

Each of the records would be:

{
   "id": <room id>,
   "settings": <room settings>,
   "players": <number of players>,
   "max_players": <maximum number of players>,
   "game_name": <application_name for the room>,
   "game_version": <application_version for the room>
}

Empty records yield means the Player in question is not playing anywhere.

Get Game Status for several players

Returns “playing” information for several players. If Players are playing, a record with information would be returned. Useful to display Player’s status in batch, when you know ID’s of each of them, for example, from a leaderboard.

← Request
GET /players
Argument Description
accounts a JSON list of Account ID’s [1, 2, 444, 888] of the Player’s in question

Access scope game is required for this request.

→ Response

In case of success, a JSON object with associated records for each player is returned:

{
   "records": {
      "1": [<record>, <record>],
      "2": [<record>],
      "444": [],
      "888": [<record>]
   }
}

It is possible for a fraction of a time, for a Player, to have multiple records (for example, player is joining into some game while being in “lobby” server).

Each of the records would be:

{
   "id": <room id>,
   "settings": <room settings>,
   "players": <number of players>,
   "max_players": <maximum number of players>,
   "game_name": <application_name for the room>,
   "game_version": <application_version for the room>
}

Empty records yield means the Player in question is not playing anywhere.

Banning System

Issue a ban

Bans a certain account from participating in Matchmaking (joining servers, etc).

Note

Once issued, the player would not be able to join a server with certain account. Upon first attempt of player’s join, player’s IP address would be also associated with that ban, so joining servers would not be possible from that IP from now on, regardless of the account in question.

← Request
POST /ban/issue
Argument Description
account Player’s account in question
reason Human-readable description of the ban
expires When the ban expires, a date in %Y-%m-%d %H:%M:%S format.

Access scope game_ban is required for this request.

→ Response

In case of success, a JSON object with ban id is returned:

{
   "id": <ban id>
}
Response Description
200 OK Everything went OK, ban information follows.
400 Bad Arguments Some arguments are missing or wrong.
406 Not Acceptable This user have already been banned
Get ban information

Returns existing ban’s information by its ID.

← Request
GET /ban/<ban-id>
Argument Description
ban-id Ban ID in question

Access scope game_ban is required for this request.

→ Response

In case of success, a JSON object with ban information is returned:

{
    "id": "<ban-id>",
    "reason": "<ban-reason>",
    "expires": "<ban-expire-date>",
    "account": "<account-id>",
    "ip": "<account's-ip>"
}
Response Description
200 OK Everything went OK, ban information follows.
404 Not Found Not such ban.
400 Bad Arguments Some arguments are missing or wrong.
Updated ban information

Updates existing ban by its ID.

← Request
POST /ban/<ban-id>
Argument Description
ban-id Ban ID in question
reason Human-readable description of the ban
expires When the ban expires, a date in %Y-%m-%d %H:%M:%S format.

Access scope game_ban is required for this request.

→ Response

In case of success, nothing is returned.

Response Description
200 OK Everything went OK, ban has been updated.
400 Bad Arguments Some arguments are missing or wrong.
Invalidate a ban

Invalidates existing ban by its ID.

← Request
DELETE /ban/<ban-id>
Argument Description
ban-id Ban ID in question

Access scope game_ban is required for this request.

→ Response

In case of success, nothing is returned.

Response Description
200 OK Everything went OK, ban has been invalidated.
400 Bad Arguments Some arguments are missing or wrong.

Parties

Create Party

Creates a fresh new Party and returns its information. Please note this request does not open Party Session.

← Request
POST /party/create/<application_name>/<application_version>/<game_server_name>
Argument Description
application_name Name of the game, see Application Name
application_version Version of the game, see Application Version
game_server_name Game Server Configuration name, see Concepts
party_settings See Party Properties
room_settings See Party Properties
max_members See Party Properties
region See Party Properties
auto_start See Party Properties
auto_close See Party Properties
close_callback See Party Properties

Access scope party_create is required for this request.

→ Response

In case of success, a JSON object with party information is returned:

{
   "party": {
      "id": "<party-id>",
      "num_members": <number-of-members>,
      "max_memvers": <meximum-numver-of-members>,
      "settings": { ... }
   }
}
Response Description
200 OK Everything went OK, room information follows.
400 Bad Arguments Some arguments are missing or wrong.
Get Party Information

Returns Party information.

← Request
GET /party/<party-id>
Argument Description
party-id Id of the party in question

Access scope party is required for this request.

→ Response

In case of success, a JSON object with party information is returned:

{
   "party": {
      "id": "<party-id>",
      "num_members": <number-of-members>,
      "max_memvers": <meximum-numver-of-members>,
      "settings": { ... }
   }
}
Response Description
200 OK Everything went OK, room information follows.
400 Bad Arguments Some arguments are missing or wrong.
Close Party

Closes an existing Party. The called does not have to be the creator of the party, but scope party_close is required.

← Request
DELETE /party/<party-id>
Argument Description
party-id Id of the party in question

Access scope party_close is required for this request.

→ Response

If the party had close_callback defined, a result of execution of such callback will be returned. Otherwise, and empty {} is returned.

Response Description
200 OK Everything went OK, room information follows.
400 Bad Arguments Some arguments are missing or wrong.
Create Party And Open Session

Creates a fresh new party and opens a Party Session on it.

Web Socket Request

Note

This request is a Web Socket request, meaning that HTTP session will be upgraded to a Web Socket session.

WEB SOCKET /party/create/<application_name>/<application_version>/<game_server_name>/session

Additional query artuments:

Query Argument Description
party_settings See Party Properties
room_settings See Party Properties
room_filters Optional, A JSON Database Query in Party Properties
max_members See Party Properties
region See Party Properties
auto_start See Party Properties
auto_close See Party Properties
close_callback See Party Properties
auto_join If true (default), the current memmber will be joined to a new session automatically.
member_profile If auto_join is true, this would be used to define member’s profile. See Member Properties

Access scope party_create is required for this request.

Connect To Existing Party

Connects to existing Party and opens a Party Session on it.

Web Socket Request

Note

This request is a Web Socket request, meaning that HTTP session will be upgraded to a Web Socket session.

WEB SOCKET /party/<party_id>/session
Argument Description
party_id Id of the party in question

Additional query artuments:

Query Argument Description
auto_join If true (default), the current memmber will be joined to a new session automatically.
member_profile If auto_join is true, this would be used to define member’s profile. See Member Properties
check_members If auto_join is true, this Profile Object may be used to theck ALL of the members for certain condition, or the automatic join will fail.

Access scope party is required for this request.

Find A Party And Open Session

Find a Party (possibly creates a new one) and opens a Party Session on it.

Web Socket Request

Note

This request is a Web Socket request, meaning that HTTP session will be upgraded to a Web Socket session.

WEB SOCKET /parties/<application_name>/<application_version>/<game_server_name>/session
Argument Description
application_name Name of the game, see Application Name
application_version Version of the game, see Application Version
game_server_name Game Server Configuration name, see Concepts

Additional query arguments:

Query Argument Description
party_filters A JSON Object filter to search the parties for. This argument is required. Simple {} means no filters.
auto_create To automatically create a new party if there’s no party that satisfies party_filters. Please note that if auto_create is true, access scope party_create is required.
member_profile Member’s profile. See Member Properties

If auto_create is true, these arguments are expected:

Query Argument Description
create_party_settings party_settings in Party Properties
create_room_settings room_settings in Party Properties
create_room_filters room_filters A JSON Database Query, in Party Properties
max_members See Party Properties
region See Party Properties
create_auto_start auto_start in Party Properties
create_auto_close auto_close in Party Properties
create_close_callback close_callback in Party Properties

The auto_join cannot be defined in this argumend as it will always do automatically join.

Access scope party is required for this request.

Party documentation

Party

Party is an group of members (potentially players) that can be created without actually instantiating a Game Server.

In certain cases, partying players together is even required before the actual Game Server being started up:

  • You want to start the Game Server only when full room of people is matched;
  • Players want to discuss the future map / game mode before starting the game;
  • Players want to join a random game with their friends.

If you would like to see the REST API for the parties, see Party REST API section below.

Party Flow

Each party has several “states”. The final goal of the party is to find or create a Game Server, then destroy itself.

  1. Party is created either by some player or by a service;
  2. Members open a Party Session upon it, using it the members can join into the party, send messages to eachother etc.
  3. Either by autometic trigger, or manually by some member, the party can be “started”, meaning an actuall Game Server is instantiated, with appropriate party settings;
  4. The memberss are relocated into the Game Server and become players by receving a notification from the Party Session;
  5. The party itself is destroyed.

Party Properties

Each party has a set of properties:

Name Description
party_settings Abstract JSON object of party-related settings, completely defined by the game. Parties can be found using these settings (see party_filters argument on some requests below)
room_settings Abstract JSON object of the actual room settings that will be applied to the Game Server once the party starts (if the room_filters below is defined, and an existing room has been found, this field is ignored)
room_filters (Optional) JSON Database Query, if defined, the party will search for appropriate room first upon party startup, instead of creating a new one.

Additional properties:

Name Description
max_members (Optional) Maximum number of party members, default is 8
auto_start (Optional) If true (default), the party will automatically start once the party gets full (reaching max_members number of members). If false, nothing will happen.
auto_close (Optional) If true (default), the party will be destroyed automatically once the last member leaves. If false, the empty party will remain.
region (Optional) A Region to start the Game Server on, default is picked automatically upon party creator’s IP.
close_callback (Optional) If defined, a callback function of the Exec Service with that name that will be called once the party is closed (see Server Code). Please note that this function should allow calling (allow_call = true)

Member Properties

Besides the actual party, each member in it can have his unique properties:

Name Description
member_profile A small JSON Object to represent the member. For example, that might be a desired color, or avatar URL of caching. This object is passed to the Game Server, so it can be used by it.
member_role A number, defining how much power the member has within the party`. This number is also passed to the Game Server.

Roles are as follows:

  • At least 500 role is required to start the party manually.
  • At least 1000 role is required to close the party manually.
  • The creator of the party gets 1000 role.
  • The regular member of the party gets 0 role.
  • As of currently, there is no way to change roles, so only the creator of the party can start is manually or force party closure.

Party Session

Party Session is a Web Socket session that allows members to have real-time communication within a party.

The actual communication is made within JSON-RPC 2.0 protocol. In short, a JSON-RPC protocol allows two nodes to send each other requests, end receive responses (in form of JSON objects):

Current Party Member -> { request JSON object } -> Game Service
Current Party Member  <- { response JSON object } <- Game Service
Party Session Initialization

The Party Session is is initialized in few steps:

  1. The user opens a Web Socket connection on the one of the possible endpoints
  2. The user then waits for the party Session Callback
  3. Once the callback is received, the Party Session is now considered initialized and the user is free to do the Session Methods

Warning

Please note that a Party Session is not considered successfully initialized until a party session callback had been received. Please see Session Callbacks.

Party Session Joining

The member can either join the party, or not. In both cases the connection can still remain. max_members only applies to joined members, so there can be more connected sessions to a party than a maximum members capacity.

Party members can be “not joined” into the party and still send and receive messages. That make the whole join functionality to be more like ready.

Session Methods

Example of the JSON-RPC Request Show/Hide Code

{
    "jsonrpc": "2.0",
    "method": "send_message",
    "params": {
        "payload": {
            "text": "hello"
        }
    },
    "id": 1
}

Response Object:

{
    "jsonrpc": "2.0",
    "result": "OK",
    "id": 1
}

send_message(payload) – to send any message object (defined with argument payload) to all other members of the session.

This could be used for chat or in-game requests etc

close_party(message) – to close the current party.

message argument defines any object that would be delivered to other party members upon closing the party.

Please note that party member needs to have at least 1000 role to close a party.

leave_party – to leave the current party.

As the connection still open, the member will still receive any in-party members, but if the party starts, the members who left the party won’t be transferred to a Game Server.

join_party(member_profile, check_members) – to join the party back.

This can be done automatically upon session creation.

member_profile – see Member Properties.

check_members – optional Profile Object that may be used to check ALL of the members for certain condition, or the join will fail.

Example Show

This complex function will ensure that no more 2 members in the party, that have field clan-name of their member_profile equal to TEST_CLAN, meaning there could be only two members total from clan TEST_CLAN.

{
    "members": {
        "@func": "<",
        "@cond": 2,
        "@value": {
            "@func": "num_child_where",
            "@test": "==",
            "@field": "clan-name",
            "@cond": "TEST_CLAN"
        }
    }
}

See Count Number Of Child Fields Which Pass Condition for more.

start_game(message) – to manually start the game.

message argument defines any object that would be delivered to other party members upon starting the game.

Please note that party member needs to have at least 500 role to start the game manually.

Session Callbacks

The party session may call some reqests methods too, meaning a Game Service initiates conversation.

Game Service -> { request JSON object } -> Current Party Member
Game Service <- { response JSON object } <- Current Party Member

party(party_info) – The party in question has been initialized

JSON-RPC Example Of “party” message Show

{
    "jsonrpc": "2.0",
    "method": "party",
    "params": {
        "party_info": {
            "party": {
                "id": "10",
                "num_members": 1,
                "max_members": 2,
                "settings": {}
            },
            "members": [
                {
                    "account": "10", "role": 1000, "profile": {}
                }
            ]
        }
    },
    "id": 1
}

party_info is a JSON object of following format:

{
    "party": {
        "id": "<party-id>",
        "num_members": <current number of party members>,
        "max_members": <maximum number of party members>,
        "settings": <current party settings>
    },
    "members": [<member>, <member>, <member>, ...]
}

Where each member would be:

{
    "account": "<account-id>",
    "role": <role>,
    "profile": <member-profile>
}

message(message_type, payload) – some message has been received by a party member

JSON-RPC Example Of “message” message Show

{
    "jsonrpc": "2.0",
    "method": "message",
    "params": {
        "message_type": "custom",
        "payload": {
            "text": "hello"
        }
    },
    "id": 1
}

message_type is a type of message, the payload depends on the message_type

Message Type Description Payload
player_joined A new member has joined the party. A JSON Object with fields: account – an account ID of the member, profile – a member_profile of the member
player_left A member has left the party. A JSON Object with fields: account – an account ID of the member, profile – a member_profile of the member
game_starting The game is about to start as a Game Server is being instantiated As described in start_game request
game_start_failed Somehow the Game Server instantiation has failed A JSON Object with fields: reason, code
game_started A game has successfully started, now the party is about to be closed. The client has now connect to the Game Server as described here A JSON Object with fields: id – room ID, slot – current player’s slot in this room, key – a room secret key, location – a location of the instantiated Game Server, settings – newly created room’s settings
custom A custom message, being sent by send_message As described in send_message
party_closed The party is being closed, expect the WebSocket communication to be closed as well. As described in close_party

Please refer to Party Session Initialization on how to open a Party Session.

Identifying A Party

A Game Server can detect if it’s being launched in a party context with environment variables.

  • party_id is such environment variable exists, then the Game Server is started in party context, and the variable contains id of the party. Please note this can be used for references only as the actual party may be destroyed at that point.

  • party_settings a party_settings from Party Properties.

  • party_members a JSON object with initial party members list in following format:

    {
       "<account-id>": {
          "profile": <member-profile>,
          "role": <member-role>
       }
    }
    

    Please note that this list is not exslusionary as players can connect from another parties later (see below)

Late connection

In some cases, party members can join the Game Server way after creation of it. For example, if room_filters is defined inside the party, the existing Game Server will be searched before creating a new one. In that case the party members may connect to existing Game Server that was spawned by another party (or without any party at all).

To deal with this, a Game Server can identify a party member by parsing the info object of the joined controller request response. The info object may contain these fields: party_id, party_profile, party_role, their definitions are described above.

See Game Controller Connecting Flow for the information about the joined request.

JSON-RPC Communication Flow

Controller Service JSON-RPC API

This section describes API calls that Game Server instance can make to the Controller Service.

Initialized Request

Called when the Game Server instance is completely initialized and ready to accept new players.

← Request

Method Name: inited. Arguments:

Argument Description
settings (Optional) Update room settings along with initialization

If the argument settings passed along the request, the rooms settings is updated with that argument. For example, if player requested to create a room with {"map": "badone"} and the Game Server instance realized there is no such map, in can choose the other map instead, and pass {"map": "goodone"} as the settings argument to the inited call. That would lead to the room have correct map setting no matter what setting the Player have passed.

→ Response

The Controller will respond {"status": "OK"} to that request if everything went fine. If the error is returned instead, the Game Server instance should exit the process (and will be forced to at some point).

Player Joined Request

Called once a Player connected to the Game Server instance. That call with exchange a Player’s registration Key for Player’s Access Token, at the same time making Player registration inside of the Room permanent.

← Request

Method Name: joined. Arguments:

Argument Description
key The registration Key
extend_token, extend_scopes (Optional) See step 2a for more information.

If both extend_token and extend_scopes are passed diring the joined request, the Access Token of the player will be extended using extend_token as master token and extend_scopes as a list of scopes the Player’s Access Token should be extended with.

Token extention is used to do strict actions server side in behalf of the Player while the Player itself cannot. For example,

  1. User Authenticates asking for profile scope. This scope allows only to read user profile, but not to write;
  2. The Game Server instance Authenticates itself with profile_write scope access (allows to modify the profile);
  3. The Game Server extends this token to the more powerful one, so server can write the profile in behalf of the Player;
  4. At the same time, user still have perfectly working access token, without such possibility;
  5. So player can only read Player’s profile, but the Game Server can also write it.
→ Response

If the request is successful, the Controller will respond:

{
    "access_token": <Player's access token>,
    "scopes": [<A list of Player's access token scopes>],
    "account": <Player's account>,
    "credential": <Player's credential>,
    "info": { ... additional info ... }
}
Player Left Request

Called once a Player disconnected from the Game Server instance. That call will remove Player’s registration from the Room allowing other Players to connect to the Room.

← Request

Method Name: left. Arguments:

Argument Description
key The registration Key
→ Response

If the request is successful, the Controller will respond with empty object {}

Update Room Settings Request

Called once Game Server instance decided to update room settings (for example, a map or mode have just changed)

← Request

Method Name: update_settings. Arguments:

Argument Description
settings New settings for the Room
→ Response

If the request is successful, the Controller will respond with empty object {}

Check Game Server Deployment Request

Called to check if the Game Server instance is still up to date (the game version may be disabled from spawning, or a new Game Server Deployment is available). Once the deployment is not valid anymore, the Game Server instance may decide to gracefully shut down at the end of

← Request

Method Name: check_deployment. No arguments.

→ Response

If the deployment is still up to date, the Controller will respond with empty object {}. Otherwise, an error will be returned, with the explanation.

Error Code Description
404 The game version is turned off or there is no such game version
410 Current deployment is outdated

d like to have a few keys with same name, put a new one under different gamespace.

Login Service

At the very moment of game experience, it is critical to ensure user’s progress is not lost, or even worse, stolen.

This service allows players to prove themselves, in a bunch of different ways.

See also

Source Code

Login process

Authentication process takes these steps:

  1. Player provides credentials and scopes of access;
  2. The system verifies if the credentials in question are valid. Each credential defines different way of verification;
  3. Account for the user is revealed. If there’s no account for such credential, a new one is created;
  4. System verifies if the account has the scopes requested;
  5. Access token for the account is generated;

Player Account

Login service have simple account system: each player have unique personal account number attached to him. If the user is logged in for a first time, a new account is created automatically.

The main purpose of Login Service is to verify that player X owns account N.

_images/login_accounts.png

Note

However, player may have multiple credentials that prove same account. For example, user could login using google or facebook, but both of them will lead to the same account.

Gamespace

As time goes, you may want to have multiple games hosted on the same environment.

Login service offers a special term for that purpose: gamespace. It allows to split the game experience from other games, like a namespace keyword in various programming languages. Yet they run on the same software server-side.

For example, if you would like to divide mobile player from PC once, you may put them in different gamespaces. But if you want to combine them, put them under the same gamespace.

Credentials

Login Service allows to Authenticate a player in many different ways.

Say a bowling club provides a free game for clients who’s birthday today. To prove the day of birth, a client may profide a passport, or driver’s license, or even a birth certificate.

These are different kinds of documents, but they prove same identity.

That’s where the credential system kick in: player may have multiple credentials that prove same account. For example, user could login using google or facebook, but both of them will lead to the same account.

anonymous

A special way to authenticate without asking a player for usernames and passwords. In order to authenticate, client application randomly generates unique username and password, and stores it in secure storage locally.

Is there’s no such username, a new one will be created.

These arguments are expected during authentication:

Argument Description
username A random username (for example, a UUID)
key A random password with considerable length

dev

Same as above, but cannot be created client side. Used for administrative credentials, tools etc.

These arguments are expected during authentication:

Argument Description
username A username
key A password, the stronger is better

google

A way to authenticate using a Google account.

To enable this feature, please do the following:

  1. Create the Web Application OAuth Client ID at the Google API Console;
  2. Add the application website (for example, http(s)://example.com/) into the Authorized redirect URIs list.
  3. Open the admin-tool and select the Login service;
  4. Select the section “Keys” and click “Add New Key”;
  5. Select google as Key Type, click Proceed
  6. Fill Client ID and Client Secret fields according to your credentials:
_images/google_key.png

After these steps, login using Google accounts will be available.

These arguments are expected during authentication:

Argument Description
code OAuth 2.0 authentication code.
redurect_uri OAuth 2.0 redirect location.

See also

OAuth 2.0

facebook

A way to authenticate using a Facebook account.

To enable this feature, please do the following:

  1. Create a New Application at the Facebook Developers section;
  2. Add the application website (for example, http(s)://example.com/) into the Valid OAuth redirect URIs section (under Facebook Login product);
  3. Open the admin-tool and select the Login service;
  4. Select the section “Keys” and click “Add New Key”;
  5. Select in facebook as a Key Type.
  6. Fill the App ID and App Secret respectively:
_images/facebook_key.png

After these steps, login using Facebook accounts will be available.

These arguments are expected during authentication:

Argument Description
code OAuth 2.0 authentication code.
redurect_uri OAuth 2.0 redirect location.

See also

OAuth 2.0

vk

A way to authenticate using a VKontakte (vk.com) account.

To enable this feature, please do the following:

  1. Create a New Application at the Developers section;
  2. Add the application website (for example, http(s)://example.com/) into the Authorized redirect URI;
  3. Open the admin-tool and select the Login service;
  4. Select the section “Keys” and click “Add New Key”;
  5. Type in vk as a Key Type;
  6. Fill Application ID and Secure Key respectively:
_images/vk_key.png

After these steps, login using VK accounts will be available.

These arguments are expected during authentication:

Argument Description
code OAuth 2.0 authentication code.
redurect_uri OAuth 2.0 redirect location.

See also

OAuth 2.0

gamecenter

A way to authenticate using a Apple’s Game Center. Please note, this way is only possible on iOS.

This way may look complicated, however it can be described in a few steps:

  1. Generate a signature for the player;
  2. At the return, you will have such: publicKeyUrl, signature, salt and timestamp;
  3. Pass them respectively as the expected arguments.

These arguments are expected during authentication:

Argument Description
public_key A publicKeyUrl returned from generation process
signature A generated signature
salt A generated salt
timestamp A generated timestamp
bundle_id Bundle ID of your Application
username A playerID retreived from iOS

steam

A way to authenticate using a Steam Account.

To enable this feature, a WebAPI key should be used:

  1. Create a WebAPI key;
  2. Open the admin-tool and select the Login service;
  3. Select the section “Keys” and click “Add New Key”;
  4. Select steam as a Key Type;
  5. Fill Steam Game ID and Encrypted App Ticket Key respectively:
_images/steam_key.png

After these steps, login using steam accounts will be available.

These arguments are expected during authentication:

Argument Description
ticket Session ticket acquired from Steam API
app_id Application ID (app_id.txt) to authenticate for

mailru

A way to authenticate using Mail.Ru Games Service (via @Mail.Ru Launcher).

To enable this feature, a Secret should be used:

  1. Create a Game Project;
  2. Open the admin-tool and select the Login service;
  3. Select the section “Keys” and click “Add New Key”;
  4. Select mailru as a Key Type;
  5. Fill Game ID and Secret respectively:
_images/mailru_key.png

After these steps, login using Mail.Ru Games accounts will be available.

These arguments are expected during authentication:

Argument Description
uid UID received from @Mail.Ru Launcher
hash OTP hash received from @Mail.Ru Launcher

token

A special way to authenticate, using existing token (for example, you would like to request more scopes, but don’t want to process a full authentication again)

These arguments are expected during authentication:

Hint

To authenticate with corresponding credential type, pass the appropriate credential type as credential field during authentication.

Scopes of Access

At some point, there should be a way to ensure the player A has the right to do something, but player B has not.

For example, administrative account can move mountains, while simple guest account can do only simple operations.

Access Scopes are used to ensure that. At authentication, player sends the complete list of access scopes he needs, then the system makes sure he owns them.

Access token

Instead of checking if user have a certain access right for each request, login service uses Json Web Tokens to ensure user’s identity.

Basically, it’s a JSON object with all information about the player:

  • Token’s UUID itself
  • Player’s account ID
  • Player’s credential
  • Scopes of Access
  • Issue and Expiration Date

So each service during request process does not have to lookup if the player have the access rights, but can lookup this object instead.

In order to validate if the Token was actually generated by Login service, a public-key cryptography method is used:

  1. An RSA public/private key pair is generated on Login service on service setup. The private key is kept secure with paraphrase protection on it. Public key, instead, is copied for each service.
  2. Once a Token is generated, it is signed using a private key only a Login service knows.
  3. Other services verify it using public key.

In addition, there’s a system that ensures the only one token is valid for account at the same time, to avoid using same account from multiple devices.

Warning

Treat access tokens as raw string at all times. The format of an access token can change at any time, so the game should make no attempt to parse it.

Anthill Keys

Login service allows to securely store private keys for external social services. For example,

  • A Google’s OAuth Secret
  • Facebook App ID and Secret
  • Steam app_id and WebAPI key

To manage these keys:

  1. Open the admin-tool;
  2. Select the Login service;
  3. Select the section “Keys”;

Other services can use these keys to communicate with external services. For example, a social service may use google key to fetch player’s friend list.

Please note that there can only be one key with same name, for a gamespace. If you would like to have a few keys with same name, put a new one under different gamespace.

REST API

OAuth 2.0

Some “external” credentials (facebook, google, vk etc) follow OAuth 2.0 protocol in order of the authorization to proceed. This authorization can be achieved with a simple flow:

  1. User has the corresponding social network’s “login page” shown, asking user consent for the authorization.
  2. After approval, the browser gets redirected back to the special redirect_uri location, with the special code argument.
  3. This redirect is caught and the code is extracted as an argument for the actual authorization in the login service.

In practice, each social service has different location of the “login page”, so login service encapsulates it with a convenient call. Open this URL in the application’s in-app browser:

http(s)://<login-service-location>/auth/<credential>?redirect_uri=<redirect_uri>&gamespace=<gamespace>
Argument Description
credential A credential type of the social service (for example, google)
redirect_uri Application’s main domain page, for example, http://example.com/. It’s important to have / at the end.
gamespace A gamespace name (alias) to authenticate in. See Authenticate call for more details.

Please note that redirect_uri value has to be added into the allowed “REDIRECT URIs” list in each social service’s settings page of your application.

  • This call will automatically redirect to the corresponding social service authorization form on which the user is asked for login and password.
  • After successful authorization, the browser is redirected to the redirect_uri page, according to the OAuth 2.0 standard.
  • This redirect will have a query argument code, and the application has to catch the redirect, extract the code, closing the browser.
  • Then code and redirect_uri has to be used during the last-step authentication call to the login service.
Admin Tool

You can log in into the admin-tool using these credentials, but the http(s)://<login-service-location>/auth/oauth2callback has to be added as a redirect_uri (see above).

Authenticate

Authenticates the user in the Anthill Platform

← Request
POST /auth
Argument Description
credential Credential type
scopes Comma-separates list of access scopes to request
gamespace A gamespace name (alias) to authenticate in

Optional arguments:

Argument Description Default value
should_have Comma-separated list of scopes the user should definitely acquire, or 403 Forbidden will be returned. Useful in cases when player is OK with not having some of scopes. *, everything requested should be retrurned.
info A JSON object of the additional info would be attached to account (for example, device ID) {}
attach_to Access token of the account to proceed the attach procedure. See Attach Credential for more information.  
unique Should the access token be unique (meaning no two tokens of the same name could exists). Setting to false would require a special access scope auth_non_unique. true
as A name for the token. Only one token of the same name could exist at the same time (if unique is true) def
full Return more information about the token returned (if form of a JSON object instead of just token) false
→ Response

In case of success, an access token is returned:

"token string"

Warning: token string format could be changed at any moment, so the client application should not rely on this response, and should threat it like a simple string.

If the argument full is set to true, a JSON object is returned instead:

{
    "token": "<token string>",
    "account": "<account ID>",
    "credential": "<credential>",
    "scopes": [<array of allowed scopes>]
}
Response Description
200 OK Everything went OK, service location follows.
404 Bad Arguments Some arguments are missing or wrong.
403 Forbidden Failed to acquire a token, either username/password is wrong, or access is denied.
409 Conflict A merge conflict is happened, a Conflict Resolve is required

Attach Credential

If you login with a credential for the first time, a fresh new account is created. However, sometimes it is not the case. For example, a player have already authenticated into credential anonymous:XX-XX-XX, so the account A is created.

anonymous:XX-XX-XX -> A

But if player also wants to login using facebook, he will end up with a different account.

anonymous:XX-XX-XX -> A
facebook:12345678  -> B

To avoid this, credential can be attached to a same account instead of creating new one.

anonymous:XX-XX-XX -> A
facebook:12345678  -> A

Simplest way to do so is to pass attach_to argument while doing Authenticate call:

  1. Authenticate, using first credential (say anonymous:XX-XX-XX), account A will be used (or created);
  2. Authenticate, using second credential (say facebook:12345678). While doing that, pass the access token from a previous authentication, as attach_to argument;
  3. The system will try to attach credential facebook:12345678 to account A as long as credential is not used elsewhere;

Account Conflict

In case credential facebook:12345678 has already attached to a different account, or already has multiple accounts attached, a conflict will occur:

{
    "result_id": "<Conflict Reason>",
    // other useful information about the conlict
}

In response to conflict, server may return resolve_token to Resolve Conflict. Possible conflict reasons:

  • merge_required

    Credential, you are trying to attach is already attached to a different account. Possible account solutions along with their profiles (if exist) are described in field accounts.

    {
        "result_id": "merge_required",
        "resolve_token": "<a resolve token>",
        "accounts": {
            "local": {
                "account": <account N>,
                "credential": <credentian N>,
                "profile": { a possible profile JSON object }
            },
            "remote": {
                "account": <account N>,
                "credential": <credentian N>,
                "profile": { a possible profile JSON object }
            }
        }
    }
    

    Profile fields may be used to describe to the Player information about the accounts (level reached, currency have, avatar etc). On of the solutions should be used as resolve_with when dealing with Resolve Conflict.

  • multiple_accounts_attached

    Credential, you are trying to attach is already attached to a multiple accounts. One of them is required to be detached first. Please note that this may happen during normal authentication.

    {
        "result_id": "multiple_accounts_attached",
        "resolve_token": "<a resolve token>",
        "accounts": [
            {
                "account": <account number>,
                "profile": { a possible profile JSON object }
            },
            {
                "account": <account number>,
                "profile": { a possible profile JSON object }
            },
            ...
        ]
    }
    

    On of the account numbers should be used as resolve_with when dealing with Resolve Conflict.

Resolve Conflict

I case of conflict, a Resolve Conflict method may be used to solve the conflict situation.

← Request
POST /resolve
Argument Description
access_token A Resolve Token, retrieved when the conflict occurred.
resolve_method A way how to resolve this conflict. Should be exactly the Conflict Reason server gave. For example, merge_required or multiple_accounts_attached.
scopes Access scopes to be acquired like in Authenticate procedure.
resolve_with A way to resolve this conflict. Varies for different Conflict Reasons

Optional arguments:

Argument Description
attach_to Access Token to the account player originally was going to attach to. Only applicable if conflict happened during Attach Credential procedure.
full Return more information about the token returned (if form of a JSON object instead of just token)
→ Response

In case of success, an access token is returned:

"token string"

Warning: token string format could be changed at any moment, so the client application should not rely on this response, and should threat it like a simple string.

If the argument full is set to true, a JSON object is returned instead:

{
    "token": "<token string>",
    "account": "<account ID>",
    "credential": "<credential>",
    "scopes": [<array of allowed scopes>]
}

Validate Access Token

Checks if the given access token is valid

← Request
GET /validate
Argument Description
access_token Access token to validate.
→ Response

This request has no response body.

Response Description
200 OK Access token is valid.
404 Bad Arguments Some arguments are missing or wrong.
403 Forbidden Token is not valid.

Extend Access Token

Allows to to give additional Access Scopes to the existing access token (account of which did not have such scopes originally), using other, more powerful account.

  1. Say there’s account A with scopes S1 and S2 allowed.
  2. There’s account B with scope S10 that A has no access to.
  3. A authenticates, requesting scope S1.
  4. B authenticates, requesting scope S10.
  5. Access token of B extends access token A using scope he had S10.
  6. A working access token for A with scopes S1 and S10 is now available.

This flow is primarily used for trusted game servers to do strict actions server side. For example,

  1. User Authenticates asking for profile scope. This scope allows only to read user profile, but not to write;
  2. The Game Server Authenticates itself using dev credential with profile_write scope;
  3. User give the access token to the server is a secure way;
  4. The Game Server extends this token to the more powerful one, so server can write the profile in behalf of the user;
  5. At the same time, user still have perfectly working access token, without such possibility;
← Request
POST /extend
Argument Description
access_token Access token to extend (the one to be improved)
extend Access token to extend with (the one that have required scopes)
scopes Scopes to improve access_token with. Default * – to use all scopes the scope extend have. Otherwise, a comma-separate list of Access Scopes.
→ Response

A JSON object with a new token and it’s expiration date.

{
    "token": "<improved access token>",
    "scopes": [<scopes of improved token>],
    "account": "<account of the original access_token>",
    "expires_in": <time, in seconds, for the new token to expire>
}

Please note that the original access token is still valid. Also, tokens have to be in a same gamespace.

Profile Service

A service designed to store player profiles – special objects to account progress of the Player inside of the game, and to sync it remotely from anywhere.

See also

Source Code

User Profile

User Profile is a JSON Profile Object that applied to store Player’s data on the server backend. It can be altered concurrently, or as a whole JSON object, depending on your requirements.

Transaction Instead of Dump

A good practice to manage User Profile would be to update it in “transactional style” instead of dumping as is.

For example, say you have this profile object:

{
    "a": 10,
    "b": 15,
    "c": 100
}

Once you have tracked the field c has increased it’s value by 20 and you would like to store that, instead of dumping the whole object, use the Profile Object magic:

{
    "c": {
        "@func": "++",
        "@value": 20
    }
}

The other fields will be left unchanged, and the field c will be incremented by 20. The result of the increment will be returned, so the server that made the request can update the changed accordingly.

That way, if several parties might alter the profile at the same time, you can ensure that no changes can be lost ever.

Profile Access

Profile service has a system to define access levels to some fields of User Profile in the admin-tool, per Gamespace.

Note

  1. The access levels might be applied only to the root fields of the User Profile.
  2. Same field might be listed in multiple access lists, thus getting mixed results.

Public Fields

Fields, listed in this list, will have public access, meaning other users would be able to read these fields. Fields, not listed in this list, will not be accessible to other users.

Warning

Other users would not be able to receive certain fields from a User Profile, unless they are listed here.

Private Fields

Fields, listed in this list will be listed and updated only with profile_private Scope, meaning there might be fields that only the server has access to.

Protected Fields

Fields, listed in this list can be listed to the player only, but even the player cannot change it. Like with Private Fields, the profile_private Scope is required to change this field.

REST API

Get User Profile

Returns a User Profile for an account.

← Request
GET /profile/<account> [/<path>]
Argument Description
account Account number of the user’s profile. A special value me refers to current user.
path Optionally, adding additional directories to the request would make the service to return only part of the User Profile, by path.

Please note that profile scope is required for this request.

→ Response

In case of success, a User Profile is returned:

{
    <profile object>
}
Response Code Description
200 OK Everything went OK, User Profile follows.
404 Not found This user has yet to upload a user profile, and there no such a user profile.

Get User Profiles Of Multiple Accounts

Returns a User Profile objects for several accounts. Useful when you have a list of users, and need User Profiles for them (for example, a player list).

← Request
GET /profiles
Argument Description
accounts A JSON list of account numbers of the user’s profiles (["1", "20", "444"])

Note

Only Public Fields will be returned.

Please note that profile scope is required for this request.

→ Response

In case of success, an object of User Profiles is returned. Those accounts who miss a profile, will return an empty object {}.

{
    "<account-id>": { profile object },
    "<account-id>": { profile object },
    "<account-id>": { profile object },

    ...
}
Response Code Description
200 OK Everything went OK, User Profiles follow.

Update User Profile

Updates a User Profile for an account.

← Request
POST /profile/<account> [/<path>]
Argument Description
account Account number of the user’s profile. A special value me refers to current user.
data A Profile Object with updates to the User Profile
merge If true, the fields of the Profile Object will be merged with old values, otherwise the values that are not mentioned in updates will be deleted as well.
path Optionally, adding additional directories to the request would make the service to update only part of the User Profile, by path.

Please note that profile_write scope is required for this request.

→ Response

In case of success, a User Profile is updated, and updated value is returned:

{
    <profile object>
}
Response Code Description
200 OK Everything went OK, User Profile follows.
400 Profile Error Some of the Functions might have failed
404 Not found This user has yet to upload a user profile, and there no such a user profile.

Report Service

A simple service that allows players to post reports to the service. Later that reports can be analyzed and used in admin-tool.

See also

Source Code

REST API

Upload a Report

Uploads a report to the service. Once done, can be viewed in admin-tool.

← Request
POST /upload/<application-name>/<application-version>
Argument Description
application-name See Application Name
application-version See Application Version
message A simple title that describes report as whole. That could be an exception type, for a crash report, or user name, for user reports.
category A category for report, can be used to filter reports by category.
format Can be one of these: binary, text, json.
info (Optional) A JSON object with additional information about the report, for example, username, OS version etc, can be used to filter reports by these.

Please note that report_upload scope is required for this request.

→ Response

In case of success, a report ID is returned:

{
    "id": 12345
}
Response Code Description
200 OK Everything went OK, report ID follows.
429 Too Many Requests A rate limit for the reports upload has been exceeded for this user.

Development

Note

This document describes steps how to setup minimal environment for development or testing purposes.

Windows

To setup minimal development environment on Windows, you will need:

  1. Install PyCharm (Community Edition is fine)
  2. Clone the dev repository:
git clone -b dev https://github.com/anthill-platform/anthill-dev.git
cd anthill-dev
git submodule update --init --recursive
  1. Run installation script
cd dev\win
setup.bat

This will install Scoop, and Scoop will install all of the required components.

  1. Open cloned repo in PyCharm.

  2. Setup the Project Interpreter.

    To do that, go to Preferences, select Project Interpreter, select Show All from the dropdown, hit “+”, select Add Local, and feed it with C:\Anthill\venv\Scripts\python.exe.

  3. Select all run configuration, hit Run

    _images/compound_configuration.png
  4. Open http://localhost:9500 in your browser

  5. Press “Proceed”, login using username root and password anthill. You should see something like this:

Mac OS X

To setup minimal development environment on Mac Os X, you will need:

  1. Install PyCharm (Community Edition is fine)
  2. Clone the dev repository:
git clone -b dev https://github.com/anthill-platform/anthill-dev.git
cd anthill-dev
git submodule update --init --recursive
  1. Run installation script
cd dev/osx
./setup.sh

This will install Homebrew, and Homebrew will install all of the required components.

  1. Open cloned repo in PyCharm.

  2. Setup the Project Interpreter.

    To do that, go to Preferences, select Project Interpreter, select Show All from the dropdown, hit “+”, select Add Local, and feed it with /usr/local/anthill/venv/bin/python.

  3. Select all run configuration, hit Run

    _images/compound_configuration.png
  4. Open http://localhost:9500 in your browser

  5. Press “Proceed”, login using username root and password anthill. You should see something like this:

    _images/admin_page.png

Linux

There is no development environment for Linux as of yet. See Installation (on Linux) for more information.

Runtimes

This document refers to various Runtimes (or SDK’s) that handle the technical issues under the hood, and provide convenient high-level API for solving design issues.

Despite being written in different languages, they follow same API design, so this section will describe Runtimes in language-agnostic way, with language-specific examples on each particular call.

Installation

This document describes the actual installation of the Anthill Runtime library in your project, if you have installed it and would like to setup the classes, see Setup.

Java

Java Runtime is build with Maven, so installation is pretty straightforward.

Gradle-based project

  1. Add the JitPack repository to your build.gradle file.
allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}
  1. Add the dependency.
dependencies {
   compile 'com.github.anthill-platform:anthill-runtime-java:0.1.4'
}

Maven-based project

  1. Add the JitPack repository to your pom.xml file.
<repositories>
   <repository>
      <id>jitpack.io</id>
      <url>https://jitpack.io</url>
   </repository>
</repositories>
  1. Add the dependency:
<dependency>
    <groupId>com.github.anthill-platform</groupId>
    <artifactId>anthill-runtime-java</artifactId>
    <version>0.1.4</version>
</dependency>

C++

CMake-based project

C++ Runtime uses CMake. If you are not familiar with CMake and have no intentions to use it in your project, see Prebuilt library binaries.

Unfortunately, CMake cannot download dependencies automatically for you. You would have to install several dependencies.

Ideally, you can put all them in same directory (for example, externals) as Git submodules.

Library Recommended directory name Repository URL
Anthill C++ Runtime itself anthill-runtime-cpp https://github.com/anthill-platform/anthill-runtime-cpp.git
cURL curl https://github.com/curl/curl.git
curlcpp curlcpp https://github.com/JosephP91/curlcpp.git
jsoncpp jsoncpp https://github.com/open-source-parsers/jsoncpp.git
libuv libuv https://github.com/jen20/libuv-cmake.git
uWebSockets uWebSockets https://github.com/anthill-utils/uWebSockets.git

Other dependencies that you might need to install: openssl, zlib.

At the end of the day you might have directory structure like this:

externals/
    anthill-runtime-cpp/
        ...
    curl/
        ...
    curlcpp/
        ...
    jsoncpp/
        ...
    libuv/
        ...
    uWebSockets/
        ...

Note

Some of the submodules have submodules themselves, so this oneliner is required:

git submodule update --init --recursive

Setup a CMakeFiles.txt for your project:

cmake_minimum_required(VERSION 2.8.11)

set(CURL_MIN_VERSION "7.28.0")

set(LIBUV_INCLUDE_DIR "${PROJECT_BINARY_DIR}/external/libuv/libuv/include")
set(CURLCPP_INCLUDE_DIR "${PROJECT_BINARY_DIR}/external/curlcpp/include")
set(JSONCPP_INCLUDE_DIR "${PROJECT_BINARY_DIR}/external/jsoncpp/include")
set(UWS_DIR "${PROJECT_BINARY_DIR}/external/uWebSockets")

add_subdirectory("${PROJECT_BINARY_DIR}/external/anthill-runtime-cpp" "build/anthill-runtime-cpp")

add_subdirectory("${PROJECT_BINARY_DIR}/external/curl" "build/curl")
add_subdirectory("${PROJECT_BINARY_DIR}/external/curlcpp" "build/curlcpp")
add_subdirectory("${PROJECT_BINARY_DIR}/external/jsoncpp" "build/jsoncpp")
add_subdirectory("${PROJECT_BINARY_DIR}/external/libuv" "build/libuv")
add_subdirectory("${PROJECT_BINARY_DIR}/external/uWebSockets" "build/uWebSockets")

# setup your project from now on, don't forget to link with ${ANTHILL_LIBRARY}

Prebuilt library binaries

If you have not intentions to use CMake in your C++ project what so ever, you can download prebuilt static library (along with dependant libraries) and simply import them in your project, as well as add include directories.

Todo

Make prebuilt library

Concepts

This documents refers to various concepts that have been implemented in the Anthill Runtime libraries. Despite being implemented in different languages, the idea is still the same.

Application Info

Application Info is a small object that is required to be filled before initialization of the Anthill Runtime.

struct online::ApplicationInfo
{
    std::string gamespace;
    std::string applicationName;
    std::string applicationVersion;
    AccessScopes requiredScopes;
    AccessScopes shouldHaveScopes;
}
public class ApplicationInfo
{
    public String gamespace;
    public String applicationName;
    public String applicationVersion;
    public AccessScopes requiredScopes;
    public AccessScopes shouldHaveScopes;

    ...
}
gamespace
See Gamespace
applicationName
A name for the game in question. This name must be registered in admin-tool. See Application Name
applicationVersion
See Application Version
requiredScopes
A list of Scopes of Access the library is about to ask.
shouldHaveScopes

A list of Scopes of Access the client should be able to obtain access to, otherwise the library will fail to launch. Default is a single *, meaning all requiredScopes must be fulfilled.

An example use case for this might be asking for several scopes in requiredScopes (for example, A, B, C) but also define shouldHaveScopes to less range of scopes ([A, B]), so only those who have access to restricted ones (C) will get it.

Storage

Anthill Runtime might need to store some information between the applications launches. The way the data is stored is up to the game, so the Storage is an abstract class the game engine should override.

class Storage
{
...

public:
    virtual void set(const std::string& key, const std::string& value) = 0;
    virtual std::string get(const std::string& key) const = 0;
    virtual bool has(const std::string& key) const = 0;
    virtual void remove(const std::string& key) = 0;
    virtual void save() = 0;
};
public abstract class Storage
{
    public abstract void set(String key, String value);
    public abstract String get(String key);
    public abstract boolean has(String key);
    public abstract void remove(String key);
    public abstract void save();
}
set
Will be called once some information named key with value value should be stored. The writing operations should not happen immediately as the method save is made for that purpose.
get
Will be called once some information named key is needed. The overrider needs to return the data asked, resorting to returning empty string if there is no such data.
has
Will be called to check if the information named key exists.
remove
Will be called to delete the information named key. If there was no such data, nothing should happen.
save
This method call should actually store all the changes, so subsequent game launches would be able to retrieve that data.

Online Listener

Depending on the application, the game might be interested in certain key events. To receive those, the game have to override the Online Listener and pass it to the Anthill Runtime during initialization.

class Listener
{
public:
    virtual void multipleAccountsAttached(
        const LoginService& service,
        const LoginService::MergeOptions options,
        LoginService::MergeResolveCallback resolve) = 0;

    virtual void servicesDiscovered(std::function<void()> proceed)
    {
        proceed();
    }

    virtual void environmentVariablesReceived(const EnvironmentInformation& variables)
    {
        //
    }

    virtual void authenticated(
        const std::string& account,
        const std::string& credential,
        const online::LoginService::Scopes& scopes)
    {
        //
    }

    virtual bool shouldHaveExternalAuthenticator()
    {
        return false;
    }

    virtual ExternalAuthenticatorPtr createExternalAuthenticator()
    {
        return nullptr;
    }

    virtual bool shouldSaveExternalStorageAccessToken()
    {
        return true;
    }
};
public abstract class Listener
{
    public abstract void multipleAccountsAttached(
        LoginService service,
        LoginService.MergeOptions mergeOptions,
        LoginService.MergeResolveCallback resolve);

    public void servicesDiscovered(Runnable proceed)
    {
        proceed.run();
    }

    public void environmentVariablesReceived(AnthillRuntime.EnvironmentInformation variables)
    {
        //
    }

    public void authenticated(String account, String credential, LoginService.Scopes scopes)
    {
        //
    }

    public boolean shouldHaveExternalAuthenticator()
    {
        return false;
    }

    public LoginService.ExternalAuthenticator createExternalAuthenticator()
    {
        return null;
    }

    public boolean shouldSaveExternalStorageAccessToken()
    {
        return true;
    }
}
multipleAccountsAttached
This event will be fired on rare occasions in which a Credential has been linked to several different Accounts. In tat cases the system has to resolve that occasion by calling resolve callback with appropriate merge option from mergeOptions.
servicesDiscovered
This even will be fired once all required services has been discovered. The proceed callback should be called once the system is ready to proceed.
environmentVariablesReceived
This even will be fired once the Environment Variables has been received.
authenticated
This even will be fired once the player has been successfully authenticated. Because Access token is a raw string that should not be parsed, this method is a rare possibility to know the account, credential and scopes from this token.
shouldHaveExternalAuthenticator
This method indicates if the system needs External Authenticator.
createExternalAuthenticator
If the system needs External Authenticator by design, this method should return a fresh new instance of it, once called.
shouldSaveExternalStorageAccessToken
If true, the system will use Storage to save acquired Access token for caching purposes.

External Authenticator

By default the system authenticates with anonymous credentials, meaning there is no dependency on external “social networks”, but in some cases the game build might rely on them heavily.

For example, games made for Steam platform should authenticate using steam credentials only. For that case, the Online Listener should set shouldHaveExternalAuthenticator to true, and then createExternalAuthenticator should return an overridden instance of the abstract External Authenticator, which should do the authentication with steam credential.

class ExternalAuthenticator
{
public:
    virtual std::string getCredentialType() = 0;

protected:
    virtual void authenticate(
        LoginService& loginService,
        const std::string& gamespace,
        const LoginService::Scopes& scopes,
        const Request::Fields& other,
        LoginService::AuthenticationCallback callback,
        LoginService::MergeRequiredCallback mergeRequiredCallback,
        const LoginService::Scopes& shouldHaveScopes = {"*"}) = 0;

    virtual void attach(
        LoginService& loginService,
        const std::string& gamespace,
        const LoginService::Scopes& scopes,
        const Request::Fields& other,
        LoginService::AuthenticationCallback callback,
        LoginService::MergeRequiredCallback mergeRequiredCallback,
        const LoginService::Scopes& shouldHaveScopes = {"*"}) = 0;
};
public static abstract class ExternalAuthenticator
{
    public abstract String getCredentialType();

    public abstract void authenticate(
        LoginService loginService,
        String gamespace,
        LoginService.Scopes scopes,
        Request.Fields other,
        LoginService.AuthenticationCallback callback,
        LoginService.MergeRequiredCallback mergeRequiredCallback,
        LoginService.Scopes shouldHaveScopes);

    public abstract void attach(
        LoginService loginService,
        String gamespace,
        LoginService.Scopes scopes,
        Request.Fields other,
        LoginService.AuthenticationCallback callback,
        LoginService.MergeRequiredCallback mergeRequiredCallback,
        LoginService.Scopes shouldHaveScopes);
};
getCredentialType
This method should return credential type for this external authenticator. For example, steam or google.
authenticate
This method should do the authentication. The actual authentication depends on the external system. User has to prepare the required data do the authentication via loginService, passing callback and mergeRequiredCallback if necessary.
attach
This method is used for rare cases when existing and working token’s credential needs to be attached to the external one. The actual attaching depends on the external system. User has to prepare the required data do the attach via loginService, passing callback and mergeRequiredCallback if necessary.

Setup

This document describes the setup process Anthill Runtime library in your project code base, if you need to install it first, see Installation.

Global Reference

Store a global reference to the library in some “root” place. That might be your game singleton object, or a static object.

#include <anthill/AnthillRuntime.h>

class TheGame {
    ...
private:
    online::AnthillRuntimePtr m_anthillRuntime;
};
import org.anthillplatform.runtime;

class TheGame {
    ...
    private AnthillRuntime anthillRuntime;
}

Setup the Application Info

Please see Application Info for more information. Pick Application Name and Application Version and register those in admin-tool. Depending on the application, you might also ask for Scopes of Access with requiredScopes and shouldHaveScopes fields.

#include <anthill/ApplicationInfo.h>

online::ApplicationInfo app;
app.gamespace = "thegame:desktop";
app.applicationName = "thegame";
app.applicationVersion = "0.1";
app.requiredScopes = {"game"};
ApplicationInfo app = new ApplicationInfo(
    "thegame:desktop", "thegame", "0.1", new AccessScopes("game"));

Override the Storage class

Anthill Runtime will need to store few things between game launches. Unfortunately, Anthill Runtime only manages what data is stored, but the game engine would need to implement how it’s stored.

Override example See

// h

#include <anthill/Storage.h>

class TheGameStorage: public online::Storage
{
public:
    virtual void set(const std::string& key, const std::string& value) override;
    virtual std::string get(const std::string& key) const override;
    virtual bool has(const std::string& key) const override;
    virtual void remove(const std::string& key) override;
    virtual void save() = 0;
};

// cpp

void TheGameStorage::set(const std::string& key, const std::string& value)
{
    //
}

std::string TheGameStorage::get(const std::string& key)
{
    //
}

bool TheGameStorage::has(const std::string& key)
{
    //
}

void TheGameStorage::remove(const std::string& key)
{
    //
}

void TheGameStorage::save()
{
    //
}
public class TheGameStorage extends Storage
{

    @Override
    public void set(String key, String value)
    {
        //
    }

    @Override
    public String get(String key)
    {
        //
    }

    @Override
    public boolean has(String key)
    {
        //
    }

    @Override
    public void remove(String key)
    {
        //
    }

    @Override
    public void save()
    {
        //
    }
}

Note

Please refer to Storage for additional documentation on the methods of this class.

(Optionally) Override a global listener

Depending on the application, the game might be interested in certain key events. To receive those, the game have to override the Online Listener and pass it to the Anthill Runtime during initialization.

If you do not need those kind of events, you can skip this step.

Create a library instance

At some point of loading of the game, instantiate the library to the global variable.

#include <anthill/AnthillRuntime.h>

void TheGame::load()
{
    online::ApplicationInfo applicationInfo = ...;

    online::StoragePtr storage = online::StoragePtr(new TheGameStorage());

    // you can skip this and do "online::ListenerPtr listener = nullptr;"
    online::ListenerPtr listener = online::ListenerPtr(new TheGameListener());

    // pick a list of services you'll need
    std::set<std::string> services = {
        online::LoginService::ID,
        online::ProfileService::ID,
        online::GameService::ID
    };

    m_anthillRuntime = online::AnthillRuntime::Create(
        // environment service location
        "http://localhost:9503",

        // things we've done earlier
        services,
        storage,
        listener,
        applicationInfo
    );
};
import org.anthillplatform.runtime;

class TheGame {
    ...
    public void load()
    {
        ApplicationInfo applicationInfo = ...;

        Storage storage = new TheGameStorage();

        // you can skip this and do "Listener listener = null;"
        Listener listener = new TheGameListener();

        // pick a list of services you'll need
        Set<String> services = new HashSet<String>();

        services.add(LoginService.ID);
        services.add(ProfileService.ID);
        services.add(GameService.ID);

        anthillRuntime = AnthillRuntime.Create(
            // environment service location
            "http://localhost:9503",

            // things we've done earlier
            services,
            storage,
            listener,
            applicationInfo
        );
    }
}

Using Services

This section covers Anthill Runtime’s implementations for various services.

Discovery Service

Note

This document covers documentation for Anthill Runtime’s implementation for the Discovery Service. If you need the documentation for the actual service, please see Discovery Service instead.

How To Get Instance

DiscoveryServicePtr service =
    online::AnthillRuntime::Instance().get<online::DiscoveryService>();
DiscoveryService service =
    AnthillRuntime.Get(DiscoveryService.ID, DiscoveryService.class);

Discover Services

Looks up services.

void discoverServices(const std::set<std::string>& services, DiscoveryInfoCallback callback);
public void discoverServices(String[] services, final DiscoveryInfoCallback callback)
services
a set of service’s services too lookup
callback
the callback that would be called once the services have been found (or not, depending on the status argument in the callback)

Please see Discover a service REST API call for more information.

Environment Service

Note

This document covers documentation for Anthill Runtime’s implementation for the Environment Service. If you need the documentation for the actual service, please see Environment Service instead.

How To Get Instance

EnvironmentServicePtr service =
    online::AnthillRuntime::Instance().get<online::EnvironmentService>();
EnvironmentService service =
    AnthillRuntime.Get(EnvironmentService.ID, EnvironmentService.class);

Get Environment Information

Returns the environment information based on the application version.

Note

The environment service location, along with game name and version should be hardcoded inside the game.

void getEnvironmentInfo(EnvironmentInfoCallback callback);
public void getEnvironmentInfo(final EnvironmentInfoCallback callback)
callback
the callback that would be called once the environment information has been received.

Please see Get the Environment information REST API call for more information.

Login Service

Note

This document covers documentation for Anthill Runtime’s implementation for the Login Service. If you need the documentation for the actual service, please see Login Service instead.

How To Get Instance

Warning

To use this, you must mention this service during Library Instantiation.

LoginServicePtr service =
    online::AnthillRuntime::Instance().get<online::LoginService>();
LoginService service =
    AnthillRuntime.Get(LoginService.ID, LoginService.class);

Authenticate

Authenticates the user in the Anthill Platform

void authenticate(
    const std::string& credentialType,
    const std::string& gamespace,
    const Scopes& scopes,
    const Request::Fields& other,
    AuthenticationCallback callback,
    MergeRequiredCallback mergeRequiredCallback,
    const Scopes& shouldHaveScopes = {"*"});
// the last argument is optional

public void authenticate(
    String credentialType,
    String gamespace,
    Scopes scopes,
    Request.Fields other,
    final AuthenticationCallback callback,
    MergeRequiredCallback mergeRequiredCallback,
    Scopes shouldHaveScopes)
credentialType
A Credential Type for the authentication
gamespace
A Gamespace (alias) this authentication goes into
scopes
See Scopes of Access
other
Other possible arguments that Credential Type might additionally require.
callback
The callback that would be called once the authentication completed.
mergeRequiredCallback
The callback that would be called in rare event of Account Conflict
shouldHaveScopes
List of scopes the user should definitely acquire, or Forbidden error will be occur. Useful in cases when player is OK with not having some of scopes. A special case of single * (default) means ALL scopes being asked should be satisfied.

Please see Authenticate REST API call for more information.

Attach Credential

Attaches some credential to existing account. Technically, this call is 90% equal to Authenticate, because, by design, attach means “authenticate, but in someone’s else account”. The account in question is determined by accessToken.

void LoginService::attach(
    const std::string& accessToken,
    const std::string& credentialType,
    const std::string& gamespace,
    const Scopes& scopes,
    const Request::Fields& other,
    LoginService::AuthenticationCallback callback,
    MergeRequiredCallback mergeRequiredCallback,
    const Scopes& shouldHaveScopes)
// the last argument is optional

public void attach(
    AccessToken accessToken,
    String gamespace,
    String credentialType,
    Scopes scopes,
    Request.Fields other,
    final AuthenticationCallback callback,
    MergeRequiredCallback mergeRequiredCallback,
    Scopes shouldHaveScopes)
accessToken
An existing Access token the authentication attaches to.
credentialType
A Credential Type for the authentication.
scopes
See Scopes of Access.
other
Other possible arguments that Credential Type might additionally require.
callback
The callback that would be called once the authentication completed.
mergeRequiredCallback
The callback that would be called in rare event of Account Conflict
shouldHaveScopes
List of scopes the user should definitely acquire, or Forbidden error will be occur. Useful in cases when player is OK with not having some of scopes. A special case of single * (default) means ALL scopes being asked should be satisfied.

Please see Attach Credential REST API call for more information.

Resolve a Conflict

Resolves the occurred Account Conflict event that may happen during Attach Credential or Authenticate.

void resolve(
    const std::string& resolveToken,
    const std::string& methodToResolve,
    const std::string& resolveWith,
    const Scopes& scopes,
    const Request::Fields& other,
    AuthenticationCallback callback,
    const Scopes& shouldHaveScopes = {"*"},
    const std::string& attachTo = "");
// the last two arguments are optional

public void resolve(
    AccessToken resolveToken,
    String methodToResolve,
    String resolveWith,
    String scopes,
    Request.Fields other,
    final AuthenticationCallback callback,
    Scopes shouldHaveScopes,
    AccessToken attachTo)
resolveToken
A Resolve Token, retrieved when the conflict occurred.
methodToResolve
A way how to resolve this conflict. Should be exactly the Conflict Reason server gave in MergeRequiredCallback. For example, merge_required or multiple_accounts_attached.
resolveWith
A way to resolve this conflict. Varies for different Conflict Reasons.
other
Other possible arguments that Credential Type might additionally require.
callback
The callback that would be called once the authentication completed.
shouldHaveScopes
(Optional) List of scopes the user should definitely acquire, or Forbidden error will be occur. Useful in cases when player is OK with not having some of scopes. A special case of single * (default) means ALL scopes being asked should be satisfied.
attachTo
An existing Access token the account player originally was going to attach to. Only applicable if conflict happened during Attach Credential procedure.

Please see Resolve Conflict REST API call for more information.

Extend An Access Token

Allows to to give additional Scopes of Access to the existing Access token (account of which did not have such scopes originally), using other, more powerful account.

void extend(
    const std::string& accessToken,
    const std::string& extendWith,
    const Scopes& scopes,
    AuthenticationCallback callback);
public void extend(
    AccessToken accessToken,
    AccessToken extendWith,
    LoginService.Scopes scopes,
    final AuthenticationCallback callback)
accessToken
Access token to extend (the one to be improved)
extendWith
Access token to extend with (the one that have required scopes)
scopes
A list of scopes to improve access_token with. A single * element can be used to have all scopes the scope extend have.
callback
the callback that would be called once the token has been extended.

Please see Extend Access Token REST API call for more information.

Validate An Access Token

Checks if the given access token is valid.

void validateAccessToken(
    const std::string& accessToken,
    ValidationCallback callback);
public void validateAccessToken(
    final AccessToken token,
    final ValidationCallback callback)
accessToken
Access token to validate
callback
The callback that would be called once the token has been validated (or not).

Please see Validate Access Token REST API call for more information.

Other Concepts

This section describes various concepts the Anthill Platform has.

JSON Database Query

JSON Database Query is a special JSON object designed to perform queries against other JSON objects (for example, User Profiles or Rooms / Parties), much like SQL language, except its very simplified.

{
    "apple": "green",
    "age": {
        "@func": ">=",
        "@value": 18
    }
}

Is equivalent of this SQL query:

SELECT *
FROM `table`
WHERE `apple`='green' AND `age`>=18;

For example, consider this object:

{
   "username": "player",
   "level": 50,
   "progress": {
       ...
   }
}

If you have such an object in the database, you can find it in the database, using this simple JSON Database Query:

{
    "username": "player"
}

Query Format

In general, JSON Database Query has such format:

{
    <field>: value or function,
    <field>: value or function,
    ...
}

It only works with AND condition, meaning ALL fields of the querying object have to satisfy the query.

  • value

    If you specify a value, for example "string" or 15, the query will search for exact value in that field.

  • function

    You can pass a function along the way. It will test the field on certain conditions.

Functions

A function is a JSON object itself, with this exact format:

{
    "@func": "<function name>",
    "@value": <a value to test the function against>
}

Supported functions are: >, <, =, >=, <=, !=, between and in.

The mathematical ones are obvious, as they do as they’re named.

  • between

    A special function for searching a field with value between a and b. It has this exclusive format:

    {
        "@func": "between",
        "@a": 10,
        "@b": 15,
    }
    
  • in

    A special function for searching a field with value listed in an array. It has this exclusive format:

    {
        "@func": "in",
        "@values": [1, 2, 4, 8, 16, 32]
    }
    

Profile Object

Profile Object is a JSON object stored on the server side that can be altered concurrently with functions.

As a storage media, it is literally just a plain JSON object, and can be received by user only as a JSON object, but while altering, a special Functions can be applied to it, making possible to implement things like:

  • Increment/decrement/change a field;
  • Make sure some field is passes to certain condition while updating the value concurrently;

Concurrency is the Key

Functions make a lot of sense in concurrent environment (for example, if two clients are applying increment at the same time to the same field, the resulting value would be a sum of those increments). With normal approach, one of the changes will be lost.

Functions

Functions are special mathematical operations that can be applied to certain JSON field.

Consider this JSON object:

{
    "test": 10
}

You can apply a function to a field test in the example above, and depending on the function, the value of the field might get changed, or certain condition might be required.

See also

Function List

How to apply a function

To apply a function, you must replace a value the field in question, with a special magic object:

{
    "<field>": {
        "@func": "<a function name>",
        "@value": "<a value that will be applied to the function>",
        "@cond": "<optional condition parameter>"
    }
}
@func
A name of the function to be applied to the field. See Function List for reference
@value, @cond, etc
Optional arguments might be required for certain functions.

Mathematically speaking, function is applied like so:

object["field"] = @func(object["field"], @value)

For example:

{
    "foo": 10,
    "bar": 50
}

To apply function Increment a Value to the field foo, you must supply this JSON object while updated the Profile Object:

{
    "foo": {
        "@func": "increment",
        "@value": 5
    }
}

The function will take the original value 10, do mathematical stuff over it, and update the object’s field with a new value:

{
    "foo": 15,
    "bar": 50
}

You can apply functions to multiple fields with one call:

{
    "foo": {
        "@func": "increment",
        "@value": 10
    },
    "bar": {
        "@func": "decrement",
        "@value": 25
    }
}

That will result in:

{
    "foo": 5,
    "bar": 25
}

Functions can be even nested (meaning @value of the functions can be functions themselves). For example, if we apply a such update to previous object:

{
    "bar": {
        "@func": "<",
        "@cond": 50,
        "@then": {
            "@func": "increment",
            "@value": 1
        }
    }
}

Then the field bar will be incremented by 1 (with concurrency support) but only if bar is smaller than 50, thus guaranteeing the total amount cannot be greater than 50 concurrently. In more traditional way, this can be viewed like so:

var bar = object["bar"];

if (bar < 50) {
    object["bar"] = bar + 1;
}

Function List

Increment a Value

Can be referenced by increment or ++. Increments a field value by @value.

Argument Description
@value The value to increment the field value with

Example:

{
    "field": {
        "@func": "++",
        "@value": 10
    }
}

Decrement a Value

Can be referenced by decrement or --. Decrements a field value by @value.

Argument Description
@value The value to decrement the field value with

Example:

{
    "field": {
        "@func": "--",
        "@value": 55
    }
}

Decrement a Value But Keep Greater Than Zero

Can be referenced by decrement_greater_zero or --/0. Decrements a field value by @value but only if the resulting value is positive/zero. If the resulting value is less than zero, produces not_enough error.

Argument Description
@value The value to decrement the field value with

Example:

{
    "field": {
        "@func": "--/0",
        "@value": 55
    }
}

Comparators (==, !=, >, >=, <, <=)

This section covers comparators. These have same arguments, but different behaviour:

Function Reference Description
Equal == Ensures a field is equal to @cond
Not equal != Ensures a field is not equal to @cond
Greater > Ensures a field is greater than @cond
Greater or equal >= Ensures a field is greater or equal to @cond
Smaller < Ensures a field is smaller than @cond
Smaller or equal <= Ensures a field is smaller or equal to @cond

Note

These functions do not change field, unless @then or @else fields provided.

All of the functions listed above have same arguments:

Argument Description
@cond An object to compare the value to
@value (Optional) a value to use instead of object’s value (see below), useful for nesting
@then (Optional) a value to use, if the condition true. If condition is true, and @then is not defined, nothing happens (object’s value remains the same)
@else (Optional) a value to use, if the condition is false. If the condition false, and @else is not defined, error is raised.

Example:

{
    "field": {
        "@func": "==",
        "@cond": 10,
        "@then": 20,
        "@else": 0
    }
}

If the field value is 10, then set it to 20, or set it to zero otherwise.

Exists

Can be referenced by exists. Ensures a field exists (opposite to Not Exists)

Argument Description
@then (Optional) a value to use, if the object exists. If object exists, and @then is not defined, nothing happens (object remains present)
@else (Optional) a value to use, if the object does not exist. If object does not exist, and @else is not defined, error not_exists is raised.

Example:

{
    "field": {
        "@func": "exists",
        "@then": {
            "@func": "--/0",
            "@value": 1
        },
        "@else": 100
    }
}

If the field exists, decrement its value until it reaches zero, otherwise set it to 100.

Not Exists

Can be referenced by not_exists. Ensures a field does not exist (opposite to Exists)

Argument Description
@then (Optional) a value to use, if the object does not exist. If object does not exist, and @then is not defined, nothing happens (object remains not present)
@else (Optional) a value to use, if the object does exist. If object does exist, and @else is not defined, error exists is raised.

Example:

{
    "field": {
        "@func": "not_exists",
        "@then": 100,
        "@else": {
            "@func": "--/0",
            "@value": 1
        }
    }
}

If the field does not exists, set it to 100, otherwise decrement its value until it reaches zero.

Append A Value To An Array

Can be referenced by array_append. Appends a new value to the end of an array [].

Argument Description
@value A value to append to the array
@limit (Optional) A maximum size of the array. If the limit is reached, error limit_exceeded is raised.
@shift (Optional) If true, and limit is reached, the first element will be deleted to free space

Example:

{
    "field": {
        "@func": "array_append",
        "@value": 5
        "@limit": 3,
        "@shift": true
    }
}

Appends a value 5 to the end of the array field, but keeps the maximum limit of the items 3 by shifting.

Count Number Of Child Fields Which Pass Condition

Can be referenced by num_child_where. This complicated function can count a number of child fields which pass a certain test condition. The function itself is useless, but it can be wrapped in other function to ensure the resulting number qualifies the quota.

Consider this example:

{
    "members": {
        "1": {
            "color": "red"
        },
        "2": {
            "color": "red"
        },
        "1": {
            "color": "green"
        }
    }
}

The function might be useful to ensure there’s no more than 2 members with color=red (see example below).

Argument Description
@field A field name of the each child to perform tests on
@test Name of the function the tests will rely on
Other The @test function will require additional arguments. For example, a comparator function will require @cond argument

Example:

{
    "members": {
        "@func": "<",
        "@cond": 2
        "@value": {
            "@func": "num_child_where",
            "@test": "==",
            "@cond": "red"
        }
    }
}

Such formula will do nothing if a total number of child objects of the members object that match the criteria color=red does not exceed a value of 2. If it does exceed, the error will be raised.

Note

This particular function might be used in join_party method of Session Methods to test against Party Members while joining the party.

Examples

Increment likes/stats/etc

{
    "likes-x": {
        "@func": "++",
        "@value": 1
    }
}

Claim a reward, but only once

{
    "reward-x": {
        "@func": "!=",
        "@cond": true,
        "@then": true
    }
}

Set a value to a filed, only if it didn’t exist earlier

{
    "value-x": {
        "@func": "not_exists",
        "@then": 55
    }
}

Applications

Anthill Platform

An open source back-end platform for games that solves the online problem.

  • Self-hosted. You have full control over the system. That makes it cheaper to maintain and a lot easier to modify.
  • Open Source. Under MIT License, actively maintained, with no black box stuff inside.
  • Scalable. Each service is easily scalable since the whole system works like an anthill itself: add more workers into it and they will do the job.
  • Easy to customise. It can hold any type of game, because it’s designed to be as much abstract as possible. The way it acts is completely up to the game.
  • Unified. Each service follows standards so its behaviour is predictable. Implement few interfaces and your new service is ready to be plugged in!
  • A platform. Not just a complete solution, it’s a platform. If there is no service that meets your exact requirements, you can easily create a new one!

Runtimes

It also comes with game client runtimes for various programming languages for quick start. They allow to interact with services and handle most of core tasks under the hood.

Please refer to Runtimes for additional information.

Installation

Please see Installation for a simple instruction on how to install Anthill Platform on a Linux machine.

If you’d like to test it out on Windows or Mac Os X, please see Development.

Core Services List

Service Description Source Code
Environment Service Define a set of environments and dynamically assign each game version to any of those Source Code
Discovery Service Discover each service location dynamically by it’s ID Source Code
Login Service Login using any kind of credential (like google or facebook), or your own Source Code
Profile Service Manages user profiles, plain JSON objects, completely defined by a game Source Code
services/social Social interactions (social network’s friend list etc) Source Code
services/config Delivers dynamic configuration to the user using handy schema editor Source Code
services/leaderboard Competes users around by anything Source Code
Exec Service Server-side game-depended javascript code execution (validation and so on) Source Code
services/event Time-Limited events service Source Code
services/common A shared library with common functionality for each service Source Code
services/admin Manage every service using a web browser Source Code
services/dlc Downloadable content delivery service Source Code
services/game Matchmaking service, hosts any game server, and matches users around it Source Code
services/message Delivers messages from anything to anything, realtime Source Code
services/store Monetization service Source Code
services/promo Promo codes service Source Code
services/static Simple static files hosting service (for players to upload) Source Code
Report Service Service for players to post reports to the service Source Code