Files
UnrealEngine/Engine/Plugins/Online/OnlineFramework/Source/Rejoin/Private/RejoinCheck.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

389 lines
10 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RejoinCheck.h"
#include "Engine/GameInstance.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "OnlineSubsystemUtils.h"
#include "Engine/LocalPlayer.h"
#include "TimerManager.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(RejoinCheck)
#define REJOIN_CHECK_TIMER 30.0f
static TAutoConsoleVariable<int32> CVarDebugRejoin(
TEXT("UI.DebugRejoin"),
-1,
TEXT("Force switch between rejoin states (-1 is off)"));
URejoinCheck::URejoinCheck()
: LastKnownStatus(ERejoinStatus::NeedsRecheck)
, bRejoinAfterCheck(false)
, bAttemptingRejoin(false)
{
}
bool URejoinCheck::IsRejoinCheckEnabled() const
{
return true;
}
#if !UE_BUILD_SHIPPING
ERejoinStatus URejoinCheck::GetStatus() const
{
int32 DbgVal = CVarDebugRejoin.GetValueOnGameThread();
if (DbgVal >= 0 && DbgVal <= (int32)ERejoinStatus::NoMatchToRejoin_MatchEnded)
{
return (ERejoinStatus)DbgVal;
}
if (!IsRejoinCheckEnabled())
{
return ERejoinStatus::NoMatchToRejoin;
}
return LastKnownStatus;
}
#endif
bool URejoinCheck::HasCompletedCheck() const
{
ERejoinStatus CurrentStatus = GetStatus();
return (CurrentStatus != ERejoinStatus::NeedsRecheck && CurrentStatus != ERejoinStatus::UpdatingStatus);
}
bool URejoinCheck::IsRejoinAvailable() const
{
ERejoinStatus CurrentStatus = GetStatus();
return (CurrentStatus != ERejoinStatus::NoMatchToRejoin &&
CurrentStatus != ERejoinStatus::NoMatchToRejoin_MatchEnded);
}
void URejoinCheck::CheckRejoinStatus(const FOnRejoinCheckComplete& InCompletionDelegate)
{
#if !UE_BUILD_SHIPPING
if (!IsRejoinCheckEnabled())
{
SetStatus(ERejoinStatus::NoMatchToRejoin);
InCompletionDelegate.ExecuteIfBound(LastKnownStatus);
return;
}
#endif
if (LastKnownStatus != ERejoinStatus::UpdatingStatus)
{
bool bSuccess = false;
SetStatus(ERejoinStatus::UpdatingStatus);
UGameInstance* GameInstance = GetGameInstance<UGameInstance>();
check(GameInstance);
UWorld* World = GetWorld();
check(World);
IOnlineSessionPtr SessionInt = Online::GetSessionInterface(World);
if (SessionInt.IsValid())
{
IOnlineIdentityPtr IdentityInt = Online::GetIdentityInterface(World);
if (IdentityInt.IsValid())
{
FUniqueNetIdRepl PrimaryUniqueId = GameInstance->GetPrimaryPlayerUniqueIdRepl();
if (ensure(PrimaryUniqueId.IsValid()))
{
ULocalPlayer* LP = GameInstance->FindLocalPlayerFromUniqueNetId(PrimaryUniqueId);
if (ensure(LP))
{
FOnFindFriendSessionCompleteDelegate CompletionDelegate;
CompletionDelegate.BindUObject(this, &ThisClass::OnCheckRejoinComplete, InCompletionDelegate);
FindFriendSessionCompleteDelegateHandle = SessionInt->AddOnFindFriendSessionCompleteDelegate_Handle(LP->GetControllerId(), CompletionDelegate);
bSuccess = SessionInt->FindFriendSession(*PrimaryUniqueId, *PrimaryUniqueId);
}
else
{
UE_LOG(LogOnline, Warning, TEXT("Invalid local player during rejoin check"));
}
}
else
{
UE_LOG(LogOnline, Warning, TEXT("Invalid user during rejoin check"));
}
}
else
{
UE_LOG(LogOnline, Warning, TEXT("No identity interface during rejoin check"));
}
}
else
{
UE_LOG(LogOnline, Warning, TEXT("No session interface during rejoin check"));
}
if (!bSuccess)
{
TArray<FOnlineSessionSearchResult> EmptySearchResults;
ProcessRejoinCheck(false, EmptySearchResults, InCompletionDelegate);
}
}
else
{
UE_LOG(LogOnline, Log, TEXT("Rejoin check in progress, ignoring call."));
InCompletionDelegate.ExecuteIfBound(ERejoinStatus::UpdatingStatus);
}
}
void URejoinCheck::OnCheckRejoinComplete(int32 ControllerId, bool bWasSuccessful, const TArray<FOnlineSessionSearchResult>& InSearchResults, FOnRejoinCheckComplete InCompletionDelegate)
{
UWorld* World = GetWorld();
check(World);
IOnlineSessionPtr SessionInt = Online::GetSessionInterface(World);
if (SessionInt.IsValid())
{
SessionInt->ClearOnFindFriendSessionCompleteDelegate_Handle(ControllerId, FindFriendSessionCompleteDelegateHandle);
}
ProcessRejoinCheck(bWasSuccessful, InSearchResults, InCompletionDelegate);
}
void URejoinCheck::ProcessRejoinCheck(bool bWasSuccessful, const TArray<FOnlineSessionSearchResult>& InSearchResults, const FOnRejoinCheckComplete& InCompletionDelegate)
{
if (LastKnownStatus == ERejoinStatus::UpdatingStatus)
{
ERejoinStatus NewStatus = ERejoinStatus::NeedsRecheck;
if (bWasSuccessful)
{
NewStatus = ERejoinStatus::NoMatchToRejoin;
if (InSearchResults.Num() > 0 && InSearchResults[0].IsValid())
{
NewStatus = GetRejoinStateFromSearchResult(InSearchResults[0]);
}
if (NewStatus == ERejoinStatus::RejoinAvailable && InSearchResults.Num() > 0)
{
if (InSearchResults[0].GetSessionIdStr() != SearchResult.GetSessionIdStr())
{
// Record the analytics before the search result assignment
// so the event is only sent once per unique session id
Analytics_RecordRejoinDetected(InSearchResults[0]);
}
SearchResult = InSearchResults[0];
}
else
{
SearchResult = FOnlineSessionSearchResult();
}
}
SetStatus(NewStatus);
// Could be external delegate or internal call to OnFinalRejoinCheckComplete
InCompletionDelegate.ExecuteIfBound(NewStatus);
if (bAttemptingRejoin)
{
if (bRejoinAfterCheck)
{
// Manual call because of call to rejoin in middle of another update
// delegate call above should not be same as this call
bRejoinAfterCheck = false;
OnFinalRejoinCheckComplete(LastKnownStatus);
}
}
else if (IsRejoinAvailable())
{
// Keep looking for the match
StartRejoinChecks();
}
}
}
void URejoinCheck::RejoinCheckTimer()
{
if (LastKnownStatus != ERejoinStatus::UpdatingStatus)
{
CheckRejoinStatus();
}
}
void URejoinCheck::RejoinLastSession(const FOnRejoinLastSessionComplete& InCompletionDelegate)
{
if (LastKnownStatus == ERejoinStatus::UpdatingStatus)
{
if (!bAttemptingRejoin)
{
bAttemptingRejoin = true;
UE_LOG(LogOnline, Warning, TEXT("RejoinLastSession called while check in progress, will react on completion"));
bRejoinAfterCheck = true;
RejoinLastSessionCompleteDelegate = InCompletionDelegate;
}
else
{
UE_LOG(LogOnline, Warning, TEXT("RejoinLastSession called already attempting a rejoin."));
InCompletionDelegate.ExecuteIfBound(ERejoinAttemptResult::RejoinInProgress);
}
}
else if (IsRejoinAvailable())
{
if (!bAttemptingRejoin)
{
bAttemptingRejoin = true;
RejoinLastSessionCompleteDelegate = InCompletionDelegate;
// Stop any recheck timer, the game will either be traveling, or reset in OnRejoinFailure
ClearTimers();
// Always check one last time to make sure nothing has changed
FOnRejoinCheckComplete CompletionDelegate;
CompletionDelegate.BindUObject(this, &ThisClass::OnFinalRejoinCheckComplete);
CheckRejoinStatus(CompletionDelegate);
}
else
{
UE_LOG(LogOnline, Warning, TEXT("RejoinLastSession called already attempting a rejoin."));
InCompletionDelegate.ExecuteIfBound(ERejoinAttemptResult::RejoinInProgress);
}
}
else
{
UE_LOG(LogOnline, Warning, TEXT("RejoinLastSession called but no session to join"));
InCompletionDelegate.ExecuteIfBound(ERejoinAttemptResult::NothingToRejoin);
}
}
void URejoinCheck::SetStatus(ERejoinStatus NewStatus)
{
if (LastKnownStatus != NewStatus)
{
LastKnownStatus = NewStatus;
OnRejoinCheckStatusChanged().Broadcast(NewStatus);
}
}
void URejoinCheck::Reset()
{
bRejoinAfterCheck = false;
bAttemptingRejoin = false;
SearchResult = FOnlineSessionSearchResult();
ClearTimers();
SetStatus(ERejoinStatus::NeedsRecheck);
}
void URejoinCheck::ClearTimers()
{
const UWorld* const World = GetWorld();
if (World)
{
if (RejoinCheckTimerHandle.IsValid())
{
FTimerManager& TM = World->GetTimerManager();
TM.ClearTimer(RejoinCheckTimerHandle);
RejoinCheckTimerHandle.Invalidate();
}
}
}
void URejoinCheck::OnFinalRejoinCheckComplete(ERejoinStatus Result)
{
UE_LOG(LogOnline, Verbose, TEXT("OnFinalRejoinCheckComplete %s"), ToString(Result));
if (Result == ERejoinStatus::RejoinAvailable)
{
// Tell the game it's expected to rejoin the discovered session
RejoinViaSession();
}
else
{
const bool bNoRejoin = (Result == ERejoinStatus::NoMatchToRejoin || Result == ERejoinStatus::NoMatchToRejoin_MatchEnded);
OnRejoinFailure(bNoRejoin ? ERejoinAttemptResult::NothingToRejoin : ERejoinAttemptResult::RejoinFailure);
}
}
void URejoinCheck::TravelToSession()
{
bool bResult = false;
// TODO: What should we do if this fails? Will need to destroy session, etc.
UGameInstance* GameInstance = GetGameInstance<UGameInstance>();
check(GameInstance);
ULocalPlayer* LP = GEngine->GetFirstGamePlayer(GetWorld());
if (ensure(LP))
{
bResult = GameInstance->ClientTravelToSession(LP->GetControllerId(), NAME_GameSession);
if (bResult)
{
UE_LOG(LogOnline, Log, TEXT("URejoinCheck::TravelToSession: Performing ClientTravelToSession"));
// Record the result of the attempt to rejoin
Analytics_RecordRejoinAttempt(SearchResult, ERejoinAttemptResult::RejoinSuccess);
// Reset the rejoin status while in game (any failure or future quit with recheck)
Reset();
OnRejoinLastSessionComplete().ExecuteIfBound(ERejoinAttemptResult::RejoinSuccess);
}
else
{
UE_LOG(LogOnline, Log, TEXT("URejoinCheck::TravelToSession: Failed to travel to session"));
}
}
else
{
UE_LOG(LogOnline, Log, TEXT("URejoinCheck::TravelToSession: Failed to find local player"));
}
if (!bResult)
{
OnRejoinFailure(ERejoinAttemptResult::RejoinTravelFailure);
}
}
void URejoinCheck::OnRejoinFailure(ERejoinAttemptResult Result)
{
UE_LOG(LogOnline, Warning, TEXT("OnRejoinFailure %s"), ToString(Result));
// Record the result of the attempt to rejoin
Analytics_RecordRejoinAttempt(SearchResult, Result);
bAttemptingRejoin = false;
if (Result == ERejoinAttemptResult::NothingToRejoin)
{
SetStatus(ERejoinStatus::NoMatchToRejoin);
}
else
{
SetStatus(ERejoinStatus::NeedsRecheck);
StartRejoinChecks();
}
OnRejoinLastSessionComplete().ExecuteIfBound(Result);
}
void URejoinCheck::StartRejoinChecks()
{
FTimerDelegate TimerDelegate;
TimerDelegate.BindUObject(this, &ThisClass::RejoinCheckTimer);
FTimerManager& TM = GetWorld()->GetTimerManager();
TM.SetTimer(RejoinCheckTimerHandle, TimerDelegate, REJOIN_CHECK_TIMER, false);
}
UWorld* URejoinCheck::GetWorld() const
{
UGameInstance* GameInstance = GetGameInstance<UGameInstance>();
if (GameInstance)
{
return GameInstance->GetWorld();
}
return nullptr;
}