Files
UnrealEngineUWP/Engine/Source/Developer/IoStoreUtilities/Private/ZenStoreWriter.cpp
zousar shaker 0832797d28 Ensure that Zen ProjectIds are unique for projects with the same name in differing roots (or in the same root) while attempting to maintain some easily recognizable desriptors for the project.
Staged builds are explicitly assigned a project id via the persistent UECommandline.txt file.  This works on mobile/consoles, but isn't implemented for Windows/Linux/Mac, so staged builds for those platforms don't have a persistent assignment of project id and instead rely on the launch by the editor including the appropriate commandlines.
Unstaged builds on Windows/Mac/Linux now determine project id independently after they have determined the project file path.  This happens after the project file has been determined and has had case corrected.  This ensures it is different/unique for multiple blueprint projects that share the same executable.
Fix a bug where second cook in editor to zen would stall because the queue thinks all additions are finished due to a boolean that isn't reset.

#rb devin.doucette
#rb per.larsson
#preflight 61b39a35a2562c8b1c40f81e

#ROBOMERGE-AUTHOR: zousar.shaker
#ROBOMERGE-SOURCE: CL 18432317 in //UE5/Release-5.0/... via CL 18435404
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v897-18405271)

[CL 18435895 by zousar shaker in ue5-release-engine-test branch]
2021-12-10 18:06:39 -05:00

