Game Server: How to Handle Player Statistic Updates During Connection Errors
Service maintenance can disrupt certain game server activities that depend on a stable connection, causing them to not working as expected. For example, if the game server updates player statistics based on match results but loses connection and reaches connection retry times out, the stats won’t be updated. To prevent this issue, developers should add a way to handle failed updates. One common approach is to use a timer to retry the request in the game level. Here's an example of how to implement this retry system with a maximum of 5 retry attempts and retry delay 10 seconds.
- Unreal Engine OSS
- Unreal Engine SDK
- Unity
...
FOnUpdateMultipleUserStatItemsComplete OnServerUpdateStatsComplete;
int MaxRetryAttempts{5};
int NRetryAttempt{0};
int RetryDelay{10};
FTimerHandle RetryTimerHandle;
...
void UpdatePlayerMMR()
{
...
auto ABSubsystem = IOnlineSubsystem::Get(ACCELBYTE_SUBSYSTEM);
auto StatsInterface = ABSubsystem->GetStatsInterface();
auto ABStatsInterface = StaticCastSharedPtr<FOnlineStatisticAccelByte>(StatsInterface);
int32 LocalUserNum = 0;
TArray<FOnlineStatsUserUpdatedStats> UpdatedUsersStats;
FOnlineStatsUserUpdatedStats UpdatedUserStats(PlayerUniqueId->AsShared());
UpdatedUserStats.Stats.Add(TTuple<FString, FOnlineStatUpdate>
{
TEXT("MMR"),
FOnlineStatUpdate
{
50,
FOnlineStatUpdate::EOnlineStatModificationType::Set
}
});
OnServerUpdateStatsComplete = FOnUpdateMultipleUserStatItemsComplete::CreateWeakLambda(
this, [this](const FOnlineError& ResultState, const TArray<FAccelByteModelsUpdateUserStatItemsResponse>& Result)
{
if (ResultState.bSucceeded)
{
// Do something when the update stats is successful.
NRetryAttempt = 0;
}
else
{
// Implement a solution to handle failed request.
if(NRetryAttempt < MaxRetryAttempts)
{
GetWorld()->GetTimerManager().SetTimer(RetryTimerHandle, [this]()
{
UpdatePlayerMMR();
}, RetryDelay, false);
NRetryAttempt++;
}
}
OnServerUpdateStatsComplete.Unbind();
});
ABStatsInterface->UpdateStats(LocalUserNum, UpdatedUsersStats, OnServerUpdateStatsComplete);
...
}
...
int MaxRetryAttempts{5};
int NRetryAttempt{0};
int RetryDelay{10};
FTimerHandle RetryTimerHandle;
...
void UpdatePlayerMMR()
{
...
FAccelByteModelsUpdateUserStatItem UserStatItem;
UserStatItem.Value = 50;
FServerApiClientPtr ServerApiClient = FMultiRegistry::GetServerApiClient();
ServerApiClient->ServerStatistic.UpdateUserStatItemValue(TEXT("<UserId>"), TEXT("MMR"), TEXT(""), UserStatItem,
THandler<FAccelByteModelsUpdateUserStatItemValueResponse>::CreateLambda([this](const FAccelByteModelsUpdateUserStatItemValueResponse& Result)
{
// Do something when the updating player stat MMR is successful.
NRetryAttempt = 0;
}), FErrorHandler::CreateLambda([this](int32 Code, const FString& Message)
{
UE_LOG(LogTemp, Warning, TEXT("Error updating player stat MMR. Code: %d, Message: %s"), ErrorCode, *ErrorMessage);
if(ErrorCode == static_cast<int32>(ErrorCodes::NetworkError))
{
// Implement a solution to handle HTTP retry timeout.
if(NRetryAttempt < MaxRetryAttempts)
{
GetWorld()->GetTimerManager().SetTimer(RetryTimerHandle, [this]()
{
UpdatePlayerMMR();
}, RetryDelay, false);
NRetryAttempt++;
}
}
})
);
...
}
...
int maxRetryAttempts = 5;
int nRetryAttempt = 0;
int retryDelay = 10;
...
public void UpdatePlayerMMR()
{
...
PublicUpdateUserStatItem publicUpdateUserStatItem = new PublicUpdateUserStatItem
{
value = 50
};
AccelByteSDK.GetClientRegistry().GetApi().GetStatistic().UpdateUserStatItemsValue("MMR", "", publicUpdateUserStatItem, result =>
{
if (!result.IsError)
{
// Do something when the accept the agreement is successful.
nRetryAttempt = 0;
}
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.
StartCoroutine(RetryUpdatePlayerMMR);
}
}
});
...
}
private IEnumerator RetryUpdatePlayerMMR()
{
if(nRetryAttempt < maxRetryAttempts)
{
yield return new WaitForSeconds(retryDelay);
nRetryAttempt++;
UpdatePlayerMMR();
}
else
{
// Show an error message pop up!
}
}
If the request encounters one of the error codes listed here, the error delegate will not be triggered when the system starts to retry the request. The error delegate will only be triggered after the retry process ends—either when the retry limit is reached (timeout) or if the final response code is not one of the listed error codes.