UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
NamedValueArray.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "CoreMinimal.h"
6#include "Algo/IsSorted.h"
7
8#define ENABLE_ANIM_CURVE_PROFILING 0
9
10#if ENABLE_ANIM_CURVE_PROFILING
11#include "Stats/Stats.h"
12#endif
13
14#if ENABLE_ANIM_CURVE_PROFILING
15#define CURVE_PROFILE_CYCLE_COUNTER(Stat) QUICK_SCOPE_CYCLE_COUNTER(Stat)
16#else
17#define CURVE_PROFILE_CYCLE_COUNTER(Stat)
18#endif
19
20#define DO_ANIM_NAMED_VALUE_SORTING_CHECKS 0
21#define DO_ANIM_NAMED_VALUE_DUPLICATE_CHECKS 0
22
23namespace UE::Anim
24{
25
26struct FNamedValueArrayUtils;
27
33template<typename InAllocatorType, typename InElementType>
35{
38
39 friend struct FNamedValueArrayUtils;
40
47 template<typename... ArgTypes>
48 void Add(ArgTypes&&... Args)
49 {
51 bSorted = false;
52
54 }
55
63 {
65 for(const FName& Name : InNameArray)
66 {
68 }
69 bSorted = false;
70
72 }
73
80 void AppendNames(std::initializer_list<const FName> InInputArgs)
81 {
83 for(const FName& Name : InInputArgs)
84 {
86 }
87 bSorted = false;
88
90 }
91
93 void Empty()
94 {
96 bSorted = false;
97 }
98
104
111 bool HasElement(FName InName) const
112 {
113 return Find(InName) != nullptr;
114 }
115
120 template<typename PredicateType>
122 {
123 for(const ElementType& Element : Elements)
124 {
125 InPredicate(Element);
126 }
127 }
128
130 int32 Num() const
131 {
132 return Elements.Num();
133 }
134
136 int32 Max() const
137 {
138 return Elements.Max();
139 }
140
142 void Shrink()
143 {
144 return Elements.Shrink();
145 }
146
147protected:
148 // Sort by FName - Note: this is not stable across serialization
150 {
151 inline bool operator()(const ElementType& InElement0, const ElementType& InElement1) const
152 {
153 return InElement0.Name.FastLess(InElement1.Name);
154 }
155 };
156
157 // Sorts the elements if they are not yet sorted
159 {
160 if(!bSorted)
161 {
163
165 bSorted = true;
166 }
167 }
168
169 // Checks whether the sorting invariant is correct
170 void CheckSorted() const
171 {
172#if DO_ANIM_NAMED_VALUE_SORTING_CHECKS
173 if(bSorted)
174 {
176 }
177#endif
178 }
179
180 // Checks whether the 'no duplicates' invariant is correct
181 void CheckDuplicates() const
182 {
183#if DO_ANIM_NAMED_VALUE_DUPLICATE_CHECKS
184 for(int32 Index0 = 0; Index0 < Elements.Num(); ++Index0)
185 {
186 for(int32 Index1 = 0; Index1 < Elements.Num(); ++Index1)
187 {
188 if(Index0 != Index1 && Elements[Index0].Name == Elements[Index1].Name)
189 {
190 checkf(false, TEXT("Duplicate curve entry found: %s"), *Elements[Index0].Name.ToString());
191 }
192 }
193 }
194#endif
195 }
196
198 int32 IndexOf(FName InName) const
199 {
201
202 return Algo::BinarySearchBy(Elements, ElementType(InName), &ElementType::Name, FElementSortPredicate());
203 }
204
206 const ElementType* Find(FName InName) const
207 {
208 const int32 ElementIndex = IndexOf(InName);
209 if(ElementIndex != INDEX_NONE)
210 {
211 return &Elements[ElementIndex];
212 }
213 return nullptr;
214 }
215
218 {
219 const int32 ElementIndex = IndexOf(InName);
220 if(ElementIndex != INDEX_NONE)
221 {
222 return &Elements[ElementIndex];
223 }
224 return nullptr;
225 }
226
227protected:
228 // Named elements, sorted by name
230
231 // Whether the elements are sorted
232 mutable bool bSorted = false;
233};
234
235// Flags passed during union operations
237{
238 // No flags
239 None = 0,
240
241 // First argument is valid
242 ValidArg0 = 0x01,
243
244 // Second argument is valid
245 ValidArg1 = 0x02,
246
247 // Both arguments are valid
249};
250
252{
253 // Helper function
254 // Uses a simple 'tape merge'
255 // Performs an operation per-element on the two value arrays. Writes result to InOutValueArray0.
256 // InValueArray0 will be the union of the two value arrays after the operation is completed (i.e.
257 // new elements in InValueArray1 are added to InValueArray0)
258 // InPredicate is called on all elements that are added to or already existing in InOutValueArray0, with
259 // appropriate flags.
260 template<typename PredicateType, typename AllocatorTypeResult, typename ElementTypeResult, typename AllocatorTypeParam, typename ElementTypeParam>
262 {
264
265 // Check arrays are not overlapping
266 check((void*)&InOutValueArray0 != (void*)&InValueArray1);
267
268 const int32 NumElements0 = InOutValueArray0.Num(); // ValueArray1 elements remain constant, but ValueArray0 can have entries added.
269 const int32 NumElements1 = InValueArray1.Num();
270
271 // Early out if we have no elements to union
272 if (NumElements1 == 0)
273 {
274 return;
275 }
276
277 // Sort both input arrays if required
278 InOutValueArray0.SortElementsIfRequired();
279 InValueArray1.SortElementsIfRequired();
280
281 // Reserve memory for 1.5x combined curve counts.
282 // This can overestimate in some circumstances, but it handles the common cases which are:
283 // - One input is empty, the other not
284 // - Both inputs are non-empty but do not share most elements
288
289 // Use pointers to iterate as this uses fewer registers and this code is very hot
291 const ElementTypeParam* RESTRICT ElementPtr1 = InValueArray1.Elements.GetData();
292
295
296 // A default element we re-use when an element from one of the two inputs is missing
298
299 // When we reach the end of either input arrays, we stop the tape merge and copy what remains
301
302 // Perform dual-iteration on the two sorted arrays
303 while (!bIsDone)
304 {
305 if (ElementPtr0->Name == ElementPtr1->Name)
306 {
307 // Elements match, run predicate and increment both indices
309
310 ++ElementPtr0;
311 ++ElementPtr1;
312
314 }
315 else if (ElementPtr0->Name.FastLess(ElementPtr1->Name))
316 {
317 // ValueArray0 element is earlier, so run predicate with only ValueArray0 and increment ValueArray0
318 DefaultElement.Name = ElementPtr0->Name;
319
321
322 ++ElementPtr0;
323
324 bIsDone = ElementPtr0 == ElementEndPtr0;
325 }
326 else
327 {
328 // ValueArray1 element is earlier, so add to ValueArray0, run predicate with only second and increment ValueArray1
330 InOutValueArray0.Elements.InsertUninitialized(ElementIndex0);
331
332 // Refresh pointers since they might have changed
333 ElementPtr0 = InOutValueArray0.Elements.GetData() + ElementIndex0;
334 ElementEndPtr0 = InOutValueArray0.Elements.GetData() + InOutValueArray0.Elements.Num();
335
336 // We use placement new to make sure the constructor is inlined to reduce redundant work
338 ElementPtr0->Name = ElementPtr1->Name;
339
341
342 ++ElementPtr0; // Increment this as well since we've inserted
343 ++ElementPtr1;
344
345 bIsDone = ElementPtr1 == ElementEndPtr1;
346 }
347 }
348
349 // Tape merge is done, copy anything that might be remaining
351 {
352 // Reached end of ValueArray0 with remaining in ValueArray1, we can just copy the remainder of ValueArray1
353 const int32 NumResults = InOutValueArray0.Elements.Num();
354 const int32 NumNewElements = static_cast<int32>(ElementEndPtr1 - ElementPtr1);
355 InOutValueArray0.Elements.Reserve(NumResults + NumNewElements);
356 InOutValueArray0.Elements.AddUninitialized(NumNewElements);
357
358 // Refresh pointers since they might have changed
359 ElementPtr0 = InOutValueArray0.Elements.GetData() + NumResults;
361
363 {
364 // We use placement new to make sure the constructor is inlined to reduce redundant work
366 ElementPtr0->Name = ElementPtr1->Name;
367
369 }
370 }
371
372 InOutValueArray0.CheckSorted();
373 }
374
375 // Helper function
376 // Uses a simple 'tape merge'
377 // Performs an operation per-element on the two value arrays. Writes result to InOutValueArray0.
378 // InValueArray0 will be the union of the two value arrays after the operation is completed (i.e.
379 // new elements in InValueArray1 are added to InValueArray0)
380 // Performs a simple copy for each element
381 template<typename AllocatorTypeResult, typename ElementType, typename AllocatorTypeParam>
383 {
384 // Early out if we just want to perform a simple copy
385 if(InOutValueArray0.Num() == 0 && InValueArray1.Num() > 0)
386 {
387 InOutValueArray0.Elements = InValueArray1.Elements;
388 InOutValueArray0.bSorted = InValueArray1.bSorted;
389 return;
390 }
391
393 {
395 {
397 }
398 });
399 }
400
401 // Helper function
402 // Uses a simple 'tape merge'
403 // Performs an operation per-element on the two value arrays. Writes result to OutResultValueArray.
404 // OutResultValueArray will be the union of the two value arrays after the operation is completed.
405 // InPredicate is called on all elements that are added to OutResultValueArray, with appropriate flags.
406 template<typename PredicateType, typename AllocatorTypeResult, typename ElementTypeResult, typename AllocatorType0, typename ElementType0, typename AllocatorType1, typename ElementType1>
407 static void Union(
412 {
414
415 // Check arrays are not overlapping
416 check((void*)&OutResultValueArray != (void*)&InValueArray0);
417 check((void*)&OutResultValueArray != (void*)&InValueArray1);
418 check((void*)&InValueArray0 != (void*)&InValueArray1);
419
420 // Make sure result is clear
421 OutResultValueArray.Elements.Reset();
422
423 const int32 NumElements0 = InValueArray0.Num();
424 const int32 NumElements1 = InValueArray1.Num();
425
426 // Sort both input arrays if required
427 InValueArray0.SortElementsIfRequired();
428 InValueArray1.SortElementsIfRequired();
429
430 // Reserve memory for 1.5x combined curve counts.
431 // This can overestimate in some circumstances, but it handles the common cases which are:
432 // - One input is empty, the other not
433 // - Both inputs are non-empty but do not share most elements
437
438 // Use pointers to iterate as this uses fewer registers and this code is very hot
439 const ElementType0* RESTRICT ElementPtr0 = InValueArray0.Elements.GetData();
440 const ElementType1* RESTRICT ElementPtr1 = InValueArray1.Elements.GetData();
441
444
445 // A default element we re-use when an element from one of the two inputs is missing
448
449 // When we reach the end of either input arrays, we stop the tape merge and copy what remains
451
452 // Perform dual-iteration on the two sorted arrays
453 while (!bIsDone)
454 {
456
457 if (ElementPtr0->Name == ElementPtr1->Name)
458 {
459 // Elements match, run predicate and increment both indices
460 NewResultElement.Name = ElementPtr0->Name;
461
463
464 ++ElementPtr0;
465 ++ElementPtr1;
466
468 }
469 else if (ElementPtr0->Name.FastLess(ElementPtr1->Name))
470 {
471 // ValueArray0 element is earlier, so run predicate with only ValueArray0 and increment ValueArray0
472 NewResultElement.Name = ElementPtr0->Name;
473
474 // Element 1 is missing, use stub
475 DefaultElement1.Name = ElementPtr0->Name;
476
478
479 ++ElementPtr0;
480
481 bIsDone = ElementPtr0 == ElementEndPtr0;
482 }
483 else
484 {
485 // ValueArray1 element is earlier, so so run predicate with only ValueArray1 and increment ValueArray1
486 NewResultElement.Name = ElementPtr1->Name;
487
488 // Element 0 is missing, use stub
489 DefaultElement0.Name = ElementPtr1->Name;
490
492
493 ++ElementPtr1;
494
495 bIsDone = ElementPtr1 == ElementEndPtr1;
496 }
497
499 }
500
501 // Tape merge is done, copy anything that might be remaining
503 {
504 // Reached end of ValueArray1 with remaining elements in ValueArray0, we can just copy the remainder of ValueArray0
505 const int32 NumResults = OutResultValueArray.Elements.Num();
508 OutResultValueArray.Elements.AddUninitialized(NumNewElements);
509
511
513 {
514 // Element 1 is missing, use stub
515 DefaultElement1.Name = ElementPtr0->Name;
516
517 // We use placement new to make sure the constructor is inlined to reduce redundant work
519 ResultElementPtr->Name = ElementPtr0->Name;
520
522 }
523 }
524 else if (ElementPtr1 < ElementEndPtr1)
525 {
526 // Reached end of ValueArray0 with remaining elements in ValueArray1, we can just copy the remainder of ValueArray1
527 const int32 NumResults = OutResultValueArray.Elements.Num();
530 OutResultValueArray.Elements.AddUninitialized(NumNewElements);
531
533
535 {
536 // Element 0 is missing, use stub
537 DefaultElement0.Name = ElementPtr1->Name;
538
539 // We use placement new to make sure the constructor is inlined to reduce redundant work
541 ResultElementPtr->Name = ElementPtr1->Name;
542
544 }
545 }
546
547 // Insertion always proceeds in sorted order, so result is sorted by default
548 OutResultValueArray.bSorted = true;
549
550 OutResultValueArray.CheckSorted();
551 }
552
553 // Helper function
554 // Calls predicate on all elements in the two passed-in value arrays.
555 template<typename PredicateType, typename AllocatorType0, typename ElementType0, typename AllocatorType1, typename ElementType1>
557 {
559
560 // Check arrays are not overlapping
561 checkSlow((void*)&InValueArray0 != (void*)&InValueArray1);
562
563 // Sort both input arrays if required
564 InValueArray0.SortElementsIfRequired();
565 InValueArray1.SortElementsIfRequired();
566
567 const int32 NumElements0 = InValueArray0.Num();
568 const int32 NumElements1 = InValueArray1.Num();
569
572
573 // Perform dual-iteration on the two sorted arrays
574 while(true)
575 {
577 {
578 // Reached end of ValueArray0 with remaining in ValueArray1, we can just iterate over the remainder of ValueArray1
580 {
583 DefaultElement0.Name = Element1->Name;
584
586 }
587 break;
588 }
590 {
591 // Reached end of ValueArray1 with remaining in ValueArray0, we can just iterate over the remainder of ValueArray0
593 {
596 DefaultElement1.Name = Element0->Name;
597
599 }
600 break;
601 }
603 {
604 // Reached end of both, exit
605 break;
606 }
607
610
611 if(Element0->Name == Element1->Name)
612 {
613 // Elements match, run predicate and increment both indices
617 }
618 else if(Element0->Name.FastLess(Element1->Name))
619 {
620 // ValueArray0 element is earlier, so run predicate with only ValueArray0 and increment ElementIndex0
622 DefaultElement1.Name = Element0->Name;
625 }
626 else
627 {
628 // ValueArray1 element is earlier, so run predicate with only ValueArray1 and increment ElementIndex1
630 DefaultElement0.Name = Element1->Name;
633 }
634 }
635 }
636
641 template<typename AllocatorType0, typename ElementType0, typename AllocatorType1, typename ElementType1, typename ValuePredicateType>
643 {
645
646 // Check arrays are not overlapping
647 checkSlow((void*)&InNamedValues0 != (void*)&InNamedValues1);
648
649 // Sort both inputs if required
650 InNamedValues0.SortElementsIfRequired();
651 InNamedValues1.SortElementsIfRequired();
652
653 const int32 NumElements0 = InNamedValues0.Num();
654 const int32 NumElements1 = InNamedValues1.Num();
655
656 // Perform dual-iteration on the two sorted arrays
659
660 while(true)
661 {
663 {
664 // Reached end of array with remaining in bulk, we can just early out
665 break;
666 }
668 {
669 // Reached end of bulk with remaining in array, we can just early out
670 break;
671 }
673 {
674 // All elements exhausted, exit
675 break;
676 }
677
680
681 if(Element0->Name == Element1->Name)
682 {
683 // Elements match so extract value
687 }
688 else if(Element0->Name.FastLess(Element1->Name))
689 {
690 // Element exists only in array, skip
692 }
693 else
694 {
695 // Element exists only in bulk, skip
697 }
698 }
699 }
700
705 template<typename AllocatorType0, typename ElementType0, typename AllocatorType1, typename ElementType1, typename ValuePredicateType>
707 {
709
710 // Check arrays are not overlapping
711 checkSlow((void*)&InNamedValues0 != (void*)&InNamedValues1);
712
713 // Sort both inputs if required
714 InNamedValues0.SortElementsIfRequired();
715 InNamedValues1.SortElementsIfRequired();
716
717 const int32 NumElements0 = InNamedValues0.Num();
718 const int32 NumElements1 = InNamedValues1.Num();
719
720 // Perform dual-iteration on the two sorted arrays
723
724 while (true)
725 {
727 {
728 // Reached end of the first array, we can just early out
729 break;
730 }
732 {
733 // Reached end of the second array, we can just early out
734 break;
735 }
737 {
738 // All elements exhausted, exit
739 break;
740 }
741
744
745 if (Element0->Name == Element1->Name)
746 {
747 // Element exists in both arrays
750 }
751 else if (Element0->Name.FastLess(Element1->Name))
752 {
753 // Element exists only in first array
756 }
757 else
758 {
759 // Element exists only in second array
761 }
762 }
763
764 // Drain any remaining elements from our first array
766 {
769 }
770 }
771
775 template<typename AllocatorType0, typename ElementType0, typename AllocatorType1, typename ElementType1, typename PredicateType>
777 {
779
780 checkSlow((void*)&InOutValueArray0 != (void*)&InValueArray1);
781
782 // Sort both input arrays if required
783 InOutValueArray0.SortElementsIfRequired();
784 InValueArray1.SortElementsIfRequired();
785
786 // Perform dual-iteration on the two sorted arrays
789
791 {
794
795 if(Element0->Name == Element1->Name)
796 {
797 // Elements match so check filter flags to see if it should be removed from InOutValueArray0
799 {
802 }
803 else
804 {
807 }
808 }
809 else if(Element0->Name.FastLess(Element1->Name))
810 {
812 }
813 else
814 {
816 }
817 }
818
819 InOutValueArray0.CheckSorted();
820 }
821};
822
823}
824
#define checkSlow(expr)
Definition AssertionMacros.h:332
#define check(expr)
Definition AssertionMacros.h:314
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
@ INDEX_NONE
Definition CoreMiscDefines.h:150
#define UE_PTRDIFF_TO_INT32(argument)
Definition CoreMiscDefines.h:442
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
#define RESTRICT
Definition Platform.h:706
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
constexpr bool EnumHasAnyFlags(Enum Flags, Enum Contains)
Definition EnumClassFlags.h:35
#define ENUM_CLASS_FLAGS(Enum)
Definition EnumClassFlags.h:6
#define CURVE_PROFILE_CYCLE_COUNTER(Stat)
Definition NamedValueArray.h:17
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
uint8_t uint8
Definition binka_ue_file_header.h:8
Definition NameTypes.h:617
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
void Reset(SizeType NewSize=0)
Definition Array.h:2246
UE_REWRITE SizeType Max() const
Definition Array.h:1161
UE_FORCEINLINE_HINT SizeType Emplace(ArgsType &&... Args)
Definition Array.h:2561
UE_FORCEINLINE_HINT void Shrink()
Definition Array.h:1278
UE_FORCEINLINE_HINT void Reserve(SizeType Number)
Definition Array.h:3016
UE_REWRITE void Sort(RangeType &&Range)
Definition Sort.h:16
UE_REWRITE bool IsSorted(const RangeType &Range)
Definition IsSorted.h:66
auto BinarySearchBy(const RangeType &Range, const ValueType &Value, ProjectionType Projection, SortPredicateType SortPredicate) -> decltype(GetNum(Range))
Definition BinarySearch.h:203
Definition AnimationAsset.h:42
ENamedValueUnionFlags
Definition NamedValueArray.h:237
Definition NamedValueArray.h:252
static void Union(TNamedValueArray< AllocatorTypeResult, ElementTypeResult > &InOutValueArray0, const TNamedValueArray< AllocatorTypeParam, ElementTypeParam > &InValueArray1, PredicateType InPredicate)
Definition NamedValueArray.h:261
static void RemoveByPredicate(TNamedValueArray< AllocatorType0, ElementType0 > &InOutValueArray0, const TNamedValueArray< AllocatorType1, ElementType1 > &InValueArray1, PredicateType InPredicate)
Definition NamedValueArray.h:776
static void Union(TNamedValueArray< AllocatorTypeResult, ElementType > &InOutValueArray0, const TNamedValueArray< AllocatorTypeParam, ElementType > &InValueArray1)
Definition NamedValueArray.h:382
static void Subtraction(const TNamedValueArray< AllocatorType0, ElementType0 > &InNamedValues0, const TNamedValueArray< AllocatorType1, ElementType1 > &InNamedValues1, ValuePredicateType InValuePredicate)
Definition NamedValueArray.h:706
static void Union(const TNamedValueArray< AllocatorType0, ElementType0 > &InValueArray0, const TNamedValueArray< AllocatorType1, ElementType1 > &InValueArray1, PredicateType InPredicate)
Definition NamedValueArray.h:556
static void Intersection(const TNamedValueArray< AllocatorType0, ElementType0 > &InNamedValues0, const TNamedValueArray< AllocatorType1, ElementType1 > &InNamedValues1, ValuePredicateType InValuePredicate)
Definition NamedValueArray.h:642
static void Union(TNamedValueArray< AllocatorTypeResult, ElementTypeResult > &OutResultValueArray, const TNamedValueArray< AllocatorType0, ElementType0 > &InValueArray0, const TNamedValueArray< AllocatorType1, ElementType1 > &InValueArray1, PredicateType InPredicate)
Definition NamedValueArray.h:407
bool operator()(const ElementType &InElement0, const ElementType &InElement1) const
Definition NamedValueArray.h:151
Definition NamedValueArray.h:35
bool bSorted
Definition NamedValueArray.h:232
void AppendNames(TConstArrayView< FName > InNameArray)
Definition NamedValueArray.h:62
int32 Num() const
Definition NamedValueArray.h:130
void Reserve(int32 InNumElements)
Definition NamedValueArray.h:100
void SortElementsIfRequired() const
Definition NamedValueArray.h:158
void AppendNames(std::initializer_list< const FName > InInputArgs)
Definition NamedValueArray.h:80
void CheckSorted() const
Definition NamedValueArray.h:170
InAllocatorType AllocatorType
Definition NamedValueArray.h:36
const ElementType * Find(FName InName) const
Definition NamedValueArray.h:206
InElementType ElementType
Definition NamedValueArray.h:37
ElementType * Find(FName InName)
Definition NamedValueArray.h:217
void Shrink()
Definition NamedValueArray.h:142
TArray< ElementType, AllocatorType > Elements
Definition NamedValueArray.h:229
int32 IndexOf(FName InName) const
Definition NamedValueArray.h:198
bool HasElement(FName InName) const
Definition NamedValueArray.h:111
void ForEachElement(PredicateType InPredicate) const
Definition NamedValueArray.h:121
int32 Max() const
Definition NamedValueArray.h:136
void CheckDuplicates() const
Definition NamedValueArray.h:181
void Empty()
Definition NamedValueArray.h:93
void Add(ArgTypes &&... Args)
Definition NamedValueArray.h:48