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
- Nginx as a reverse proxy and a load balancer
- MySQL 5.7 database for primary content storage
- Redis for fast key/value
- RabbitMQ for internal communication across services
- Supervisor to roll actual services
- Python 3.6 with bunch of packages
- 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
andanthill.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.Login using username
root
and passwordanthill
.
Authentication Keys¶
Anthill Platform uses Public-key cryptography to authenticate users. The idea is goes as follows:
- User authenticates himself in the system, giving credentials
- 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):
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"
}
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: }
}
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.
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: }
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.
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"
}
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:
- Generate SSH key pair:
ssh-keygen
- Upload your public key (
id_rsa.pub
) to your repository provider. - Add your private key (
id_rsa
) to your puppet configuration (environment/<environment>/modules/keys/files/ssh.pem
) - 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)
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:
- PuppetDB component is required for this feature to work.
- You should ensure there is no two client nodes with same
client_index
- 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.
HTTPS¶
If you would like to serve your services through https:
- Generate the SSL Bundle file and the Private Key file for your domain
(for example,
example_com.ssl-bunlde
andexample_com.key
) - Add these files to your puppet configuration:
environments/
<environment>/
modules/
keys/
files/
example_com.key
example_com.ssl-bunlde
- 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"
}
- Enable HTTPS, with
anthill
class:
class { anthill:
enable_https => true,
protocol => 'https'
}
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.
See also
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.
→ 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:
- There should be one well-known location to start communication from;
- Production environment should be divided from development with no side effects
See also
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:
- Environment Location (e.g.
https://environment.example.com
) - 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:

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
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.
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.
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 -
Delays the execution for some time.
Argument Description delay
Time for delay in seconds
web¶
An object to access to the internet
config¶
An object to access to the ../config
store¶
An object to access to the ../store
-
Returns the configuration of the given Store
Argument Description name
Store name 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 -
Updates the given order. No additional documentation so far.
Argument Description order_id
Order id to update -
Updates all unfinished orders of the user. No additional documentation so far.
profile¶
An object to access to the Profile Service
-
Returns the user’s profile.
Argument Description path
(Optional). Path of the profile to get. If not defined, the whole profile is returned 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 -
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.
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
Game Master Service¶
When it comes to hosting a server for a multiplayer game, major problems appear:
- Game servers actually need to be put somewhere;
- The life cycle of the game server need to be maintained (as game servers may crash or become outdated);
- 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);
- The system overall must be scalable to add new servers into the pool when the concurrent players number explode;
- 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
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, andmain
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¶

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) oripc://<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 thesettings
argument to theinited
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:
- The Player successfully “joins” into the Room, gets room
location
andkey
in return.- Using the
location
information, the Player connects to the Game Server using any protocol, that’s up to the game- The Player sends the
key
to the Game Server. The Game Server checks thekey
, 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,
- User Authenticates asking for
profile
scope. This scope allows only to read user profile, but not to write; - The Game Server instance Authenticates itself with
profile_write
scope access (allows to modify the profile); - The Game Server extends this token to the more powerful one, so server can write the profile in behalf of the Player;
- At the same time, user still have perfectly working access token, without such possibility;
- 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.
→ 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.
- Party is created either by some player or by a service;
- Members open a Party Session upon it, using it the members can join into the party, send messages to eachother etc.
- 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;
- The memberss are relocated into the Game Server and become players by receving a notification from the Party Session;
- 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:
- The user opens a Web Socket connection on the one of the possible endpoints
- The user then waits for the
party
Session Callback - 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 theirmember_profile
equal toTEST_CLAN
, meaning there could be only two members total from clanTEST_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, thepayload
depends on themessage_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
– amember_profile
of the memberplayer_left
A member has left the party. A JSON Object with fields: account
– an account ID of the member,profile
– amember_profile
of the membergame_starting
The game is about to start as a Game Server is being instantiated As described in start_game
requestgame_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 settingscustom
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
aparty_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,
- User Authenticates asking for
profile
scope. This scope allows only to read user profile, but not to write; - The Game Server instance Authenticates itself with
profile_write
scope access (allows to modify the profile); - The Game Server extends this token to the more powerful one, so server can write the profile in behalf of the Player;
- At the same time, user still have perfectly working access token, without such possibility;
- 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.
→ 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
Login process¶
Authentication process takes these steps:
- Player provides credentials and scopes of access;
- The system verifies if the credentials in question are valid. Each credential defines different way of verification;
- Account for the user is revealed. If there’s no account for such credential, a new one is created;
- System verifies if the account has the scopes requested;
- 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
.

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:
- Create the Web Application OAuth Client ID at the Google API Console;
- Add the application website (for example,
http(s)://example.com/
) into theAuthorized redirect URIs
list.- Open the admin-tool and select the Login service;
- Select the section “Keys” and click “Add New Key”;
- Select
- Fill Client ID and Client Secret fields according to your credentials:
![]()
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
facebook¶
A way to authenticate using a Facebook account.
To enable this feature, please do the following:
- Create a New Application at the Facebook Developers section;
- Add the application website (for example,
http(s)://example.com/
) into theValid OAuth redirect URIs
section (under Facebook Login product);- Open the admin-tool and select the Login service;
- Select the section “Keys” and click “Add New Key”;
- Select in
- Fill the App ID and App Secret respectively:
![]()
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
vk¶
A way to authenticate using a VKontakte (vk.com) account.
To enable this feature, please do the following:
- Create a New Application at the Developers section;
- Add the application website (for example,
http(s)://example.com/
) into theAuthorized redirect URI
;- Open the admin-tool and select the Login service;
- Select the section “Keys” and click “Add New Key”;
- Type in
vk
as a Key Type;- Fill Application ID and Secure Key respectively:
![]()
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
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:
- Generate a signature for the player;
- At the return, you will have such:
publicKeyUrl
,signature
,salt
andtimestamp
;- 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:
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:
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:
- 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.
- Once a Token is generated, it is signed using a private key only a Login service knows.
- 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:
- Open the admin-tool;
- Select the Login service;
- 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:
- User has the corresponding social network’s “login page” shown, asking user consent for the authorization.
- After approval, the browser gets redirected back to the special
redirect_uri
location, with the specialcode
argument. - 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 thecode
, closing the browser. - Then
code
andredirect_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:
- Authenticate, using first credential (say
anonymous:XX-XX-XX
), accountA
will be used (or created); - Authenticate, using second credential (say
facebook:12345678
). While doing that, pass the access token from a previous authentication, asattach_to
argument; - The system will try to attach credential
facebook:12345678
to accountA
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
→ 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.
- Say there’s account
A
with scopesS1
andS2
allowed. - There’s account
B
with scopeS10
thatA
has no access to. A
authenticates, requesting scopeS1
.B
authenticates, requesting scopeS10
.- Access token of
B
extends access tokenA
using scope he hadS10
. - A working access token for
A
with scopesS1
andS10
is now available.
This flow is primarily used for trusted game servers to do strict actions server side. For example,
- User Authenticates asking for
profile
scope. This scope allows only to read user profile, but not to write; - The Game Server Authenticates itself using
dev
credential withprofile_write
scope; - User give the access token to the server is a secure way;
- The Game Server extends this token to the more powerful one, so server can write the profile in behalf of the user;
- 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
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
- The access levels might be applied only to the root fields of the User Profile.
- 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.
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
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:
- Install PyCharm (Community Edition is fine)
- Clone the dev repository:
git clone -b dev https://github.com/anthill-platform/anthill-dev.git
cd anthill-dev
git submodule update --init --recursive
- Run installation script
cd dev\win
setup.bat
This will install Scoop, and Scoop will install all of the required components.
Open cloned repo in PyCharm.
Setup the Project Interpreter.
To do that, go to
Preferences
, selectProject Interpreter
, selectShow All
from the dropdown, hit “+”, selectAdd Local
, and feed it withC:\Anthill\venv\Scripts\python.exe
.Select
all
run configuration, hit RunOpen http://localhost:9500 in your browser
Press “Proceed”, login using username
root
and passwordanthill
. You should see something like this:
Mac OS X¶
To setup minimal development environment on Mac Os X, you will need:
- Install PyCharm (Community Edition is fine)
- Clone the dev repository:
git clone -b dev https://github.com/anthill-platform/anthill-dev.git
cd anthill-dev
git submodule update --init --recursive
- Run installation script
cd dev/osx
./setup.sh
This will install Homebrew, and Homebrew will install all of the required components.
Open cloned repo in PyCharm.
Setup the Project Interpreter.
To do that, go to
Preferences
, selectProject Interpreter
, selectShow All
from the dropdown, hit “+”, selectAdd Local
, and feed it with/usr/local/anthill/venv/bin/python
.Select
all
run configuration, hit RunOpen http://localhost:9500 in your browser
Press “Proceed”, login using username
root
and passwordanthill
. You should see something like this:
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¶
- Add the JitPack repository to your
build.gradle
file.allprojects { repositories { ... maven { url "https://jitpack.io" } } }
- Add the dependency.
dependencies { compile 'com.github.anthill-platform:anthill-runtime-java:0.1.4' }
Maven-based project¶
- Add the JitPack repository to your
pom.xml
file.<repositories> <repository> <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> </repositories>
- 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 --recursiveSetup 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 allrequiredScopes
must be fulfilled.An example use case for this might be asking for several scopes in
requiredScopes
(for example,A
,B
,C
) but also defineshouldHaveScopes
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 valuevalue
should be stored. The writing operations should not happen immediately as the methodsave
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 frommergeOptions
. 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
andscopes
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
orgoogle
. 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
, passingcallback
andmergeRequiredCallback
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
, passingcallback
andmergeRequiredCallback
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 inMergeRequiredCallback
. For example,merge_required
ormultiple_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 scopeextend
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"
or15
, 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
andb
. 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
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 ifbar
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¶
- User Profile
join_party
method of Session Methods
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 |