Skip to content

Commit

Permalink
Switch to PIE world type so that UNetDriver::GetNetMode properly retu…
Browse files Browse the repository at this point in the history
…rns NM_Dedicated
  • Loading branch information
slonopotamus committed Aug 23, 2024
1 parent 734148e commit 6ab87bf
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 13 deletions.
28 changes: 22 additions & 6 deletions Source/UEST/Private/ScopedGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ struct FCVarsGuard final : FNoncopyable

static int32 NumScopedGames = 0;

// We are limited by MAX_PIE_INSTANCES :(
static TArray<int32> FreePIEInstances{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

static TUniquePtr<FCVarsGuard> CVarsGuard;

FScopedGameInstance::FScopedGameInstance(TSubclassOf<UGameInstance> GameInstanceClass, const bool bGarbageCollectOnDestroy, const TMap<FString, FCVarConfig>& CVars)
: GameInstanceClass{MoveTemp(GameInstanceClass)}
, LastPIEInstance{0}
, bGarbageCollectOnDestroy{bGarbageCollectOnDestroy}
{
if (NumScopedGames == 0)
Expand All @@ -78,7 +80,6 @@ FScopedGameInstance::FScopedGameInstance(TSubclassOf<UGameInstance> GameInstance
FScopedGameInstance::FScopedGameInstance(FScopedGameInstance&& Other)
: GameInstanceClass{MoveTemp(Other.GameInstanceClass)}
, Games{MoveTemp(Other.Games)}
, LastPIEInstance{Other.LastPIEInstance}
, bGarbageCollectOnDestroy{Other.bGarbageCollectOnDestroy}
{
++NumScopedGames;
Expand Down Expand Up @@ -110,6 +111,11 @@ FScopedGameInstance::~FScopedGameInstance()

UGameInstance* FScopedGameInstance::CreateGame(const EScopedGameType Type, const FString& MapToLoad, const bool bWaitForConnect)
{
if (!ensureAlwaysMsgf(!FreePIEInstances.IsEmpty(), TEXT("Attempt to create too many games at the same time!")))
{
return nullptr;
}

auto* Game = NewObject<UGameInstance>(GEngine, GameInstanceClass);
if (!ensureAlwaysMsgf(Game, TEXT("Failed to create game instance")))
{
Expand All @@ -118,9 +124,9 @@ UGameInstance* FScopedGameInstance::CreateGame(const EScopedGameType Type, const

Games.Emplace(Game);

const auto bRunAsDedicated = Type == EScopedGameType::Server;
const auto PIEInstance = ++LastPIEInstance;
int32 PIEInstance = FreePIEInstances.Pop();

const auto bRunAsDedicated = Type == EScopedGameType::Server;
if (auto* TestableGame = Cast<IUESTGameInstance>(Game))
{
TestableGame->InitializeForTests(bRunAsDedicated, PIEInstance);
Expand All @@ -137,6 +143,8 @@ UGameInstance* FScopedGameInstance::CreateGame(const EScopedGameType Type, const
return nullptr;
}

ensureAlwaysMsgf(WorldContext->PIEInstance == PIEInstance, TEXT("WorldContext must use supplied PIEInstance"));

if (Type == EScopedGameType::Client)
{
WorldContext->GameViewport = NewObject<UGameViewportClient>(Game->GetEngine(), Game->GetEngine()->GameViewportClientClass);
Expand All @@ -158,7 +166,9 @@ UGameInstance* FScopedGameInstance::CreateGame(const EScopedGameType Type, const
URL.AddOption(TEXT("listen"));
}

FGWorldGuard GWorldGuard;
const TGuardValue GIsPlayInEditorWorldGuard(GIsPlayInEditorWorld, false);
const TGuardValue GPlayInEditorIDGuard(GPlayInEditorID, Game->GetWorldContext()->PIEInstance);
const FGWorldGuard GWorldGuard;

FString Error;
const auto BrowseResult = Game->GetEngine()->Browse(*WorldContext, URL, Error);
Expand Down Expand Up @@ -284,6 +294,11 @@ bool FScopedGameInstance::DestroyGame(UGameInstance* Game)
continue;
}

if (const auto* WorldContext = Game->GetWorldContext())
{
FreePIEInstances.Push(WorldContext->PIEInstance);
}

DestroyGameInternal(*Game);
Games.RemoveAt(Index);

Expand All @@ -304,8 +319,9 @@ void FScopedGameInstance::TickInternal(const float DeltaSeconds, const ELevelTic

for (const auto& Game : Games)
{
const TGuardValue GIsPlayInEditorWorldGuard(GIsPlayInEditorWorld, false);
const TGuardValue GPlayInEditorIDGuard(GPlayInEditorID, Game->GetWorldContext()->PIEInstance);
const FGWorldGuard GWorldGuard;
const TGuardValue GPlayInEditorInstanceIDGuard(GPlayInEditorID, Game->GetWorldContext()->PIEInstance);

Game->GetEngine()->TickWorldTravel(*Game->GetWorldContext(), DeltaSeconds);
Game->GetWorld()->Tick(TickType, DeltaSeconds);
Expand Down
34 changes: 32 additions & 2 deletions Source/UEST/Private/ScopedGameTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ TEST(UEST, ScopedGame, Simple)

// You can create a dedicated server
UGameInstance* Server = Tester.CreateGame(EScopedGameType::Server, TEXT("/Engine/Maps/Entry"));
ASSERT_THAT(Server, Is::Not::Null);
ASSERT_THAT(Server->GetWorld()->GetNetMode(), Is::EqualTo<ENetMode>(NM_DedicatedServer));

// You can connect a client to it
UGameInstance* Client = Tester.CreateClientFor(Server);
ASSERT_THAT(Client, Is::Not::Null);

// Actually, you can connect as many clients as you want!
for (int32 Index = 0; Index < 10; ++Index)
// Actually, you can connect as many clients as you want! (Unfortunately, max number of game instances is, see MAX_PIE_INSTANCES in UE sources)
for (int32 Index = 0; Index < 5; ++Index)
{
Tester.CreateClientFor(Server);
}
Expand Down Expand Up @@ -43,3 +45,31 @@ TEST(UEST, ScopedGame, Simple)

// Tester automatically cleans everything up when goes out of scope
}

TEST(UEST, ScopedGame, CheckNetMode)
{
auto Tester = FScopedGame().Create();

UGameInstance* Server = Tester.CreateGame(EScopedGameType::Server, TEXT("/Engine/Maps/Entry"));
ASSERT_THAT(Server, Is::Not::Null);

UWorld* ServerWorld = Server->GetWorld();
ASSERT_THAT(ServerWorld, Is::Not::Null);
const auto ServerNetMode = ServerWorld->GetNetMode();
ASSERT_THAT(ServerNetMode, Is::EqualTo<ENetMode>(NM_DedicatedServer));

UGameInstance* Client = Tester.CreateClientFor(Server);
ASSERT_THAT(Client, Is::Not::Null);

UWorld* ClientWorld = Client->GetWorld();
ASSERT_THAT(ClientWorld, Is::Not::Null);
const auto ClientNetMode = ClientWorld->GetNetMode();
ASSERT_THAT(ClientNetMode, Is::EqualTo<ENetMode>(NM_Client));

UGameInstance* Standalone = Tester.CreateGame(EScopedGameType::Client, TEXT("/Engine/Maps/Entry"));
ASSERT_THAT(Standalone, Is::Not::Null);
UWorld* StandaloneWorld = Standalone->GetWorld();
ASSERT_THAT(StandaloneWorld, Is::Not::Null);
const auto StandaloneNetMode = StandaloneWorld->GetNetMode();
ASSERT_THAT(StandaloneNetMode, Is::EqualTo<ENetMode>(NM_Standalone));
}
7 changes: 4 additions & 3 deletions Source/UEST/Private/UESTGameInstance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@
// UGameInstance::WorldContext is not public, and neither InitializeStandalone nor InitializeForPlayInEditor is suitable for us.
// So we use this template hack to access WorldContext field.
// Source: https://ledas.com/post/857-how-to-hack-c-with-templates-and-friends/
static FWorldContext*& FieldGetter(UGameInstance&);

template<typename T, auto T::*Field, typename RetVal>
struct Stealer
{
static friend RetVal& FieldGetter(T& Object)
friend RetVal& FieldGetter(T& Object)
{
return Object.*Field;
}
};

template struct Stealer<UGameInstance, &UGameInstance::WorldContext, FWorldContext*>;
static FWorldContext*& FieldGetter(UGameInstance&);

void IUESTGameInstance::DefaultInitializeForTests(UGameInstance& GameInstance, const bool bRunAsDedicated, const int32 PIEInstance)
{
constexpr auto WorldType = EWorldType::Game;
constexpr auto WorldType = WITH_EDITOR ? EWorldType::PIE : EWorldType::Game;

auto& WorldContext = GameInstance.GetEngine()->CreateNewWorldContext(WorldType);
WorldContext.OwningGameInstance = &GameInstance;
Expand Down
2 changes: 0 additions & 2 deletions Source/UEST/Public/ScopedGame.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ class UEST_API FScopedGameInstance : FNoncopyable

static void DestroyGameInternal(UGameInstance& Game);

int32 LastPIEInstance;

bool bGarbageCollectOnDestroy;

void TickInternal(float DeltaSeconds, const ELevelTick TickType);
Expand Down

0 comments on commit 6ab87bf

Please sign in to comment.