Files
UnrealEngine/Engine/Plugins/Media/PixelStreaming/Source/PixelStreamingServers/Private/ServerUtils.cpp
2025-05-18 13:04:45 +08:00

296 lines
9.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ServerUtils.h"
#include "HAL/PlatformFileManager.h"
#include "PixelStreamingServers.h"
#include "PixelStreamingServersModule.h"
#include "Containers/UnrealString.h"
#include "PixelStreamingServersLog.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"
#include "Serialization/JsonReader.h"
#include "Policies/CondensedJsonPrintPolicy.h"
#include "Misc/Paths.h"
#include "GenericPlatform/GenericPlatformFile.h"
namespace UE::PixelStreamingServers::Utils
{
TSharedPtr<FMonitoredProcess> LaunchChildProcess(FString ExecutableAbsPath, FString Args, FString LogPrefix, bool bRunAsScript)
{
// Check if the binary actually exists
IPlatformFile& FileManager = FPlatformFileManager::Get().GetPlatformFile();
if (!FileManager.FileExists(*ExecutableAbsPath))
{
UE_LOG(LogPixelStreamingServers, Error, TEXT("Cannot start child process - the specified file did not exist. File=%s"), *ExecutableAbsPath);
return TSharedPtr<FMonitoredProcess>();
}
if(bRunAsScript)
{
// Get the executable we will use to run the scripts (e.g. cmd.exe on Windows)
#if PLATFORM_WINDOWS
Args = FString::Printf(TEXT("/c \"\"%s\" %s\""), *ExecutableAbsPath, *Args);
ExecutableAbsPath = TEXT("cmd.exe");
#elif PLATFORM_LINUX
Args = FString::Printf(TEXT(" -- \"%s\" %s --nosudo"), *ExecutableAbsPath, *Args);
ExecutableAbsPath = TEXT("/usr/bin/bash");
#elif PLATFORM_MAC
Args = FString::Printf(TEXT(" -- \"%s\" %s --nosudo"), *ExecutableAbsPath, *Args);
ExecutableAbsPath = TEXT("/bin/bash");
#else
UE_LOG(LogPixelStreamingServers, Error, TEXT("Unsupported platform for Pixel Streaming."));
return TSharedPtr<FMonitoredProcess>();
#endif
}
TSharedPtr<FMonitoredProcess> ChildProcess = MakeShared<FMonitoredProcess>(
ExecutableAbsPath,
Args,
true,
#if PLATFORM_MAC
// Pipes cause UE to lockup when destroying on Mac
false
#else
true
#endif
);
// Bind to output so we can capture the output in the log
ChildProcess->OnOutput().BindLambda([LogPrefix](FString Output) {
UE_LOG(LogPixelStreamingServers, Log, TEXT("%s - %s"), *LogPrefix, *Output);
});
// Run the child process
UE_LOG(LogPixelStreamingServers, Log, TEXT("Launch child process - %s %s"), *ExecutableAbsPath, *Args);
ChildProcess->Launch();
return ChildProcess;
}
bool ExtractValueFromArgs(FString ArgsString, FString ArgKey, FString FallbackValue, FString& OutValue)
{
// Tokenize string in single whitespace " ".
TArray<FString> ArgTokens;
ArgsString.ParseIntoArray(ArgTokens, TEXT(" "), true);
for (FString& Token : ArgTokens)
{
Token.TrimStartAndEndInline();
if (!Token.StartsWith(ArgKey, ESearchCase::Type::CaseSensitive))
{
continue;
}
// We have a matching token for our search "key" - split on it.
FString RightStr;
if (!Token.Split(TEXT("="), nullptr, &RightStr))
{
continue;
}
OutValue = RightStr;
return true;
}
OutValue = FallbackValue;
return false;
}
FString QueryOrSetProcessArgs(FLaunchArgs& LaunchArgs, FString ArgKey, FString FallbackArgValue)
{
FString OutValue;
bool bExtractedValue = ExtractValueFromArgs(LaunchArgs.ProcessArgs, ArgKey, FallbackArgValue, OutValue);
// No key was present so we will inject our own.
if (!bExtractedValue)
{
LaunchArgs.ProcessArgs += FString::Printf(TEXT(" %s%s"), *ArgKey, *FallbackArgValue);
}
return OutValue;
}
bool GetResourcesDir(FString& OutResourcesDir)
{
#if WITH_EDITOR
OutResourcesDir = FPaths::EnginePluginsDir() / TEXT("Media") / TEXT("PixelStreaming") / TEXT("Resources");
#else
OutResourcesDir = FPaths::ProjectDir() / TEXT("Samples") / TEXT("PixelStreaming");
#endif // WITH_EDITOR
OutResourcesDir = FPaths::ConvertRelativePathToFull(OutResourcesDir);
if(FPaths::DirectoryExists(OutResourcesDir))
{
return true;
}
return false;
}
bool GetWebServersDir(FString& OutWebServersAbsPath)
{
bool bResourceDirExists = GetResourcesDir(OutWebServersAbsPath);
if(!bResourceDirExists)
{
return false;
}
OutWebServersAbsPath = OutWebServersAbsPath / TEXT("WebServers");
if(FPaths::DirectoryExists(OutWebServersAbsPath))
{
return true;
}
return false;
}
bool GetDownloadedServer(FString& OutAbsPath, FString ServerDirectoryName)
{
bool bServersDirExists = GetWebServersDir(OutAbsPath);
if(!bServersDirExists)
{
return false;
}
// Now add the {ServerDirectoryName}/plaform_script/{os_specific_path}/{run_local.bat|sh}
OutAbsPath = OutAbsPath / ServerDirectoryName / TEXT("platform_scripts");
#if PLATFORM_WINDOWS
OutAbsPath = OutAbsPath / TEXT("cmd") / TEXT("run_local.bat");
#elif PLATFORM_LINUX || PLATFORM_MAC
OutAbsPath = OutAbsPath / TEXT("bash") / TEXT("run_local.sh");
#else
UE_LOG(LogPixelStreamingServers, Error, TEXT("Unsupported platform for Pixel Streaming scripts."));
return false;
#endif
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
if(PlatformFile.FileExists(*OutAbsPath))
{
return true;
}
return false;
}
TSharedPtr<FMonitoredProcess> DownloadPixelStreamingServers(bool bSkipIfPresent)
{
FString OutScriptPath;
if(bSkipIfPresent && GetDownloadedServer(OutScriptPath, FString(TEXT("SignallingWebServer"))))
{
UE_LOG(LogPixelStreamingServers, Log, TEXT("Found pixel streaming servers, skipping download."));
// empty process
return TSharedPtr<FMonitoredProcess>();
}
bool bHasWebServersDir = GetWebServersDir(OutScriptPath);
if(!bHasWebServersDir)
{
UE_LOG(LogPixelStreamingServers, Error, TEXT("Could not download ps servers, no PixelStreaming/Resources/WebServers directory found."));
}
FString Args = TEXT("");
#if PLATFORM_WINDOWS
OutScriptPath = OutScriptPath / TEXT("get_ps_servers.bat");
#elif PLATFORM_LINUX || PLATFORM_MAC
OutScriptPath = OutScriptPath / TEXT("get_ps_servers.sh");
#else
UE_LOG(LogPixelStreamingServers, Error, TEXT("Unsupported platform for Pixel Streaming scripts."));
// empty process
return TSharedPtr<FMonitoredProcess>();
#endif
return LaunchChildProcess(OutScriptPath, Args, FString(TEXT("Download ps servers")), true /*bRunAsScript*/);
}
void PopulateCirrusEndPoints(FLaunchArgs& InLaunchArgs, TMap<EEndpoint, FURL>& OutEndPoints)
{
FPixelStreamingServersModule& Module = FPixelStreamingServersModule::Get();
// Query for ports, or set them if they don't exist
FString StreamerPort = Utils::QueryOrSetProcessArgs(InLaunchArgs, TEXT("--StreamerPort="), FString::FromInt(Module.NextPort()));
FString SFUPort = Utils::QueryOrSetProcessArgs(InLaunchArgs, TEXT("--SFUPort="), FString::FromInt(Module.NextPort()));
FString MatchmakerPort = Utils::QueryOrSetProcessArgs(InLaunchArgs, TEXT("--MatchmakerPort="), FString::FromInt(Module.NextPort()));
FString HttpPort = Utils::QueryOrSetProcessArgs(InLaunchArgs, TEXT("--HttpPort="), FString::FromInt(Module.NextPort()));
// Construct endpoint urls
// Streamer
FURL SignallingStreamerUrl;
SignallingStreamerUrl.Protocol = TEXT("ws");
SignallingStreamerUrl.Host = TEXT("127.0.0.1");
SignallingStreamerUrl.Port = FCString::Atoi(*StreamerPort);
SignallingStreamerUrl.Map = FString();
// Players
FURL PlayersUrl;
PlayersUrl.Protocol = TEXT("ws");
PlayersUrl.Host = TEXT("127.0.0.1");
PlayersUrl.Port = FCString::Atoi(*HttpPort);
PlayersUrl.Map = FString();
// Webserver
FURL WebServerUrl;
WebServerUrl.Protocol = TEXT("http");
WebServerUrl.Host = TEXT("127.0.0.1");
WebServerUrl.Port = FCString::Atoi(*HttpPort);
WebServerUrl.Map = FString();
// SFU
FURL SFUUrl;
SFUUrl.Protocol = TEXT("ws");
SFUUrl.Host = TEXT("127.0.0.1");
SFUUrl.Port = FCString::Atoi(*SFUPort);
SFUUrl.Map = FString();
// Matchmaker
FURL MatchmakerUrl;
MatchmakerUrl.Protocol = TEXT("ws");
MatchmakerUrl.Host = TEXT("127.0.0.1");
MatchmakerUrl.Port = FCString::Atoi(*MatchmakerPort);
MatchmakerUrl.Map = FString();
OutEndPoints.Add(EEndpoint::Signalling_Streamer, SignallingStreamerUrl);
OutEndPoints.Add(EEndpoint::Signalling_Players, PlayersUrl);
OutEndPoints.Add(EEndpoint::Signalling_SFU, SFUUrl);
OutEndPoints.Add(EEndpoint::Signalling_Matchmaker, MatchmakerUrl);
OutEndPoints.Add(EEndpoint::Signalling_Webserver, WebServerUrl);
}
FString ToString(FURL Url)
{
return FString::Printf(TEXT("%s://%s:%d"), *(Url.Protocol), *(Url.Host), Url.Port);
}
FString ToString(TArrayView<uint8> UTF8Bytes)
{
FUTF8ToTCHAR Converted((const ANSICHAR*)UTF8Bytes.GetData(), UTF8Bytes.Num());
FString OutString = FString::ConstructFromPtrSize(Converted.Get(), Converted.Length());
return OutString;
}
FString ToString(TSharedPtr<FJsonObject> JSONObj)
{
FString Res;
auto JsonWriter = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create(&Res);
bool bSerialized = FJsonSerializer::Serialize(JSONObj.ToSharedRef(), JsonWriter);
if(!bSerialized)
{
UE_LOG(LogPixelStreamingServers, Error, TEXT("Failed to stringify JSON object."));
}
return Res;
}
TSharedPtr<FJsonObject> ToJSON(const FString& InString)
{
TSharedPtr<FJsonObject> OutJSON = MakeShared<FJsonObject>();
const auto JsonReader = TJsonReaderFactory<TCHAR>::Create(InString);
if (FJsonSerializer::Deserialize(JsonReader, OutJSON))
{
return OutJSON;
}
return nullptr;
}
} // UE::PixelStreamingServers::Utils