Files
UnrealEngine/Engine/Source/Developer/CQTest/Private/Components/PIENetworkComponent.cpp
2025-05-18 13:04:45 +08:00

295 lines
8.7 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Components/PIENetworkComponent.h"
#if ENABLE_PIE_NETWORK_TEST
#include "LevelEditor.h"
#include "Editor/UnrealEdEngine.h"
#include "Settings/LevelEditorPlaySettings.h"
#include "UnrealEdGlobals.h"
#include "Tests/AutomationEditorCommon.h"
#include "Engine/NetConnection.h"
#include "Engine/NetDriver.h"
#include "GameFramework/GameMode.h"
#include "Modules/ModuleManager.h"
#include "GameMapsSettings.h"
DEFINE_LOG_CATEGORY_STATIC(LogNetworkTest, Log, All);
FBasePIENetworkComponent::FBasePIENetworkComponent(FAutomationTestBase* InTestRunner, FTestCommandBuilder& InCommandBuilder, bool IsInitializing, TOptional<FTimespan> Timeout)
: TestRunner(InTestRunner)
, CommandBuilder(&InCommandBuilder)
{
if (IsInitializing)
{
return;
}
FTimespan TimeoutValue = MakeTimeout(Timeout);
CommandBuilder
->Do(TEXT("Stop PIE"), [this]() { StopPie(); })
.Then(TEXT("Create New Map"), [this]() { FAutomationEditorCommonUtils::CreateNewMap(); })
.Then(TEXT("Start PIE"), [this]() { StartPie(); })
.Until(TEXT("Set Worlds"), [this]() { return SetWorlds(); }, TimeoutValue)
.Then(TEXT("Setup Packet Settings"), [this]() { SetPacketSettings(); })
.Then(TEXT("Connect Clients to Server"), [this]() { ConnectClientsToServer(); })
.Until(TEXT("Await Clients Ready"), [this]() { return AwaitClientsReady(); }, TimeoutValue)
.OnTearDown(TEXT("Restore Editor State"), [this]() { RestoreState(); });
}
FBasePIENetworkComponent& FBasePIENetworkComponent::Then(TFunction<void()> Action)
{
CommandBuilder->Then(Action);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::Do(TFunction<void()> Action)
{
CommandBuilder->Do(Action);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::Then(const TCHAR* Description, TFunction<void()> Action)
{
CommandBuilder->Then(Description, Action);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::Do(const TCHAR* Description, TFunction<void()> Action)
{
CommandBuilder->Do(Description, Action);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::Until(TFunction<bool()> Query, TOptional<FTimespan> Timeout)
{
FTimespan TimeoutValue = MakeTimeout(Timeout);
CommandBuilder->Until(Query, TimeoutValue);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::Until(const TCHAR* Description, TFunction<bool()> Query, TOptional<FTimespan> Timeout)
{
FTimespan TimeoutValue = MakeTimeout(Timeout);
CommandBuilder->Until(Description, Query, TimeoutValue);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::StartWhen(TFunction<bool()> Query, TOptional<FTimespan> Timeout)
{
FTimespan TimeoutValue = MakeTimeout(Timeout);
CommandBuilder->StartWhen(Query, TimeoutValue);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::StartWhen(const TCHAR* Description, TFunction<bool()> Query, TOptional<FTimespan> Timeout)
{
FTimespan TimeoutValue = MakeTimeout(Timeout);
CommandBuilder->StartWhen(Description, Query, TimeoutValue);
return *this;
}
void FBasePIENetworkComponent::StopPie()
{
if (ServerState == nullptr)
{
TestRunner->AddError(TEXT("Failed to initialize Network Component"));
return;
}
GUnrealEd->RequestEndPlayMap();
}
void FBasePIENetworkComponent::StartPie()
{
ULevelEditorPlaySettings* PlaySettings = NewObject<ULevelEditorPlaySettings>();
if (ServerState->bIsDedicatedServer)
{
PlaySettings->SetPlayNetMode(EPlayNetMode::PIE_Client);
PlaySettings->SetPlayNumberOfClients(ServerState->ClientCount);
}
else
{
PlaySettings->SetPlayNetMode(EPlayNetMode::PIE_ListenServer);
PlaySettings->SetPlayNumberOfClients(ServerState->ClientCount + 1); // The listen server counts as a client, so we need to add one more to get a real client as well
}
PlaySettings->bLaunchSeparateServer = ServerState->bIsDedicatedServer;
PlaySettings->GameGetsMouseControl = false;
PlaySettings->SetRunUnderOneProcess(true);
FLevelEditorModule& LevelEditorModule = FModuleManager::Get().GetModuleChecked<FLevelEditorModule>(TEXT("LevelEditor"));
FRequestPlaySessionParams SessionParams;
SessionParams.WorldType = EPlaySessionWorldType::PlayInEditor;
SessionParams.DestinationSlateViewport = LevelEditorModule.GetFirstActiveViewport();
SessionParams.EditorPlaySettings = PlaySettings;
if (GameMode != nullptr)
{
SessionParams.GameModeOverride = GameMode;
}
else
{
SessionParams.GameModeOverride = AGameModeBase::StaticClass();
}
GUnrealEd->RequestPlaySession(SessionParams);
GUnrealEd->StartQueuedPlaySessionRequest();
}
bool FBasePIENetworkComponent::SetWorlds()
{
auto IsValidContext = [](const FWorldContext& Context) -> bool {
return Context.WorldType == EWorldType::PIE &&
IsValid(Context.World()) &&
IsValid(Context.World()->GetNetDriver());
};
auto IsValidServerWorld = [this](UWorld* World) -> bool {
const bool bIsDedicated = ServerState->bIsDedicatedServer;
const bool bExpectsDedicated = World->GetNetMode() == NM_DedicatedServer;
return bExpectsDedicated == bIsDedicated;
};
auto IsClientWorldClaimed = [this](UWorld* World) -> bool {
for (const auto& State : ClientStates)
{
if (IsValid(State->World) && State->World == World)
{
return true;
}
}
return false;
};
int32 ClientWorldCount = 0;
for (const auto& WorldContext : GEngine->GetWorldContexts())
{
if (!IsValidContext(WorldContext))
{
continue;
}
UWorld* World = WorldContext.World();
if (World->GetNetDriver()->IsServer())
{
if (ServerState->World == nullptr)
{
if (!IsValidServerWorld(World))
{
TestRunner->AddError(TEXT("Failed to set up dedicated server. Does your game's editor module override the PIE settings?"));
return true;
}
ServerState->World = World;
}
}
else
{
if (!IsClientWorldClaimed(World))
{
bool bClaimed = false;
for (const auto& State : ClientStates)
{
if (State->World == nullptr)
{
State->World = World;
bClaimed = true;
break;
}
}
if (!bClaimed)
{
TestRunner->AddError(TEXT("Failed to claim client world. Network component was not able to be initialized."));
return true;
}
}
ClientWorldCount++;
}
}
return IsValid(ServerState->World) && ClientWorldCount == ServerState->ClientCount;
}
void FBasePIENetworkComponent::SetPacketSettings() const
{
if (PacketSimulationSettings)
{
ServerState->World->GetNetDriver()->SetPacketSimulationSettings(*PacketSimulationSettings);
for (const auto& ClientState : ClientStates)
{
ClientState->World->GetNetDriver()->SetPacketSimulationSettings(*PacketSimulationSettings);
}
}
}
void FBasePIENetworkComponent::ConnectClientsToServer()
{
auto& ServerConnections = ServerState->World->GetNetDriver()->ClientConnections;
for(int32 ClientIndex = ServerState->ClientConnections.Num(); ClientIndex < ServerState->ClientCount; ClientIndex++)
{
const int32 ClientLocalPort = ClientStates[ClientIndex]->World->GetNetDriver()->GetLocalAddr()->GetPort();
TObjectPtr<UNetConnection>* ServerConnection = ServerConnections.FindByPredicate([ClientLocalPort](UNetConnection* ClientConnection) {
return ClientConnection->GetRemoteAddr()->GetPort() == ClientLocalPort;
});
if (ServerConnection == nullptr)
{
TestRunner->AddError(TEXT("Failed to find connection to server for client. Network component was not able to be initialized."));
return;
}
ServerState->ClientConnections[ClientIndex] = *ServerConnection;
}
}
bool FBasePIENetworkComponent::AwaitClientsReady() const
{
if (ServerState == nullptr || !IsValid(ServerState->World))
{
TestRunner->AddError(TEXT("Failed to get server state. Network component was not able to be initialized."));
return true;
}
if (ServerState->World->GetNetDriver()->ClientConnections.Num() != ServerState->ClientCount)
{
return false;
}
for (const UNetConnection* ClientConnection : ServerState->World->GetNetDriver()->ClientConnections)
{
if (ClientConnection->ViewTarget == nullptr)
{
return false;
}
}
return true;
}
void FBasePIENetworkComponent::RestoreState()
{
if (ServerState != nullptr)
{
GUnrealEd->RequestEndPlayMap();
StateRestorer.Restore();
}
}
FTimespan FBasePIENetworkComponent::MakeTimeout(TOptional<FTimespan> Timeout)
{
if (Timeout.IsSet())
{
return Timeout.GetValue();
}
else if (IConsoleVariable* ConsoleVariable = IConsoleManager::Get().FindConsoleVariable(CQTestConsoleVariables::NetworkTimeoutName))
{
return FTimespan::FromSeconds(ConsoleVariable->GetFloat());
}
else
{
UE_LOG(LogNetworkTest, Warning, TEXT("CVar '%s' was not found. Defaulting to %f seconds."), CQTestConsoleVariables::NetworkTimeoutName, CQTestConsoleVariables::NetworkTimeout);
return FTimespan::FromSeconds(CQTestConsoleVariables::NetworkTimeout);
}
}
#endif // ENABLE_PIE_NETWORK_TEST