You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb PJ.Kack #jira UE-136126 #rnx #preflight ### VirtualizationSystem - Added a new overload for Push to VirtualizationSystem that takes an array of FPushRequest, which is a new structure representing a single payload request. - Filtering by package name is currently disabled, this is because the API has been forced into changing and passing the package name in via a FString rather than FPackagePath which means we would need to be more careful. This will be done in a future submit. - The backend interface has been extended to also have a batch version of PushData, by default this will attempt to submit each request one at a time so payloads don't have to try and implement a batched version if there is no need. - The context being passed with a payload when being pushed has been changed from FPackagePath to FString due to include order issues, as the FPackagePath lives in CoreUObject and the API for virtualization lives in Core. Additionally in the future the payloads might not be owned by a package (there is nothing specifically enforcing this) so the context being a string makes more sense. - NOTE: Due to the context change we currently no longer support the filtering feature, which allows for payloads belonging to packages under specific directories to be excluded from virtualization. This is something that will be solved in a future submit. ### SourceControlBackend - Now that we can submit multiple payloads in the same submit, the CL description has been changed slightly. We will now print a list of payload identifiers -> the package trying to submit that payload. This will only tell the users which package originally caused the payload to submit. If a user submits a new package at a later date that contains the same payload we will not be updating the description. ### PackageSubmissionChecks - Converted the submission process to use the new batch push operation in VirtualizationSystem. -- This means that we do a single push and then have to update the package trailers to convert the now pushed payloads from local to virtualized. - Added new define UE_PRECHECK_PAYLOAD_STATUS that makes it easy to toggle off the checks to see which payloads need to be submitted to the persistent backend. This is useful to test if it actually helps speed up the overall operations or if it is faster to just perform the batch push operations on all payloads and check the return values. -- The hope is that over time the submission processes will become fast enough that we can remove the precheck. - Fixed up logging to not always assume more than one package or payload. ### General Notes - Errors and logging is now a bit more vague as we often not just report that X payloads failed etc rather than specific payload identifiers. This probably doesn't affect the user too much since those identifiers as fairly meaningless to them anyway. - The source control submission could be further optimized by first checking the status of the files in thge depot and only then creating/switching workspace etc. - As currently written, we need to load all of the payloads into memory, then the backends will do what they need (in the case of source control this results in the payloads being written to disk then submitted) which could create quite a large memory spike when submitting a large number of packages. -- One solution would be to change the batch push API to take a "payload provider" interface and have the payloads requested as needed rather than passing in the FCompressedBuffer directly. This would let us immediately write the payload to disk for submission then discard it from memory, preventing larger spikes. Although it could cause overhead if there are multiple backends being submitted to. Internally we are unlikely to have more than one backend per storage solution so maybe we should just make it a config option? #ROBOMERGE-AUTHOR: paul.chipchase #ROBOMERGE-SOURCE: CL 18403735 in //UE5/Release-5.0/... via CL 18403737 #ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v896-18170469) [CL 18403738 by paul chipchase in ue5-release-engine-test branch]
225 lines
7.1 KiB
C++
225 lines
7.1 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "VirtualizationFileBackend.h"
|
|
|
|
#include "HAL/FileManager.h"
|
|
#include "HAL/PlatformProcess.h"
|
|
#include "Misc/Parse.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Virtualization/PayloadId.h"
|
|
#include "VirtualizationUtilities.h"
|
|
|
|
namespace UE::Virtualization
|
|
{
|
|
|
|
FFileSystemBackend::FFileSystemBackend(FStringView ConfigName, FStringView DebugName)
|
|
: IVirtualizationBackend(ConfigName, DebugName, EOperations::Both)
|
|
{
|
|
}
|
|
|
|
bool FFileSystemBackend::Initialize(const FString& ConfigEntry)
|
|
{
|
|
if (!FParse::Value(*ConfigEntry, TEXT("Path="), RootDirectory))
|
|
{
|
|
UE_LOG(LogVirtualization, Error, TEXT("[%s] 'Path=' not found in the config file"), *GetDebugName());
|
|
return false;
|
|
}
|
|
|
|
FPaths::NormalizeDirectoryName(RootDirectory);
|
|
|
|
if (RootDirectory.IsEmpty())
|
|
{
|
|
UE_LOG(LogVirtualization, Error, TEXT("[%s] Config file entry 'Path=' was empty"), *GetDebugName());
|
|
return false;
|
|
}
|
|
|
|
// TODO: Validate that the given path is usable?
|
|
|
|
int32 RetryCountIniFile = INDEX_NONE;
|
|
if (FParse::Value(*ConfigEntry, TEXT("RetryCount="), RetryCountIniFile))
|
|
{
|
|
RetryCount = RetryCountIniFile;
|
|
}
|
|
|
|
int32 RetryWaitTimeMSIniFile = INDEX_NONE;
|
|
if (FParse::Value(*ConfigEntry, TEXT("RetryWaitTime="), RetryWaitTimeMSIniFile))
|
|
{
|
|
RetryWaitTimeMS = RetryWaitTimeMSIniFile;
|
|
}
|
|
|
|
// Now log a summary of the backend settings to make issues easier to diagnose
|
|
UE_LOG(LogVirtualization, Log, TEXT("[%s] Using path: '%s'"), *GetDebugName(), *RootDirectory);
|
|
UE_LOG(LogVirtualization, Log, TEXT("[%s] Will retry failed read attempts %d times with a gap of %dms betwen them"), *GetDebugName(), RetryCount, RetryWaitTimeMS);
|
|
|
|
return true;
|
|
}
|
|
|
|
EPushResult FFileSystemBackend::PushData(const FPayloadId& Id, const FCompressedBuffer& Payload, const FString& PackageContext)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FFileSystemBackend::PushData);
|
|
|
|
if (DoesPayloadExist(Id))
|
|
{
|
|
UE_LOG(LogVirtualization, Verbose, TEXT("[%s] Already has a copy of the payload '%s'."), *GetDebugName(), *Id.ToString());
|
|
return EPushResult::PayloadAlreadyExisted;
|
|
}
|
|
|
|
// Make sure to log any disk write failures to the user, even if this backend will often be optional as they are
|
|
// not expected and could indicate bigger problems.
|
|
//
|
|
// First we will write out the payload to a temp file, after which we will move it to the correct storage location
|
|
// this helps reduce the chance of leaving corrupted data on disk in the case of a power failure etc.
|
|
const FString TempFilePath = FPaths::CreateTempFilename(*FPaths::ProjectSavedDir(), TEXT("miragepayload"));
|
|
|
|
TUniquePtr<FArchive> FileAr(IFileManager::Get().CreateFileWriter(*TempFilePath));
|
|
|
|
if (FileAr == nullptr)
|
|
{
|
|
TStringBuilder<MAX_SPRINTF> SystemErrorMsg;
|
|
Utils::GetFormattedSystemError(SystemErrorMsg);
|
|
|
|
UE_LOG(LogVirtualization, Error, TEXT("[%s] Failed to write payload '%s' to '%s' due to system error: %s"),
|
|
*GetDebugName(),
|
|
*Id.ToString(),
|
|
*TempFilePath,
|
|
SystemErrorMsg.ToString());
|
|
|
|
return EPushResult::Failed;
|
|
}
|
|
|
|
for (const FSharedBuffer& Buffer : Payload.GetCompressed().GetSegments())
|
|
{
|
|
// Const cast because FArchive requires a non-const pointer!
|
|
FileAr->Serialize(const_cast<void*>(Buffer.GetData()), static_cast<int64>(Buffer.GetSize()));
|
|
}
|
|
|
|
if (!FileAr->Close())
|
|
{
|
|
TStringBuilder<MAX_SPRINTF> SystemErrorMsg;
|
|
Utils::GetFormattedSystemError(SystemErrorMsg);
|
|
|
|
UE_LOG(LogVirtualization, Error, TEXT("[%s] Failed to write payload '%s' contents to '%s' due to system error: %s"),
|
|
*GetDebugName(),
|
|
*Id.ToString(),
|
|
*TempFilePath,
|
|
SystemErrorMsg.ToString());
|
|
|
|
IFileManager::Get().Delete(*TempFilePath, true, false, true); // Clean up the temp file if it is still around but do not failure cases to the user
|
|
|
|
return EPushResult::Failed;
|
|
}
|
|
|
|
TStringBuilder<512> FilePath;
|
|
CreateFilePath(Id, FilePath);
|
|
|
|
// If the file already exists we don't need to replace it, we will also do our own error logging.
|
|
if (!IFileManager::Get().Move(FilePath.ToString(), *TempFilePath, /*Replace*/ false, /*EvenIfReadOnly*/ false, /*Attributes*/ false, /*bDoNotRetryOrError*/ true))
|
|
{
|
|
// Store the error message in case we need to display it
|
|
TStringBuilder<MAX_SPRINTF> SystemErrorMsg;
|
|
Utils::GetFormattedSystemError(SystemErrorMsg);
|
|
|
|
IFileManager::Get().Delete(*TempFilePath, true, false, true); // Clean up the temp file if it is still around but do not failure cases to the user
|
|
|
|
// Check if another thread or process was writing out the payload at the same time, if so we
|
|
// don't need to give an error message.
|
|
if (DoesPayloadExist(Id))
|
|
{
|
|
UE_LOG(LogVirtualization, Verbose, TEXT("[%s] Already has a copy of the payload '%s'."), *GetDebugName(), *Id.ToString());
|
|
return EPushResult::PayloadAlreadyExisted;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG( LogVirtualization, Error, TEXT("[%s] Failed to move payload '%s' to it's final location '%s' due to system error: %s"),
|
|
*GetDebugName(),
|
|
*Id.ToString(),
|
|
*FilePath,
|
|
SystemErrorMsg.ToString());
|
|
|
|
return EPushResult::Failed;
|
|
}
|
|
}
|
|
|
|
return EPushResult::Success;
|
|
}
|
|
|
|
FCompressedBuffer FFileSystemBackend::PullData(const FPayloadId& Id)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FFileSystemBackend::PullData);
|
|
|
|
TStringBuilder<512> FilePath;
|
|
CreateFilePath(Id, FilePath);
|
|
|
|
// TODO: Should we allow the error severity to be configured via ini or just not report this case at all?
|
|
if (!IFileManager::Get().FileExists(FilePath.ToString()))
|
|
{
|
|
UE_LOG(LogVirtualization, Verbose, TEXT("[%s] Does not contain the payload '%s'"), *GetDebugName(), *Id.ToString());
|
|
return FCompressedBuffer();
|
|
}
|
|
|
|
TUniquePtr<FArchive> FileAr = OpenFileForReading(FilePath.ToString());
|
|
|
|
if (FileAr == nullptr)
|
|
{
|
|
TStringBuilder<MAX_SPRINTF> SystemErrorMsg;
|
|
Utils::GetFormattedSystemError(SystemErrorMsg);
|
|
|
|
UE_LOG(LogVirtualization, Error, TEXT("[%s] Failed to load payload '%s' from file '%s' due to system error: %s"),
|
|
*GetDebugName(),
|
|
*Id.ToString(),
|
|
FilePath.ToString(),
|
|
SystemErrorMsg.ToString());
|
|
|
|
return FCompressedBuffer();
|
|
}
|
|
|
|
return FCompressedBuffer::Load(*FileAr);
|
|
}
|
|
|
|
bool FFileSystemBackend::DoesPayloadExist(const FPayloadId& Id)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FFileSystemBackend::DoesPayloadExist);
|
|
|
|
TStringBuilder<512> FilePath;
|
|
CreateFilePath(Id, FilePath);
|
|
|
|
return IFileManager::Get().FileExists(FilePath.ToString());
|
|
}
|
|
|
|
void FFileSystemBackend::CreateFilePath(const FPayloadId& PayloadId, FStringBuilderBase& OutPath)
|
|
{
|
|
TStringBuilder<52> PayloadPath;
|
|
Utils::PayloadIdToPath(PayloadId, PayloadPath);
|
|
|
|
OutPath << RootDirectory << TEXT("/") << PayloadPath;
|
|
}
|
|
|
|
TUniquePtr<FArchive> FFileSystemBackend::OpenFileForReading(const TCHAR* FilePath)
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FFileSystemBackend::OpenFileForReading);
|
|
|
|
int32 Retries = 0;
|
|
|
|
while (Retries < RetryCount)
|
|
{
|
|
TUniquePtr<FArchive> FileAr(IFileManager::Get().CreateFileReader(FilePath));
|
|
if (FileAr)
|
|
{
|
|
return FileAr;
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogVirtualization, Warning, TEXT("[%s] Failed to open '%s' for reading attempt retrying (%d/%d) in %dms..."), *GetDebugName(), FilePath, Retries, RetryCount, RetryWaitTimeMS);
|
|
FPlatformProcess::SleepNoStats(RetryWaitTimeMS * 0.001f);
|
|
|
|
Retries++;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
UE_REGISTER_VIRTUALIZATION_BACKEND_FACTORY(FFileSystemBackend, FileSystem);
|
|
|
|
} // namespace UE::Virtualization
|