// Copyright Epic Games, Inc. All Rights Reserved. #include "Installer/ChunkReferenceTracker.h" #include "Templates/Greater.h" #include "Algo/Sort.h" #include "Misc/ScopeLock.h" #include "IBuildManifestSet.h" DECLARE_LOG_CATEGORY_EXTERN(LogChunkReferenceTracker, Warning, All); DEFINE_LOG_CATEGORY(LogChunkReferenceTracker); namespace BuildPatchServices { class FChunkReferenceTracker : public IChunkReferenceTracker { public: // Construct the list of chunks from a manifest and an ordered list of files to construct. FChunkReferenceTracker(const IBuildManifestSet* ManifestSet, const TSet& FilesToConstruct); // Pass in a direct ordered list of guids to use as chunks. FChunkReferenceTracker(TArray CustomUseList); ~FChunkReferenceTracker(); // IChunkReferenceTracker interface begin. virtual void CopyOutOrderedUseList(TArray& OutUseList) const override { int32 LocalCurrentPosition = CurrentPosition.load(std::memory_order_acquire); OutUseList.Append(UseList.GetData() + LocalCurrentPosition, UseList.Num() - LocalCurrentPosition); } virtual TSet GetReferencedChunks() const override; virtual int32 GetReferenceCount(const FGuid& ChunkId) const override; virtual void SortByUseOrder(TArray& ChunkList, ESortDirection Direction) const override; virtual TArray GetNextReferences(int32 Count, const TFunction& SelectPredicate) const override; virtual TArray SelectFromNextReferences(int32 Count, const TFunction& SelectPredicate) const override; virtual bool PopReference(const FGuid& ChunkId) override; virtual int32 GetRemainingChunkCount() const override; virtual int32 GetNextUsageForChunk(const FGuid& ChunkId, int32& OutLastUsageIndex) const override; virtual int32 GetCurrentUsageIndex() const override { return CurrentPosition.load(std::memory_order_relaxed); } // IChunkReferenceTracker interface end. private: // Index of the next chunk to be used in UseList. std::atomic CurrentPosition = 0; // Ordered list of guids in order of consumption. TArray UseList; // A sorted array (guid then index), where the indeces are the location of the guid in UseList, in ascending // order. // i.e. for all X in GuidUsagePositions, UseList[X.Value] = X.Key; TArray> GuidUsagePositions; }; FChunkReferenceTracker::FChunkReferenceTracker(const IBuildManifestSet* ManifestSet, const TSet& FilesToConstruct) { // Iterate each file in reference order to construct the ordered list of chunks we // will need to construct the files, and track when we are going to use them. int32 UsageIndex = 0; for (const FString& File : FilesToConstruct) { const FFileManifest* NewFileManifest = ManifestSet->GetNewFileManifest(File); if (NewFileManifest != nullptr) { for (const FChunkPart& ChunkPart : NewFileManifest->ChunkParts) { UseList.Add(ChunkPart.Guid); GuidUsagePositions.Add({ChunkPart.Guid, UsageIndex}); UsageIndex++; } } } Algo::Sort(GuidUsagePositions); } FChunkReferenceTracker::FChunkReferenceTracker(TArray CustomChunkReferences) : UseList(MoveTemp(CustomChunkReferences)) { int32 ChunkIndex = 0; for (const FGuid& Chunk : UseList) { GuidUsagePositions.Add({Chunk, ChunkIndex}); ChunkIndex++; } Algo::Sort(GuidUsagePositions); } FChunkReferenceTracker::~FChunkReferenceTracker() { } TSet FChunkReferenceTracker::GetReferencedChunks() const { int32 LocalCurrentPosition = CurrentPosition.load(std::memory_order_acquire); TSet ReferencedChunks; FGuid CurrentGuid; for (const TPair& ReferencedChunk : GuidUsagePositions) { if (ReferencedChunk.Value < LocalCurrentPosition) { continue; } if (CurrentGuid != ReferencedChunk.Key) { ReferencedChunks.Add(ReferencedChunk.Key); CurrentGuid = ReferencedChunk.Key; } } return ReferencedChunks; } int32 FChunkReferenceTracker::GetReferenceCount(const FGuid& ChunkId) const { int32 LocalCurrentPosition = CurrentPosition.load(std::memory_order_acquire); int32 GuidPosition = Algo::LowerBound(GuidUsagePositions, TPair { ChunkId, CurrentPosition }); int32 StartGuidPosition = GuidPosition; while (GuidPosition < GuidUsagePositions.Num() && GuidUsagePositions[GuidPosition].Key == ChunkId) { GuidPosition++; } return GuidPosition - StartGuidPosition; } void FChunkReferenceTracker::SortByUseOrder(TArray& ChunkList, ESortDirection Direction) const { int32 LocalCurrentPosition = CurrentPosition.load(std::memory_order_acquire); // Get the next index for each chunk. TMap NextUsageIndexes; for (FGuid& Guid : ChunkList) { int32 GuidPosition = Algo::LowerBound(GuidUsagePositions, TPair { Guid, CurrentPosition }); int32 UsageIndex = TNumericLimits::Max(); // Unused chunks need to be sorted as though they are never used if (GuidPosition < GuidUsagePositions.Num() && GuidUsagePositions[GuidPosition].Key == Guid) { UsageIndex = GuidUsagePositions[GuidPosition].Value; } NextUsageIndexes.Add(Guid, UsageIndex); } switch (Direction) { case ESortDirection::Ascending: { Algo::SortBy(ChunkList, [&NextUsageIndexes](const FGuid& Id) { return NextUsageIndexes[Id]; }, TLess()); break; } case ESortDirection::Descending: { Algo::SortBy(ChunkList, [&NextUsageIndexes](const FGuid& Id) { return NextUsageIndexes[Id]; }, TGreater()); break; } } } TArray FChunkReferenceTracker::GetNextReferences(int32 Count, const TFunction& SelectPredicate) const { // Returns "Count" unique references that match SelectPredicate. int32 LocalCurrentPosition = CurrentPosition.load(std::memory_order_acquire); TSet AddedIds; TArray NextReferences; for (; LocalCurrentPosition < UseList.Num() && NextReferences.Num() < Count; LocalCurrentPosition++) { const FGuid& UseId = UseList[LocalCurrentPosition]; if (AddedIds.Contains(UseId) == false && SelectPredicate(UseId)) { AddedIds.Add(UseId); NextReferences.Add(UseId); } } return NextReferences; } int32 FChunkReferenceTracker::GetNextUsageForChunk(const FGuid& ChunkId, int32& OutLastUsageIndex) const { int32 LocalCurrentPosition = CurrentPosition.load(std::memory_order_acquire); int32 GuidPosition = Algo::LowerBound(GuidUsagePositions, TPair { ChunkId, LocalCurrentPosition }); if (GuidPosition >= GuidUsagePositions.Num() || GuidUsagePositions[GuidPosition].Key != ChunkId) { return -1; } int32 NextUsage = GuidUsagePositions[GuidPosition].Value; while (GuidPosition + 1 < GuidUsagePositions.Num() && GuidUsagePositions[GuidPosition + 1].Key == ChunkId) { GuidPosition++; } OutLastUsageIndex = GuidUsagePositions[GuidPosition].Value; return NextUsage; } int32 FChunkReferenceTracker::GetRemainingChunkCount() const { int32 LocalCurrentPosition = CurrentPosition.load(std::memory_order_acquire); return UseList.Num() - LocalCurrentPosition; } TArray FChunkReferenceTracker::SelectFromNextReferences(int32 Count, const TFunction& SelectPredicate) const { // Original code /* for (int32 UseStackIdx = UseStack.Num() - 1; UseStackIdx >= 0 && Count > 0; --UseStackIdx) { const FGuid& UseId = UseStack[UseStackIdx]; if (AddedIds.Contains(UseId) == false) { --Count; if (SelectPredicate(UseId)) { AddedIds.Add(UseId); NextReferences.Add(UseId); } } } */ // This is obfuscated a bit but is actually exactly the same as GetNextReferences. Since we can only decrease Count // on a new unique chunk and we only add a new unique chunk when we add a reference, this is the same. return GetNextReferences(Count, SelectPredicate); } bool FChunkReferenceTracker::PopReference(const FGuid& ChunkId) { int32 LocalCurrentPosition = CurrentPosition.load(std::memory_order_acquire); do { if (LocalCurrentPosition >= UseList.Num() || UseList[LocalCurrentPosition] != ChunkId) { return false; } } while (!CurrentPosition.compare_exchange_weak(LocalCurrentPosition, LocalCurrentPosition + 1, std::memory_order_acq_rel)); return true; } IChunkReferenceTracker* FChunkReferenceTrackerFactory::Create(const IBuildManifestSet* ManifestSet, const TSet& FilesToConstruct) { return new FChunkReferenceTracker(ManifestSet, FilesToConstruct); } IChunkReferenceTracker* FChunkReferenceTrackerFactory::Create(TArray CustomChunkReferences) { return new FChunkReferenceTracker(MoveTemp(CustomChunkReferences)); } }