Integrating the Pragma Server SDK #

Now that we’ve created our initial files, we can start building out the first piece in earnest. We’ll start building out PragmaGameServer with the necessary functionality to communicate with the Pragma Platform. This contains the basic logic for communicating with Pragma Engine.

Setting up the header file #

  1. Open PragmaGameServer.h.

  2. In your PragmaGameServer.h header file, ensure that your includes look like the following:

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "Dto/PragmaGameInstanceRpcDto.h"
#include "PragmaRuntime.h"
#include "PragmaPlayer.h"
#include "PragmaGameServerSubsystem.h"
#include "PragmaGameServer.generated.h"
  1. Below the includes, add the following:
class UPragmaSession;

UCLASS(Config=Game)
  1. In the UPragmaGameServer class under GENERATED_BODY(), add the following code:
public:
	// Initializes the server and begins broadcasting capacity to the Pragma backend.
	void Start();

Building PragmaGameServer functionality #

Now, let’s provide the definition for Start().

  1. Open PragmaGameServer.cpp and add the following code:
void UPragmaGameServer::Start()
{
	InitConnectionDetails();
	
	auto* World = GetWorld();
	checkf(World, TEXT("PragmaGameServer -- Must initialize UPragmaGameServer with an outer object that has a UWorld."));
	auto* GameInstance = World->GetGameInstance();
	check(GameInstance);
	if (!GameInstance->IsDedicatedServerInstance())
	{
		PRAGMA_LOG(Verbose, "PragmaGameServer --  NOT running because we are not a dedicated server.");
		return;
	}
	PRAGMA_LOG(Log, "PragmaGameServer -- starting...");

	Subsystem = GameInstance->GetSubsystem<UPragmaGameServerSubsystem>();
	Runtime = Subsystem->Runtime();
	Server = Subsystem->Server();
	
	Runtime->Config().PartnerBackendAddress = "http://" + PRAGMA_ADDRESS + ":" + FString::FromInt(PRAGMA_PORT);
	Runtime->Config().PartnerSessionSocialAuthToken = SOCIAL_TOKEN;
	Runtime->Config().PartnerSessionGameAuthToken = GAME_TOKEN;
	
	Runtime->Server()->Connect(Pragma::FServer::FConnectedDelegate::CreateLambda([this](const TPragmaResult<>& Result)
		{
			if (Result.IsFailure())
			{
				PRAGMA_LOG(Log, "Connect failed");
				return;
			}
			ReportCapacity();
		}));
	
	if (!FParse::Value(FCommandLine::Get(), *ServerIdSourceCliParam, ServerId) || ServerId.IsEmpty())
	{
		ServerId = FGuid::NewGuid().ToString(EGuidFormats::DigitsWithHyphens);
		PRAGMA_LOG(Error, "PragmaGameServer -- No ServerId specified with configured command line param '%s'. A new ServerId has been generated: %s", *ServerIdSourceCliParam, *ServerId);
	}
}
  1. Start() depends on several helper methods, so let’s define those before we dig into what’s happening. Go back to PragmaGameServer.h, then add the following under the protected access modifier:
protected:
    UPROPERTY()
	UPragmaGameServerSubsystem* Subsystem;
	
	Pragma::FServerPtr Server;
	Pragma::FRuntimePtr Runtime;

These are variables to hold the Pragma Game Server Subsystem and pointers to the Pragma Server and Runtime objects.

  1. Also add the following under the private access modifier:
private:
    void InitConnectionDetails();
	void ReportCapacity();

    FString PRAGMA_ADDRESS = "127.0.0.1";
	int32 PRAGMA_PORT = 10100;
	FString SOCIAL_TOKEN = "";
	FString GAME_TOKEN = "";
  1. Don’t worry about the tokens being empty. We’ll create our tokens in a later step. For now, switch back over to PragmaGameServer.cpp and define InitConnectionDetails().
