iFun Engine Component Model

Creating an iFun Engine component

Note

This section is for advanced users who want to add features in the form of iFun Engine component. If you create a project using funapi_initiator, the project already contains a component named after the given project name and suffixed with Server. In many cases, this becomes only one component that the users care about. But if you want to do more sophisticated jobs requiring to manipulate the component, you can refer to this section.

Otherwise, if you are OK with the default server component generated by funapi_initiator, you can skip the section.

For extensibility, iFun Engine provides its features as components. This means that the iFun Engine core features, various services for games such as billing and leaderboard, and even the game itself that the developer implements (more precisely, the game logic) are all in the form of components.

In general, ``funapi_initiator `` automatically generates a skeleton code for your game logic component, as explained in creating-project. So, it’s very unusual for you to manually create a component class from scratch. But if you want, you can do in the following way.

Create my_component.cc with the following contents.

// First include funapi/framework/component.h.
#include <funapi/framework/component.h>

// You can use any namespace.
// This isn't required, but we use an unnamed namespace to prevent
// namespace collision.
namespace {

// Define the Installer class.
// The Installer class must publicly inherit Component.
class MyComponentInstaller : public Component {
 public:
  // When a component is initialized, the Install() function is called.
  // The argument for this function is an ArgumentMap that contains
  // configuration for this component.
  // Refer to the MANIFEST.json section on configuring components.
  static bool Install(const ArgumentMap &/*arguments*/) {
    // If you need to initialize any global variables, do so here.
    // At this point, Install() for any components that this depends on
    // were already called.
    // Thus, it is safe to use any features of this component.

    // Return true if installation was successful.
    // If false is returned, iFun Engine assumes the component failed to initialize and exits.
    return true;
  }

  // iFun Engine will invoke the Start() method when the component should start its jobs.
  // Please be aware that Start() is invoked only after Install() method mentioned above and
  // the Start() methods of other components on which the component relies.
  // Please add code in the method if the component has any active jobs that needs to be done.
  static bool Start() {
    // Since the invocation of the method implies all the components in the dependency list
    // have been invoked, you can use the components from here.

    // Please return true if the component succeeded to start.
    // On the return of false, iFun Engine treats it as a initialization failure and stops.
    return true;
  }

  // Uninstall() will be called when iFun Engine shuts down and needs to purge the component.
  static bool Uninstall() {
    // You should return true if the component succeeded to exit.
    return true;
  }
};

}  // unnamed namespace


// Connects the Installer class with the component.
// MyComponentName is the component name that iFun Engine recognizes.
// You must use the same name in MANIFEST.json.
// Note that the name is not in double-quotes like a string ("...").
REGISTER_STARTABLE_COMPONENT(MyComponentName, MyComponentInstaller)

An Installer is simply for installing a component. For example, if a developer creates a feature in my_feature.h and my_feature.cc , MyFeatureInstaller should be defined for my_feature and added at the end of my_feature.cc . Components that use my_feature can include my_feature.h and use the feature. Of course, dependencies for my_feature need to be declared. Dependencies will be explain in the following MANIFEST.json section.

MANIFEST.json

The role of MANIFEST.json for iFun Engine components are the following three.

  1. Not all games use all the iFun Engine components, so only the necessary components are listed.

  2. It declares the dependency between components. iFun Engine decides the component initialization order based on the dependencies.

  3. The arguments for when initializing components are defined.

For this, MANIFEST.json is structured as follows. In the example below, lines starting with // are comments to help comprehension, and are not included in the JSON file.

Description of the MANIFEST.json file

