302 lines
9.0 KiB
C++
302 lines
9.0 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UbaControllerModule.h"
|
|
#include "HttpModule.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "UbaJobProcessor.h"
|
|
#include "UbaHordeConfig.h"
|
|
#include "Features/IModularFeatures.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "Misc/CoreDelegates.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "CoreMinimal.h"
|
|
|
|
|
|
DEFINE_LOG_CATEGORY(LogUbaController);
|
|
|
|
namespace UbaControllerModule
|
|
{
|
|
static constexpr int32 SubFolderCount = 32;
|
|
|
|
static bool bDumpTraceFiles = true;
|
|
static FAutoConsoleVariableRef CVarDumpTraceFiles(
|
|
TEXT("r.UbaController.DumpTraceFiles"),
|
|
bDumpTraceFiles,
|
|
TEXT("If true, UBA controller dumps trace files for later use with UBA visualizer in the Saved folder under UbaController (Enabled by default)"));
|
|
|
|
static FString MakeAndGetDebugInfoPath()
|
|
{
|
|
// Build machines should dump to the AutomationTool/Saved/Logs directory and they will upload as build artifacts via the AutomationTool.
|
|
FString BaseDebugInfoPath = FPaths::ProjectSavedDir();
|
|
if (GIsBuildMachine)
|
|
{
|
|
BaseDebugInfoPath = FPaths::Combine(*FPaths::EngineDir(), TEXT("Programs"), TEXT("AutomationTool"), TEXT("Saved"), TEXT("Logs"));
|
|
}
|
|
|
|
FString AbsoluteDebugInfoDirectory = IFileManager::Get().ConvertToAbsolutePathForExternalAppForWrite(*(BaseDebugInfoPath / TEXT("UbaController")));
|
|
FPaths::NormalizeDirectoryName(AbsoluteDebugInfoDirectory);
|
|
|
|
// Create directory if it doesn't exit yet
|
|
if (!IFileManager::Get().DirectoryExists(*AbsoluteDebugInfoDirectory))
|
|
{
|
|
IFileManager::Get().MakeDirectory(*AbsoluteDebugInfoDirectory, true);
|
|
}
|
|
|
|
return AbsoluteDebugInfoDirectory;
|
|
}
|
|
|
|
static const FString GetTempDir()
|
|
{
|
|
static FString HordeSharedDir = FPlatformMisc::GetEnvironmentVariable(TEXT("UE_HORDE_SHARED_DIR"));
|
|
if (HordeSharedDir.IsEmpty())
|
|
HordeSharedDir = FPlatformProcess::UserTempDir();
|
|
return HordeSharedDir;
|
|
}
|
|
|
|
};
|
|
|
|
FUbaControllerModule::FUbaControllerModule()
|
|
: bSupported(false)
|
|
, bModuleInitialized(false)
|
|
, bControllerInitialized(false)
|
|
, RootWorkingDirectory(FPaths::Combine(*UbaControllerModule::GetTempDir(), TEXT("UbaControllerWorkingDir")))
|
|
, WorkingDirectory(FPaths::Combine(RootWorkingDirectory, FGuid::NewGuid().ToString(EGuidFormats::Digits)))
|
|
, NextFileID(0)
|
|
, NextTaskID(0)
|
|
{
|
|
}
|
|
|
|
FString FUbaControllerModule::GetTempDir()
|
|
{
|
|
return UbaControllerModule::GetTempDir();
|
|
}
|
|
|
|
FUbaControllerModule::~FUbaControllerModule()
|
|
{
|
|
if (JobDispatcherThread)
|
|
{
|
|
JobDispatcherThread->Stop();
|
|
// Wait until the thread is done
|
|
FPlatformProcess::ConditionalSleep([&](){ return JobDispatcherThread && JobDispatcherThread->IsWorkDone(); },0.1f);
|
|
}
|
|
|
|
CleanWorkingDirectory();
|
|
}
|
|
|
|
static bool IsUbaControllerEnabled()
|
|
{
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("NoUbaController")) || // Compatibility to old terminology
|
|
FParse::Param(FCommandLine::Get(), TEXT("NoUbaShaderCompile")) || // Same terminology as other shader controllers
|
|
FParse::Param(FCommandLine::Get(), TEXT("NoShaderWorker")))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check if UbaController is enabled via command line argument
|
|
if (FParse::Param(FCommandLine::Get(), TEXT("UBA")) ||
|
|
FParse::Param(FCommandLine::Get(), TEXT("UBAEnableHorde")))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Check if UbaController is enabled via INI configuration in [UbaController] section.
|
|
return FUbaHordeConfig::Get().bIsProviderEnabled;
|
|
}
|
|
|
|
bool FUbaControllerModule::IsSupported()
|
|
{
|
|
if (bControllerInitialized)
|
|
{
|
|
return bSupported;
|
|
}
|
|
|
|
const bool bEnabled = IsUbaControllerEnabled();
|
|
|
|
bSupported = FPlatformProcess::SupportsMultithreading() && bEnabled;
|
|
return bSupported;
|
|
}
|
|
|
|
void FUbaControllerModule::CleanWorkingDirectory() const
|
|
{
|
|
if (UE::GetMultiprocessId() != 0) // Only director is allowed to clean
|
|
{
|
|
return;
|
|
}
|
|
|
|
IFileManager& FileManager = IFileManager::Get();
|
|
|
|
if (!RootWorkingDirectory.IsEmpty())
|
|
{
|
|
if (!FileManager.DeleteDirectory(*RootWorkingDirectory, false, true))
|
|
{
|
|
UE_LOG(LogUbaController, Log, TEXT("%s => Failed to delete current working Directory => %s"), ANSI_TO_TCHAR(__FUNCTION__), *RootWorkingDirectory);
|
|
}
|
|
}
|
|
}
|
|
|
|
FString GetUbaBinariesPath()
|
|
{
|
|
#if PLATFORM_WINDOWS
|
|
#if PLATFORM_CPU_ARM_FAMILY
|
|
const TCHAR* BinariesArch = TEXT("arm64");
|
|
#else
|
|
const TCHAR* BinariesArch = TEXT("x64");
|
|
#endif
|
|
return FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries"), TEXT("Win64"), TEXT("UnrealBuildAccelerator"), BinariesArch);
|
|
#elif PLATFORM_MAC
|
|
return FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries"), TEXT("Mac"), TEXT("UnrealBuildAccelerator"));
|
|
#elif PLATFORM_LINUX
|
|
return FPaths::Combine(FPaths::EngineDir(), TEXT("Binaries"), TEXT("Linux"), TEXT("UnrealBuildAccelerator"));
|
|
#else
|
|
#error Unsupported platform to compile UbaController plugin. Only Win64, Mac, and Linux are supported!
|
|
#endif
|
|
}
|
|
|
|
void FUbaControllerModule::LoadDependencies()
|
|
{
|
|
const FString UbaBinariesPath = GetUbaBinariesPath();
|
|
FPlatformProcess::AddDllDirectory(*UbaBinariesPath);
|
|
#if PLATFORM_WINDOWS
|
|
FPlatformProcess::GetDllHandle(*(FPaths::Combine(UbaBinariesPath, "UbaHost.dll")));
|
|
#elif PLATFORM_LINUX
|
|
FPlatformProcess::GetDllHandle(*(FPaths::Combine(UbaBinariesPath, "libUbaHost.so")));
|
|
#endif
|
|
}
|
|
|
|
void FUbaControllerModule::StartupModule()
|
|
{
|
|
check(!bModuleInitialized);
|
|
|
|
LoadDependencies();
|
|
|
|
IModularFeatures::Get().RegisterModularFeature(GetModularFeatureType(), this);
|
|
|
|
bModuleInitialized = true;
|
|
|
|
FCoreDelegates::OnEnginePreExit.AddLambda([&]()
|
|
{
|
|
if (bControllerInitialized && JobDispatcherThread)
|
|
{
|
|
JobDispatcherThread->Stop();
|
|
FPlatformProcess::ConditionalSleep([&]() { return JobDispatcherThread && JobDispatcherThread->IsWorkDone(); }, 0.1f);
|
|
JobDispatcherThread = nullptr;
|
|
}
|
|
});
|
|
}
|
|
|
|
void FUbaControllerModule::ShutdownModule()
|
|
{
|
|
check(bModuleInitialized);
|
|
|
|
IModularFeatures::Get().UnregisterModularFeature(GetModularFeatureType(), this);
|
|
|
|
if (bControllerInitialized)
|
|
{
|
|
// Stop the jobs thread
|
|
if (JobDispatcherThread)
|
|
{
|
|
JobDispatcherThread->Stop();
|
|
// Wait until the thread is done
|
|
FPlatformProcess::ConditionalSleep([&](){ return JobDispatcherThread && JobDispatcherThread->IsWorkDone(); },0.1f);
|
|
}
|
|
|
|
FDistributedBuildTask* Task;
|
|
while (PendingRequestedCompilationTasks.Dequeue(Task))
|
|
{
|
|
Task->Cancel();
|
|
delete Task;
|
|
}
|
|
|
|
PendingRequestedCompilationTasks.Empty();
|
|
}
|
|
|
|
CleanWorkingDirectory();
|
|
bModuleInitialized = false;
|
|
bControllerInitialized = false;
|
|
}
|
|
|
|
void FUbaControllerModule::InitializeController()
|
|
{
|
|
// We should never Initialize the controller twice
|
|
if (ensureAlwaysMsgf(!bControllerInitialized, TEXT("Multiple initialization of UBA controller!")))
|
|
{
|
|
CleanWorkingDirectory();
|
|
|
|
if (IsSupported())
|
|
{
|
|
IFileManager::Get().MakeDirectory(*WorkingDirectory, true);
|
|
|
|
// Pre-create the directories so we don't have to explicitly register them to uba later
|
|
for (int32 FolderIndex = 0; FolderIndex < UbaControllerModule::SubFolderCount; ++FolderIndex)
|
|
{
|
|
IFileManager::Get().MakeDirectory(*FPaths::Combine(WorkingDirectory, FString::FromInt(FolderIndex)));
|
|
}
|
|
|
|
if (UbaControllerModule::bDumpTraceFiles)
|
|
{
|
|
DebugInfoPath = UbaControllerModule::MakeAndGetDebugInfoPath();
|
|
}
|
|
// Make sure http module is loaded in game thread before launch UBA client
|
|
FModuleManager::LoadModuleChecked<FHttpModule>("HTTP");
|
|
JobDispatcherThread = MakeShared<FUbaJobProcessor>(*this);
|
|
JobDispatcherThread->StartThread();
|
|
}
|
|
|
|
bControllerInitialized = true;
|
|
}
|
|
}
|
|
|
|
bool FUbaControllerModule::SupportsLocalWorkers()
|
|
{
|
|
// UbaController supports local workers if the maximum number of local cores is greater than zero or -1 (special value to use all available cores)
|
|
const int32 MaxLocalCores = FUbaHordeConfig::Get().MaxParallelActions;
|
|
return MaxLocalCores > 0 || MaxLocalCores == -1;
|
|
}
|
|
|
|
FString FUbaControllerModule::CreateUniqueFilePath()
|
|
{
|
|
check(bSupported);
|
|
const int32 FileID = NextFileID++;
|
|
const int32 FolderID = FileID % UbaControllerModule::SubFolderCount; // We use sub folders to be nicer to file system (we can end up with 20000 files in one folder otherwise)
|
|
return FPaths::Combine(WorkingDirectory, FString::FromInt(FolderID), FString::Printf(TEXT("%d.uba"), FileID));
|
|
}
|
|
|
|
TFuture<FDistributedBuildTaskResult> FUbaControllerModule::EnqueueTask(const FTaskCommandData& CommandData)
|
|
{
|
|
check(bSupported);
|
|
|
|
TPromise<FDistributedBuildTaskResult> Promise;
|
|
TFuture<FDistributedBuildTaskResult> Future = Promise.GetFuture();
|
|
|
|
// Enqueue the new task
|
|
FDistributedBuildTask* Task = new FDistributedBuildTask(NextTaskID++, CommandData, MoveTemp(Promise));
|
|
{
|
|
PendingRequestedCompilationTasks.Enqueue(Task);
|
|
}
|
|
|
|
return MoveTemp(Future);
|
|
}
|
|
|
|
bool FUbaControllerModule::PollStats(FDistributedBuildStats& OutStats)
|
|
{
|
|
return JobDispatcherThread != nullptr && JobDispatcherThread->PollStats(OutStats);
|
|
}
|
|
|
|
void FUbaControllerModule::ReportJobProcessed(const FTaskResponse& InTaskResponse, FDistributedBuildTask* CompileTask)
|
|
{
|
|
if (CompileTask)
|
|
{
|
|
CompileTask->Finalize(InTaskResponse.ReturnCode);
|
|
delete CompileTask;
|
|
}
|
|
}
|
|
|
|
UBACONTROLLER_API FUbaControllerModule& FUbaControllerModule::Get()
|
|
{
|
|
return FModuleManager::LoadModuleChecked<FUbaControllerModule>(TEXT("UbaController"));
|
|
}
|
|
|
|
IMPLEMENT_MODULE(FUbaControllerModule, UbaController);
|