Skip to main content

Manage backfill tickets using AGS SDK

Last updated on November 18, 2024

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.

  • Your game client integrated with AGS Matchmaking.

  • To have V2 sessions enabled in your DefaultEngine.ini (for Unreal Engine):

    [OnlineSubsystemAccelByte]
    bEnableV2Sessions=true
  • Your game servers to have the followings permissions:

    PermissionsActionUsage
    NAMESPACE:{namespace}:MATCHMAKING:BACKFILLCREATE, READ, UPDATE, DELETETo 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).

  1. 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.

  2. Once a DS is ready, the connection information will be returned to the game session, and it will attempt to connect.

  3. After the game session is successfully connected to the dedicated server, the server can query the session data.

  4. 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.

  5. If a backfill request has been submitted, either automatically or manually, AGS Matchmaking will return proposals to the dedicated server.

  6. 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.

  7. 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.

  1. To begin, you need to register your game server by retrieving the V2 session interface:

    FOnlineSessionV2AccelBytePtr SessionInterface;
    if (!ensure(FOnlineSessionV2AccelByte::GetFromWorld(GetWorld(), SessionInterface)))
    {
    return;
    }
  2. 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);
  3. Invoke the RegisterServer method:

    const FOnRegisterServerComplete OnRegisterServerCompleteDelegate =
    FOnRegisterServerComplete::CreateUObject(
    this, &MyClass::OnRegisterServerComplete);
    SessionInterface->RegisterServer(SessionName, OnRegisterServerCompleteDelegate);
  4. 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.

tip

It's generally good practice to clear the OnServerReceivedSession delegate using the FDelegateHandle used earlier.

Enable and disable backfill

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);
note

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);

Handle backfill proposals

tip

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.

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.

Accept an entire backfill proposal

FOnAcceptBackfillProposalComplete OnAcceptBackfillProposalCompleteDelegate =
FOnAcceptBackfillProposalComplete::CreateUObject(
this, &MyClass::OnAcceptBackfillProposalComplete);
SessionInterface->AcceptBackfillProposal(
NAME_GameSession, Proposal, false, OnAcceptBackfillProposalCompleteDelegate);

Reject backfill proposal

FOnRejectBackfillProposalComplete OnRejectBackfillProposalCompleteDelegate = 
FOnRejectBackfillProposalComplete::CreateUObject(
this, &MyClass::OnRejectBackfillProposalComplete);
SessionInterface->RejectBackfillProposal(
NAME_GameSession, Proposal, false, OnRejectBackfillProposalCompleteDelegate);
note

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.

note

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

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
);
}

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.

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();

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:

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.

note

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.