{
  // This is the version for the MANIFEST.json schema. Currently, it must be 1.
  "version": 1,

  // List of the components that are used
  // Components that are used by other components are included automatically and need not be listed.
  "components": [
    // The first component, described in the form of a JSON object
    {
      // Name of the component
      // The name must match the name used to register the component through REGISTER_STARTABLE_COMPONENT().
      "name": "MyComponentName",

      // List of the default argument values for this component
      // When another component uses this component, these default values are used unless explicitly specified.
      "arguments": {
        "my_component_arg1": integer_value,
        "my_component_arg2": "string_value"
      },

      // The name of the dynamic library that contains the implementation of this component
      // If funapi_initiator is used, this value is automatically filled in.
      "library": "libmy_project.so",

      // List of other components that are required by this component
      // The components listed here should have their own MANIFEST.json somewhere with its name,
      //    dependencies, and default values. You can also override the default argument values here.
      "dependency": {

        // First required component
        "AnotherComponent": {
          // "key":  "value" pairs are used to override the default arguments for AnotherComponent
        },

        // Second required component
        // Here, to illustrate how overriding works, we will use an actual argument called io_service_threads_size.
        // For the curious, IoService is defined in /usr/share/funapi/manifests/network/MANIFEST.json
        // and the default value for io_service_threads_size is 4.
        "IoService": {
          "io_service_threads_size": 2
        }
      }
    }
  ]
}

The example below is the automatically generated MANIFEST.json for my_project above. In the dependency section, you can see how my_project changes the argument values for components that it uses.

Example MANIFEST.json for my_project

{
  "version": 1,
  "components": [
    {
      "name": "MyServer",
      "dependency": {
          "AppInfo": {
            "app_id": "MyServer",
            "client_current_version": "0.0.3",
            "client_compatible_versions": ["0.0.1", "0.0.2"],
            "client_update_info": "",
            "client_update_uri": ""
          },
          "EventDispatcher": {
             "event_threads_size": 4
          },
          "Logging": {
            "activity_log_output": "json://activity/activity_log.json",
            "activity_log_rotation_interval": 60,
            "glog_flush_interval": 1
          },
          "IoService": {
            "io_service_threads_size": 4
          },
          "SessionService": {
            "tcp_json_port": 8012,
            "udp_json_port": 0,
            "http_json_port": 8018,
            "tcp_protobuf_port": 0,
            "udp_protobuf_port": 0,
            "http_protobuf_port": 0,
            "session_timeout_in_second" : 3600,
            "use_encryption": false,
            "tcp_encryptions": ["ife1", "ife2"],
            "udp_encryptions": ["ife2"],
            "http_encryptions": [],
            "enable_http_message_list": true
          },
          "Timer": {},
          "Object": {
            "cache_expiration_in_ms" : 3000,
            "enable_database" : false,
            "db_mysql_server_address" : "tcp://127.0.0.1:3306",
            "db_mysql_id" : "funapi",
            "db_mysql_pw" : "funapi",
            "db_mysql_database" : "funapi",
            "db_read_threads_size" : 8,
            "db_write_threads_size" : 16
          },
          "CounterService": {
          },
          "ApiService": {
            "api_service_port": 8014
          },
          "AuthenticationClient": {
            "use_authenticator" : false,
            "remote_authenticator_ip_address" : "127.0.0.1",
            "remote_authenticator_port" : 12800
          },
          "BillingClient": {
            "use_biller" : false,
            "remote_biller_ip_address" : "127.0.0.1",
            "remote_biller_port" : 12810,
            "googleplay_refresh_token" : "",
            "googleplay_client_id" : "",
            "googleplay_client_secret" : ""
          },
          "LeaderboardClient": {
            "use_leaderboard" : false,
            "remote_leaderboard_ip_address" : "127.0.0.1",
            "remote_leaderboard_port" : 12820
          }
      },
      "library": "libmy_server.so"
    }
  ]
}

The manifest files for the iFun Engine core features and services are defined in separate directories inside /usr/share/funapi/manifests. If you want iFun Engine to read the MANIFEST.json for a manually created component, add it to the --framework_manifest_path parameter of the launcher script. For the default component auto-generated by funapi_initiator, this jobs is automatically done and you do not have to it manually. For more details, see the Launcher section.

Bundled components

