Lobby WebSocket Recovery: Handling Disruptions and Reconnection
In real-time applications, maintaining a stable WebSocket connection is essential for a smooth user experience. However, network disruptions or server issues when there is service maintenance can cause connection failures. When retry attempts reach their timeout limit and reconnection is unsuccessful, implementing fallback mechanisms becomes crucial to ensure that key features remain functional. This article outlines best practices for Lobby WebSocket recovery, helping developers build more resilient and user-friendly systems. To avoid a bad user experience for the players, developers should:
β Display clear UI notifications when a connection is lost.
β Provide manual reconnection options when automatic methods fail.
By handling WebSocket reconnections properly, developers can ensure a smooth, uninterrupted experience for players. π
Websocket Reconnect Strategyβ
- Unreal Engine
- Unity
The default websocket reconnection strategy is using Balanced Reconnect Strategy. Here's how the general reconnect strategy works:
- The first reconnect starts with an initial backoff delay. The value depends on the reconnect strategy.
- Each retry backoff delay is multiplied with the base factor backoff of the reconnect strategy.
- The backoff delay is clamped between 1 seconds as minimum value, and the maximum retry interval as maximum value.
- A small random variation is added by taking a value between -backoff delay and +backoff delay, dividing it by four, and applying it to the current backoff delay. This prevents retries from happening at perfectly predictable intervals.
- If the total time spent on retries exceeds the total timeout of the reconnect strategy, the system stops reconnecting and triggers OnConnectionClosed delegate with error status code is
EWebsocketErrorTypes::DisconnectFromExternalReconnect
.
The websocket reconnection strategy support was implemented by default and released on:
Work in progress β updates soon!
If the request encounters connection error, the request delegate will not be triggered when the system starts to retry the request. The delegate will only be triggered after the retry process endsβeither when the retry limit is reached (timeout) or when the connection is successfully restored.
Reconnection Notificationβ
- Unreal Engine
- Unity
When the lobby connection is lost, AccelByte SDK has a retry mechanism to reconnect the lobby. Meanwhile the AccelByte SDK is in retry-state, the Byte Wars will popup a reconnecting notification.
Byte Wars Unreal Engine project's file references:
- A Header file:
/Source/AccelByteWars/TutorialModules/Play/OnlineSessionUtilsAccelByteWarsOnlineSession.h
- A CPP file:
/Source/AccelByteWars/TutorialModules/Play/OnlineSessionUtils/AccelByteWarsOnlineSession.cpp
Lobby reconnection binding.
After the lobby connection is succesfully connected, bind the lobby reconnecting handling into delegate
AccelByteOnLobbyReconnectedDelegates
of AccelByte OSSFOnlineIdentityAccelByte
.void UAccelByteWarsOnlineSession::OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error)
{
// ...
FOnlineIdentityAccelBytePtr ABIdentityInt = GetABIdentityInt();
if (!ensureMsgf(ABIdentityInt, TEXT("AB OnlineIdentity interface is nullptr.")))
{
return;
}
// ...
ABIdentityInt->AccelByteOnLobbyReconnectingDelegates->RemoveAll(this);
// ...
ABIdentityInt->AccelByteOnLobbyReconnectingDelegates->AddUObject(this, &ThisClass::OnLobbyReconnecting);
// ...
}Pop up a reconnecting message.
// ...
#define LOBBY_RECONNECTING_MESSAGE NSLOCTEXT(BYTEWARS_LOCTEXT_NAMESPACE, "Lobby Reconnecting", "Reconnecting to AGS.")void UAccelByteWarsOnlineSession::OnLobbyReconnecting(int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
{
UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
if (!ensureMsgf(GameInstance, TEXT("Game Instance is null")))
{
return;
}
GameInstance->bIsReconnecting = true;
GetPromptSubystem()->ShowLoading(LOBBY_RECONNECTING_MESSAGE);
}
At the moment, support for a reconnecting delegate is not yet available in the AGS Unity SDK.
Lobby Reconnection Timeoutβ
When the lobby websocket reconnection reaches a timeout, the game must have some handling to ensure good experience for the player. The handling could be a notification popup, game level auto or manual connect, disable online feature, etc. You can configure the websocket reconnection following these steps.
- Unreal Engine
- Unity
In the Byte Wars Unreal Engine project, there are 2 steps to handle the lobby reconnection timed out.
Game-level Lobby Auto-Connectβ
If either lobby fails to connect or the lobby reconecting mechanism has failed, the AccelByte OSS FOnlineIdentityAccelByte
will trigger delegate AccelByteOnLobbyConnectionClosedDelegates
. Therefore, you have to bind your connection lost handling code to that delegate.
Lobby connection close binding.
After the lobby connection is succesfully connected, bind the lobby reconnecting handling into delegate
AccelByteOnLobbyConnectionClosedDelegates
of AccelByte OSSFOnlineIdentityAccelByte
.void UAccelByteWarsOnlineSession::OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error)
{
// ...
FOnlineIdentityAccelBytePtr ABIdentityInt = GetABIdentityInt();
if (!ensureMsgf(ABIdentityInt, TEXT("AB OnlineIdentity interface is nullptr.")))
{
return;
}
// ...
ABIdentityInt->AccelByteOnLobbyConnectionClosedDelegates->RemoveAll(this);
// ...
ABIdentityInt->AccelByteOnLobbyConnectionClosedDelegates->AddUObject(this, &ThisClass::OnLobbyConnectionClosed);
}Lobby (Manual) Auto Reconnect Timeout Handling in the Game Code
void UAccelByteWarsOnlineSession::OnLobbyConnectionClosed(int32 LocalUserNum, const FUniqueNetId& UserId, int32 StatusCode, const FString& Reason, bool bWasClean)
{
UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
if (!ensureMsgf(GameInstance, TEXT("Game Instance is null")))
{
return;
}
GameInstance->bIsReconnecting = false;
GetPromptSubystem()->HideLoading();
if (StatusCode == static_cast<int32>(AccelByte::EWebsocketErrorTypes::DisconnectFromExternalReconnect))
{
// Do some manual handle to reconnect lobby
LobbyConnect(LocalUserNum);
GameInstance->bIsReconnecting = true;
GetPromptSubystem()->ShowLoading(LOBBY_RECONNECTING_MESSAGE);
}
}There are different types of WebSocket errors when the lobby connection is closed. If the lobby reconnecting takes too long and times out, make sure to run the lobby connect only when the WebSocket error type is
DisconnectFromExternalReconnect
.
Lobby Manual Connectβ
If the auto lobby connect fails to return the lobby connection back, Byte Wars prompt out a dialog box to the player with options to connect lobby manually or log out the game. If the player choose option to log out the game, the game will be redirected to the Main Menu level and shows the login page.
// ...
#define LOBBY_FAILED_CONNECT_MESSAGE NSLOCTEXT(BYTEWARS_LOCTEXT_NAMESPACE, "Lobby Connection Failed", "Failed to connect AGS Lobby. Try to Reconnect")
#define LOBBY_FAILED_RECONNECT_MESSAGE NSLOCTEXT(BYTEWARS_LOCTEXT_NAMESPACE, "Lobby Reconnect Failed", "Failed to reconnect AGS Lobby. Try to Reconnect")
// ...
#define LOBBY_RECONNECTING_MESSAGE NSLOCTEXT(BYTEWARS_LOCTEXT_NAMESPACE, "Lobby Reconnecting", "Reconnecting to AGS.")
void UAccelByteWarsOnlineSession::OnConnectLobbyComplete(int32 LocalUserNum, bool bSucceeded, const FUniqueNetId& UserId, const FString& Error)
{
// ...
FOnlineIdentityAccelBytePtr ABIdentityInt = GetABIdentityInt();
if (!ensureMsgf(ABIdentityInt, TEXT("AB OnlineIdentity interface is nullptr.")))
{
return;
}
// ...
if (!bSucceeded)
{
// Lobby Connect and Reconnect failed
FText message = GameInstance->bIsReconnecting ? LOBBY_FAILED_RECONNECT_MESSAGE : LOBBY_FAILED_CONNECT_MESSAGE;
GetPromptSubystem()->ShowDialoguePopUp(ERROR_PROMPT_TEXT, message, EPopUpType::ConfirmationYesNo,
FPopUpResultDelegate::CreateWeakLambda(this, [this, LocalUserNum](EPopUpResult Result)
{
switch (Result)
{
// Accept manual reconnect attempt
case Confirmed:
{
GetPromptSubystem()->ShowLoading(LOBBY_RECONNECTING_MESSAGE);
LobbyConnect(LocalUserNum);
UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
GameInstance->bIsReconnecting = true;
}
break;
// Deny reconnect and logout
case Declined:
{
UGameplayStatics::OpenLevel(GetWorld(), TEXT("MainMenu"));
// Pending Perform Logout after Main Menu level opened
GetWorld()->GetTimerManager().SetTimerForNextTick(FVoidHandler::CreateLambda([this, LocalUserNum]()
{
// Open Login Menu
UTutorialModuleDataAsset* AuthEssentialsDataAsset =
UTutorialModuleUtility::GetTutorialModuleDataAsset(FPrimaryAssetId("TutorialModule:AUTHESSENTIALS"), this);
TSubclassOf<UAccelByteWarsActivatableWidget> LoginWidgetClass = AuthEssentialsDataAsset->GetTutorialModuleUIClass();
UAccelByteWarsGameInstance* GameInstance = Cast<UAccelByteWarsGameInstance>(GetGameInstance());
GameInstance->GetBaseUIWidget()->PushWidgetToStack(EBaseUIStackType::Menu, LoginWidgetClass.Get());
GetPromptSubystem()->ShowLoading();
GetABIdentityInt()->Logout(LocalUserNum);
GameInstance->bIsReconnecting = false;
}));
}
break;
}
}));
GameInstance->bIsReconnecting = false;
return;
}
// ...
}
Currently, the Byte Wars Unity project only handles the lobby disconnected by forcing the player to logout.
private void OnLobbyDisconnected(WsCloseCode code)
{
BytewarsLogger.Log($"Lobby service disconnected with code: {code}");
HashSet<WsCloseCode> loginDisconnectCodes = new()
{
WsCloseCode.Normal,
WsCloseCode.DisconnectDueToMultipleSessions,
WsCloseCode.DisconnectDueToIAMLoggedOut
};
if (loginDisconnectCodes.Contains(code))
{
lobby.Connected -= OnLobbyConnected;
lobby.Disconnected -= OnLobbyDisconnected;
AuthEssentialsHelper.OnUserLogout?.Invoke();
}
}
Auto-retrieve Missing Notificationsβ
When the game client and server get disconnected from the lobby service, they might miss important notifications. Luckily, the Unreal SDK and Unity SDK have a built-in system to detect and recover these lost notifications once the WebSocket connection is restored. After successfully retrieving the missing notifications, the SDK triggers the relevant event delegates, ensuring the game doesn't lose critical gameplay-related updates. The SDK triggers delegates based on the sequence number of the notifications. This means the oldest missing notification will be processed first, ensuring that events are handled in the correct sequence.
Since AccelByte's session and matchmaking features depend heavily on lobby notifications, this mechanism also checks for any missed updates in these areas. This ensures that matchmaking and session management continue smoothly, even if the WebSocket connection is temporarily disrupted.