1115 lines
35 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ZenStoreWriter.h"
#include "ZenStoreHttpClient.h"
#include "ZenFileSystemManifest.h"
#include "PackageStoreOptimizer.h"
#include "Algo/BinarySearch.h"
#include "Algo/IsSorted.h"
#include "Algo/Sort.h"
#include "AssetRegistry/AssetRegistryState.h"
#include "Async/Async.h"
#include "Containers/Queue.h"
#include "Serialization/ArrayReader.h"
#include "Serialization/CompactBinaryPackage.h"
#include "Serialization/CompactBinaryWriter.h"
#include "Serialization/LargeMemoryWriter.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformFileManager.h"
#include "IO/IoDispatcher.h"
#include "Misc/App.h"
#include "Misc/FileHelper.h"
#include "Misc/StringBuilder.h"
#include "Interfaces/ITargetPlatform.h"
#include "Misc/Paths.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "UObject/SavePackage.h"
DEFINE_LOG_CATEGORY_STATIC(LogZenStoreWriter, Log, All);
using namespace UE;
// Note that this is destructive - we yank out the buffer memory from the
// IoBuffer into the FSharedBuffer
FSharedBuffer IoBufferToSharedBuffer(FIoBuffer& InBuffer)
{
InBuffer.EnsureOwned();
const uint64 DataSize = InBuffer.DataSize();
uint8* DataPtr = InBuffer.Release().ValueOrDie();
return FSharedBuffer{ FSharedBuffer::TakeOwnership(DataPtr, DataSize, FMemory::Free) };
};
FCbObjectId ToObjectId(const FIoChunkId& ChunkId)
{
return FCbObjectId(MakeMemoryView(ChunkId.GetData(), ChunkId.GetSize()));
}
FMD5Hash IoHashToMD5(const FIoHash& IoHash)
{
const FIoHash::ByteArray& Bytes = IoHash.GetBytes();
FMD5 MD5Gen;
MD5Gen.Update(Bytes, sizeof(FIoHash::ByteArray));
FMD5Hash Hash;
Hash.Set(MD5Gen);
return Hash;
}
class FZenStoreWriter::FCommitQueue
{
public:
FCommitQueue()
: NewCommitEvent(EEventMode::AutoReset)
, QueueEmptyEvent(EEventMode::ManualReset) {}
void Enqueue(const FCommitPackageInfo& Commit)
{
{
FScopeLock _(&QueueCriticalSection);
Queue.Enqueue(Commit);
QueueNum++;
}
NewCommitEvent->Trigger();
}
bool BlockAndDequeue(FCommitPackageInfo& OutCommit)
{
for (;;)
{
{
FScopeLock _(&QueueCriticalSection);
if (Queue.Dequeue(OutCommit))
{
QueueNum--;
return true;
}
QueueEmptyEvent->Trigger();
}
if (bCompleteAdding)
{
return false;
}
NewCommitEvent->Wait();
}
}
void CompleteAdding()
{
bCompleteAdding = true;
NewCommitEvent->Trigger();
}
void ResetAdding()
{
bCompleteAdding = false;
}
void WaitUntilEmpty()
{
bool bWait = false;
{
FScopeLock _(&QueueCriticalSection);
if (QueueNum > 0)
{
QueueEmptyEvent->Reset();
bWait = true;
}
}
if (bWait)
{
QueueEmptyEvent->Wait();
}
}
private:
FEventRef NewCommitEvent;
FEventRef QueueEmptyEvent;
FCriticalSection QueueCriticalSection;
TQueue<FCommitPackageInfo> Queue;
int32 QueueNum = 0;
TAtomic<bool> bCompleteAdding{false};
};
TArray<const UTF8CHAR*> FZenStoreWriter::ReservedOplogKeys;
void FZenStoreWriter::StaticInit()
{
if (ReservedOplogKeys.Num() > 0)
{
return;
}
ReservedOplogKeys.Append({ UTF8TEXT("files"), UTF8TEXT("key"), UTF8TEXT("package"), UTF8TEXT("packagestoreentry") });
Algo::Sort(ReservedOplogKeys, [](const UTF8CHAR* A, const UTF8CHAR* B)
{
return FUtf8StringView(A).Compare(FUtf8StringView(B), ESearchCase::IgnoreCase) < 0;
});;
}
FZenStoreWriter::FZenStoreWriter(
const FString& InOutputPath,
const FString& InMetadataDirectoryPath,
const ITargetPlatform* InTargetPlatform
)
: TargetPlatform(*InTargetPlatform)
, OutputPath(InOutputPath)
, MetadataDirectoryPath(InMetadataDirectoryPath)
, PackageStoreManifest(InOutputPath)
, PackageStoreOptimizer(new FPackageStoreOptimizer())
, CookMode(ICookedPackageWriter::FCookInfo::CookByTheBookMode)
, bInitialized(false)
{
StaticInit();
FString ProjectId = FZenStoreHttpClient::GenerateDefaultProjectId();
FString OplogId = InTargetPlatform->PlatformName();
HttpClient = MakeUnique<UE::FZenStoreHttpClient>();
FString RootDir = FPaths::RootDir();
FString EngineDir = FPaths::EngineDir();
FPaths::NormalizeDirectoryName(EngineDir);
FString ProjectDir = FPaths::ProjectDir();
FPaths::NormalizeDirectoryName(ProjectDir);
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
FString AbsServerRoot = PlatformFile.ConvertToAbsolutePathForExternalAppForRead(*RootDir);
FString AbsEngineDir = PlatformFile.ConvertToAbsolutePathForExternalAppForRead(*EngineDir);
FString AbsProjectDir = PlatformFile.ConvertToAbsolutePathForExternalAppForRead(*ProjectDir);
HttpClient->TryCreateProject(ProjectId, OplogId, AbsServerRoot, AbsEngineDir, AbsProjectDir);
PackageStoreOptimizer->Initialize();
FPackageStoreManifest::FZenServerInfo& ZenServerInfo = PackageStoreManifest.EditZenServerInfo();
#if UE_WITH_ZEN
const UE::Zen::FZenServiceInstance& ZenServiceInstance = HttpClient->GetZenServiceInstance();
ZenServerInfo.Settings = ZenServiceInstance.GetServiceSettings();
#endif
ZenServerInfo.ProjectId = ProjectId;
ZenServerInfo.OplogId = OplogId;
ZenFileSystemManifest = MakeUnique<FZenFileSystemManifest>(TargetPlatform, OutputPath);
CommitQueue = MakeUnique<FCommitQueue>();
Compressor = FOodleDataCompression::ECompressor::Mermaid;
CompressionLevel = FOodleDataCompression::ECompressionLevel::VeryFast;
}
FZenStoreWriter::~FZenStoreWriter()
{
FScopeLock _(&PackagesCriticalSection);
if (PendingPackages.Num())
{
UE_LOG(LogZenStoreWriter, Warning, TEXT("Pending packages at shutdown!"));
}
}
void FZenStoreWriter::WritePackageData(const FPackageInfo& Info, FLargeMemoryWriter& ExportsArchive, const TArray<FFileRegion>& FileRegions)
{
check(Info.ChunkId.IsValid());
int64 DataSize = ExportsArchive.TotalSize();
FIoBuffer PackageData(FIoBuffer::AssumeOwnership, ExportsArchive.ReleaseOwnership(), DataSize);
TRACE_CPUPROFILER_EVENT_SCOPE(FZenStoreWriter::WritePackageData);
FIoBuffer CookedHeaderBuffer = FIoBuffer(PackageData.Data(), Info.HeaderSize, PackageData);
FIoBuffer CookedExportsBuffer = FIoBuffer(PackageData.Data() + Info.HeaderSize, PackageData.DataSize() - Info.HeaderSize, PackageData);
TUniquePtr<FPackageStorePackage> Package{PackageStoreOptimizer->CreatePackageFromCookedHeader(Info.PackageName, CookedHeaderBuffer)};
PackageStoreOptimizer->FinalizePackage(Package.Get());
TArray<FFileRegion> FileRegionsCopy(FileRegions);
for (FFileRegion& Region : FileRegionsCopy)
{
// Adjust regions so they are relative to the start of the exports buffer
Region.Offset -= Info.HeaderSize;
}
FIoBuffer PackageBuffer = PackageStoreOptimizer->CreatePackageBuffer(Package.Get(), CookedExportsBuffer, &FileRegionsCopy);
FIoChunkId ChunkId = CreateIoChunkId(Package->GetId().Value(), 0, EIoChunkType::ExportBundleData);
PackageStoreManifest.AddPackageData(Info.PackageName, Info.LooseFilePath, Info.ChunkId);
for (FFileRegion& Region : FileRegionsCopy)
{
// Adjust regions once more so they are relative to the exports bundle buffer
Region.Offset -= Package->GetHeaderSize();
}
//WriteFileRegions(*FPaths::ChangeExtension(Info.LooseFilePath, FString(".uexp") + FFileRegion::RegionsFileExtension), FileRegionsCopy);
// Commit to Zen build store
FCbObjectId ChunkOid = ToObjectId(Info.ChunkId);
FPendingPackageState& ExistingState = GetPendingPackage(Info.PackageName);
FPackageDataEntry& Entry = ExistingState.PackageData;
Entry.CompressedPayload = Async(EAsyncExecution::TaskGraph, [this, PackageBuffer]()
{
return FCompressedBuffer::Compress(FSharedBuffer::MakeView(PackageBuffer.GetView()), Compressor, CompressionLevel);
});
Entry.Info = Info;
Entry.ChunkId = ChunkOid;
Entry.PackageStoreEntry = PackageStoreOptimizer->CreatePackageStoreEntry(Package.Get());
Entry.IsValid = true;
}
void FZenStoreWriter::WriteIoStorePackageData(const FPackageInfo& Info, const FIoBuffer& PackageData, const FPackageStoreEntryResource& PackageStoreEntry, const TArray<FFileRegion>& FileRegions)
{
check(Info.ChunkId.IsValid());
TRACE_CPUPROFILER_EVENT_SCOPE(WriteIoStorePackageData);
PackageStoreManifest.AddPackageData(Info.PackageName, Info.LooseFilePath, Info.ChunkId);
//WriteFileRegions(*FPaths::ChangeExtension(Info.LooseFilePath, FString(".uexp") + FFileRegion::RegionsFileExtension), FileRegionsCopy);
FCbObjectId ChunkOid = ToObjectId(Info.ChunkId);
FPendingPackageState& ExistingState = GetPendingPackage(Info.PackageName);
FPackageDataEntry& Entry = ExistingState.PackageData;
PackageData.EnsureOwned();
Entry.CompressedPayload = Async(EAsyncExecution::TaskGraph, [this, PackageData]()
{
return FCompressedBuffer::Compress(FSharedBuffer::MakeView(PackageData.GetView()), Compressor, CompressionLevel);
});
Entry.Info = Info;
Entry.ChunkId = ChunkOid;
Entry.PackageStoreEntry = PackageStoreEntry;
Entry.IsValid = true;
}
void FZenStoreWriter::WriteBulkData(const FBulkDataInfo& Info, const FIoBuffer& BulkData, const TArray<FFileRegion>& FileRegions)
{
check(Info.ChunkId.IsValid());
FCbObjectId ChunkOid = ToObjectId(Info.ChunkId);
FPendingPackageState& ExistingState = GetPendingPackage(Info.PackageName);
FBulkDataEntry& BulkEntry = ExistingState.BulkData.AddDefaulted_GetRef();
BulkData.EnsureOwned();
BulkEntry.CompressedPayload = Async(EAsyncExecution::TaskGraph, [this, BulkData]()
{
return FCompressedBuffer::Compress(FSharedBuffer::MakeView(BulkData.GetView()), Compressor, CompressionLevel);
});
BulkEntry.Info = Info;
BulkEntry.ChunkId = ChunkOid;
BulkEntry.IsValid = true;
PackageStoreManifest.AddBulkData(Info.PackageName, Info.LooseFilePath, Info.ChunkId);
// WriteFileRegions(*(Info.LooseFilePath + FFileRegion::RegionsFileExtension), FileRegions);
}
void FZenStoreWriter::WriteAdditionalFile(const FAdditionalFileInfo& Info, const FIoBuffer& FileData)
{
const FZenFileSystemManifest::FManifestEntry& ManifestEntry = ZenFileSystemManifest->CreateManifestEntry(Info.Filename);
FPendingPackageState& ExistingState = GetPendingPackage(Info.PackageName);
FFileDataEntry& FileEntry = ExistingState.FileData.AddDefaulted_GetRef();
FileData.EnsureOwned();
FileEntry.CompressedPayload = Async(EAsyncExecution::TaskGraph, [this, FileData]()
{
return FCompressedBuffer::Compress(FSharedBuffer::MakeView(FileData.GetView()), Compressor, CompressionLevel);
});
FileEntry.Info = Info;
FileEntry.Info.ChunkId = ManifestEntry.FileChunkId;
FileEntry.ZenManifestServerPath = ManifestEntry.ServerPath;
FileEntry.ZenManifestClientPath = ManifestEntry.ClientPath;
}
void FZenStoreWriter::WriteLinkerAdditionalData(const FLinkerAdditionalDataInfo& Info, const FIoBuffer& Data, const TArray<FFileRegion>& FileRegions)
{
// LinkerAdditionalData is not yet implemented in this writer; it is only used for VirtualizedBulkData which is not used in cooked content
checkNoEntry();
}
void FZenStoreWriter::Initialize(const FCookInfo& Info)
{
CookMode = Info.CookMode;
if (!bInitialized)
{
if (Info.bFullBuild)
{
UE_LOG(LogZenStoreWriter, Display, TEXT("Deleting %s..."), *OutputPath);
const bool bRequireExists = false;
const bool bTree = true;
IFileManager::Get().DeleteDirectory(*OutputPath, bRequireExists, bTree);
}
FString ProjectId = FZenStoreHttpClient::GenerateDefaultProjectId();
FString OplogId = TargetPlatform.PlatformName();
bool bOplogEstablished = HttpClient->TryCreateOplog(ProjectId, OplogId, Info.bFullBuild);
UE_CLOG(!bOplogEstablished, LogZenStoreWriter, Fatal, TEXT("Failed to establish oplog on the ZenServer"));
if (!Info.bFullBuild)
{
UE_LOG(LogZenStoreWriter, Display, TEXT("Fetching oplog..."), *ProjectId, *OplogId);
TFuture<FIoStatus> FutureOplogStatus = HttpClient->GetOplog().Next([this](TIoStatusOr<FCbObject> OplogStatus)
{
if (!OplogStatus.IsOk())
{
return OplogStatus.Status();
}
FCbObject Oplog = OplogStatus.ConsumeValueOrDie();
if (Oplog["entries"])
{
for (FCbField& OplogEntry : Oplog["entries"].AsArray())
{
FCbObject OplogObj = OplogEntry.AsObject();
if (OplogObj["package"])
{
FCbObject PackageObj = OplogObj["package"].AsObject();
const FGuid PkgGuid = PackageObj["guid"].AsUuid();
const FIoHash PkgHash = PackageObj["data"].AsHash();
const int64 PkgDiskSize = PackageObj["disksize"].AsUInt64();
FPackageStoreEntryResource Entry = FPackageStoreEntryResource::FromCbObject(OplogObj["packagestoreentry"].AsObject());
const FName PackageName = Entry.PackageName;
const int32 Index = PackageStoreEntries.Num();
PackageStoreEntries.Add(MoveTemp(Entry));
FOplogCookInfo& CookInfo = CookedPackagesInfo.Add_GetRef(
FOplogCookInfo{
FCookedPackageInfo {PackageName, IoHashToMD5(PkgHash), PkgGuid, PkgDiskSize }
});
PackageNameToIndex.Add(PackageName, Index);
for (FCbFieldView Field : OplogObj)
{
FUtf8StringView FieldName = Field.GetName();
if (IsReservedOplogKey(FieldName))
{
continue;
}
if (Field.IsHash())
{
const UTF8CHAR* AttachmentId = UE::FZenStoreHttpClient::FindOrAddAttachmentId(FieldName);
CookInfo.Attachments.Add({ AttachmentId, Field.AsHash() });
}
}
CookInfo.Attachments.Shrink();
check(Algo::IsSorted(CookInfo.Attachments,
[](const FOplogCookInfo::FAttachment& A, const FOplogCookInfo::FAttachment& B)
{
return FUtf8StringView(A.Key).Compare(FUtf8StringView(B.Key), ESearchCase::IgnoreCase) < 0;
}));
}
}
}
return FIoStatus::Ok;
});
UE_LOG(LogZenStoreWriter, Display, TEXT("Fetching file manifest..."), *ProjectId, *OplogId);
TIoStatusOr<FCbObject> FileStatus = HttpClient->GetFiles().Get();
if (FileStatus.IsOk())
{
FCbObject FilesObj = FileStatus.ConsumeValueOrDie();
for (FCbField& FileEntry : FilesObj["files"])
{
FCbObject FileObj = FileEntry.AsObject();
FCbObjectId FileId = FileObj["id"].AsObjectId();
FString ServerPath = FString(FileObj["serverpath"].AsString());
FString ClientPath = FString(FileObj["clientpath"].AsString());
FIoChunkId FileChunkId;
FileChunkId.Set(FileId.GetView());
ZenFileSystemManifest->AddManifestEntry(FileChunkId, MoveTemp(ServerPath), MoveTemp(ClientPath));
}
UE_LOG(LogZenStoreWriter, Display, TEXT("Fetched '%d' files(s) from oplog '%s/%s'"), ZenFileSystemManifest->NumEntries(), *ProjectId, *OplogId);
}
else
{
UE_LOG(LogZenStoreWriter, Warning, TEXT("Failed to fetch file(s) from oplog '%s/%s'"), *ProjectId, *OplogId);
}
if (FutureOplogStatus.Get().IsOk())
{
UE_LOG(LogZenStoreWriter, Display, TEXT("Fetched '%d' packages(s) from oplog '%s/%s'"), PackageStoreEntries.Num(), *ProjectId, *OplogId);
}
else
{
UE_LOG(LogZenStoreWriter, Warning, TEXT("Failed to fetch oplog '%s/%s'"), *ProjectId, *OplogId);
}
}
bInitialized = true;
}
else
{
if (Info.bFullBuild)
{
RemoveCookedPackages();
}
}
}
void FZenStoreWriter::BeginCook()
{
if (CookMode == ICookedPackageWriter::FCookInfo::CookOnTheFlyMode)
{
FCbPackage Pkg;
FCbWriter PackageObj;
PackageObj.BeginObject();
PackageObj << "key" << "CookOnTheFly";
const bool bGenerateContainerHeader = false;
CreateProjectMetaData(Pkg, PackageObj, bGenerateContainerHeader);
PackageObj.EndObject();
FCbObject Obj = PackageObj.Save().AsObject();
Pkg.SetObject(Obj);
TIoStatusOr<uint64> Status = HttpClient->AppendOp(Pkg);
UE_CLOG(!Status.IsOk(), LogZenStoreWriter, Fatal, TEXT("Failed to append OpLog"));
}
if (FPlatformProcess::SupportsMultithreading())
{
CommitQueue->ResetAdding();
CommitThread = AsyncThread([this]()
{
for(;;)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FZenStoreWriter::WaitingOnCooker);
FCommitPackageInfo Commit;
if (!CommitQueue->BlockAndDequeue(Commit))
{
break;
}
CommitPackageInternal(MoveTemp(Commit));
}
});
}
}
void FZenStoreWriter::EndCook()
{
Flush();
CommitQueue->CompleteAdding();
FCbPackage Pkg;
FCbWriter PackageObj;
PackageObj.BeginObject();
PackageObj << "key" << "EndCook";
const bool bGenerateContainerHeader = true;
CreateProjectMetaData(Pkg, PackageObj, bGenerateContainerHeader);
PackageObj.EndObject();
FCbObject Obj = PackageObj.Save().AsObject();
Pkg.SetObject(Obj);
TIoStatusOr<uint64> Status = HttpClient->EndBuildPass(Pkg);
UE_CLOG(!Status.IsOk(), LogZenStoreWriter, Fatal, TEXT("Failed to append OpLog and end the build pass"));
PackageStoreManifest.Save(*(MetadataDirectoryPath / TEXT("packagestore.manifest")));
UE_LOG(LogZenStoreWriter, Display, TEXT("Input:\t%d Packages"), PackageStoreOptimizer->GetTotalPackageCount());
UE_LOG(LogZenStoreWriter, Display, TEXT("Output:\t%d Export bundles"), PackageStoreOptimizer->GetTotalExportBundleCount());
UE_LOG(LogZenStoreWriter, Display, TEXT("Output:\t%d Export bundle entries"), PackageStoreOptimizer->GetTotalExportBundleEntryCount());
UE_LOG(LogZenStoreWriter, Display, TEXT("Output:\t%d Internal export bundle arcs"), PackageStoreOptimizer->GetTotalInternalBundleArcsCount());
UE_LOG(LogZenStoreWriter, Display, TEXT("Output:\t%d External export bundle arcs"), PackageStoreOptimizer->GetTotalExternalBundleArcsCount());
UE_LOG(LogZenStoreWriter, Display, TEXT("Output:\t%d Public runtime script objects"), PackageStoreOptimizer->GetTotalScriptObjectCount());
}
void FZenStoreWriter::BeginPackage(const FBeginPackageInfo& Info)
{
FPendingPackageState& State = AddPendingPackage(Info.PackageName);
State.PackageName = Info.PackageName;
PackageStoreManifest.BeginPackage(Info.PackageName);
}
bool FZenStoreWriter::IsReservedOplogKey(FUtf8StringView Key)
{
int32 Index = Algo::LowerBound(ReservedOplogKeys, Key,
[](const UTF8CHAR* Existing, FUtf8StringView Key)
{
return FUtf8StringView(Existing).Compare(Key, ESearchCase::IgnoreCase) < 0;
});
return Index != ReservedOplogKeys.Num() &&
FUtf8StringView(ReservedOplogKeys[Index]).Equals(Key, ESearchCase::IgnoreCase);
}
TFuture<FMD5Hash> FZenStoreWriter::CommitPackage(FCommitPackageInfo&& Info)
{
TFuture<FMD5Hash> PkgHash;
if (EnumHasAnyFlags(Info.WriteOptions, EWriteOptions::ComputeHash))
{
FPendingPackageState& ExistingState = GetPendingPackage(Info.PackageName);
PkgHash = ExistingState.HashPromise.GetFuture();
}
UE::SavePackageUtilities::IncrementOutstandingAsyncWrites();
if (FPlatformProcess::SupportsMultithreading())
{
CommitQueue->Enqueue(Info);
}
else
{
CommitPackageInternal(Forward<FCommitPackageInfo>(Info));
}
return PkgHash;
}
void FZenStoreWriter::CommitPackageInternal(FCommitPackageInfo&& CommitInfo)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FZenStoreWriter::CommitPackage);
TUniquePtr<FPendingPackageState> PackageState = RemovePendingPackage(CommitInfo.PackageName);
checkf(PackageState.IsValid(), TEXT("Trying to commit non-pending package '%s'"), *CommitInfo.PackageName.ToString());
IPackageStoreWriter::FCommitEventArgs CommitEventArgs;
CommitEventArgs.PlatformName = FName(*TargetPlatform.PlatformName());
CommitEventArgs.PackageName = CommitInfo.PackageName;
CommitEventArgs.EntryIndex = INDEX_NONE;
const bool bComputeHash = EnumHasAnyFlags(CommitInfo.WriteOptions, EWriteOptions::ComputeHash);
const bool bWritePackage = EnumHasAnyFlags(CommitInfo.WriteOptions, EWriteOptions::Write);
if (CommitInfo.bSucceeded && bWritePackage)
{
FPackageDataEntry& PkgData = PackageState->PackageData;
checkf(PkgData.IsValid, TEXT("CommitPackage called with bSucceeded but without first calling WritePackageData"))
checkf(EnumHasAllFlags(CommitInfo.WriteOptions, EWriteOptions::Write), TEXT("Partial EWriteOptions::Write options are not yet implemented."));
checkf(!EnumHasAnyFlags(CommitInfo.WriteOptions, EWriteOptions::SaveForDiff), TEXT("-diffonly -savefordiff is not yet implemented."));
{
FWriteScopeLock _(EntriesLock);
CommitEventArgs.EntryIndex = PackageNameToIndex.FindOrAdd(CommitInfo.PackageName, PackageStoreEntries.Num());
if (CommitEventArgs.EntryIndex == PackageStoreEntries.Num())
{
PackageStoreEntries.Emplace();
CookedPackagesInfo.Emplace();
}
}
PackageStoreEntries[CommitEventArgs.EntryIndex] = PkgData.PackageStoreEntry;
FMD5 PkgHashGen;
FCbPackage OplogEntry;
FCbAttachment PkgDataAttachment = FCbAttachment(PkgData.CompressedPayload.Get());
PkgHashGen.Update(PkgDataAttachment.GetHash().GetBytes(), sizeof(FIoHash::ByteArray));
OplogEntry.AddAttachment(PkgDataAttachment);
// Commit attachments
FOplogCookInfo& CookInfo = CookedPackagesInfo[CommitEventArgs.EntryIndex];
CookInfo = FOplogCookInfo
{
FCookedPackageInfo
{
CommitInfo.PackageName,
IoHashToMD5(PkgDataAttachment.GetHash()),
CommitInfo.PackageGuid,
int64(PkgDataAttachment.AsCompressedBinary().GetRawSize())
}
};
CookInfo.bUpToDate = true;
const int32 NumAttachments = CommitInfo.Attachments.Num();
TArray<FCbAttachment, TInlineAllocator<2>> CbAttachments;
if (NumAttachments)
{
TArray<const FCommitAttachmentInfo*, TInlineAllocator<2>> SortedAttachments;
SortedAttachments.Reserve(NumAttachments);
for (const FCommitAttachmentInfo& AttachmentInfo : CommitInfo.Attachments)
{
SortedAttachments.Add(&AttachmentInfo);
}
SortedAttachments.Sort([](const FCommitAttachmentInfo& A, const FCommitAttachmentInfo& B)
{
return A.Key.Compare(B.Key, ESearchCase::IgnoreCase) < 0;
});
CbAttachments.Reserve(NumAttachments);
CookInfo.Attachments.Reserve(NumAttachments);
for (const FCommitAttachmentInfo* AttachmentInfo : SortedAttachments)
{
check(!IsReservedOplogKey(AttachmentInfo->Key));
const FCbAttachment& CbAttachment = CbAttachments.Add_GetRef(CreateAttachment(AttachmentInfo->Value.GetBuffer().ToShared()));
OplogEntry.AddAttachment(CbAttachment);
CookInfo.Attachments.Add(FOplogCookInfo::FAttachment
{
UE::FZenStoreHttpClient::FindOrAddAttachmentId(AttachmentInfo->Key), CbAttachment.GetHash()
});
}
}
// Create the oplog entry object
FCbWriter OplogEntryDesc;
OplogEntryDesc.BeginObject();
FString PackageNameKey = CommitInfo.PackageName.ToString();
PackageNameKey.ToLowerInline();
OplogEntryDesc << "key" << PackageNameKey;
// NOTE: The package GUID and disk size are used for legacy iterative cooks when comparing asset registry package data
OplogEntryDesc.BeginObject("package");
OplogEntryDesc << "id" << PkgData.ChunkId;
OplogEntryDesc << "guid" << CommitInfo.PackageGuid;
OplogEntryDesc << "data" << PkgDataAttachment;
OplogEntryDesc << "disksize" << PkgDataAttachment.AsCompressedBinary().GetRawSize();
OplogEntryDesc.EndObject();
OplogEntryDesc << "packagestoreentry" << PkgData.PackageStoreEntry;
if (PackageState->BulkData.Num())
{
OplogEntryDesc.BeginArray("bulkdata");
for (FBulkDataEntry& Bulk : PackageState->BulkData)
{
FCbAttachment BulkAttachment(Bulk.CompressedPayload.Get());
PkgHashGen.Update(BulkAttachment.GetHash().GetBytes(), sizeof(FIoHash::ByteArray));
OplogEntry.AddAttachment(BulkAttachment);
OplogEntryDesc.BeginObject();
OplogEntryDesc << "id" << Bulk.ChunkId;
OplogEntryDesc << "type" << LexToString(Bulk.Info.BulkDataType);
OplogEntryDesc << "data" << BulkAttachment;
OplogEntryDesc.EndObject();
}
OplogEntryDesc.EndArray();
}
if (PackageState->FileData.Num())
{
OplogEntryDesc.BeginArray("files");
for (FFileDataEntry& File : PackageState->FileData)
{
FCbAttachment FileDataAttachment(File.CompressedPayload.Get());
PkgHashGen.Update(FileDataAttachment.GetHash().GetBytes(), sizeof(FIoHash::ByteArray));
OplogEntry.AddAttachment(FileDataAttachment);
OplogEntryDesc.BeginObject();
OplogEntryDesc << "id" << ToObjectId(File.Info.ChunkId);
OplogEntryDesc << "data" << FileDataAttachment;
OplogEntryDesc << "serverpath" << File.ZenManifestServerPath;
OplogEntryDesc << "clientpath" << File.ZenManifestClientPath;
OplogEntryDesc.EndObject();
CommitEventArgs.AdditionalFiles.Add(FAdditionalFileInfo
{
CommitInfo.PackageName,
File.ZenManifestClientPath,
File.Info.ChunkId
});
}
OplogEntryDesc.EndArray();
}
FMD5Hash PkgHash;
PkgHash.Set(PkgHashGen);
PackageState->HashPromise.SetValue(PkgHash);
for (int32 Index = 0; Index < NumAttachments; ++Index)
{
FCbAttachment& CbAttachment = CbAttachments[Index];
FOplogCookInfo::FAttachment& CookInfoAttachment = CookInfo.Attachments[Index];
OplogEntryDesc << CookInfoAttachment.Key << CbAttachment;
}
OplogEntryDesc.EndObject();
OplogEntry.SetObject(OplogEntryDesc.Save().AsObject());
TIoStatusOr<uint64> Status = HttpClient->AppendOp(MoveTemp(OplogEntry));
UE_CLOG(!Status.IsOk(), LogZenStoreWriter, Error, TEXT("Failed to commit oplog entry '%s' to Zen"), *CommitInfo.PackageName.ToString());
}
else if (CommitInfo.bSucceeded && bComputeHash)
{
FPackageDataEntry& PkgData = PackageState->PackageData;
checkf(PkgData.IsValid, TEXT("CommitPackage called with bSucceeded but without first calling WritePackageData"));
FMD5 PkgHashGen;
{
FCompressedBuffer Payload = PkgData.CompressedPayload.Get();
FIoHash IoHash = Payload.GetRawHash();
PkgHashGen.Update(IoHash.GetBytes(), sizeof(FIoHash::ByteArray));
}
for (FBulkDataEntry& Bulk : PackageState->BulkData)
{
FCompressedBuffer Payload = Bulk.CompressedPayload.Get();
FIoHash IoHash = Payload.GetRawHash();
PkgHashGen.Update(IoHash.GetBytes(), sizeof(FIoHash::ByteArray));
}
for (FFileDataEntry& File : PackageState->FileData)
{
FCompressedBuffer Payload = File.CompressedPayload.Get();
FIoHash IoHash = Payload.GetRawHash();
PkgHashGen.Update(IoHash.GetBytes(), sizeof(FIoHash::ByteArray));
}
FMD5Hash PkgHash;
PkgHash.Set(PkgHashGen);
PackageState->HashPromise.SetValue(PkgHash);
}
else
{
PackageState->HashPromise.SetValue(FMD5Hash());
}
if (bWritePackage)
{
BroadcastCommit(CommitEventArgs);
}
UE::SavePackageUtilities::DecrementOutstandingAsyncWrites();
}
void FZenStoreWriter::GetEntries(TFunction<void(TArrayView<const FPackageStoreEntryResource>, TArrayView<const FOplogCookInfo>)>&& Callback)
{
FReadScopeLock _(EntriesLock);
Callback(PackageStoreEntries, CookedPackagesInfo);
}
void FZenStoreWriter::Flush()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FZenStoreWriter::Flush);
UE_LOG(LogZenStoreWriter, Display, TEXT("Flushing..."));
CommitQueue->WaitUntilEmpty();
}
TUniquePtr<FAssetRegistryState> FZenStoreWriter::LoadPreviousAssetRegistry()
{
// Load the previous asset registry to return to CookOnTheFlyServer, and set the packages enumerated in both *this and
// the returned asset registry to the intersection of the oplog and the previous asset registry;
// to report a package as already cooked we have to have the information from both sources.
FString PreviousAssetRegistryFile = FPaths::Combine(MetadataDirectoryPath, GetDevelopmentAssetRegistryFilename());
FArrayReader SerializedAssetData;
if (!IFileManager::Get().FileExists(*PreviousAssetRegistryFile) ||
!FFileHelper::LoadFileToArray(SerializedAssetData, *PreviousAssetRegistryFile))
{
RemoveCookedPackages();
return TUniquePtr<FAssetRegistryState>();
}
TUniquePtr<FAssetRegistryState> PreviousState = MakeUnique<FAssetRegistryState>();
PreviousState->Load(SerializedAssetData);
TSet<FName> RemoveSet;
const TMap<FName, const FAssetPackageData*>& PreviousStatePackages = PreviousState->GetAssetPackageDataMap();
for (const TPair<FName, const FAssetPackageData*>& Pair : PreviousStatePackages)
{
FName PackageName = Pair.Key;
if (!PackageNameToIndex.Find(PackageName))
{
RemoveSet.Add(PackageName);
}
}
if (RemoveSet.Num())
{
PreviousState->PruneAssetData(TSet<FName>(), RemoveSet, FAssetRegistrySerializationOptions());
}
TArray<FName> RemoveArray;
for (TPair<FName, int32>& Pair : PackageNameToIndex)
{
FName PackageName = Pair.Key;
if (!PreviousStatePackages.Find(PackageName))
{
RemoveArray.Add(PackageName);
}
}
if (RemoveArray.Num())
{
RemoveCookedPackages(RemoveArray);
}
return PreviousState;
}
FCbObject FZenStoreWriter::GetOplogAttachment(FName PackageName, FUtf8StringView AttachmentKey)
{
const int32* Idx = PackageNameToIndex.Find(PackageName);
if (!Idx)
{
return FCbObject();
}
const UTF8CHAR* AttachmentId = UE::FZenStoreHttpClient::FindAttachmentId(AttachmentKey);
if (!AttachmentId)
{
return FCbObject();
}
FUtf8StringView AttachmentIdView(AttachmentId);
const FOplogCookInfo& CookInfo = CookedPackagesInfo[*Idx];
int32 AttachmentIndex = Algo::LowerBound(CookInfo.Attachments, AttachmentIdView,
[](const FOplogCookInfo::FAttachment& Existing, FUtf8StringView AttachmentIdView)
{
return FUtf8StringView(Existing.Key).Compare(AttachmentIdView, ESearchCase::IgnoreCase) < 0;
});
if (AttachmentIndex == CookInfo.Attachments.Num())
{
return FCbObject();
}
const FOplogCookInfo::FAttachment& Existing = CookInfo.Attachments[AttachmentIndex];
if (!FUtf8StringView(Existing.Key).Equals(AttachmentIdView, ESearchCase::IgnoreCase))
{
return FCbObject();
}
TIoStatusOr<FIoBuffer> BufferResult = HttpClient->ReadOpLogAttachment(WriteToString<48>(Existing.Hash));
if (!BufferResult.IsOk())
{
return FCbObject();
}
FIoBuffer Buffer = BufferResult.ValueOrDie();
if (Buffer.DataSize() == 0)
{
return FCbObject();
}
FSharedBuffer SharedBuffer = IoBufferToSharedBuffer(Buffer);
return FCbObject(SharedBuffer);
}
void FZenStoreWriter::RemoveCookedPackages(TArrayView<const FName> PackageNamesToRemove)
{
TSet<int32> PackageIndicesToKeep;
for (int32 Idx = 0, Num = PackageStoreEntries.Num(); Idx < Num; ++Idx)
{
PackageIndicesToKeep.Add(Idx);
}
for (const FName& PackageName : PackageNamesToRemove)
{
if (const int32* Idx = PackageNameToIndex.Find(PackageName))
{
PackageIndicesToKeep.Remove(*Idx);
}
}
const int32 NumPackagesToKeep = PackageIndicesToKeep.Num();
TArray<FPackageStoreEntryResource> PreviousPackageStoreEntries = MoveTemp(PackageStoreEntries);
TArray<FOplogCookInfo> PreviousCookedPackageInfo = MoveTemp(CookedPackagesInfo);
PackageNameToIndex.Empty();
if (NumPackagesToKeep > 0)
{
PackageStoreEntries.Reserve(NumPackagesToKeep);
CookedPackagesInfo.Reserve(NumPackagesToKeep);
PackageNameToIndex.Reserve(NumPackagesToKeep);
int32 EntryIndex = 0;
for (int32 Idx : PackageIndicesToKeep)
{
const FName PackageName = PreviousCookedPackageInfo[Idx].CookInfo.PackageName;
PackageStoreEntries.Add(MoveTemp(PreviousPackageStoreEntries[Idx]));
CookedPackagesInfo.Add(MoveTemp(PreviousCookedPackageInfo[Idx]));
PackageNameToIndex.Add(PackageName, EntryIndex++);
}
}
}
void FZenStoreWriter::RemoveCookedPackages()
{
PackageStoreEntries.Empty();
CookedPackagesInfo.Empty();
PackageNameToIndex.Empty();
}
void FZenStoreWriter::MarkPackagesUpToDate(TArrayView<const FName> UpToDatePackages)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FZenStoreWriter::MarkPackagesUpToDate);
IPackageStoreWriter::FMarkUpToDateEventArgs MarkUpToDateEventArgs;
MarkUpToDateEventArgs.PackageIndexes.Reserve(UpToDatePackages.Num());
{
FWriteScopeLock _(EntriesLock);
for (FName PackageName : UpToDatePackages)
{
int32* Index = PackageNameToIndex.Find(PackageName);
if (!Index)
{
if (!FPackageName::IsScriptPackage(WriteToString<128>(PackageName)))
{
UE_LOG(LogZenStoreWriter, Warning, TEXT("MarkPackagesUpToDate called with package %s that is not in the oplog."),
*PackageName.ToString());
}
continue;
}
MarkUpToDateEventArgs.PackageIndexes.Add(*Index);
CookedPackagesInfo[*Index].bUpToDate = true;
}
}
if (MarkUpToDateEventArgs.PackageIndexes.Num())
{
BroadcastMarkUpToDate(MarkUpToDateEventArgs);
}
}
void FZenStoreWriter::CreateProjectMetaData(FCbPackage& Pkg, FCbWriter& PackageObj, bool bGenerateContainerHeader)
{
// File Manifest
{
// Only append new file entries to the Oplog
int32 NumEntries = ZenFileSystemManifest->NumEntries();
const int32 NumNewEntries = ZenFileSystemManifest->Generate();
if (NumNewEntries > 0)
{
TArrayView<FZenFileSystemManifest::FManifestEntry const> Entries = ZenFileSystemManifest->ManifestEntries();
TArrayView<FZenFileSystemManifest::FManifestEntry const> NewEntries = Entries.Slice(NumEntries, NumNewEntries);
PackageObj.BeginArray("files");
for (const FZenFileSystemManifest::FManifestEntry& NewEntry : NewEntries)
{
FCbObjectId FileOid = ToObjectId(NewEntry.FileChunkId);
PackageObj.BeginObject();
PackageObj << "id" << FileOid;
PackageObj << "data" << FIoHash::Zero;
PackageObj << "serverpath" << NewEntry.ServerPath;
PackageObj << "clientpath" << NewEntry.ClientPath;
PackageObj.EndObject();
}
PackageObj.EndArray();
}
FString ManifestPath = FPaths::Combine(MetadataDirectoryPath, TEXT("zenfs.manifest"));
UE_LOG(LogZenStoreWriter, Display, TEXT("Saving Zen filesystem manifest '%s'"), *ManifestPath);
ZenFileSystemManifest->Save(*ManifestPath);
}
// Metadata section
{
PackageObj.BeginArray("meta");
// Summarize Script Objects
FIoBuffer ScriptObjectsBuffer = PackageStoreOptimizer->CreateScriptObjectsBuffer();
FCbObjectId ScriptOid = ToObjectId(CreateIoChunkId(0, 0, EIoChunkType::ScriptObjects));
FCbAttachment ScriptAttachment = CreateAttachment(ScriptObjectsBuffer);
Pkg.AddAttachment(ScriptAttachment);
PackageObj.BeginObject();
PackageObj << "id" << ScriptOid;
PackageObj << "name" << "ScriptObjects";
PackageObj << "data" << ScriptAttachment;
PackageObj.EndObject();
// Generate Container Header
if (bGenerateContainerHeader)
{
FCbObjectId HeaderOid = ToObjectId(CreateIoChunkId(ContainerId.Value(), 0, EIoChunkType::ContainerHeader));
FIoBuffer HeaderBuffer;
{
FIoContainerHeader Header = PackageStoreOptimizer->CreateContainerHeader(ContainerId, PackageStoreEntries);
FLargeMemoryWriter HeaderAr(0, true);
HeaderAr << Header;
int64 DataSize = HeaderAr.TotalSize();
HeaderBuffer = FIoBuffer(FIoBuffer::AssumeOwnership, HeaderAr.ReleaseOwnership(), DataSize);
}
FCbAttachment HeaderAttachment = CreateAttachment(HeaderBuffer);
Pkg.AddAttachment(HeaderAttachment);
PackageObj.BeginObject();
PackageObj << "id" << HeaderOid;
PackageObj << "name" << "ContainerHeader";
PackageObj << "data" << HeaderAttachment;
PackageObj.EndObject();
}
PackageObj.EndArray(); // End of Meta array
}
}
void FZenStoreWriter::BroadcastCommit(IPackageStoreWriter::FCommitEventArgs& EventArgs)
{
FScopeLock CommitEventLock(&CommitEventCriticalSection);
if (CommitEvent.IsBound())
{
FReadScopeLock _(EntriesLock);
EventArgs.Entries = PackageStoreEntries;
CommitEvent.Broadcast(EventArgs);
}
}
void FZenStoreWriter::BroadcastMarkUpToDate(IPackageStoreWriter::FMarkUpToDateEventArgs& EventArgs)
{
FScopeLock CommitEventLock(&CommitEventCriticalSection);
if (MarkUpToDateEvent.IsBound())
{
FReadScopeLock _(EntriesLock);
EventArgs.PlatformName = FName(*TargetPlatform.PlatformName());
EventArgs.Entries = PackageStoreEntries;
EventArgs.CookInfos = CookedPackagesInfo;
MarkUpToDateEvent.Broadcast(EventArgs);
}
}
FCbAttachment FZenStoreWriter::CreateAttachment(FSharedBuffer AttachmentData)
{
check(AttachmentData.GetSize() > 0);
FCompressedBuffer CompressedBuffer = FCompressedBuffer::Compress(AttachmentData, Compressor, CompressionLevel);
check(!CompressedBuffer.IsNull());
return FCbAttachment(CompressedBuffer);
}
FCbAttachment FZenStoreWriter::CreateAttachment(FIoBuffer AttachmentData)
{
return CreateAttachment(IoBufferToSharedBuffer(AttachmentData));
}