Skip to content

Commit

Permalink
get both with/without forking working
Browse files Browse the repository at this point in the history
  • Loading branch information
mikeseese committed Mar 13, 2024
1 parent 8b4e4c6 commit 0d65a83
Show file tree
Hide file tree
Showing 10 changed files with 268 additions and 80 deletions.
Git LFS file not shown
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const FString UHathoraForkingSubsystem::ExposedPortNamePrefix(TEXT("game"));

// This is how much time we will wait between polling GetActiveRoomsForProcess to check if
// a room has been allocated to this process yet (in seconds).
const float UHathoraForkingSubsystem::RoomAllocationPollingInterval(1.0f);
const float UHathoraForkingSubsystem::RoomAllocationPollingInterval(5.0f);

void UHathoraForkingSubsystem::Initialize(FSubsystemCollectionBase &Collection)
{
Expand Down Expand Up @@ -71,17 +71,6 @@ void UHathoraForkingSubsystem::Fork()
{
FHathoraServerEnvironment HathoraEnvVars = UHathoraSDK::GetServerEnvironment();

// This will fill an array of all the Hathora-exposed ports for the child processes.
// We use these ports in the RoomConfig to let clients know which port to connect to.
// This function blocks.
GetExposedPorts();

if (ExposedPorts.Num() != HathoraEnvVars.RoomsPerProcess)
{
UE_LOG(LogHathoraSDK, Error, TEXT("The number of exposed ports (%d) does not match the number of rooms per process (%d), will not attempt to fork"), ExposedPorts.Num(), HathoraEnvVars.RoomsPerProcess);
return;
}

// We only want to fork if the RoomsPerProcess environment variable
// (injected by Hathora) is greater than 1
if (HathoraEnvVars.RoomsPerProcess > 1) {
Expand Down Expand Up @@ -186,6 +175,17 @@ void UHathoraForkingSubsystem::Fork()
// the setup happens, and it won't be called again.
FHathoraForkProcess::OnEndFramePostFork();

// This will fill an array of all the Hathora-exposed ports for the child processes.
// We use these ports in the RoomConfig to let clients know which port to connect to.
// This function blocks.
GetExposedPorts();

if (ExposedPorts.Num() != HathoraEnvVars.RoomsPerProcess)
{
UE_LOG(LogHathoraSDK, Error, TEXT("The number of exposed ports (%d) does not match the number of rooms per process (%d), will not attempt to fork"), ExposedPorts.Num(), HathoraEnvVars.RoomsPerProcess);
return;
}

// Block for this child until there is a room that hasn't been assigned to this process.
// We denote how this is determined within WaitForRoomAllocation by checking the RoomConfig
// for UHathoraForkingSubsystem::RoomConfigPortKey.
Expand Down Expand Up @@ -220,8 +220,8 @@ void UHathoraForkingSubsystem::Fork()
#endif
}

// This function calls GetProcessInfo once on the parent process to get the exposed ports for
// the child processes. This will be put into a 0-based array that the child processes will use
// This function calls GetProcessInfo once on each of the child process to get the exposed ports.
// This will be put into a 0-based array that the child processes will use
// to let clients know which port to connect to. This function blocks.
void UHathoraForkingSubsystem::GetExposedPorts()
{
Expand Down
111 changes: 111 additions & 0 deletions SDKDemo/Plugins/HathoraSDK/Source/HathoraSDK/Private/HathoraSDK.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include "Interfaces/IHttpRequest.h"
#include "Interfaces/IHttpResponse.h"
#include "Dom/JsonObject.h"
#include "Forking/HathoraForkingSubsystem.h"

void UHathoraSDK::GetRegionalPings(const FHathoraOnGetRegionalPings& OnComplete, int32 NumPingsPerRegion)
{
Expand Down Expand Up @@ -120,6 +121,116 @@ FString UHathoraSDK::ParseErrorMessage(FString Content)
}
}

void UHathoraSDK::GetServerRoomId(float PollingInterval, FOnGetRoomId OnComplete)
{
UHathoraSDK* SDK = UHathoraSDK::CreateHathoraSDK();
SDK->AddToRoot(); // make sure this doesn't get garbage collected
SDK->QueryServerRoomId(
PollingInterval,
FOnGetRoomId::CreateLambda(
[OnComplete, SDK](const FString Result)
{
OnComplete.ExecuteIfBound(Result);
SDK->RemoveFromRoot(); // Allow this SDK reference to be garbage collected
}
)
);
}

void UHathoraSDK::QueryServerRoomId(float PollingInterval, FOnGetRoomId OnComplete)
{
UWorld *World = GWorld;

if (IsValid(World) && World->GetNetMode() == ENetMode::NM_DedicatedServer)
{
if (UHathoraSDK::IsUsingBuiltInForking())
{
if (World->GetGameInstance() != nullptr)
{
UHathoraForkingSubsystem* ForkingSubsystem = World->GetGameInstance()->GetSubsystem<UHathoraForkingSubsystem>();
if (ForkingSubsystem != nullptr)
{
FString RoomId = ForkingSubsystem->GetRoomId();

if (RoomId == TEXT(""))
{
AsyncTask(ENamedThreads::AnyThread, [PollingInterval, OnComplete, this]()
{
FPlatformProcess::Sleep(PollingInterval);
AsyncTask(ENamedThreads::GameThread, [PollingInterval, OnComplete, this]()
{
QueryServerRoomId(PollingInterval, OnComplete);
});
});
}
else
{
OnComplete.ExecuteIfBound(RoomId);
}
}
}
else
{
UE_LOG(LogHathoraSDK, Error, TEXT("ERROR: GameInstance is null; could not get the Room ID."));
OnComplete.ExecuteIfBound(TEXT(""));
}
}
else
{
FHathoraServerEnvironment HathoraEnvVars = UHathoraSDK::GetServerEnvironment();

RoomV2->GetActiveRoomsForProcess(
HathoraEnvVars.ProcessId,
UHathoraSDKRoomV2::FHathoraOnGetRoomsForProcess::CreateLambda(
[HathoraEnvVars, PollingInterval, OnComplete, this](const FHathoraGetRoomsForProcessResult& Result)
{
FString RoomId;

if (Result.ErrorMessage.IsEmpty())
{
if (!Result.Data.IsEmpty())
{
RoomId = Result.Data[0].RoomId;
}
}
else
{
UE_LOG(LogHathoraSDK, Error, TEXT("ERROR: Could not get active rooms for process id %s: %s"), *HathoraEnvVars.ProcessId, *Result.ErrorMessage);
}

if (RoomId == TEXT(""))
{
AsyncTask(ENamedThreads::AnyThread, [PollingInterval, OnComplete, this]()
{
FPlatformProcess::Sleep(PollingInterval);
AsyncTask(ENamedThreads::GameThread, [PollingInterval, OnComplete, this]()
{
QueryServerRoomId(PollingInterval, OnComplete);
});
});
}
else
{
OnComplete.ExecuteIfBound(RoomId);
}
}
)
);
}
}
else
{
UE_LOG(LogHathoraSDK, Error, TEXT("Called QueryServerRoomId from a client or the world is not valid."));
OnComplete.ExecuteIfBound(TEXT(""));
}
}

bool UHathoraSDK::IsUsingBuiltInForking()
{
const UHathoraSDKConfig* Config = GetDefault<UHathoraSDKConfig>();
return Config->GetUseBuiltInForking();
}

void UHathoraSDK::SetAuthToken(FString Token)
{
const UHathoraSDKConfig* Config = GetDefault<UHathoraSDKConfig>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2023 Hathora, Inc.

#include "LatentActions/HathoraGetServerRoomId.h"
#include "HathoraSDK.h"
#include "HathoraSDKModule.h"

UHathoraGetServerRoomId *UHathoraGetServerRoomId::GetServerRoomId(
UObject *WorldContextObject,
float PollingInterval
) {
UHathoraGetServerRoomId *Action = NewObject<UHathoraGetServerRoomId>();
Action->PollingInterval = PollingInterval;
Action->RegisterWithGameInstance(WorldContextObject);
return Action;
}

void UHathoraGetServerRoomId::Activate()
{
if (!IsValid(this))
{
UE_LOG(LogHathoraSDK, Error, TEXT("GetServerRoomId failed because the underlying Hathora API is not valid."));
return;
}

UHathoraSDK::GetServerRoomId(
PollingInterval,
UHathoraSDK::FOnGetRoomId::CreateLambda(
[this](const FString& RoomId)
{
OnComplete.Broadcast(RoomId);
SetReadyToDestroy();
}
)
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class HATHORASDK_API UHathoraForkingSubsystem : public UGameInstanceSubsystem

FString AddPortToRoomConfig(const FString &RoomConfig);

UFUNCTION(BlueprintPure, Category = "HathoraSDK|Forking")
FString GetRoomId() const { return RoomId; }

private:
int32 StartingPort;
FString RoomId;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ This `Fork()` method will do the following:

### Enabling via Project Settings

This functionality is disabled by default and must be enabled in your Project Settings. Under `Plugins > HathoraSDK`, enable `Use Built In Forking` to enable processing supporting forking functionality.
This functionality is disabled by default and must be enabled in your Project Settings. Under `Plugins > Hathora SDK`, enable `Use Built In Forking` to enable processing supporting forking functionality. You may need to click the `Set as Default` button to save the setting to the `Config/DefaultGame.ini` config file for it to be properly packaged in.

### More Technical Details

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class HATHORASDK_API UHathoraSDK : public UBlueprintFunctionLibrary
UFUNCTION(BlueprintPure, Category = "HathoraSDK")
static FString ParseErrorMessage(FString Content);

typedef TDelegate<void(const FString)> FOnGetRoomId;
static void GetServerRoomId(float PollingInterval, FOnGetRoomId OnComplete);

UFUNCTION(BlueprintPure, Category = "HathoraSDK")
static bool IsUsingBuiltInForking();

// Set the auth token to use for all requests; primarily on the
// client after the player has logged in.
// @param Token The JWT auth token to use for all requests.
Expand All @@ -73,4 +79,6 @@ class HATHORASDK_API UHathoraSDK : public UBlueprintFunctionLibrary

private:
void SetCredentials(FString AppId, FHathoraSDKSecurity Security);

void QueryServerRoomId(float PollingInterval, FOnGetRoomId OnComplete);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2023 Hathora, Inc.

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "HathoraTypes.h"
#include "HathoraGetServerRoomId.generated.h"

UDELEGATE()
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(
FHathoraGetServerRoomIdComplete, FString, RoomId
);

UCLASS()
class HATHORASDK_API UHathoraGetServerRoomId : public UBlueprintAsyncActionBase
{
GENERATED_BODY()

public:
virtual void Activate() override;

// Get the current Room ID for this Unreal process. Call this from a server.
// @param PollingInterval The number of seconds to wait between polling to retrieve the Room ID.
UFUNCTION(
BlueprintCallable,
meta =
(BlueprintInternalUseOnly = "true",
Category = "HathoraSDK",
WorldContext = "WorldContextObject")
)
static UHathoraGetServerRoomId *GetServerRoomId(
UObject *WorldContextObject,
float PollingInterval = 1.0f
);

UPROPERTY(BlueprintAssignable)
FHathoraGetServerRoomIdComplete OnComplete;

float PollingInterval;
};
22 changes: 20 additions & 2 deletions SDKDemo/Source/SDKDemo/DemoRoomConfigFunctionLibrary.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,22 @@ class UDemoRoomConfigFunctionLibrary : public UBlueprintFunctionLibrary
UHathoraSDKConfig* Config = GetMutableDefault<UHathoraSDKConfig>();
if (Config->GetUseBuiltInForking())
{
UHathoraForkingSubsystem* ForkingSubsystem = GEngine->GetWorldFromContextObjectChecked(WorldContextObject)->GetSubsystem<UHathoraForkingSubsystem>();
OutString = ForkingSubsystem->AddPortToRoomConfig(OutString);
UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull);

if (World)
{
UGameInstance* GameInstance = World->GetGameInstance();

if (GameInstance)
{
UHathoraForkingSubsystem* ForkingSubsystem = GameInstance->GetSubsystem<UHathoraForkingSubsystem>();

if (ForkingSubsystem)
{
OutString = ForkingSubsystem->AddPortToRoomConfig(OutString);
}
}
}
}
#endif

Expand All @@ -59,6 +73,10 @@ class UDemoRoomConfigFunctionLibrary : public UBlueprintFunctionLibrary
{
JsonStringWithoutPort = UHathoraForkingSubsystem::RemovePortFromRoomConfig(JsonString);
}
else
{
JsonStringWithoutPort = JsonString;
}

FDemoRoomConfig OutRoomConfig;
bool bResult = FJsonObjectConverter::JsonObjectStringToUStruct(JsonStringWithoutPort, &OutRoomConfig, 0, 0);
Expand Down
Loading

0 comments on commit 0d65a83

Please sign in to comment.