Appearance
Custom Connection Handshake Data
Learn how to send game-specific data from the client during login and at session start, and how to read that data on the server.
Appearance
Learn how to send game-specific data from the client during login and at session start, and how to read that data on the server.
The Metaplay handshake exposes several hooks where your game can attach a custom payload to the connection flow. The client can attach data to the login request and to the session start request. The server can attach data to the session start response. All are sent after Logic Version negotiation is complete.
ILoginRequestGamePayload is sent by the client during login.ISessionStartRequestGamePayload is sent by the client at session start.ISessionStartSuccessGamePayload is sent by the server in the session start response.Each payload is a [MetaSerializableDerived] type defined in shared code, then supplied and read on each side. The sections below walk through each one.
The client-side hooks live on your connection delegate. Subclass DefaultMetaplayConnectionDelegate and override the hooks you need in the sections below:
public class GameConnectionDelegate : DefaultMetaplayConnectionDelegate
{
public override Handshake.ILoginRequestGamePayload GetLoginPayload() { /*...*/ }
public override ISessionStartRequestGamePayload GetSessionStartRequestPayload() { /*...*/ }
}Register it with MetaplayClient.Initialize():
MetaplayClient.Initialize(new MetaplayClientOptions
{
/* ... */
ConnectionDelegate = new GameConnectionDelegate(),
});ILoginRequestGamePayload is sent by the client during login. It can be used to pass login-time data the server needs while setting up the session, such as a platform authentication token.
API Not Yet Available
The login payload is intended for login management, for which no public API is currently exposed. For attaching general game data, use the Session Start Request Payload instead.
For example, if we had a platform authentication system whose user ID we wanted to forward to the backend for server-side verification, we can attach that user ID to the login request.
Define the payload type in shared code accessible to both the client and the server:
[MetaSerializableDerived(1)]
public class GameLoginPayload : Handshake.ILoginRequestGamePayload
{
[MetaMember(1)] public string PlatformUserId { get; private set; }
GameLoginPayload() { }
public GameLoginPayload(string platformUserId) { PlatformUserId = platformUserId; }
}Supply it from the client:
Override GameConnectionDelegate.GetLoginPayload():
public override Handshake.ILoginRequestGamePayload GetLoginPayload()
=> new GameLoginPayload(PlatformServices.UserId);Override BotClient.CreateLoginRequestGamePayload():
protected override Handshake.ILoginRequestGamePayload CreateLoginRequestGamePayload()
=> new GameLoginPayload(platformUserId: null);On the server, the login payload is delivered to SessionActor.GameCreatePlayerSessionParams(). The server can handle the payload there, or forward it to the PlayerActor, as this example does. Define a PlayerSessionGameParamsBase subclass to carry it to the player actor:
public class GamePlayerSessionParams : PlayerSessionGameParamsBase
{
public GameLoginPayload LoginPayload { get; set; }
}Populate it in SessionActor.GameCreatePlayerSessionParams():
protected override PlayerSessionGameParamsBase GameCreatePlayerSessionParams(
SessionStartParams sessionStart, SessionToken sessionToken)
{
var loginPayload = (GameLoginPayload)sessionStart.Meta.LoginGamePayload;
// Pass to PlayerActor. Also possible to handle here.
return new GamePlayerSessionParams { LoginPayload = loginPayload };
}Then read it in PlayerActor.OnClientSessionHandshakeAsync():
protected override async Task OnClientSessionHandshakeAsync(PlayerSessionParams sessionParams)
{
var gameParams = (GamePlayerSessionParams)sessionParams.SessionGameParams;
_log.Debug("Player logging in with platform id {Id}", gameParams.LoginPayload.PlatformUserId);
}ISessionStartRequestGamePayload is sent by the client at session start. It can be used to pass data the server should act on right at session start, such as a state from an external service.
For example, if an external A/B testing system assigns each player a test group, we can include that group in the session start request so the server can record or act on it immediately on session start.
Define the payload type in shared code accessible to both the client and the server:
[MetaSerializableDerived(1)]
public class GameSessionStartPayload : ISessionStartRequestGamePayload
{
[MetaMember(1)] public string ActiveABTestGroup { get; private set; }
GameSessionStartPayload() { }
public GameSessionStartPayload(string abGroup) { ActiveABTestGroup = abGroup; }
}Supply it from the client:
Override GameConnectionDelegate.GetSessionStartRequestPayload():
public override ISessionStartRequestGamePayload GetSessionStartRequestPayload()
=> new GameSessionStartPayload(ABTestManager.CurrentGroup);Override BotClient.CreateSessionStartRequestGamePayload():
protected override ISessionStartRequestGamePayload CreateSessionStartRequestGamePayload()
=> new GameSessionStartPayload(abGroup: null);On the server, the payload is available directly on PlayerSessionParams in PlayerActor.OnClientSessionHandshakeAsync():
protected override async Task OnClientSessionHandshakeAsync(PlayerSessionParams sessionParams)
{
var payload = (GameSessionStartPayload)sessionParams.SessionStartPayload;
Model.ActiveABTestGroup = payload.ActiveABTestGroup;
}ISessionStartSuccessGamePayload is sent by the server in the session start response, and the client reads it once the session has started. It can be used to hand the client server-computed data it needs at session start.
For example, if the server assigned each player to a matchmaking region, we can send that region down to the client so it connects to the right services.
Define the payload type in shared code accessible to both the client and the server:
[MetaSerializableDerived(1)]
public class GameSessionStartSuccessPayload : ISessionStartSuccessGamePayload
{
[MetaMember(1)] public string AssignedRegion { get; private set; }
GameSessionStartSuccessPayload() { }
public GameSessionStartSuccessPayload(string assignedRegion) { AssignedRegion = assignedRegion; }
}Supply it from the server by overriding SessionActor.GameGetSessionStartPayload():
protected override async Task<ISessionStartSuccessGamePayload> GameGetSessionStartPayload(SessionStartParams sessionStart)
{
string region = await ResolveRegionAsync(sessionStart);
return new GameSessionStartSuccessPayload(region);
}On the client, the payload is delivered on MetaplaySession.SessionStartGamePayload:
Read it after ConnectAsync() completes:
async Task SessionLoop()
{
...
MetaplaySession session = await MetaplayClient.ConnectAsync();
var payload = (GameSessionStartSuccessPayload)session.SessionStartGamePayload;
Debug.Log($"Server assigned region {payload.AssignedRegion}");
...
}Read it in OnSessionStartedAsync():
Task IMetaplayLifecycleDelegate.OnSessionStartedAsync(IMetaplayLifecycleDelegate.SessionStartedArgs args)
{
var payload = (GameSessionStartSuccessPayload)args.Session.SessionStartGamePayload;
Debug.Log($"Server assigned region {payload.AssignedRegion}");
return Task.CompletedTask;
}Read it in BotClient.OnSessionStartedAsync():
protected override Task OnSessionStartedAsync(BotSessionStartedArgs args)
{
var payload = (GameSessionStartSuccessPayload)args.SessionStartGamePayload;
_log.Debug("Server assigned region {Region}", payload.AssignedRegion);
return Task.CompletedTask;
}