// Copyright Epic Games, Inc. All Rights Reserved. #include "Packing.h" #include "Algo/Sort.h" #include "Containers/BitArray.h" #include "Containers/Map.h" #include "Containers/Set.h" #include "SortSpecialCases.h" namespace Algo { void ScheduleValues(TConstArrayView Values, int32 NumBuckets, TConstArrayView> ExclusionGroups, TArray>& OutBucketsIndices) { // Set the size of the output array int32 NumValues = Values.Num(); TArray>& Buckets = OutBucketsIndices; check(NumBuckets > 0); Buckets.SetNum(NumBuckets, EAllowShrinking::No); for (TArray& BucketIndices : Buckets) { BucketIndices.Reset(); } // Verify exclusion groups are valid; invalid exclusiongroups will cause us to be unable to find any packing for (const TArray& ExclusionGroup : ExclusionGroups) { check(ExclusionGroup.Num() <= NumBuckets); } // Handle the trivial case if (NumBuckets == 1) { Buckets[0].Append(RangeArray>(0, NumValues)); return; } // Reverse the information in exclusiongroups so we can quickly find which exclusiongroup a valueindex is in TMap ExclusionGroupOfValueIndex; int32 NumExclusionGroups = ExclusionGroups.Num(); for (int32 ExclusionGroupIndex = 0; ExclusionGroupIndex < NumExclusionGroups; ++ExclusionGroupIndex) { for (int32 ValueIndex : ExclusionGroups[ExclusionGroupIndex]) { ExclusionGroupOfValueIndex.FindOrAdd(ValueIndex) = ExclusionGroupIndex; } } // Initialize remaining values to the entire list of values, and sort them into the order in which we should // try to place them. TArray RemainingValueIndices = RangeArray>(0, NumValues); auto ValueIndexSelectionOrder = [&Values, &ExclusionGroupOfValueIndex](int32 AValueIndex, int32 BValueIndex) { int32* AExclusionGroup = ExclusionGroupOfValueIndex.Find(AValueIndex); int32* BExclusionGroup = ExclusionGroupOfValueIndex.Find(BValueIndex); if ((AExclusionGroup != nullptr) != (BExclusionGroup != nullptr)) { // Place values in an exclusiongroup into a bucket before placing unconstrained values return AExclusionGroup != nullptr; } if (AExclusionGroup != BExclusionGroup) { // Place all members of one exclusiongroup before starting the next exclusiongroup return AExclusionGroup < BExclusionGroup; } // Place bigger pieces before smaller pieces return Values[AValueIndex] > Values[BValueIndex]; }; Algo::Sort(RemainingValueIndices, ValueIndexSelectionOrder); // After every piece we place we need to resort the modified smallest bucket back into the list and find the new // smallest. FBucketData is our sortable information about a bucket struct FBucketData { TArray* Bucket = nullptr; int32 Size = 0; TBitArray<> HasExclusionGroup; }; TArray SortedBuckets; SortedBuckets.Reserve(NumBuckets); for (int32 BucketIndex = 0; BucketIndex < NumBuckets; ++BucketIndex) { FBucketData& BucketData = SortedBuckets.Emplace_GetRef(); BucketData.Bucket = &Buckets[BucketIndex]; BucketData.Size = 0; BucketData.HasExclusionGroup.Init(false, NumExclusionGroups); } auto SortBucketDataDescending = [](const FBucketData& A, const FBucketData& B) { if (A.Size != B.Size) { return A.Size > B.Size; } // When buckets are equal, prefer to add to the earlier buckets, by pushing them to the back of the list return A.Bucket > B.Bucket; }; Algo::Sort(SortedBuckets, SortBucketDataDescending); // Main loop: iterate over each piece and put it into the current smallest bucket (or the smallest valid bucket // if the piece is in an exclusion group) for (int32 ValueIndex : RemainingValueIndices) { int32* ExclusionGroupIndex = ExclusionGroupOfValueIndex.Find(ValueIndex); int32 BestBucketIndex = INDEX_NONE; if (!ExclusionGroupIndex) { BestBucketIndex = NumBuckets - 1; } else { for (int32 BucketIndex = NumBuckets - 1; BucketIndex >= 0; --BucketIndex) { FBucketData& Bucket = SortedBuckets[BucketIndex]; if (!Bucket.HasExclusionGroup[*ExclusionGroupIndex]) { Bucket.HasExclusionGroup[*ExclusionGroupIndex] = true; BestBucketIndex = BucketIndex; break; } } // exclusiongroups are <= NumBuckets in size so we always have a free bucket for an exclusiongroup member check(BestBucketIndex != INDEX_NONE); } FBucketData& SmallestBucket = SortedBuckets[BestBucketIndex]; SmallestBucket.Bucket->Add(ValueIndex); SmallestBucket.Size += Values[ValueIndex]; RestoreSort(SortedBuckets, NumBuckets-1, SortBucketDataDescending); } } }