Player Data Key Concepts #

In this topic, we’ll look at key concepts for creating your own player data features in Pragma Engine.

Player Data Service #

The Player Data service houses the structures and interfaces for building your own player data features. The service acts as a toolbox containing all the tools you can use to customize and integrate your player data feature into Pragma Engine. Since a feature can be designed for various different purposes, the Player Data service has endpoints for every type of gateway and data model for a player data feature.

Everything you author in the Player Data service is written in its own Maven module underneath the pre-existing 5-ext module in Pragma Engine. The Player Data service has a Maven module because the service uses reflection and code generation to streamline the process of writing customizable endpoints and data structures for a player data feature.

Code generation and reflection #

Pragma Engine uses protobuf definitions as the interface definition language (IDL) for serializing strongly typed data. The Player Data service, along with all other services in Pragma Engine, use protobufs for managing API endpoints and its corresponding data.

Instead of writing your endpoints in protos and Kotlin like a typical custom service, the Player Data service generates code you create in Kotlin into protobufs for the Pragma Engine layer. In other words, the interfaces utilized in the Player Data service to author your player data features reflect your code so you don’t have to write business logic in more than one language and in more than one place.

Modules #

The core of the Player Data service lies in the concept of a Player Data Module, which houses the Sub Service class for containing all the API endpoint logic (Operations), live data (Entities), and content data for a player data feature. Modules are designed to help organize and create maintainable code structures that the Player Data service can work with. For instance, when you design your own Player Data Module with Operations and data logic, the code you create will be generated into the larger Pragma Engine ecosystem and into the PragmaSDK for client use.

A Module can be designed for specific Player Data features like loadouts, battlepasses, and player ranks. It can also be designed to handle common events that occur throughout the lifecycle of a game or a player’s account history, such as granting rewards on account creation and season event gifts. Modules are able to access other Module Operations and data, so a player data feature can span across multiple different user-defined systems.

At a basic level, the real-time process of a player data feature in a Module works as follows:

  1. A player activates a Request Operation on the game client side.

  2. The Request Operation contacts PlayerDataService to connect to the corresponding Module’s PlayerDataSubService, which contains all the logic for modifying the Player Data content from the Request Operation.

  3. The PlayerDataSubService’s Operation function then returns a Response defined by the developer based on the Operation function’s business logic so the client can respond accordingly.

All of the code you build for a player data feature will reside in a Player Data Module folder in 5-ext/ext-player-data/player-data/src/main/kotlin. In short, Player Data Modules house everything related to your player data feature, such as the feature’s respective endpoints, business logic, and data modeling.

Authoring API endpoints and data #

A player data feature must have defined API endpoints so the endpoint’s assigned gateway can communicate with the backend for data requests and updates. These endpoints in the Player Data service are called Operations and are responsible for creating, managing, and assigning live data and JSON content for players.

Operation Request and Response types for a player data feature are authored by the developer and are given business logic inside the Operation’s respective Sub Service class. By defining the Operations yourself, you get to perfectly contextualize your request and response endpoints with your player data feature and how they’ll handle player data content involved in the call.

All data in a Player Data Module is defined by the developer as Kotlin classes before being assigned in a Sub Service’s Operation. As the developer, you get to define the live data structures and game data schemas.

In the Player Data service, you can utilize two different data structures for authoring a feature’s data: Entity and PlayerDataContentSchema. Note that a Sub Service can utilize both of these structures in tandem or just one of them.

Data StructuresDescription
EntityA wrapper that contains live player data authored in Kotlin and saved in the database.
PlayerDataContentSchemaA data class authored in Kotlin for structuring content as JSON catalogs. Every Sub Service has access to this interface and its user-defined content via the ContentLibrary class.

Since the Player Data service allows for a lot of flexibility in how you design your player data feature, it’s important to know how to model your data workflow in a Player Data Sub Service.

Because all of the code you author for a Player Data Sub Service is generated and reflected, the way you define Sub Services, Operations and data must follow the Player Data service’s conventions to operate properly. For example, some conventions include Sub Service classes inheriting the PlayerDataSubService interface, and all Operations containing the same parameters for its respective Kotlin function.

To learn more about each of these specific conventions, check out the Task pages for other topics in Player Data.

Performance and scalability #

The Player Data service is responsible for the routing and networking layer for player data features, as well as the data persistence layer behind your business logic. This means that as the developer, you only have to manage the design of your player data features rather than their implementation and context within the backend structure.

When designing a feature in the Player Data service, all data is cached and stored in a database by Pragma Engine, and all player data updates are performed in one database transaction. This means that any error when updating the player’s data results in zero data changes, and no partial processes will occur. If there is an error in a database transaction, Pragma Engine logs the database error so you can see what went wrong.

It’s important to design your player data features to limit the rate of Operation and database calls made by game client and server to Pragma Engine. The Player Data service is designed to handle high loads and scale, but it’s important to make your Operation workflow in a Sub Service as optimal as possible. Additionally, you’ll have visibility into your Sub Services’ Operation traffic through metrics via Grafana panels.

PragmaSDK #

Everything that you author on the Kotlin side of a player data feature is auto-generated into protobufs for the engine layer and Unreal structs in the PragmaSDK for use on the client side. The generated Unreal structs belong to a Sub Service in the PragmaSDK and are generated based on their definitions on the Kotlin side. In other words, every Sub Service has its own generated API where each Operation maps to a usable API method.

 Boxes and line arrows showcasing the Player Data SDK code generation step.

Since everything you author in the Player Data service is generated for the PragmaSDK, it’s also important to keep track of how you author your features in the backend, as they’ll directly affect how you interact with the service in the SDK.