iFun Engine provides all of its features as components. This includes the system core feature and all the services for games. The components of iFun Engine are defined in /usr/share/funapi/manifests. The following is the list of the components.

  • Logging: The logging component for iFun Engine and the iFun Engine game. More details on Logging is here.

  • EventDispatcher : The component that processes iFun Engine events.

  • WallClock : iFun Engine uses a monotonic clock to prevent time from going backwards during time synchronization. However, the monotonic clock is not easily human-readable, so a WallClock is also provided.

  • Timer : Timers that game developers can use. Periodic tasks in games such as ticks can be processed in the Timer handler.

  • RandomGenerator : Random number generator component.

  • IoService : Raw I/O processing component. Internally, it corresponds to io_service in boost::asio.

  • CounterService : This component provides various internal server state through a HTTP RESTful API. More details are here.

  • SessionService : This is the top-layer of the iFun Engine networking stack. Games will use this layer to send and receive messages. For more details on networking, see the Networking subsystem section.

  • Object : The component that manages game objects. It automatically processes game objects without complex code for locking, databases, etc. For more details, see here.

  • AppInfo : This is a placeholder component that contains information about the game. It contains the current version, compatible previous versions, and URLs for patches if needed. It also contains how to process authorization or billing.

  • AuthenticationClient : iFun Engine has a separate agent for processing authorization. This is the component for using this agent. You can specify the IP and port of the agent.

  • BillingClient : Like authorization, billing has a separate agent as well. This is the component to use this agent.

  • LeaderboardClient : Leaderboard also has a separate agent. This is the component to use this agent.

Game server component

In iFun Engine, creating a game server means developing a game server component. Previously, in the sections Creating an iFun Engine component and What is MANIFEST.json?, we explained how to create a component and write a MANIFEST.json file to use it. However, doing this every time is tedious and error-prone, so iFun Engine provides a script that uses template code to generate basic game server code. This script is explained in the creating-project section. We’ll show you the code generated by the script here. Below is the generated game server code for MyServer.

The installer for the game server component generated by funapi_initiator

class MyServerInstaller : public Component {
 public:
  static bool Install(const ArgumentMap &/*arguments*/) {
    HandlerRegistry::Install2(
        my_server::OnSessionOpened,
        my_server::OnSessionClosed);


    // Handlers below are for your reference.
    // You can delete the statements and their function definitions.
    // See also event_handlers.cc
    HandlerRegistry::Register("login", my_server::OnAccountLogin);
    HandlerRegistry::Register("echo", my_server::OnEchoMessage);


    // Default timer.
    // Adjust time if you want fast/slow repeating timer.
    // If you want no timer, simply delete this statement.
    Timer::ExpireRepeatedly(kOneSecond, my_server::OnTick);

    return true;
  }

  static bool Start() {
    return true;
  }

  static bool Uninstall() {
    return true;
  }
};

}  // unnamed namespace


REGISTER_STARTABLE_COMPONENT(MyServer, MyServerInstaller)

As you can see above, the installer for the component is automatically generated. The component is built as libmy_server.so. funapi_initiator generates the following MANIFEST.json, enabling the component to be used.

MANIFEST.json for the game server component generated by funapi_initiator

