Files
UnrealEngine/Engine/Source/Editor/VirtualizationEditor/Private/GeneratePayloadManifestCommandlet.cpp
2025-05-18 13:04:45 +08:00

266 lines
6.4 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "GeneratePayloadManifestCommandlet.h"
#include "CommandletUtils.h"
#include "HAL/FileManager.h"
#include "Misc/PackageName.h"
#include "Misc/PackagePath.h"
#include "Misc/Paths.h"
#include "UObject/PackageTrailer.h"
#include "VirtualizationExperimentalUtilities.h"
ENUM_CLASS_FLAGS(UGeneratePayloadManifestCommandlet::EPayloadFilter);
class FSpreadSheet
{
public:
bool OpenNewSheet()
{
if (NumSheets == 0)
{
if (!CleanupExistingFiles())
{
return false;
}
}
Ar.Reset();
TStringBuilder<512> CSVPath;
CreateFilePath(++NumSheets, CSVPath);
Ar = TUniquePtr<FArchive>(IFileManager::Get().CreateFileWriter(CSVPath.ToString()));
if (Ar.IsValid())
{
const ANSICHAR* Headings = "Path,PayloadId,SizeOnDisk,UncompressedSize,StorageType, FilterReason\n";
Ar->Serialize((void*)Headings, FPlatformString::Strlen(Headings));
return true;
}
else
{
UE_LOG(LogVirtualization, Error, TEXT("Failed to open '%s' for write"), CSVPath.ToString());
return false;
}
}
bool PrintRow(FAnsiStringView Row)
{
if (++NumRowsInSheet >= MaxNumRowsPerSheet || (Ar->Tell() + Row.Len()) >= MaxFileSize)
{
if (!OpenNewSheet())
{
return false;
}
NumRowsInSheet = 0;
}
Ar->Serialize((void*)Row.GetData(), Row.Len() * sizeof(FAnsiStringView::ElementType));
return true;
}
void Flush()
{
Ar.Reset();
}
FString GetDebugInfo() const
{
TStringBuilder<1024> Output;
Output << TEXT("Manifest is comprised of ") << NumSheets << TEXT(" sheet(s) written to:");
for (int32 Index = 0; Index < NumSheets; ++Index)
{
TStringBuilder<512> Path;
CreateFilePath(Index, Path);
Output << LINE_TERMINATOR << Path;
}
return Output.ToString();
}
private:
bool CleanupExistingFiles()
{
int32 SheetToDelete = 1;
TStringBuilder<512> Path;
CreateFilePath(SheetToDelete, Path);
while (IFileManager::Get().FileExists(Path.ToString()))
{
if (!IFileManager::Get().Delete(Path.ToString()))
{
return false;
}
CreateFilePath(++SheetToDelete, Path);
}
return true;
}
static void CreateFilePath(int32 Index, FStringBuilderBase& Path)
{
Path.Reset();
Path << FPaths::ProjectSavedDir() << TEXT("PayloadManifest/sheet") << Index << TEXT(".csv");
}
// With any values above these we had trouble importing the finished cvs into various spread sheet programs
const int64 MaxNumRowsPerSheet = 250000;
const int64 MaxFileSize = 45 * 1024 * 1024;
int32 NumSheets = 0;
int32 NumRowsInSheet = 0;
TUniquePtr<FArchive> Ar;
};
UGeneratePayloadManifestCommandlet::UGeneratePayloadManifestCommandlet(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
}
int32 UGeneratePayloadManifestCommandlet::Main(const FString& Params)
{
TRACE_CPUPROFILER_EVENT_SCOPE(UGeneratePayloadManifestCommandlet);
if (!ParseCmdline(Params))
{
UE_LOG(LogVirtualization, Error, TEXT("Failed to parse the command line correctly"));
return -1;
}
TArray<FString> PackageNames = UE::Virtualization::DiscoverPackages(Params, UE::Virtualization::EFindPackageFlags::ExcludeEngineContent);
UE_LOG(LogVirtualization, Display, TEXT("Found %d files to look in"), PackageNames.Num());
int32 PackageTrailerCount = 0;
int32 PayloadCount = 0;
FSpreadSheet Sheet;
if (!Sheet.OpenNewSheet())
{
return 1;
}
{
TRACE_CPUPROFILER_EVENT_SCOPE(ParsePackageTrailers);
UE_LOG(LogVirtualization, Display, TEXT("Parsing files..."));
uint32 FilesParsed = 0;
for (const FString& PackagePath : PackageNames)
{
if (FPackageName::IsPackageFilename(PackagePath))
{
UE::FPackageTrailer Trailer;
if (UE::FPackageTrailer::TryLoadFromFile(PackagePath, Trailer))
{
PackageTrailerCount++;
Trailer.ForEachPayload([&Sheet, &PackagePath, &PayloadCount, OutputFilter = Filter](const FIoHash& Id, uint64 SizeOnDisk, uint64 RawSize, UE::EPayloadAccessMode Mode, UE::Virtualization::EPayloadFilterReason Reason)->void
{
if (Mode != UE::EPayloadAccessMode::Virtualized)
{
if (EnumHasAllFlags(OutputFilter, EPayloadFilter::VirtualizedOnly))
{
return;
}
Reason = UE::Virtualization::Utils::FixFilterFlags(PackagePath, SizeOnDisk, Reason);
if (EnumHasAllFlags(OutputFilter, EPayloadFilter::PendingOnly) && Reason != UE::Virtualization::EPayloadFilterReason::None)
{
return;
}
if (EnumHasAllFlags(OutputFilter, EPayloadFilter::FilteredOnly) && Reason == UE::Virtualization::EPayloadFilterReason::None)
{
return;
}
}
else
{
if (EnumHasAnyFlags(OutputFilter, EPayloadFilter::LocalOnly))
{
return;
}
}
PayloadCount++;
TAnsiStringBuilder<256> LineBuilder;
LineBuilder << PackagePath << "," << Id << "," << SizeOnDisk << "," << RawSize << "," << Mode << "," << *LexToString(Reason) <<"\n";
Sheet.PrintRow(LineBuilder);
});
}
}
if (++FilesParsed % 10000 == 0)
{
float PercentCompleted = ((float)FilesParsed / (float)PackageNames.Num()) * 100.0f;
UE_LOG(LogVirtualization, Display, TEXT("Parsed %d/%d (%.0f%%)"), FilesParsed, PackageNames.Num(), PercentCompleted);
}
}
}
Sheet.Flush();
UE_LOG(LogVirtualization, Display, TEXT("Found %d package trailers with %d payloads"), PackageTrailerCount, PayloadCount);
UE_LOG(LogVirtualization, Display, TEXT("%s"), *Sheet.GetDebugInfo());
return 0;
}
bool UGeneratePayloadManifestCommandlet::ParseCmdline(const FString& Params)
{
TArray<FString> Tokens;
TArray<FString> Switches;
ParseCommandLine(*Params, Tokens, Switches);
auto ApplyFilter = [this, &Switches](const TCHAR* FilterName, EPayloadFilter FilterType)->bool
{
if (Switches.Contains(FilterName))
{
if (Filter != EPayloadFilter::None)
{
UE_LOG(LogVirtualization, Error, TEXT("Comandlet cannot have multiple filters active at the same time"));
return false;
}
Filter = FilterType;
}
return true;
};
TPair<const TCHAR*, EPayloadFilter> Filters[] =
{
{TEXT("LocalOnly"), EPayloadFilter::LocalOnly},
{TEXT("PendingOnly"), EPayloadFilter::PendingOnly},
{TEXT("FilteredOnly"), EPayloadFilter::FilteredOnly},
{TEXT("VirtualizedOnly"), EPayloadFilter::VirtualizedOnly}
};
for (const TPair<const TCHAR*, EPayloadFilter>& It : Filters)
{
if (!ApplyFilter(It.Key, It.Value))
{
return false;
}
}
return true;
}