Files
sebastian lewicki 695acb1980 Upgrading the CQTest plugin to an Engine module
Introducing the CQTestEnhancedInput plugin as this requires use of the EnhancedInput Engine Plugin (Engine modules cannot make use of the Engine Plugin)
#jira UE-217806
#rb Devin.Doucette, Jerome.Delattre, rob.huyett, sean.sweeney

[CL 36039088 by sebastian lewicki in ue5-main branch]
2024-09-05 10:24:20 -04:00

272 lines
7.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)
: TestRunner(InTestRunner)
, CommandBuilder(&InCommandBuilder)
{
if (IsInitializing)
{
return;
}
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(); })
.Then(TEXT("Setup Packet Settings"), [this]() { SetPacketSettings(); })
.Then(TEXT("Connect Clients to Server"), [this]() { ConnectClientsToServer(); })
.Until(TEXT("Await Clients Ready"), [this]() { return AwaitClientsReady(); })
.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, FTimespan Timeout)
{
CommandBuilder->Until(Query, Timeout);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::Until(const TCHAR* Description, TFunction<bool()> Query, FTimespan Timeout)
{
CommandBuilder->Until(Description, Query, Timeout);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::StartWhen(TFunction<bool()> Query, FTimespan Timeout)
{
CommandBuilder->StartWhen(Query, Timeout);
return *this;
}
FBasePIENetworkComponent& FBasePIENetworkComponent::StartWhen(const TCHAR* Description, TFunction<bool()> Query, FTimespan Timeout)
{
CommandBuilder->StartWhen(Description, Query, Timeout);
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();
}
}
#endif // ENABLE_PIE_NETWORK_TEST