Matchmaking Error Handling – Best Practices
Handling matchmaking connection errors is crucial to ensuring a smooth player experience. When a connection issue occurs, the game should provide clear feedback to the player and attempt to recover gracefully. Implementing proper error handling can prevent frustration and help players reconnect quickly without disrupting their gameplay. How to handle matchmaking connection issues:
- Display a clear and informative message based on the error of each of step delegates (i.e start matchmaking, joining session, travelling to the DS, etc), so players understand why the matchmaking flow failed. You can use Byte Wars tutorial module for the he Playing in DS flow reference (Unreal Engine or Unity).
- Disable matchmaking feature if the lobby connection is lost.
- Show a manual retry option in a popup or message in each of steps of the matchmaking.
Matchmaking Error Handling
- Unreal Engine OSS
- Unreal Engine SDK
- Unity
The Unreal Engine OSS has a built-in polling system to check the match ticket status. Once a match ticket is successfully created in the matchmaking service, the system will poll for updates every 15 seconds. The polling will automatically stop when the match ticket is either found or not found (response with error code 520303) in the matchmaking service. However, if a connection disruption occurs, but the player stays logged in, the polling will continue running in the background until it completes.
...
auto ABSubsystem = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);
auto SessionInterface = ABSubsystem->GetSessionInterface();
SessionInterface->OnMatchmakingCompleteDelegates.AddWeakLambda(this, [this](FName SessionName, bool bWasSuccessful)
{
if (bWasSuccessful)
{
// Do something when the matchmaking request is successful.
}
else
{
// Implement a solution to handle failed request.
}
});
...
Currently, if a matchmaking fails, the AGS OSS does not provide details about whether the failure was caused by a websocket timeout or another issue.
To retrieve the matchmaking status when using Unreal Engine AGS SDK, the game must implement a polling to retrieve the match status detail. We have this polling mechanism in the Unreal Engine AGS OSS with the polling interval is 15 seconds. If either the retrieving the match status has failed or the match ticket is not active and not proposed, the game should identify that the matchmaking process has failed.
...
bool isMatchFound{true};
...
...
FApiClientPtr ApiClient = FMultiRegistry::GetApiClient();
ApiClient->MatchmakingV2.GetMatchTicketDetails("<match-ticket-id>",
THandler<FAccelByteModelsV2MatchmakingCreateTicketResponse>::CreateLambda([&isMatchFound](const FAccelByteModelsV2MatchmakingGetTicketDetailsResponse& Response)
{
// Do something when the matchmaking request is successful.
// if the ticket is currently not active and not proposed means ticket is canceled
if(!Response.IsActive && Response.ProposedProposal.Status.IsEmpty())
{
isMatchFound = false;
}
}), FCreateMatchmakingTicketErrorHandler::CreateLambda([&isMatchFound](int32 ErrorCode, const FString& ErrorMessage, const FErrorCreateMatchmakingTicketV2& CreateTicketErrorInfo)
{
UE_LOG(LogTemp, Warning, TEXT("Error matchmaking request. Code: %d, Message: %s"), ErrorCode, *ErrorMessage);
isMatchFound = false;
})
);
...
There is built-in a polling to retrieve the match ticket status. If the match ticket is succesfully created on the matchmaking service, the Unity SDK will poll the match ticket status every 5 seconds. The polling will stop either if the match ticket is found or the matchmaking service is downn (HTTP error code 404). When the service is down, the polling will stop and trigger MatchmakingV2TicketExpired
callback.
...
public bool isMatchFound = true;
...
...
AccelByteSDK.AccelByteSDK.GetClientRegistry().GetApi().GetLobby().MatchmakingV2TicketExpired += notificationPayload =>
{
// Implement a solution to handle match ticket expiration.
isMatchFound = false;
};
...
Joining Session Error Handling
- Unreal Engine OSS
- Unreal Engine SDK
- Unity
...
auto ABSubsystem = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);
auto SessionInterface = ABSubsystem->GetSessionInterface();
SessionInterface->OnJoinSessionCompleteDelegates.AddWeakLambda(this, [this](Name SessionName, EOnJoinSessionCompleteResult::Type Result)
{
if(Result == EOnJoinSessionCompleteResult::Success)
{
// Do something when the joining game session is successful.
}
else
{
// Implement a solution to handle failed joining game session request.
}
});
...
Currently, if a matchmaking fails, the AGS OSS does not provide details about whether the failure was caused by a websocket timeout or another issue.
...
FApiClientPtr ApiClient = FMultiRegistry::GetApiClient();
ApiClient->Session.JoinGameSession("<SessionID>",
THandler<FAccelByteModelsV2GameSession>::CreateLambda([](const FAccelByteModelsV2GameSession& Result)
{
// Do something when the joining game session is successful.
}), FErrorHandler::CreateLambda([](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Warning, TEXT("Error joining game session. Code: %d, Message: %s"), ErrorCode, *ErrorMessage);
if(ErrorCode == static_cast<int32>(ErrorCodes::NetworkError))
{
// Implement a solution to handle HTTP retry timeout.
}
})
);
...
...
AccelByteSDK.GetClientRegistry().GetApi().GetSession().JoinGameSession("<sessionId>", result =>
{
if (!result.IsError)
{
// Do something when the joining game session is successful.
}
else
{
// Implement a solution to handle failed request.
Debug.LogWarning($"Unable to login. Code: {result.Error.Code}, Message: {result.Error.Message}");
if(result.Error.Code.Equals(ErrorCode.NetworkError))
{
// Implement a solution to handle HTTP retry timeout.
}
}
});
...
Sessions Timeouts Handling
When the player is disconnected from the session service for any reason, the session service will identify the player as inactive member of the session. The inactive member has a Inactive Timeout
limit which could be configured here. You can set the value of Inactive Timeout
greater than the websocket connection timeout to ensure when the websocket is succesfully reconnected, the player is still in the game session.
Recovery Game Session
If a player gets disconnected from the WebSocket connection but hasn’t been removed from the game session by session service, the game client can restore their session. This means that once the WebSocket connection is successfully re-established, the player can seamlessly return to their session.
- Unreal Engine OSS
- Unreal Engine SDK
- Unity
- Restore the game session.
...
auto ABSubsystem = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);
auto SessionInterface = ABSubsystem->GetSessionInterface();
SessionInterface->RestoreActiveSessions("<UserId>",
FOnRestoreActiveSessionsComplete::CreateWeakLambda(this, [this](const FUniqueNetId& LocalUserId, const FOnlineError& ResultState)
{
if (ResultState.bSucceeded)
{
// Do something when the restoring game session is successful.
}
else
{
// Implement a solution to handle failed request.
}
})
);
...
- Rejoin the DS after succesfully restoring the game session.
...
auto ABSubsystem = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);
auto SessionInterface = ABSubsystem->GetSessionInterface();
auto ABSessionInterface = StaticCastSharedPtr<FOnlineSessionV2AccelByte>(SessionInterface);
FString ServerAddress = "";
ABSessionInterface->GetResolvedConnectString(Name_GameSession, ServerAddress);
APlayerController::ClientTravel(ServerAddress, TRAVEL_Absolute);
...
- Restore the game session.
...
FAccelByteModelsV2GameSession CurrentGameSession{};
...
...
FApiClientPtr ApiClient = FMultiRegistry::GetApiClient();
ApiClient->Lobby.SetConnectSuccessDelegate(Lobby::FConnectSuccess::CreateLambda([this]()
{
...
ApiClient->Session.GetMyGameSessions(THandler<FAccelByteModelsV2PaginatedGameSessionQueryResult>::CreateLambda([&CurrentGameSession](const FAccelByteModelsV2PaginatedGameSessionQueryResult& Result)
{
// Do something when the retrieving game session is successful.
if(Result.Num())
{
// Commonly, the player only has 1 active session.
CurrentGameSession = Result[0];
}
}), FErrorHandler::CreateLambda([](int32 ErrorCode, const FString& ErrorMessage)
{
UE_LOG(LogTemp, Warning, TEXT("Error retrieving game session. Code: %d, Message: %s"), ErrorCode, *ErrorMessage);
// Implement a solution to handle failed requst.
})
);
...
}));
ApiClient->Lobby.Connect();
...
- Rejoin the DS after succesfully restoring the game session.
...
FString Url = FString::Printf(TEXT("%s:%d"), *CurrentGameSession.DSInformation.Server.Ip, DSInformation.Server.Port);
APlayerController::ClientTravel(Url, TRAVEL_Absolute);
...
- Restore the game session.
...
SessionV2GameSession currentGameSession;
...
...
AccelByteSDK.GetClientRegistry().GetApi().GetSession().GetUserGameSessions(null, null, null, result =>
{
if (!result.IsError)
{
// Do something when the retrieving game session is successful.
if(result.Value.data.Length > 0)
{
CurrentGameSession = result.Value.data[0];
}
}
else
{
// Implement a solution to handle failed request.
}
});
...
- Rejoin the DS after succesfully restoring the game session.
...
ushort port = currentGameSession.dsInformation.server.port;
string ip = currentGameSession.dsInformation.server.ip;
GameManager.Instance.StartAsClient(ip, port, null);
...