{
  "version": 1,
  "components": [
    {
      "name": "MyServer",
      "dependency": {
          "AppInfo": {
            "app_id": "MyServer",
            "client_current_version": "0.0.3",
            "client_compatible_versions": ["0.0.1", "0.0.2"],
            "client_update_info": "",
            "client_update_uri": ""
          },
          "EventDispatcher": {
             "event_threads_size": 4
          },
          "Logging": {
            "activity_log_output": "json://activity/activity_log.json",
            "activity_log_rotation_interval": 60,
            "glog_flush_interval": 1
          },
          "IoService": {
            "io_service_threads_size": 4
          },
          "SessionService": {
            "tcp_json_port": 8012,
            "udp_json_port": 0,
            "http_json_port": 8018,
            "tcp_protobuf_port": 0,
            "udp_protobuf_port": 0,
            "http_protobuf_port": 0,
            "session_timeout_in_second" : 3600,
            "use_encryption": false,
            "tcp_encryptions": ["ife1", "ife2"],
            "udp_encryptions": ["ife2"],
            "http_encryptions": [],
            "enable_http_message_list": true
          },
          "Timer": {},
          "Object": {
            "cache_expiration_in_ms" : 3000,
            "enable_database" : false,
            "db_mysql_server_address" : "tcp://127.0.0.1:3306",
            "db_mysql_id" : "funapi",
            "db_mysql_pw" : "funapi",
            "db_mysql_database" : "funapi",
            "db_read_threads_size" : 8,
            "db_write_threads_size" : 16
          },
          "CounterService": {
          },
          "ApiService": {
            "api_service_port": 8014
          },
          "AuthenticationClient": {
            "use_authenticator" : false,
            "remote_authenticator_ip_address" : "127.0.0.1",
            "remote_authenticator_port" : 12800
          },
          "BillingClient": {
            "use_biller" : false,
            "remote_biller_ip_address" : "127.0.0.1",
            "remote_biller_port" : 12810,
            "googleplay_refresh_token" : "",
            "googleplay_client_id" : "",
            "googleplay_client_secret" : ""
          },
          "LeaderboardClient": {
            "use_leaderboard" : false,
            "remote_leaderboard_ip_address" : "127.0.0.1",
            "remote_leaderboard_port" : 12820
          }
      },
      "library": "libmy_server.so"
    }
  ]
}

The generated MyServer component uses AppInfo, EventDispatcher, Logging, IoService, SessionService, Timer, BillingClient, and LeaderboardClient. iFun Engine uses this information to initialize all the aforementioned components and then initializes MyServer .

Overriding MANIFEST.json

MANIFEST.json holds rather static information shared among production servers. Hence, it is not recommended to locally modify a MANIFEST.json on a production server, for this is extremely error-prone especially when you have a large number of servers.

Under very unusual circumstance, however, you might need to urgently (and temporarily) patch installed MANIFEST.json. In this case, you can specify overridden information in a separate file. This file is considered temporary and on a per-host basis, and hence it is not included when you package your game.

Create a file as /etc/<project_name>/MANIFEST.override.json and put overriding information like following. (If you use Flavors: Identifying servers according to their role, you need to create /etc/<project_name>/MANIFEST.<flavor_name>.override.json. E.g., /etc/my_project/MANIFEST.chat.override.json)

{
  "override": {
    "ApiService": {
      "api_service_port": 9014
    },
    "SessionService": {
      "tcp_json_port": 0,
      "udp_json_port": 8017
    }
  }
}

You must have override JSON property and it holds what you would normally specify in the dependency section of your regular MANIFEST.json. In this example, we overrode api_service_port flag of the ApiService component and tcp_json_port and udp_json_port of the SessionService component.

Encrypting contents in MANIFEST.json

MANIFEST.json holds sensitive information such as DB credential.

This may be not a big issue in some cases, for only a few developers with the source repository access permission and system administrators can access the file. iFun Engine, however, has a capability to encrypt sensitive information.

Only string values in MANIFEST.json can be encrypted. In addition, you should pass funapi_argument_encryptor a license file (i.e., account.ilf) that will be deployed to live servers.

Encrypting a string

Example 1: Encrypting a string “mypassword” with /etc/ifunfactory/account.ilf

$ funapi_argument_encryptor "mypassword"
/etc/ifunfactory/account.ilf loaded.
Argument: mypassword
Encrypted argument: __ENCA_[ASnbrseCc050UsCHZWzV5A]

Example 1: Encrypting a string “mypassword” with a specific license file

$ funapi_argument_encryptor "mypassword" /path/to/account.ilf
/path/to/account.ilf loaded.
Argument: mypassword
Encrypted argument: __ENCA_[ASnbrseCc050UsCHZWzV5A]

Putting an encrypted string in MANIFEST.json

Copy and paste the value in the Encrypted argument above. For example, we can put the result to encrypt db_mysql_pw.

...
"Object": {
    ...,
    "db_mysql_server_address" : "tcp://127.0.0.1:3306",
    "db_mysql_id" : "funapi",
    "db_mysql_pw" : "__ENCA_[ASnbrseCc050UsCHZWzV5A]",
    "db_mysql_database" : "funapi",
    ...
},
...