Operations Tasks #
Operations can serve multiple different purposes when designing the endpoints and data modeling for a player data feature. This page describes how to create an Operation, as well as common Operation structures that are frequently implemented in a Player Data Sub Service.
Common tasks when authoring an Operation include:
- Defining a Sub Service
- Creating the Request and Response classes
- Authoring an Operation function
- Modifying players’ data
- Accessing Content Schema data in an Operation
- Performing a Content Request
- Utilizing an Operation in the PragmaSDK
Define a Sub Service #
Sub Services are defined as Kotlin classes and house Operation functions.
All Player Data Sub Services are authored in the 5-ext/ext-player-data
directory and are responsible for housing Operation functions.
To create a Sub Service, go to the 5-ext/ext-player-data
directory and create a new Kotlin file for housing your Sub Service. Then, create a Kotlin class that inherits the PlayerDataSubService
interface with a primary constructor that takes a parameter for PlayerDataContentLibrary
. This parameter accesses the Player Data service’s content ecosystem for storing and retrieving content data.
Learn more about PlayerDataContentLibrary
by checking out the Content Library section in the Content overview page.
Below is an example of a Sub Service and its generated SDK API:
package exampleGamesStudio.playerdata
class ExampleOperations(contentLibrary: PlayerDataContentLibrary): PlayerDataSubService(contentLibrary)
namespace PragmaPlayerData {
class PRAGMASDK_API ExampleOperationsSubService {
public:
void SetDependencies(FPlayerDataRequestDelegate Delegate);
private:
FPlayerDataRequestDelegate RequestDelegate;
};
}
After the SubService
class is defined, you must run a make ext
command to generate code for the engine layer and build 5-ext
with your new Player Data classes.
Create Request and Response classes #
An Operation’s Request
and Response
payloads are defined as Kotlin classes.
An Operation has a Request
and Response
class that must be defined before you can write any business logic for a player data endpoint. These classes also allow you to define specific fields for the Operation’s Request
or Response
type depending on what is necessary for your Operation.
First, make sure that you’ve already created your PlayerDataSubService
class that will use your Operation.
Author your Request
and Response
classes so that they inherit the PlayerDataRequest
and PlayerDataResponse
interfaces. Without these structures, the Player Data service won’t be able to identify the Request
and Response
classes used for a specific Operation and won’t be able to generate into protobufs or SDK APIs.
Below is an example of what the classes for an Operation’s Request
and Response
type look like in Kotlin and their generated protobuf and SDK code:
data class ExampleEchoRequest(
val message: String
): PlayerDataRequest
data class ExampleEchoResponse(
val message: String
): PlayerDataResponse
message ExampleEchoRequestProto {
string message = 1;
}
message ExampleEchoResponseProto {
string message = 1;
}
USTRUCT(BlueprintType, Category=Pragma)
struct FPragma_PlayerData_ExampleEchoRequestProto
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Pragma)
FString Message;
};
USTRUCT(BlueprintType, Category=Pragma)
struct FPragma_PlayerData_ExampleEchoResponseProto
{
GENERATED_BODY()
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category=Pragma)
FString Message;
};
After the Request
and Response
classes are defined, you must run a make ext
command to generate code for the engine layer and build 5-ext
with your new Player Data classes.
Author an Operation function #
An Operation’s business logic is written as Kotlin functions inside a Sub Service’s class.
Once you have the PlayerDataRequest
and PlayerDataResponse
classes defined, you can use those classes to author a specific Kotlin function for implementing business logic into an Operation’s workflow. These functions are always written inside a PlayerDataSubService
class.
In order for an Operation to work properly, the function must start with the @PlayerDataOperation()
annotation. This annotation contains a parameter for sessionTypes
, which allows you to limit and assign Pragma Engine gateway session types that can call this Operation. These gateways can be either PLAYER
, OPERATOR
, PARTNER
, or SERVICE
.
An Operation must have a unique name and a parameter for the request
property and the context
property.
After the Operation class is defined, you run a make ext
command to generate code for the engine layer and build 5-ext
with your new Player Data classes.
An example of an Operation function’s structure in a Sub Service and its generated SDK code can be seen below:
package exampleGamesStudio.playerdata
data class ExampleEchoRequest(
val message: String
): PlayerDataRequest
data class ExampleEchoResponse(
val message: String
): PlayerDataResponse
class ExampleOperations(contentLibrary: PlayerDataContentLibrary): PlayerDataSubService(contentLibrary) {
@PlayerDataOperation(sessionTypes = [SessionType.PLAYER]) // Required Annotation
fun echo(
// Function must be defined with these parameters
request: ExampleEchoRequest,
context: Context
): ExampleEchoResponse { // Function must return a PlayerDataResponse class
TODO("implement")
}
}
namespace PragmaPlayerData {
class PRAGMASDK_API ExampleOperationsSubService {
public:
void SetDependencies(FPlayerDataRequestDelegate Delegate);
/* The parameters correspond to the fields of its request. */
void Echo(const FString& Message, FOnExampleOperationsEchoDelegate Delegate) const;
private:
FPlayerDataRequestDelegate RequestDelegate;
};
}
Every Operation includes a generated Delegate
for the SDK, which provides access to the Operation’s Response
object. The SDK only calls the generated Delegate
after the client side cache has been updated. This means that anytime you access cached data in the Delegate
, the Delegate
provides the most up to date version of the player’s data.
See the Player Data Service SDK reference topic for more information.
Below is an example of a generated Unreal Delegate
:
DECLARE_DELEGATE_OneParam(FOnExampleOperationsEchoDelegate, TOptional<FPragma_PlayerData_EchoResponseProto>);
Modify player data #
Write business logic for creating and modifying live data using the Snapshot
and Context
objects.
After your Operation has Request
and Response
classes along with an Operation function, you can write the business logic involving live data by using context
and PlayerDataSnapshot
.
The context
parameter provides access to PlayerDataSnapshot
and playerId
, and includes helper functions for making content data Requests
and retrieving other Sub Service Operations. PlayerDataSnapshot
is a class containing multiple helper functions specifically for modifying live data (Entities).
These two objects are often used in tandem when authoring an Operation’s business logic. Check out the Reference documentation on Context and Snapshot to learn more about the ways these two objects can be used in an Operation,
To create or modify data in an Operation, integrate an instance of the context
or context.snapshot
object into your Operation function’s business logic. Usually you’ll want to integrate this instance as a value or variable.
Below is an example of a PlayerDataSnapshot
helper function utilized in an Operation function:
@PlayerDataOperation(sessionTypes = [SessionType.PLAYER])
fun echo(
request: ExampleEchoRequest,
context: Context
): ExampleEchoResponse {
...
val entity = context.snapshot.getOrCreateUniqueEntity(name)
...
return ExampleEchoResponse()
}
}
Access Content Schema data in an Operation #
Data using a Content Schema structure is accessed using the Content Library object.
Every Sub Service class is constructed with a parameter including the PlayerDataContentLibrary
object. This object contains a function called getCatalog()
that allows the developer to acquire content data so an Operation can modify it. If you want to get a specific value from a JSON catalog entry use the getValue()
function.
To learn more about content data and how you can use it in an Operation, see the Content Overview page.
Below is an example of getCatalog()
acquiring data from a Content Schema’s associated JSON file:
val ExampleContentData = contentLibrary.getCatalog("custom-content-item-1.json", ExampleContentSchema::class).getValue(“exampleData”)
Perform a Content Request #
The Context object contains a function for performing Content Schema defined Request endpoints.
Content-oriented endpoints have Request
entries that can be accessed in an Operation function by utilizing the context
object. This object contains doContentRequest()
, which allows you to perform Content Schema defined ContentRequest
endpoints.
To learn more about content data endpoints, see the Author Content Endpoints Task.
Below is an example of a ContentRequest
accessing a part of its data called exampleEndpoint
:
val response: Response =
context.doContentRequest(exampleContentRequest1.exampleRequest)
Utilize an Operation in the PragmaSDK #
Operations are accessed through the UPragmaPlayerDataService
on the game client and UPragmaPlayerDataPartnerService
for the game server.
To access an Operation in the Pragma SDK, first you need to know what kind of session the Operation falls under. For example, the UPragmaPlayerDataService
provides access to Operations allowed on a player session and the UPragmaPlayerDataPartnerService
provides access to Operations allowed on a partner session.
Operations are also accessed through the Player Data Service API and the Operation’s respective Sub Service. The Operation’s parameters correspond with the properties of the Kotlin Request
class. The last parameter of the Operation is a delegate
that provides access to the Response.
Below is an example of accessing an Operation called Echo()
that belongs to the ExampleOperationsSubService
:
Player->PlayerDataService()->ExampleOperationsSubService().Echo(
"Hello from the client!", // request property
FOnExampleOperationsExampleDelegate::CreateLambda(
[](TOptional<FPragma_PlayerData_EchoResponseProto> Response){
if (Response)
{
auto DataFromPragmaOperation = Response.GetValue().DataForClient;
}
else
{
/* If there was an error with the operation, check the logs. */
}
}
)
);