Creating a Custom Service #
To create the skeleton of a basic custom service, there are three key steps:
- Making the protos and generating them for Unreal/Unity
- Building the service on the server-side
- Building the corresponding structure on the client-side
Make and generate the protos #
Protos define the shape of every call we’ll be making with the custom service, which means we’ll need to build out the structure of our requests and responses. Once that’s done, we generate the protos for use in Unreal or Unity.
Proto creation #
The proto file should be created in the 5-ext/ext-protos/src/main/proto/
directory.
This file will need to contain:
- A
package
line with the desired Kotlin namespace that the generated protos are available under. - Namespace lines used when generating protos for the SDK
- Unity3D =
csharp_namespace
- Unreal =
pragma.unreal_namespace
- Unity3D =
- Request and response messages with versioned names so breaking changes can be handled in a backwards-compatible way.
- An option that specifies the message type (
REQUEST
orRESPONSE
). - An option that specifies the session type.
session type | definition | examples |
---|---|---|
PLAYER | SDK or game client requests | getting inventory, purchasing items |
PARTNER | game server or third-party requests | granting items, match end processes |
OPERATOR | administration tasks | bans, account management, customer support |
SERVICE | communications between Pragma Engine service instances | querying the inventory for a quest |
After adding these new proto messages, you must run make protos
on the command line so Pragma Engine can access them.
Generate for Unity/Unreal #
Once the protos have been created, they need to be generated for the SDKs. When setting up Unreal/Unity, you configured the update-pragma-sdk.sh
scripts for your game project. To generate the protos, run this script.
Once the SDK types are generated from the protos, new files will appear in the dto
folders that represent the protos in the relevant engine language.
Unity:
PragmaSDK/src/dto
Unreal:
PragmaSDK/Source/PragmaSDK/Public/Dto
Build the service on the server-side #
Now that we have proto structure in place, the custom service itself can be created as a Kotlin file in the 5-ext/ext/src/main/kotlin/
directory, within folders that reflect the namespace.
Service file structure:
@Suppress
and@PragmaService
annotations and the class definition@PragmaRPC
annotation and suspend function
Custom services must have the @PragmaService
annotation which includes the backend types the service supports (GAME
and/or SOCIAL
), and any dependencies that should be injected. It can also specify the dependencies to be injected on instantiation of the service.
Due to the way services are called within Kotlin code, incorrect IDE errors will need to be suppressed using the @Suppress
annotation.
There are several options for extending the custom service from existing service classes:
service | definition | examples |
---|---|---|
DistributedService | for scalable systems, can be enabled/disabled across different Pragma Nodes | Inventory, Accounts |
AlwaysStartedNodeService | services common across all nodes | LoggingNodeService, MetricsNodeService |
The default service should be extended from DistributedService
. Only use the node service in the rare occasion it’s necessary, such as when all running services on a single node use one instance of the same resource.
The service functions can be configured as externally accessible RPC endpoints, but must follow these rules:
- have the @PragmaRPC annotation
- be a suspend function
- match names of established request/response proto messages
The @PragmaRPC
annotation has a few properties you can set. The first is the sessionType
, which follows the same pragma_session_type
as defined in the protos (PLAYER
, PARTNER
, OPERATOR
, SERVICE
).
The second is the routingMethod
, which can define different rules for how RPC requests are routed to which service instances. By default, we recommend using SESSION_PRAGMA_ID
for player-facing services.
routing method | definition |
---|---|
RANDOM | any service instance, randomly distributed |
REQUEST_FIELD | protobuf field used to determine service instance to hit |
SINGLETON | always route to a single service instance |
DIRECT | route to a specified service instance |
GAME_SESSION_PARAM | uses game session attribute to route |
SOCIAL_SESSION_PARAM | uses social session attribute to route |
SESSION_PRAGMA_ID | uses a player’s PragmaId to route |
If you specifyREQUEST_FIELD
, the annotation requires another property,protoRoutingFieldName
, which is the name of the field in the proto used to determine which service instance to route to.
Scaling and coroutine-safe design #
A distributed service may exist as a single instance, as multiple instances on a single server, or as multiple instances on multiple servers. Because of this, RPC endpoints and service behavior must take into account the intended scaling approach. This is an advanced topic and might not be needed for a simple service. Visit the Concurrency page for more information.
Build the corresponding structure on the client-side #
A raw service file will need to be created to interface directly with RPC requests. A best practice is to separate the API method definitions and generated DTOs from the game logic and structs. This isolates Pragma SDK changes from your game logic, making it easier to handle SDK updates, especially in the case where you want to support two versions of an API at once but have them operate the same in your game code. The [ServiceName]ServiceRaw and [ServiceName]Service classes mentioned earlier are an example of this structure.
Note: The raw service file is generated automatically and registered with the appropriate session in Unreal. These can be found in the PragmaSDK/Source/PragmaSDK/Public/Dto
folder alongside the RPC DTOs.
The custom service must be registered with the Player
or Server
objects (depending on which session it is available for) which can be done with the RegisterApi()
method. We recommend creating a registration method on your custom service to attach to the service properly.
The custom service can then be retrieved from the session when the [ServiceName]Service class needs to communicate with the SDK by using the Api()
method. As RPC endpoints are added to the service, methods will need to be added to [ServiceName]Service and [ServiceName]ServiceRaw in the SDK.