void UPragmaGameServer::InitConnectionDetails()
{
	if (!FParse::Value(FCommandLine::Get(), *HostnameCliParam, HostConnectionDetails.Hostname))
	{
		PRAGMA_LOG(Error, "PragmaGameServer -- Failed to get Hostname from cli parameter '%s' and no hostname was configured in ini. This is likely a problem with backend configuration. Please contact your Pragma representative with this error message.", *HostnameCliParam);
		ShutdownServer();
		return;
	}
	checkf(GetWorld(), TEXT("PragmaGameServer -- needs to be created with an outer class that has access to World."));
	HostConnectionDetails.Port = GetWorld()->URL.Port;
	PRAGMA_LOG(Log, "PragmaGameServer -- Game server is located at: %s:%d", *HostConnectionDetails.Hostname, HostConnectionDetails.Port)
}
  1. InitConnectionDetails() uses some variables and methods that we haven’t yet defined, so switch back over to PragmaGameServer.h and add the following:
public:
	// Shuts down the server (closes the application).
	void ShutdownServer();

private:
    FPragma_GameInstance_HostConnectionDetails HostConnectionDetails;

Back in PragmaGameServer.cpp, define ShutdownServer():

void UPragmaGameServer::ShutdownServer()
{
	FGenericPlatformMisc::RequestExit(false);
}
InitConnectionDetails overview
This method ensures that we have the hostname and port for our game server. This information is required for the game clients to connect to the game server. Our game server provides this information to Pragma, which then provides the connection details to the game clients when the game server is ready to accept clients.
  1. We haven’t yet defined every helper that our Start() method calls, so let’s continue that work. In the cpp file, provide the following definition for ReportCapacity():
void UPragmaGameServer::ReportCapacity()
{
	FParse::Value(FCommandLine::Get(), TEXT("-PragmaReportCapacityTimeout="), ReportCapacityTimeout);
	
	Server->MatchApi().OnGameStart.AddWeakLambda(this, [this](FPragma_GameInstance_GameStart GameStart)
	{
		OnGameStartDataReceived.Broadcast(MoveTemp(GameStart));
	});
	
	Server->MatchApi().OnGameStartFailed.AddWeakLambda(this,[this](FPragmaError Error)
	{
		PRAGMA_LOG(Error, "PragmaGameServer -- ReportCapacity failed with error: %s", *Error.ErrorCode());
		ShutdownServer();
	});
	
	Server->MatchApi().StartReportCapacityPolling(ServerId, GameServerVersion(), FString{}, ReportCapacityTimeout);
}
ReportCapacity overview
ReportCapacity() is called when the game server successfully connects to the Pragma backend. It starts report capacity polling, which is the game server reporting to the pragma backend how many matches it is currently running and how many it can handle. It also reports with its unique ID and its version. This report occurs every second.
  1. ReportCapacity() has references to ReportCapacityTimeout, ServerId, and GameServerVersion(), so let’s define those in our header. Switch back to PragmaGameServer.h and add the following:
protected:
	// How long to wait for a game to be allocated before shutting down the server with a failure.
	// -1 means it will never time out. Override with -PragmaReportCapacityTimeout=1234.
	UPROPERTY(Config)
	float ReportCapacityTimeout{10.f};

private:
    const FString& GameServerVersion();
    FString ServerId;
  1. Then in PragmaGameServer.cpp, define GameServerVersion():
const FString& UPragmaGameServer::GameServerVersion()
{
	FParse::Value(FCommandLine::Get(), TEXT("-PragmaGameServerVersion="), TestGameVersion);
	return TestGameVersion;
}

This method sets the game server version. In this tutorial, we’ll set the version in the header file. The two methods we just defined use variables that we have not yet defined, so let’s go back to PragmaGameServer.h and create them:

public:
	DECLARE_EVENT_OneParam(UPragmaGameServer, FGameStartDataReceivedEvent, FPragma_GameInstance_GameStart /* GameStartData */)
	FGameStartDataReceivedEvent OnGameStartDataReceived;

protected:
	UPROPERTY(Config)
	FString TestGameVersion{ "GameServerVersion1" };

The game server version can be set via override, commandline parameter, or be hardcoded. Pragma Engine can enforce game server version compatibility; for this tutorial, we’re hardcoding the game server version to GameServerVersion1. In a later step, we’ll enforce this game server version.

Now that we’ve built all the functions that Start() depends on, we can now look at what it does. After ensuring that we have the connection details for the game server, it does some Unreal boilerplate, then checks to ensure we are a game server. It then initializes the Pragma SDK and connects to the Pragma Engine backend. Finally, it checks that the game server has been assigned an ID. If no ID has been assigned, it generates one.