Manage backfill tickets using AGS SDK
Overview
This guide covers the basics of managing backfill ticket using AccelByte Gaming Services (AGS) Game SDK. The AGS Game SDK is used to interact with AGS Matchmaking sessions and matchmaking backfill.
Goals
This article aims to provide an understanding of:
- Fetching session information from a dedicated server.
- Accepting or rejecting backfill proposals from a dedicated server.
- Enabling or disabling backfill from a dedicated server.
Prerequisites
To complete all the steps in this article, you will need:
A familiarity with AGS Lobby, Matchmaking and Session.
Access to the AGS Admin Portal.
- To have configured a session template of type dedicated server, a match ruleset, and a match pool.
Your game client integrated with AGS Matchmaking.
To have V2 sessions enabled in your DefaultEngine.ini (for Unreal Engine):
[OnlineSubsystemAccelByte]
bEnableV2Sessions=trueYour game servers to have the followings permissions:
Permissions Action Usage NAMESPACE:{namespace}:MATCHMAKING:BACKFILL
CREATE, READ, UPDATE, DELETE
To manage backfill ticket
Backfill proposal flow
This section provides an overview of the backfill proposal flow that is executed between AGS Matchmaking and a dedicated server (DS).
After a game session has been created and invites are sent to the matched players, AGS Session will request that a dedicated server be provisioned through the DS Hub.
Once a DS is ready, the connection information will be returned to the game session, and it will attempt to connect.
After the game session is successfully connected to the dedicated server, the server can query the session data.
If the game session is not full, it can match with additional players if either auto-backfill has been enabled or by submitting a backfill ticket directly to AGS Matchmaking.
If a backfill request has been submitted, either automatically or manually, AGS Matchmaking will return proposals to the dedicated server.
The dedicated server will then process the proposal and evaluate if it is viable. It is important to check if there is still room in the game session before accepting the proposal, as another player may have already taken the open spot due to a race condition.
The dedicated server then accepts or rejects the proposal, informing AGS Matchmaking. If accepted, the new player will receive an invite to join the game session.
Fetch session information
Once a game session is connected to a dedicated server, players will be able to use the connection details stored in the session. This will enable them to connect with the dedicated server. At the same time, the server can query information about the sessions and connected players.
- OSS
- Unity
To begin, you need to register your game server by retrieving the V2 session interface:
FOnlineSessionV2AccelBytePtr SessionInterface;
if (!ensure(FOnlineSessionV2AccelByte::GetFromWorld(GetWorld(), SessionInterface)))
{
return;
}Listen for an event which marks the session assignment to the server,
OnServerReceivedSession
:const FOnServerReceivedSessionDelegate OnServerReceivedSessionDelegate =
FOnServerReceivedSessionDelegate::CreateUObject(
this, &MyClass::OnServerReceivedSession);
FDelegateHandle OnServerReceivedSessionDelegateHandle =
SessionInterface->AddOnServerReceivedSessionDelegate_Handle(
OnServerReceivedSessionDelegate);Invoke the
RegisterServer
method:const FOnRegisterServerComplete OnRegisterServerCompleteDelegate =
FOnRegisterServerComplete::CreateUObject(
this, &MyClass::OnRegisterServerComplete);
SessionInterface->RegisterServer(SessionName, OnRegisterServerCompleteDelegate);Inside the delegate handler for
OnServerReceivedSession
, you can fetch the session the same way as a game client would. Retrieve the session interface again, then the session:// Here `SessionName` is a param to the session received delegate
FNamedOnlineSession* Session = SessionInterface->GetNamedSession(SessionName);
if (!ensure(Session != nullptr))
{
return;
}
The server can now interact with the session settings and data.
It's generally good practice to clear the OnServerReceivedSession
delegate using the FDelegateHandle
used earlier.
To begin, you need to listen for an event which marks session assignment to the server.
var dsHub = AccelByteSDK.GetServerRegistry().GetApi().GetDsHub();
dsHub.MatchmakingV2ServerClaimed += result =>
{
if (result.IsError)
{
// Do something if MatchmakingV2ServerClaimed fails
Debug.Log($"Error MatchmakingV2ServerClaimed, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if MatchmakingV2ServerClaimed is successfully received
};Invoke the
SendReadyMessage
method to mark the server as ready.infoPlease follow AMS Guideline on how to setup AGS multiplayer server.
var ams = AccelByteSDK.GetServerRegistry().GetAMS();
ams.SendReadyMessage();Inside
MatchmakingV2ServerClaimed
event, you can fetch the session ID and interact with it to get the session detail.var session = AccelByteSDK.GetServerRegistry().GetApi().GetSession();
var sessionId = result.Value.sessionId;
var gameMode = result.Value.gameMode;
var matchingAllies = result.Value.matchingAllies;
session.GetGameSessionDetails(sessionId, sessionResult =>
{
if (result.IsError)
{
// Do something if GetGameSessionDetails fails
Debug.Log($"Error GetGameSessionDetails, Error Code: {sessionResult.Error.Code} Error Message: {sessionResult.Error.Message}");
return;
}
// Do something if GetGameSessionDetails is successfully received
});
Enable and disable backfill
- OSS
- Unity
Match backfill can be enabled in one of two ways: first, as a configuration setting from within a match ruleset if it's applicable for your game mode, or second, you can create and submit a backfill ticket directly from the dedicated server.
To enable backfill, retrieve the session interface, then create a backfill ticket:
const FOnCreateBackfillTicketComplete OnCreateBackfillTicketCompleteDelegate =
FOnCreateBackfillTicketComplete::CreateUObject(
this, &MyClass::OnCreateBackfillTicketComplete);
SessionInterface->CreateBackfillTicket(
NAME_GameSession, OnCreateBackfillTicketComplete);
You can also supply a match pool name to this method if you want a particular match pool other than the original.
To disable backfill, delete the backfill ticket:
const FOnDeleteBackfillTicketComplete OnDeleteBackfillTicketCompleteDelegate =
FOnDeleteBackfillTicketComplete::CreateUObject(
this, &MyClass::OnDeleteBackfillTicketComplete);
SessionInterface->DeleteBackfillTicket(
NAME_GameSession, OnDeleteBackfillTicketCompleteDelegate);
This feature is not yet supported in AGS Game SDK for Unity.
Handle backfill proposals
It is recommended that the dedicated server periodically checks if the session is not at full capacity. This allows the server to recreate backfill tickets, as the team member of the session may be out of sync with the backfill ticket. By doing so, the existing game session has an opportunity to reach full capacity through the matchmaking process.
If session backfill has been requested, either by having auto-backfill configured in the match ruleset, or requested directly from the server, the server will begin to receive proposals if the game session has open spots for more players.
- OSS
- Unity
Matchmaking backfill proposals are sent to the dedicated server, where they can be accepted or rejected. Before registering the server, add a delegate handler for receiving backfill proposals:
FOnBackfillProposalReceivedDelegate OnBackfillProposalReceivedDelegate =
FOnBackfillProposalReceivedDelegate::CreateUObject(
this, &MyClass::OnBackfillProposalReceived);
FDelegateHandle OnBackfillProposalReceivedDelegateHandle =
SessionInterface->AddOnBackfillProposalReceivedDelegate_Handle(
OnBackfillProposalReceivedDelegate);
Inside that handler, you can decide whether to accept or reject the proposal. First, retrieve the session interface. Then, after some decision logic, accept or reject the proposal.
Add an event listener for receiving backfill proposals.
var dsHub = AccelByteSDK.GetServerRegistry().GetApi().GetDsHub();
MatchmakingV2BackfillProposalNotification backfillProposal;
dsHub.MatchmakingV2BackfillProposalReceived += result =>
{
if (result.IsError)
{
// Do something if MatchmakingV2BackfillProposalReceived fails
Debug.Log($"Error MatchmakingV2BackfillProposalReceived, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if MatchmakingV2BackfillProposalReceived is successfully received
backfillProposal = result.Value;
// Implement accept or reject the backfill proposal function here
};
Accept an entire backfill proposal
- OSS
- Unity
FOnAcceptBackfillProposalComplete OnAcceptBackfillProposalCompleteDelegate =
FOnAcceptBackfillProposalComplete::CreateUObject(
this, &MyClass::OnAcceptBackfillProposalComplete);
SessionInterface->AcceptBackfillProposal(
NAME_GameSession, Proposal, false, OnAcceptBackfillProposalCompleteDelegate);
var matchmaking = AccelByteSDK.GetServerRegistry().GetApi().GetMatchmakingV2();
var optionalParams = new AcceptBackfillProposalOptionalParams()
{
// Set this to true if you want to stop the backfilling
StopBackfilling = false
};
matchmaking.AcceptBackfillProposal(backfillProposal, optionalParams, result =>
{
if (result.IsError)
{
// Do something if AcceptBackfillProposal fails
Debug.Log($"Error AcceptBackfillProposal, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if AcceptBackfillProposal succeeded
});
Reject backfill proposal
- OSS
- Unity
FOnRejectBackfillProposalComplete OnRejectBackfillProposalCompleteDelegate =
FOnRejectBackfillProposalComplete::CreateUObject(
this, &MyClass::OnRejectBackfillProposalComplete);
SessionInterface->RejectBackfillProposal(
NAME_GameSession, Proposal, false, OnRejectBackfillProposalCompleteDelegate);
The third argument to both of the accept and reject methods is a boolean that tells the session interface whether you want to stop backfilling. In the examples, you will still receive backfill proposals after rejecting or accepting due to the false
argument passed to the respective methods.
var matchmaking = AccelByteSDK.GetServerRegistry().GetApi().GetMatchmakingV2();
// Set this to true if you want to stop the backfilling
bool stopBackfilling = false;
matchmaking.RejectBackfillProposal(backfillProposal, stopBackfilling, result =>
{
if (result.IsError)
{
// Do something if RejectBackfillProposal fails
Debug.Log($"Error RejectBackfillProposal, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if RejectBackfillProposal succeeded
});
If you still receive the backfill proposals after rejecting or accepting them, it may be due to the stopBackfilling
parameter's value being set to false
.
Backfill partially from a proposal
- OSS
- Unity
Listen for the incoming backfill proposal and try to process it by selecting the participant based on the ticket ID.
After the legitimate player accumulates into an array of FString
, pass the array to struct FAccelByteModelsV2MatchmakingBackfillAcceptanceOptionalParam.
Pass the struct to the AccelByte Session interface function to accept it (FOnlineSessionV2AccelByte::AcceptBackfillProposal
)
FOnBackfillProposalReceivedDelegate OnBackfillProposalReceivedDelegate =
FOnBackfillProposalReceivedDelegate::CreateUObject(
this, &MyClass::OnBackfillProposalReceived);
FDelegateHandle OnBackfillProposalReceivedDelegateHandle =
SessionInterface->AddOnBackfillProposalReceivedDelegate_Handle(
OnBackfillProposalReceivedDelegate);
void MyClass::OnBackfillProposalReceived(const FAccelByteModelsV2MatchmakingBackfillProposalNotif& BackfillProposalNotif)
{
TArray<FString> AcceptedTicketIDs{};
for (int i = 0; i < BackfillProposalNotif.AddedTickets.Num(); i++)
{
// Example to accept a specific player:
// Iterate the proposal tickets and the player ID inside the ticket.
if (BackfillProposalNotif.AddedTickets[i].Players.FindByPredicate([&](const FAccelByteModelsV2MatchmakingTicketPlayerData& PlayerData)
{
return (PlayerData.PlayerID.Equals(/*targeted player's ID to accept*/);
}) != nullptr)
{
// If the iteration found the targeted player, add the Player's s ticket to the list of accepted ticket array.
AcceptedTicketIDs.Add(BackfillProposalNotif.AddedTickets[i].TicketID);
}
}
FAccelByteModelsV2MatchmakingBackfillAcceptanceOptionalParam AcceptanceOptionalParameter{};
AcceptanceOptionalParameter.AcceptedTicketIDs = AcceptedTicketIDs;
SessionInterface->AcceptBackfillProposal(
NAME_GameSession,
BackfillProposalNotif,
false,
// handle accordingly based on the need
FOnAcceptBackfillProposalComplete::CreateLambda([&](bool bSuccess) { }),
AcceptanceOptionalParameter
);
}
Listen for the incoming backfill proposal and try to process it by selecting the participant based on the ticket ID.
After the legitimate player accumulates into an array of string
, pass the array to the AcceptBackfillProposal
optional parameter
var userIdToAccept = new string[]
{
"accepted-user-id-1",
"accepted-user-id-2",
};
var dsHub = AccelByteSDK.GetServerRegistry().GetApi().GetDsHub();
var matchmaking = AccelByteSDK.GetServerRegistry().GetApi().GetMatchmakingV2();
dsHub.MatchmakingV2BackfillProposalReceived += result =>
{
if (result.IsError)
{
// Do something if notification has an error
Debug.Log($"Error MatchmakingV2BackfillProposalReceived, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if MatchmakingV2BackfillProposalReceived is successfully received
var backfillNotification = result.Value;
System.Collections.Generic.List<string> acceptedIds = new System.Collections.Generic.List<string>();
// Example to accept a specific player:
// Iterate the proposal tickets and the player ID inside the ticket.
foreach (var backfillTicket in backfillNotification.BackfillProposalTickets)
{
var ticketWithAcceptedUserId = backfillTicket.players.Where(player =>
{
return userIdToAccept.Contains(player.playerId);
});
if (ticketWithAcceptedUserId.Any())
{
acceptedIds.Add(backfillTicket.ticketId);
}
}
var optionalParam = new AcceptBackfillProposalOptionalParams()
{
AcceptedTicketIds = acceptedIds.ToArray(),
StopBackfilling = false
};
matchmaking.AcceptBackfillProposal(backfillNotification, optionalParam, acceptResult =>
{
if (acceptResult.IsError)
{
// Do something if MatchmakingV2BackfillProposalReceived failed
Debug.Log($"Error AcceptBackfillProposal, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if AcceptBackfillProposal succeeded
});
};
Get a backfill ticket ID
When the auto_backfill match ruleset setting is enabled and the matchmaking process produces a non-full game, a backfill ticket will be generated before the session is created. The resulting effect is that when a DS receives its session, if desired, the DS can check for the presence of a backfill ticket ID to indicate whether the incoming match will have automatic backfill.
- OSS
- Unity
const FNamedOnlineSession* Session =
SessionInterface->GetNamedSession(NAME_GameSession);
if (!ensure(Session != nullptr))
{
return;
}
const TSharedPtr<FOnlineSessionInfoAccelByteV2> SessionInfo =
StaticCastSharedPtr<FOnlineSessionInfoAccelByteV2>(Session->SessionInfo);
if (!ensure(SessionInfo.IsValid()))
{
return;
}
const TSharedPtr<FAccelByteModelsV2GameSession> SessionData =
SessionInfo->GetBackendSessionDataAsGameSession();
if (!ensure(SessionData.IsValid()))
{
return;
}
const bool bIsBackfillEnabled = !SessionData->BackfillTicketID.IsEmpty();
var session = AccelByteSDK.GetServerRegistry().GetApi().GetSession();
string sessionId = "current-session-id";
session.GetGameSessionDetails(sessionId, result =>
{
if (result.IsError)
{
// Do something if GetGameSessionDetails fails
Debug.Log($"Error GetGameSessionDetails, Error Code: {result.Error.Code} Error Message: {result.Error.Message}");
return;
}
// Do something if GetGameSessionDetails succeeds
bool isBackfillEnabled = !string.IsNullOrEmpty(result.Value.backfillTicketId);
});
Troubleshooting
In this section, you can find common errors and issues that may occur when using the service, along with recommendations on how to resolve them.
Session update fails due to a version conflict
Currently, a DS will not automatically receive session updates, so it's possible that the server can run into issues trying to update a session. When the session data on the DS side becomes stale, updating the session can result in a version conflict error. While it's possible to detect this kind of error from the error message logged when invoking UpdateSession
, you can also make use of an error handler for this specific case:
- OSS
- Unity
FOnSessionUpdateConflictErrorDelegate OnSessionUpdateConflictErrorDelegate =
FOnSessionUpdateConflictErrorDelegate::CreateUObject(
this, &MyClass::OnSessionUpdateConflictError);
SessionInterface->AddOnSessionUpdateConflictErrorDelegate_Handle(
OnSessionUpdateConflictErrorDelegate);
Because there is no real generic solution for the session service, or the AGS OSS, to decide which session settings to update in the case of a conflict, the above delegate can be used to retry the update and/or to house logic for resolving conflicts.
When an update fails, the session data will automatically be refreshed from the backend, and the conflict error handler will receive a copy of the session settings passed to the failed update.
In Unity, if an update request failed because of old data, it will respond with error code ErrorCode.SessionVersionMismatch
. If you received this error code, you must query the backend first to get the latest session data, then retry the update with the latest session version.