// Copyright Epic Games, Inc. All Rights Reserved. #include "BuildPatchGeneration.h" #include "Templates/UniquePtr.h" #include "Templates/Greater.h" #include "Templates/Tuple.h" #include "Algo/Sort.h" #include "Misc/ConfigCacheIni.h" #include "Misc/SecureHash.h" #include "Misc/OutputDeviceRedirector.h" #include "Misc/CommandLine.h" #include "Containers/Ticker.h" #include "Containers/Queue.h" #include "Async/Future.h" #include "Async/Async.h" #include "BuildPatchManifest.h" #include "BuildPatchServicesModule.h" #include "BuildPatchHash.h" #include "Core/BlockStructure.h" #include "Core/BlockData.h" #include "Common/FileSystem.h" #include "Data/ChunkData.h" #include "Generation/DataScanner.h" #include "Generation/BuildStreamer.h" #include "Generation/CloudEnumeration.h" #include "Generation/ManifestBuilder.h" #include "Generation/FileAttributesParser.h" #include "Generation/ChunkWriter.h" #include "Generation/ChunkMatchProcessor.h" #include "BuildPatchUtil.h" using namespace BuildPatchServices; DECLARE_LOG_CATEGORY_EXTERN(LogPatchGeneration, Log, All); DEFINE_LOG_CATEGORY(LogPatchGeneration); namespace BuildPatchServices { struct FScannerDetails { public: FScannerDetails(int32 InLayer, uint64 InLayerOffset, bool bInIsFinalScanner, uint64 InPaddingSize, TArray InData, FBlockStructure InStructure, const TArray& ChunkWindowSizes, const ICloudEnumeration* CloudEnumeration, FStatsCollector* StatsCollector) : Layer(InLayer) , LayerOffset(InLayerOffset) , bIsFinalScanner(bInIsFinalScanner) , PaddingSize(InPaddingSize) , Data(MoveTemp(InData)) , Structure(MoveTemp(InStructure)) , Scanner(FDataScannerFactory::Create(ChunkWindowSizes, Data, CloudEnumeration, StatsCollector)) {} public: const int32 Layer; const uint64 LayerOffset; const bool bIsFinalScanner; const uint64 PaddingSize; const TArray Data; const FBlockStructure Structure; const TUniquePtr Scanner; }; } namespace PatchGenerationHelpers { int32 GetMaxScannerBacklogCount() { int32 MaxScannerBacklogCount = 75; GConfig->GetInt(TEXT("BuildPatchServices"), TEXT("MaxScannerBacklog"), MaxScannerBacklogCount, GEngineIni); MaxScannerBacklogCount = FMath::Clamp(MaxScannerBacklogCount, 5, 500); return MaxScannerBacklogCount; } bool ScannerArrayFull(const TArray>& Scanners) { static int32 MaxScannerBacklogCount = GetMaxScannerBacklogCount(); return (FDataScannerCounter::GetNumIncompleteScanners() > FDataScannerCounter::GetNumRunningScanners()) || (Scanners.Num() >= MaxScannerBacklogCount); } FSHAHash GetShaForDataSet(const uint8* DataSet, uint32 DataSize) { FSHAHash SHAHash; FSHA1::HashBuffer(DataSet, DataSize, SHAHash.Hash); return SHAHash; } } bool FBuildDataGenerator::ChunkBuildDirectory(const BuildPatchServices::FChunkBuildConfiguration& Settings) { const uint64 StartTime = FStatsCollector::GetCycles(); bool bIsGameThread = IsInGameThread(); // Check for the required output filename. if (Settings.OutputFilename.IsEmpty()) { UE_LOG(LogPatchGeneration, Error, TEXT("Manifest OutputFilename was not provided")); return false; } // Ensure that cloud directory exists, and create it if not. IFileManager::Get().MakeDirectory(*Settings.CloudDirectory, true); if (!IFileManager::Get().DirectoryExists(*Settings.CloudDirectory)) { UE_LOG(LogPatchGeneration, Error, TEXT("Unable to create specified cloud directory %s"), *Settings.CloudDirectory); return false; } // Output to log for builder info. UE_LOG(LogPatchGeneration, Log, TEXT("Running NEW Chunks Patch Generation for: %u:%s %s"), Settings.AppId, *Settings.AppName, *Settings.BuildVersion); // The last time we logged out data processed. double LastProgressLog = FPlatformTime::Seconds(); const double TimeGenStarted = LastProgressLog; // Load settings from config. float GenerationScannerSizeMegabytes = 32.5f; float StatsLoggerTimeSeconds = 10.0f; GConfig->GetFloat(TEXT("BuildPatchServices"), TEXT("GenerationScannerSizeMegabytes"), GenerationScannerSizeMegabytes, GEngineIni); GConfig->GetFloat(TEXT("BuildPatchServices"), TEXT("StatsLoggerTimeSeconds"), StatsLoggerTimeSeconds, GEngineIni); GenerationScannerSizeMegabytes = FMath::Clamp(GenerationScannerSizeMegabytes, 10.0f, 500.0f); StatsLoggerTimeSeconds = FMath::Clamp(StatsLoggerTimeSeconds, 1.0f, 60.0f); const uint64 ScannerDataSize = GenerationScannerSizeMegabytes * 1048576; // Create stat collector. TUniquePtr StatsCollector(FStatsCollectorFactory::Create()); // Setup Generation stats. volatile int64* StatTotalTime = StatsCollector->CreateStat(TEXT("Generation: Total Time"), EStatFormat::Timer); volatile int64* StatLayers = StatsCollector->CreateStat(TEXT("Generation: Layers"), EStatFormat::Value); volatile int64* StatNumScanners = StatsCollector->CreateStat(TEXT("Generation: Scanner Backlog"), EStatFormat::Value); volatile int64* StatUnknownDataAlloc = StatsCollector->CreateStat(TEXT("Generation: Unmatched Buffers Allocation"), EStatFormat::DataSize); volatile int64* StatUnknownDataNum = StatsCollector->CreateStat(TEXT("Generation: Unmatched Buffers Use"), EStatFormat::DataSize); int64 MaxLayer = 0; // Create a chunk writer. TUniquePtr FileSystem(FFileSystemFactory::Create()); TUniquePtr ChunkDataSerialization(FChunkDataSerializationFactory::Create(FileSystem.Get(), Settings.FeatureLevel)); TUniquePtr ChunkWriter(FParallelChunkWriterFactory::Create({5, 5, 50, 8, Settings.CloudDirectory, Settings.FeatureLevel}, FileSystem.Get(), ChunkDataSerialization.Get(), StatsCollector.Get())); // Create a manifest details. FManifestDetails ManifestDetails; ManifestDetails.FeatureLevel = Settings.FeatureLevel; ManifestDetails.AppId = Settings.AppId; ManifestDetails.AppName = Settings.AppName; ManifestDetails.BuildVersion = Settings.BuildVersion; ManifestDetails.LaunchExe = Settings.LaunchExe; ManifestDetails.LaunchCommand = Settings.LaunchCommand; ManifestDetails.PrereqIds = Settings.PrereqIds; ManifestDetails.PrereqName = Settings.PrereqName; ManifestDetails.PrereqPath = Settings.PrereqPath; ManifestDetails.PrereqArgs = Settings.PrereqArgs; ManifestDetails.CustomFields = Settings.CustomFields; // Load the required file attributes. if (!Settings.AttributeListFile.IsEmpty()) { FFileAttributesParserRef FileAttributesParser = FFileAttributesParserFactory::Create(); if (!FileAttributesParser->ParseFileAttributes(Settings.AttributeListFile, ManifestDetails.FileAttributesMap)) { UE_LOG(LogPatchGeneration, Error, TEXT("Attributes list file did not parse %s"), *Settings.AttributeListFile); return false; } } // Enumerate Chunks. const FDateTime Cutoff = Settings.bShouldHonorReuseThreshold ? FDateTime::UtcNow() - FTimespan::FromDays(Settings.DataAgeThreshold) : FDateTime::MinValue(); TUniquePtr CloudEnumeration(FCloudEnumerationFactory::Create(Settings.CloudDirectory, Cutoff, Settings.FeatureLevel, StatsCollector.Get())); // Start the build stream. FDirectoryBuildStreamerConfig BuildStreamConfig({Settings.RootDirectory, Settings.InputListFile, Settings.IgnoreListFile}); FDirectoryBuildStreamerDependencies BuildStreamDependencies({StatsCollector.Get(), FileSystem.Get()}); TUniquePtr BuildStream(FBuildStreamerFactory::Create(MoveTemp(BuildStreamConfig), MoveTemp(BuildStreamDependencies))); TArray EnumeratedFiles = BuildStream->GetAllFilenames(); // Check existence of launch exe, if specified. if (!Settings.LaunchExe.IsEmpty() && !EnumeratedFiles.Contains(FPaths::Combine(Settings.RootDirectory, Settings.LaunchExe))) { UE_LOG(LogPatchGeneration, Error, TEXT("Provided launch executable file was not found within the build root. %s"), *Settings.LaunchExe); return false; } // Check existence of prereq exe, if specified. if (!Settings.PrereqPath.IsEmpty() && !EnumeratedFiles.Contains(FPaths::Combine(Settings.RootDirectory, Settings.PrereqPath))) { UE_LOG(LogPatchGeneration, Error, TEXT("Provided prerequisite executable file was not found within the build root. %s"), *Settings.PrereqPath); return false; } // We've got to wait for enumeration to complete as that shares a thread pool. while (CloudEnumeration->IsComplete() == false) { // Log collected stats. if (bIsGameThread) { GLog->FlushThreadedLogs(); } FStatsCollector::Set(StatTotalTime, FStatsCollector::GetCycles() - StartTime); StatsCollector->LogStats(StatsLoggerTimeSeconds); // Sleep to allow other threads. FPlatformProcess::Sleep(0.01f); } // Grab the window sizes we are trying to match against. TArray WindowSizes; if (Settings.bShouldMatchAnyWindowSize) { WindowSizes.Append(CloudEnumeration->GetUniqueWindowSizes().Array()); } else { WindowSizes.Add(Settings.OutputChunkWindowSize); } Algo::Sort(WindowSizes, TGreater()); const uint32 LargestWindowSize = FMath::Max(Settings.OutputChunkWindowSize, WindowSizes.Num() > 0 ? WindowSizes[0] : 0); const uint64 ScannerOverlapSize = LargestWindowSize - 1; // Construct the chunk match processor. TUniquePtr ChunkMatchProcessor(FChunkMatchProcessorFactory::Create()); // Keep a record of the new chunk inventory. TMap> ChunkInventory; TMap ChunkShaHashes; // Tracking info per layer for rescanning. TMap LayerToScannerCount; TMap LayerToBuildSpaceStructure; TMap LayerToCreatedScannerOffset; TMap LayerToScannedSize; TMap LayerToTotalDataSize; TMap LayerToUnknownLayerSpaceStructure; TMap LayerToUnknownBuildSpaceStructure; TMap> LayerToLayerSpaceBlockData; // This is a blatant hack :( TMap OriginalWindowSizes; // Create the manifest builder. FManifestBuilderConfig ManifestBuilderConfig; ManifestBuilderConfig.bAllowEmptyBuilds = Settings.bAllowEmptyBuild; IManifestBuilderRef ManifestBuilder = FManifestBuilderFactory::Create(FileSystem.Get(), ManifestBuilderConfig, ManifestDetails); FBlockStructure AcceptedBuildSpaceMatches; FBlockStructure CreatedBuildSpaceMatches; TSet NewCreatedChunks; TMap LayerCreatingScannersTest; TMap LayerCreatingScannersLayerSpaceTest; TMap LayerCreatingChunksTest; TMap LayerCreatingFinalTest; // Run the main loop. TArray DataBuffer; uint64 DataBufferFirstIdx = 0; uint32 ReadLen = 0; TArray> Scanners; bool bHasUnknownData = true; while (!BuildStream->HasAborted() && (!BuildStream->IsEndOfData() || Scanners.Num() > 0 || bHasUnknownData)) { // Grab a scanner result. if (Scanners.Num() > 0 && Scanners[0]->Scanner->IsComplete()) { FScannerDetails& ScannerDetails = *Scanners[0]; TArray ChunkMatches = ScannerDetails.Scanner->GetResultWhenComplete(); for (FChunkMatch& ChunkMatch : ChunkMatches) { // Translate to build space. FBlockStructure BuildSpaceChunkStructure; const uint64 BytesFound = ScannerDetails.Structure.SelectSerialBytes(ChunkMatch.DataOffset, ChunkMatch.WindowSize, BuildSpaceChunkStructure); const bool bFoundOk = ScannerDetails.bIsFinalScanner || BytesFound == ChunkMatch.WindowSize; if (!bFoundOk) { // Fatal error if the scanner returned a matched range that doesn't fit inside it's data. UE_LOG(LogPatchGeneration, Error, TEXT("Chunk match was not within scanner's data structure.")); return false; } ChunkMatch.DataOffset += ScannerDetails.LayerOffset; if (ChunkMatch.WindowSize != BytesFound) { OriginalWindowSizes.Add(ChunkMatch.ChunkGuid, ChunkMatch.WindowSize); } ChunkMatch.WindowSize = BytesFound; ChunkMatchProcessor->ProcessMatch(ScannerDetails.Layer, ChunkMatch, MoveTemp(BuildSpaceChunkStructure)); } FBlockStructure OverlapStructure = LayerToBuildSpaceStructure.FindOrAdd(ScannerDetails.Layer).Intersect(ScannerDetails.Structure); uint64 OverlapBytes = BlockStructureHelpers::CountSize(OverlapStructure); check(OverlapBytes == ScannerOverlapSize || ScannerDetails.LayerOffset == 0); // Store the layer build space. LayerToBuildSpaceStructure.FindOrAdd(ScannerDetails.Layer).Add(ScannerDetails.Structure); // Add to layer space block data. We include padding that comes at the end of any layer as that may be included. const uint64 LayerDataStartIndex = ScannerDetails.LayerOffset == 0 ? 0 : ScannerOverlapSize; const uint64 LayerDataSize = ScannerDetails.LayerOffset == 0 ? ScannerDetails.Data.Num() : ScannerDetails.Data.Num() - ScannerOverlapSize; TBlockData& LayerSpaceBlockData = LayerToLayerSpaceBlockData.FindOrAdd(ScannerDetails.Layer); LayerSpaceBlockData.AddData(FBlockStructure(ScannerDetails.LayerOffset + LayerDataStartIndex, LayerDataSize), ScannerDetails.Data.GetData() + LayerDataStartIndex, LayerDataSize); // Give some flush time to the processor. check(ScannerDetails.PaddingSize == 0 || ScannerDetails.bIsFinalScanner); const FBlockRange ScannerRange = FBlockRange::FromFirstAndSize(ScannerDetails.LayerOffset, ScannerDetails.Data.Num() - ScannerDetails.PaddingSize); const uint64 SafeFlushSize = ScannerDetails.bIsFinalScanner ? ScannerRange.GetLast() + 1 : ScannerRange.GetLast() - ScannerOverlapSize; ChunkMatchProcessor->FlushLayer(ScannerDetails.Layer, SafeFlushSize); if (ScannerDetails.bIsFinalScanner) { LayerToTotalDataSize.Add(ScannerDetails.Layer, SafeFlushSize); } // Remove scanner from list. LayerToScannerCount.FindOrAdd(ScannerDetails.Layer)--; Scanners.RemoveAt(0); } // Handle accepted chunk matches, and unknown data tracking. for (int32 LayerIdx = 0; LayerIdx <= MaxLayer; ++LayerIdx) { TArray AcceptedChunkMatches; const FBlockStructure& LayerBuildSpaceStructure = LayerToBuildSpaceStructure.FindOrAdd(LayerIdx); uint64& LayerScannedSize = LayerToScannedSize.FindOrAdd(LayerIdx); const FBlockRange CollectionRange = ChunkMatchProcessor->CollectLayer(LayerIdx, AcceptedChunkMatches); if (CollectionRange.GetSize() > 0) { // Add new chunk matches to the manifest builder, and track new unknown data. TBlockData& LayerSpaceBlockData = LayerToLayerSpaceBlockData.FindOrAdd(LayerIdx); FBlockStructure BlockDataToRemove; FBlockStructure NewUnknownLayerSpaceStructure(CollectionRange.GetFirst(), CollectionRange.GetSize()); FBlockStructure NewUnknownBuildSpaceStructure; const uint64 BytesFound = LayerBuildSpaceStructure.SelectSerialBytes(CollectionRange.GetFirst(), CollectionRange.GetSize(), NewUnknownBuildSpaceStructure); checkSlow(BytesFound == CollectionRange.GetSize()); const uint64 BeforeDataCount = LayerSpaceBlockData.GetDataCount(); const uint64 BeforeStructureCount = CollectionRange.GetSize(); for (FMatchEntry& AcceptedChunkMatch : AcceptedChunkMatches) { const FChunkMatch& ChunkMatch = AcceptedChunkMatch.ChunkMatch; const FBlockStructure& BlockStructure = AcceptedChunkMatch.BlockStructure; const FBlockStructure LayerSpaceStructure(ChunkMatch.DataOffset, ChunkMatch.WindowSize); check(BlockStructureHelpers::CountSize(LayerSpaceStructure.Intersect(NewUnknownLayerSpaceStructure)) == ChunkMatch.WindowSize); check(BlockStructureHelpers::CountSize(LayerSpaceStructure.Intersect(BlockDataToRemove)) == 0); check(BlockStructureHelpers::CountSize(NewUnknownBuildSpaceStructure.Intersect(BlockStructure)) == ChunkMatch.WindowSize); checkf(CreatedBuildSpaceMatches.Intersect(BlockStructure).GetHead() == nullptr, TEXT("ACCEPTEDCHUNK Overlap %llu bytes with created struct!"), BlockStructureHelpers::CountSize(CreatedBuildSpaceMatches.Intersect(BlockStructure))); checkf(AcceptedBuildSpaceMatches.Intersect(BlockStructure).GetHead() == nullptr, TEXT("ACCEPTEDCHUNK Overlap %llu bytes with accepted struct!"), BlockStructureHelpers::CountSize(AcceptedBuildSpaceMatches.Intersect(BlockStructure))); AcceptedBuildSpaceMatches.Add(BlockStructure); NewUnknownBuildSpaceStructure.Remove(BlockStructure); ManifestBuilder->AddChunkMatch(ChunkMatch.ChunkGuid, BlockStructure); // Do we need to re-save the chunk at current feature level? if (!CloudEnumeration->IsChunkFeatureLevelMatch(ChunkMatch.ChunkGuid)) { // Grab the data. TArray ChunkDataArray; LayerSpaceBlockData.CopyTo(ChunkDataArray, LayerSpaceStructure); check(ChunkDataArray.Num() == ChunkMatch.WindowSize); // Ensure padding if necessary. const uint32 TrueWindowSize = OriginalWindowSizes.Contains(ChunkMatch.ChunkGuid) ? OriginalWindowSizes[ChunkMatch.ChunkGuid] : ChunkMatch.WindowSize; ChunkDataArray.SetNumZeroed(TrueWindowSize); // Save it out. const uint64& ChunkHash = CloudEnumeration->GetChunkHash(ChunkMatch.ChunkGuid); const FSHAHash& ChunkSha = CloudEnumeration->GetChunkShaHash(ChunkMatch.ChunkGuid); check(ChunkHash == FRollingHash::GetHashForDataSet(ChunkDataArray.GetData(), TrueWindowSize)); check(ChunkSha == PatchGenerationHelpers::GetShaForDataSet(ChunkDataArray.GetData(), TrueWindowSize)); ChunkWriter->AddChunkData(MoveTemp(ChunkDataArray), ChunkMatch.ChunkGuid, ChunkHash, ChunkSha); } NewUnknownLayerSpaceStructure.Remove(LayerSpaceStructure); BlockDataToRemove.Add(LayerSpaceStructure); } const uint64 BlockDataToRemoveSize = BlockStructureHelpers::CountSize(BlockDataToRemove); LayerSpaceBlockData.RemoveData(BlockDataToRemove); const uint64 AfterDataCount = LayerSpaceBlockData.GetDataCount(); const uint64 AfterStructureCount = BlockStructureHelpers::CountSize(NewUnknownLayerSpaceStructure); const uint64 RemovedDataCount = BeforeDataCount - AfterDataCount; const uint64 RemovedStructureCount = BeforeStructureCount - AfterStructureCount; check(BeforeDataCount >= AfterDataCount); check(BeforeStructureCount >= AfterStructureCount); check(RemovedDataCount == RemovedStructureCount); check(RemovedDataCount == BlockDataToRemoveSize); check(BlockStructureHelpers::CountSize(NewUnknownLayerSpaceStructure) == BlockStructureHelpers::CountSize(NewUnknownBuildSpaceStructure)); // Grab layer tracking. FBlockStructure& UnknownLayerSpaceStructure = LayerToUnknownLayerSpaceStructure.FindOrAdd(LayerIdx); FBlockStructure& UnknownBuildSpaceStructure = LayerToUnknownBuildSpaceStructure.FindOrAdd(LayerIdx); // Expect to never get overlap with this new system. check(!BlockStructureHelpers::HasIntersection(UnknownLayerSpaceStructure, NewUnknownLayerSpaceStructure)); check(!BlockStructureHelpers::HasIntersection(UnknownBuildSpaceStructure, NewUnknownBuildSpaceStructure)); // Add unknown tracking to the structures. UnknownLayerSpaceStructure.Add(NewUnknownLayerSpaceStructure); UnknownBuildSpaceStructure.Add(NewUnknownBuildSpaceStructure); check(BlockStructureHelpers::CountSize(UnknownLayerSpaceStructure) == BlockStructureHelpers::CountSize(UnknownBuildSpaceStructure)); // Count processed data LayerScannedSize = CollectionRange.GetLast() + 1; } } // Collect unknown data into new chunks. for (int32 LayerIdx = 0; LayerIdx <= MaxLayer; ++LayerIdx) { FBlockStructure& UnknownLayerSpaceStructure = LayerToUnknownLayerSpaceStructure.FindOrAdd(LayerIdx); FBlockStructure& UnknownBuildSpaceStructure = LayerToUnknownBuildSpaceStructure.FindOrAdd(LayerIdx); check(BlockStructureHelpers::CountSize(UnknownLayerSpaceStructure) == BlockStructureHelpers::CountSize(UnknownBuildSpaceStructure)); const uint64& LayerScannedSize = LayerToScannedSize.FindOrAdd(LayerIdx); const bool bLayerComplete = LayerToTotalDataSize.Contains(LayerIdx) && (LayerScannedSize >= LayerToTotalDataSize[LayerIdx]); FBlockStructure ChunkedLayerSpaceStructure; FBlockStructure ChunkedBuildSpaceStructure; const FBlockEntry* UnknownLayerBlock = UnknownLayerSpaceStructure.GetHead(); const bool bIsFinalSingleBlock = bLayerComplete && UnknownLayerBlock != nullptr && UnknownLayerBlock == UnknownLayerSpaceStructure.GetTail(); uint64 UnknownBlockByteCount = 0; if (UnknownLayerBlock != nullptr) { UE_LOG(LogPatchGeneration, VeryVerbose, TEXT("Unknown layer[%d] data at %llu bytes"), LayerIdx, BlockStructureHelpers::CountSize(UnknownLayerSpaceStructure)); } TBlockData& LayerSpaceBlockData = LayerToLayerSpaceBlockData.FindOrAdd(LayerIdx); FBlockStructure BlockDataToRemove; while (UnknownLayerBlock != nullptr) { uint64 UnknownBlockOffset = UnknownLayerBlock->GetOffset(); uint64 UnknownBlockSize = UnknownLayerBlock->GetSize(); bool bFinalLayerChunk = false; while (UnknownBlockSize >= LargestWindowSize || (bIsFinalSingleBlock && !bFinalLayerChunk)) { // Copy out the chunk data. FBlockStructure NewChunkLayerSpace(UnknownBlockOffset, FMath::Min(Settings.OutputChunkWindowSize, UnknownBlockSize)); checkf((BlockStructureHelpers::CountSize(NewChunkLayerSpace) == Settings.OutputChunkWindowSize) || bIsFinalSingleBlock, TEXT("Expected correct chunk size! (%llu == %u) || %d"), BlockStructureHelpers::CountSize(NewChunkLayerSpace), Settings.OutputChunkWindowSize, bIsFinalSingleBlock); TArray NewChunkDataArray; LayerSpaceBlockData.CopyTo(NewChunkDataArray, NewChunkLayerSpace); check( bIsFinalSingleBlock || NewChunkDataArray.Num() == Settings.OutputChunkWindowSize); check(!bIsFinalSingleBlock || NewChunkDataArray.Num() == FMath::Min(Settings.OutputChunkWindowSize, UnknownBlockSize)); // Ensure padding if necessary. NewChunkDataArray.SetNumZeroed(Settings.OutputChunkWindowSize); // Create data for new chunk. const FGuid NewChunkGuid = FGuid::NewGuid(); const uint64 NewChunkHash = FRollingHash::GetHashForDataSet(NewChunkDataArray.GetData(), Settings.OutputChunkWindowSize); const FSHAHash NewChunkSha = PatchGenerationHelpers::GetShaForDataSet(NewChunkDataArray.GetData(), Settings.OutputChunkWindowSize); // Save it out. ChunkWriter->AddChunkData(MoveTemp(NewChunkDataArray), NewChunkGuid, NewChunkHash, NewChunkSha); ChunkShaHashes.Add(NewChunkGuid, NewChunkSha); ChunkInventory.FindOrAdd(NewChunkHash).Add(NewChunkGuid); BlockDataToRemove.Add(NewChunkLayerSpace); UE_LOG(LogPatchGeneration, Verbose, TEXT("Created layer[%d] chunk @ %llu for %d out of %llu"), LayerIdx, UnknownBlockOffset, Settings.OutputChunkWindowSize, UnknownBlockSize); // Add to manifest builder. FBlockStructure BuildSpaceChunkStructure; const uint64 ChunkBuildSize = UnknownBuildSpaceStructure.SelectSerialBytes(UnknownBlockByteCount, Settings.OutputChunkWindowSize, BuildSpaceChunkStructure); bFinalLayerChunk = bIsFinalSingleBlock && (UnknownBlockSize == ChunkBuildSize); // Chunk build space should either be window size, or size minus any padding if the final piece. check( bIsFinalSingleBlock || ChunkBuildSize == Settings.OutputChunkWindowSize); check(!bIsFinalSingleBlock || ChunkBuildSize == FMath::Min(Settings.OutputChunkWindowSize, UnknownBlockSize)); // This new chunk must not overlap any previous chunks. check(CreatedBuildSpaceMatches.Intersect(BuildSpaceChunkStructure).GetHead() == nullptr); check(AcceptedBuildSpaceMatches.Intersect(BuildSpaceChunkStructure).GetHead() == nullptr); CreatedBuildSpaceMatches.Add(BuildSpaceChunkStructure); NewCreatedChunks.Add(NewChunkGuid); LayerCreatingChunksTest.FindOrAdd(LayerIdx).Add(BuildSpaceChunkStructure); ManifestBuilder->AddChunkMatch(NewChunkGuid, BuildSpaceChunkStructure); // Track data selected. ChunkedLayerSpaceStructure.Add(UnknownBlockOffset, ChunkBuildSize); ChunkedBuildSpaceStructure.Add(BuildSpaceChunkStructure); check(BlockStructureHelpers::CountSize(ChunkedLayerSpaceStructure) == BlockStructureHelpers::CountSize(ChunkedBuildSpaceStructure)); UnknownBlockOffset += ChunkBuildSize; UnknownBlockSize -= ChunkBuildSize; UnknownBlockByteCount += ChunkBuildSize; check(!bFinalLayerChunk || UnknownBlockSize == 0); } UnknownBlockByteCount += UnknownBlockSize; UnknownLayerBlock = UnknownLayerBlock->GetNext(); check(!bFinalLayerChunk || UnknownLayerBlock == nullptr); } UnknownLayerSpaceStructure.Remove(ChunkedLayerSpaceStructure); UnknownBuildSpaceStructure.Remove(ChunkedBuildSpaceStructure); LayerSpaceBlockData.RemoveData(BlockDataToRemove); } // Create new scanners from unknown data. while (!PatchGenerationHelpers::ScannerArrayFull(Scanners)) { bool bScannerCreated = false; for (int32 LayerIdx = 0; LayerIdx <= MaxLayer; ++LayerIdx) { // Check that we have enough slack space in the data array to be queuing up more scanners on the next layer. const int32 NextLayer = LayerIdx + 1; const uint64 NextLayerSpaceBlockCount = LayerToLayerSpaceBlockData.FindOrAdd(NextLayer).GetDataCount(); const int32 OneGigabyte = 1073741824; const bool bQueuedDataFull = NextLayerSpaceBlockCount > OneGigabyte; if (bQueuedDataFull) { UE_LOG(LogPatchGeneration, Verbose, TEXT("Not making new scanners on next layer %d due to current backlog %llu bytes"), NextLayer, NextLayerSpaceBlockCount); break; } FBlockStructure& UnknownLayerSpaceStructure = LayerToUnknownLayerSpaceStructure.FindOrAdd(LayerIdx); FBlockStructure& UnknownBuildSpaceStructure = LayerToUnknownBuildSpaceStructure.FindOrAdd(LayerIdx); TBlockData& LayerSpaceBlockData = LayerToLayerSpaceBlockData.FindOrAdd(LayerIdx); const uint64& LayerScannedSize = LayerToScannedSize.FindOrAdd(LayerIdx); const bool bLayerComplete = LayerToTotalDataSize.Contains(LayerIdx) && (LayerScannedSize >= LayerToTotalDataSize[LayerIdx]); FBlockStructure NewScannerBuildSpaceStructure; FBlockStructure NewScannerLayerSpaceStructure; const uint64 SelectedBuildSpaceSize = UnknownBuildSpaceStructure.SelectSerialBytes(0, ScannerDataSize, NewScannerBuildSpaceStructure); const uint64 UnknownDataSize = BlockStructureHelpers::CountSize(UnknownBuildSpaceStructure); // Make sure there are enough bytes available for a scanner, plus a chunk, so that we know no more chunks will get // made from this sequential unknown data. const uint64 RequiredScannerBytes = ScannerDataSize + LargestWindowSize; const bool bHasEnoughData = bLayerComplete || UnknownDataSize > RequiredScannerBytes; if (bHasEnoughData && (SelectedBuildSpaceSize == ScannerDataSize || (bLayerComplete && SelectedBuildSpaceSize > 0))) { check(bHasEnoughData || bLayerComplete); const uint64 SelectedLayerSpaceSize = UnknownLayerSpaceStructure.SelectSerialBytes(0, ScannerDataSize, NewScannerLayerSpaceStructure); check(SelectedBuildSpaceSize == SelectedLayerSpaceSize); bScannerCreated = true; LayerToScannerCount.FindOrAdd(NextLayer)++; MaxLayer = FMath::Max(MaxLayer, NextLayer); FStatsCollector::Set(StatLayers, MaxLayer); uint64& NextLayerScannerOffset = LayerToCreatedScannerOffset.FindOrAdd(NextLayer); TArray ScannerData; LayerSpaceBlockData.CopyTo(ScannerData, NewScannerLayerSpaceStructure); check(ScannerData.Num() == SelectedLayerSpaceSize); const bool bIsFinalScanner = bLayerComplete && UnknownDataSize <= SelectedBuildSpaceSize; const uint64 PadSize = bIsFinalScanner ? ScannerOverlapSize : 0; ScannerData.AddZeroed(PadSize); // Test overlaps. FBlockStructure OverlapStructure = LayerCreatingScannersTest.FindOrAdd(NextLayer).Intersect(NewScannerBuildSpaceStructure); uint64 OverlapBytes = BlockStructureHelpers::CountSize(OverlapStructure); FBlockStructure OverlapLayerSpaceStructure = LayerCreatingScannersLayerSpaceTest.FindOrAdd(NextLayer).Intersect(NewScannerLayerSpaceStructure); uint64 OverlapLayerSpaceBytes = BlockStructureHelpers::CountSize(OverlapLayerSpaceStructure); check(OverlapLayerSpaceBytes == ScannerOverlapSize || NextLayerScannerOffset == 0); check(OverlapBytes == ScannerOverlapSize || NextLayerScannerOffset == 0); LayerCreatingScannersTest.FindOrAdd(NextLayer).Add(NewScannerBuildSpaceStructure); LayerCreatingScannersLayerSpaceTest.FindOrAdd(NextLayer).Add(NewScannerLayerSpaceStructure); // Check only one final scanner. if (bIsFinalScanner) { check(LayerCreatingFinalTest.Contains(NextLayer) == false); LayerCreatingFinalTest.Add(NextLayer, true); } UE_LOG(LogPatchGeneration, Verbose, TEXT("Creating scanner on layer %d at %llu. IsFinal:%d. Mapping:%s, BuildMapping:%s"), NextLayer, NextLayerScannerOffset, bIsFinalScanner, *NewScannerLayerSpaceStructure.ToString(), *NewScannerBuildSpaceStructure.ToString()); Scanners.Emplace(new FScannerDetails(NextLayer, NextLayerScannerOffset, bIsFinalScanner, PadSize, MoveTemp(ScannerData), NewScannerBuildSpaceStructure, WindowSizes, CloudEnumeration.Get(), StatsCollector.Get())); NextLayerScannerOffset += ScannerDataSize - ScannerOverlapSize; // Remove blocks from structures. NewScannerBuildSpaceStructure.Empty(); NewScannerLayerSpaceStructure.Empty(); const uint64 SerialBytesToSelect = bIsFinalScanner ? ScannerDataSize : ScannerDataSize - ScannerOverlapSize; const uint64 SizeBuildRemoving = UnknownBuildSpaceStructure.SelectSerialBytes(0, SerialBytesToSelect, NewScannerBuildSpaceStructure); const uint64 SizeLayerRemoving = UnknownLayerSpaceStructure.SelectSerialBytes(0, SerialBytesToSelect, NewScannerLayerSpaceStructure); UnknownBuildSpaceStructure.Remove(NewScannerBuildSpaceStructure); UnknownLayerSpaceStructure.Remove(NewScannerLayerSpaceStructure); LayerSpaceBlockData.RemoveData(NewScannerLayerSpaceStructure); check(SizeBuildRemoving == SizeLayerRemoving); check(SizeBuildRemoving == SerialBytesToSelect || SizeBuildRemoving == UnknownDataSize); check(!bIsFinalScanner || BlockStructureHelpers::CountSize(UnknownBuildSpaceStructure) == 0); check(!bIsFinalScanner || BlockStructureHelpers::CountSize(UnknownLayerSpaceStructure) == 0); } else { UE_LOG(LogPatchGeneration, Verbose, TEXT("Not making Layer[%d] unknown data scanners.. RequiredScannerBytes:%llu UnknownDataSize:%llu"), LayerIdx, RequiredScannerBytes, UnknownDataSize); } } // Stop when we cannot make scanners anymore. if (!bScannerCreated) { break; } } // Stream some build data. if (!PatchGenerationHelpers::ScannerArrayFull(Scanners)) { // Check that we have enough slack space in the data array to be queuing up more scanners on layer 0. const uint64 BottomLayerSpaceBlockCount = LayerToLayerSpaceBlockData.FindOrAdd(0).GetDataCount(); const int32 OneGigabyte = 1073741824; const bool bQueuedDataFull = BottomLayerSpaceBlockCount > OneGigabyte; if (bQueuedDataFull) { UE_LOG(LogPatchGeneration, Verbose, TEXT("Not making new scanners on layer 0 due to current backlog %llu bytes."), BottomLayerSpaceBlockCount); } else { // Create a scanner from new build data? if (!BuildStream->IsEndOfData()) { // Keep the overlap data from previous scanner. int32 PreviousSize = DataBuffer.Num(); if (PreviousSize > 0) { check(PreviousSize > ScannerOverlapSize); uint8* CopyTo = DataBuffer.GetData(); uint8* CopyFrom = CopyTo + (PreviousSize - ScannerOverlapSize); FMemory::Memcpy(CopyTo, CopyFrom, ScannerOverlapSize); DataBuffer.SetNum(ScannerOverlapSize, EAllowShrinking::No); DataBufferFirstIdx += (PreviousSize - ScannerOverlapSize); } // Grab some data from the build stream. PreviousSize = DataBuffer.Num(); DataBuffer.SetNumUninitialized(ScannerDataSize); const bool bWaitForData = true; ReadLen = BuildStream->DequeueData(DataBuffer.GetData() + PreviousSize, ScannerDataSize - PreviousSize, bWaitForData); DataBuffer.SetNum(PreviousSize + ReadLen, EAllowShrinking::No); // Only make a scanner if we are getting new data. if (ReadLen > 0) { // Pad scanner data if end of build uint64 PadSize = BuildStream->IsEndOfData() ? ScannerOverlapSize : 0; DataBuffer.AddZeroed(PadSize); // Create data scanner. const bool bIsFinalScanner = BuildStream->IsEndOfData(); FBlockStructure Structure; Structure.Add(DataBufferFirstIdx, DataBuffer.Num() - PadSize); // Test overlaps. FBlockStructure OverlapStructure = LayerCreatingScannersTest.FindOrAdd(0).Intersect(Structure); FBlockStructure OverlapLayerSpaceStructure = LayerCreatingScannersLayerSpaceTest.FindOrAdd(0).Intersect(Structure); uint64 OverlapBytes = BlockStructureHelpers::CountSize(OverlapStructure); uint64 OverlapLayerSpaceBytes = BlockStructureHelpers::CountSize(OverlapLayerSpaceStructure); check(OverlapBytes == ScannerOverlapSize || DataBufferFirstIdx == 0); check(OverlapLayerSpaceBytes == ScannerOverlapSize || DataBufferFirstIdx == 0); LayerCreatingScannersTest.FindOrAdd(0).Add(Structure); LayerCreatingScannersLayerSpaceTest.FindOrAdd(0).Add(Structure); // Check only one final scanner. if (bIsFinalScanner) { check(LayerCreatingFinalTest.Contains(0) == false); LayerCreatingFinalTest.Add(0, true); } UE_LOG(LogPatchGeneration, Verbose, TEXT("Creating scanner on layer 0 at %llu. IsFinal:%d. Mapping:%s"), DataBufferFirstIdx, BuildStream->IsEndOfData(), *Structure.ToString()); Scanners.Emplace(new FScannerDetails(0, DataBufferFirstIdx, BuildStream->IsEndOfData(), PadSize, DataBuffer, MoveTemp(Structure), WindowSizes, CloudEnumeration.Get(), StatsCollector.Get())); LayerToScannerCount.FindOrAdd(0)++; } } } } // Did we run out of unknown data? bHasUnknownData = false; for (const TPair& UnknownBuildSpaceStructurePair : LayerToUnknownBuildSpaceStructure) { if (UnknownBuildSpaceStructurePair.Value.GetHead() != nullptr) { bHasUnknownData = true; break; } } // Update some stats. int64 UnknownDataAlloc = 0; int64 UnknownDataNum = 0; for (const TPair>& LayerToLayerSpaceBlockDataPair : LayerToLayerSpaceBlockData) { UnknownDataNum += LayerToLayerSpaceBlockDataPair.Value.GetDataCount(); UnknownDataAlloc += LayerToLayerSpaceBlockDataPair.Value.GetAllocatedSize(); } FStatsCollector::Set(StatUnknownDataAlloc, UnknownDataAlloc); FStatsCollector::Set(StatUnknownDataNum, UnknownDataNum); FStatsCollector::Set(StatNumScanners, Scanners.Num()); // Log collected stats. if (bIsGameThread) { GLog->FlushThreadedLogs(); } FStatsCollector::Set(StatTotalTime, FStatsCollector::GetCycles() - StartTime); StatsCollector->LogStats(StatsLoggerTimeSeconds); // Sleep to allow other threads. FPlatformProcess::Sleep(0.01f); } if (BuildStream->HasAborted()) { UE_LOG(LogPatchGeneration, Error, TEXT("Directory Build Streamer aborted")); return false; } // Complete chunk writer. FParallelChunkWriterSummaries ChunkWriterSummaries = ChunkWriter->OnProcessComplete(); // Produce final stats log. const uint64 EndTime = FStatsCollector::GetCycles(); FStatsCollector::Set(StatTotalTime, EndTime - StartTime); StatsCollector->LogStats(); // Collect chunk info for the manifest builder. TMap ChunkInfoMap; TMap ChunkFileSizes = CloudEnumeration->GetChunkFileSizes(); TMap ChunkWindowSizes = CloudEnumeration->GetChunkWindowSizes(); ChunkFileSizes.Append(ChunkWriterSummaries.ChunkOutputSizes); for (const TPair>& ChunkInventoryPair : CloudEnumeration->GetChunkInventory()) { TSet& ChunkSet = ChunkInventory.FindOrAdd(ChunkInventoryPair.Key); ChunkSet = ChunkSet.Union(ChunkInventoryPair.Value); } ChunkShaHashes.Append(CloudEnumeration->GetChunkShaHashes()); for (const TPair>& ChunkInventoryPair : ChunkInventory) { for (const FGuid& ChunkGuid : ChunkInventoryPair.Value) { if (ChunkShaHashes.Contains(ChunkGuid) && ChunkFileSizes.Contains(ChunkGuid)) { FChunkInfo& ChunkInfo = ChunkInfoMap.FindOrAdd(ChunkGuid); ChunkInfo.Guid = ChunkGuid; ChunkInfo.Hash = ChunkInventoryPair.Key; FMemory::Memcpy(ChunkInfo.ShaHash.Hash, ChunkShaHashes[ChunkGuid].Hash, FSHA1::DigestSize); ChunkInfo.FileSize = ChunkFileSizes[ChunkGuid]; ChunkInfo.GroupNumber = FCrc::MemCrc32(&ChunkGuid, sizeof(FGuid)) % 100; ChunkInfo.WindowSize = ChunkWindowSizes.Contains(ChunkGuid) ? ChunkWindowSizes[ChunkGuid] : Settings.OutputChunkWindowSize; } } } // Finalize the manifest data. TArray ChunkInfoList; ChunkInfoMap.GenerateValueArray(ChunkInfoList); if (ManifestBuilder->FinalizeData(BuildStream->GetAllFiles(), MoveTemp(ChunkInfoList)) == false) { UE_LOG(LogPatchGeneration, Error, TEXT("Finalizing manifest failed.")); return false; } uint64 NewChunkBytes = 0; for (const FGuid& NewChunk : NewCreatedChunks) { NewChunkBytes += ChunkWriterSummaries.ChunkOutputSizes[NewChunk]; } UE_LOG(LogPatchGeneration, Log, TEXT("Created %d chunks (%llu build bytes) (%llu compressed bytes)"), NewCreatedChunks.Num(), BlockStructureHelpers::CountSize(CreatedBuildSpaceMatches), NewChunkBytes); UE_LOG(LogPatchGeneration, Log, TEXT("Completed in %s."), *FPlatformTime::PrettyTime(FStatsCollector::CyclesToSeconds(*StatTotalTime))); // Save manifest out to the cloud directory. const FString OutputFilename = Settings.CloudDirectory / Settings.OutputFilename; if (ManifestBuilder->SaveToFile(OutputFilename) == false) { UE_LOG(LogPatchGeneration, Error, TEXT("Saving manifest failed.")); return false; } UE_LOG(LogPatchGeneration, Log, TEXT("Saved manifest to %s."), *OutputFilename); return true; }