Calling with custom plugins #

In advanced cases, custom services may need to be called within other Pragma Engine RPC flows. We’ve provided a set of configurable plugins that can be customized and overridden to add in your functionality.

Plugins are classes that can be enabled via configuration. When enabled, they provide an opportunity to inject custom code into existing Pragma Engine services.

It’s possible to call your custom service via a service-to-service RPC call from within a plugin.

Copy over a relevant provided default plugin from platform/2-pragma/game-common/src/main/kotlin/pragma/ into your custom service directory to preserve existing behavior. To add the new service, add a new dependent job line to the listOf().

MyCustom example

During the MatchLifecycle, when a match ends, basic functionality can grant rewards or items on completion. For the MyCustom example, we’ll use a MatchEndPlugin to add additional match-end processes.

Create a file called MyCustomMatchEndPlugin.kt in the 5-ext/src/main/kotlin/myproject/matchlifecycle/ directory, and replicate the DefaultPragmaMatchEndPlugin from platform/2-pragma/game-common/src/main/kotlin/pragma/matchlifecycle/. Add the MyCustomMatchEndDependentJob to the listOf to add the MyCustom service to the MatchEndPlugin.

package myproject.matchlifecycle
class MyCustomEndPlugin(val service: Service, val contentDataNodeService: ContentDataNodeService) : MatchEndPlugin {

    override suspend fun getMatchEndJobs(
        playerId: UUID,
        matchEnd: MatchEnd,
    ): List<MatchEndDependentJob> {
        return listOf(
            InventoryMatchEndDependentJob(playerId, matchEnd, service),
            ProgressionMatchEndDependentJob(playerId, matchEnd, service),
            MyCustomMatchEndDependentJob(playerId, matchEnd, service)
        )
    }
}

Remember to override default plugin behavior by specifying your plugin in your YAML config file.

Classes referenced in the config need to have their fully qualified name provided.
MyCustom example

The following config will override the MatchEndPlugin with our newer version that contains the MyCustom service.

  pluginConfigs:
    MatchLifecycleService.matchEndPlugin:
      class: "mycustom.matchlifecycle.MyCustomMatchEndPlugin"

Service-to-Service RPC #

Using the SERVICE session within a plugin enables communication with other services. This requires building a custom RPC endpoint using the SERVICE session, which needs its own request/response protos.

MyCustom example

The RPC endpoint should look something like this:

val result = service.requestRpc(
    MyCustomRpc.MyCustomActionServiceV1Request.newBuilder()
        .setCustomId(customId)
        .setCustomData(customData)
        .build(),
    MyCustomRpc.MyCustomActionServiceV1Response::class
)

when (result) {
    is PragmaResultResponse -> {
        logger.info("MyCustomAction returned ${result.response} successfully.")
    }
    is PragmaResultError -> {
        logger.error("Something went wrong with MyCustomAction.")
    }
}

Dependent Jobs #

The dependent job pattern handles interactions between multiple interdependent RPC service calls.

There are two key pieces: dependenciesMet and makeRequest.

dependenciesMet is called before running this dependent job to determine if prior RPC calls this job depends on have completed.

makeRequest is called when all required dependencies have been found on the context. Within this function you can perform calculations, business logic or send off requests to other services and await their response.

MyCustom example

The MyCustomMatchEndPlugin can trigger multiple interdependent RPC service calls. The MyCustomMatchEndDependentJob handles these interactions.

package myproject.matchlifecycle

class MyCustomMatchEndDependentJob(
    playerId: UUID,
    val matchEnd: MatchEnd,
    val service: Service,
) : MatchEndDependentJob() {
    override fun dependenciesMet(context: DependentJobContext): Boolean {
        return true
    }

    override suspend fun makeRequest(
        context: DependentJobContext,
        mutable: ThreadsafeMutable<GameDataRpc.MatchProcessedV3Notification.Builder>
    ) {
        val result = service.requestRpc(
          MyCustomRpc.MyCustomMatchEndActionServiceV1Request.newBuilder()
            .setPlayerId(playerId.toFixed128())
            .setMatchEnd(matchEnd.toProtoV4())
            .build(),
          MyCustomRpc.MyCustomMatchEndActionServiceV1Response::class
      )
         ...
    }
}

If you want to use the response from your custom service in future dependent jobs, make sure to set the response on the context.

MyCustom example
val result = service.requestRpc(
  MyCustomRpc.MyCustomMatchEndActionServiceV1Request.newBuilder()
    .setPlayerId(playerId.toFixed128())
    .setMatchEnd(matchEnd.toProtoV4())
    .build(),
  MyCustomRpc.MyCustomMatchEndActionServiceV1Response::class
)

when (result) {
  is PragmaResultResponse -> {
    val response = result.response
      mutable.write {
        ...
       context.setResult(result.response)
    }
    is PragmaResultError -> {
      ...
    }
}