UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
SetKeyFuncs.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include "HAL/Platform.h"
6#include "HAL/UnrealMemory.h"
8#include "Math/UnrealMath.h"
10#include "Templates/Function.h"
12
14
42template <typename ElementType, typename KeyFuncsType>
44{
45public:
46 struct FIterator;
47 struct FIterationSentinel;
48
49public:
50 TSetKeyFuncs(KeyFuncsType KeyFuncs, int32 ExpectedNumElements = 0);
56
57 void SetKeyFuncs(KeyFuncsType KeyFuncs);
58
59 void Reset();
66 void ResizeToTargetSize();
67
68 int32 Num() const;
71
72 template <typename CompareType>
73 const ElementType* Find(const CompareType& Key) const;
74 template <typename CompareType>
75 const ElementType* FindByHash(uint32 TypeHash, const CompareType& Key) const;
76
77 void Add(ElementType Value, bool* bAlreadyExists = nullptr);
78 void AddByHash(uint32 TypeHash, ElementType Value, bool* bAlreadyExists = nullptr);
79 int32 Remove(const ElementType& Value);
80 int32 RemoveByHash(uint32 TypeHash, const ElementType& Value);
81
82public:
84 struct FIterator
85 {
87 const ElementType& operator*() const;
90
91 private:
92 const TSetKeyFuncs& Owner;
93 uint32 Bucket;
94 };
95
98 {
99 };
100
101private:
102 // Hidden friends for ranged for-loops
111
112 void AddByHashNoReallocate(uint32 TypeHash, ElementType Value, bool* bAlreadyExists);
113 void Reallocate(uint32 NewHashSize);
114 uint32 GetTargetHashSize() const;
115 uint32 GetTargetHashSize(uint32 TargetNumValues) const;
120 uint32 HashSpaceToBucketSpace(uint32 HashKey) const;
121 void DestructHash();
122
123private:
124 constexpr static float MaxLoadFactorDuringAdd = 0.75f;
125 constexpr static float TargetLoadFactor = 0.5f;
126 constexpr static uint32 InitialAllocationSize = 8;
127 constexpr static uint32 MinimumNonZeroSize = 8;
128
129 mutable KeyFuncsType KeyFuncs;
130 ElementType* Hash = nullptr;
131 uint32 HashSize = 0;
132 uint32 NumValues = 0;
133};
134
142
143
145// Inline implementations
147
148
149template <typename ElementType, typename KeyFuncsType>
155
156template <typename ElementType, typename KeyFuncsType>
161
162template <typename ElementType, typename KeyFuncsType>
167
168template <typename ElementType, typename KeyFuncsType>
171{
172 if (this == &Other)
173 {
174 return *this;
175 }
176 DestructHash();
177
178 KeyFuncs = Other.KeyFuncs;
179 HashSize = Other.HashSize;
180 NumValues = Other.NumValues;
181
182 if (HashSize > 0)
183 {
184 Hash = reinterpret_cast<ElementType*>(FMemory::Malloc(sizeof(ElementType) * HashSize, alignof(ElementType)));
185 for (uint32 Bucket = 0; Bucket < HashSize; ++Bucket)
186 {
187 new (&Hash[Bucket]) ElementType(Other.Hash[Bucket]);
188 }
189 }
190 return *this;
191}
192
193template <typename ElementType, typename KeyFuncsType>
196{
197 if (this == &Other)
198 {
199 return *this;
200 }
201 DestructHash();
202
203 KeyFuncs = MoveTemp(Other.KeyFuncs);
204 Hash = Other.Hash;
205 HashSize = Other.HashSize;
206 NumValues = Other.NumValues;
207
208 Other.Hash = nullptr;
209 Other.HashSize = 0;
210 Other.NumValues = 0;
211 return *this;
212}
213
214template <typename ElementType, typename KeyFuncsType>
216{
217 DestructHash();
218}
219
220template <typename ElementType, typename KeyFuncsType>
222{
223 // Use the destructor and move constructor rather than the move operator= so that the KeyFunc author
224 // can use operator= for TSetKeyFuncs::operator=, and use constructor for SetKeyFuncs assignment.
225 KeyFuncs.~KeyFuncsType();
226 new (&KeyFuncs) KeyFuncsType(MoveTemp(InKeyFuncs));
227}
228
229template <typename ElementType, typename KeyFuncsType>
231{
232 NumValues = 0;
233 if (HashSize > 0)
234 {
235 ElementType InvalidValue = KeyFuncs.GetInvalidElement();
236 for (uint32 Bucket = 0; Bucket < HashSize; ++Bucket)
237 {
238 Hash[Bucket].~ElementType();
239 new (&Hash[Bucket]) ElementType(InvalidValue);
240 }
241 }
242}
243
244template <typename ElementType, typename KeyFuncsType>
246{
247 DestructHash();
248 HashSize = GetTargetHashSize(static_cast<uint32>(FMath::Max(0,ExpectedNumElements)));
249 NumValues = 0;
250
251 if (HashSize > 0)
252 {
253 ElementType InvalidValue = KeyFuncs.GetInvalidElement();
254 Hash = reinterpret_cast<ElementType*>(FMemory::Malloc(sizeof(ElementType) * HashSize, alignof(ElementType)));
255 for (uint32 Bucket = 0; Bucket < HashSize; ++Bucket)
256 {
257 new (&Hash[Bucket]) ElementType(InvalidValue);
258 }
259 }
260}
261
262template <typename ElementType, typename KeyFuncsType>
264{
265 uint32 NewHashSize = GetTargetHashSize(static_cast<uint32>(FMath::Max(0, ExpectedNumElements)));
266 if (NewHashSize > HashSize)
267 {
268 Reallocate(NewHashSize);
269 }
270}
271
272template <typename ElementType, typename KeyFuncsType>
274{
275 uint32 TargetHashSize = GetTargetHashSize();
276 if (TargetHashSize != HashSize)
277 {
278 Reallocate(TargetHashSize);
279 }
280}
281
282template <typename ElementType, typename KeyFuncsType>
284{
285 return IntCastChecked<int32>(NumValues);
286}
287
288template <typename ElementType, typename KeyFuncsType>
290{
291 return sizeof(ElementType) * HashSize;
292}
293
294template <typename ElementType, typename KeyFuncsType>
297{
298 FSetKeyFuncsStats Result;
299 if (NumValues == 0)
300 {
301 Result.AverageSearch = 0.f;
302 Result.LongestSearch = 0;
303 return Result;
304 }
305 check(HashSize > 0 && Hash != nullptr);
306
307 // Start measuring at the first collision chain on or after 0. That collision chain may
308 // have started before 0 and wrapped around.
311 if (!KeyFuncs.IsInvalid(Hash[EnumerateStart]))
312 {
313 EnumerateStart = HashSize - 1;
315 for (; CollisionChainCount < HashSize; ++CollisionChainCount)
316 {
317 if (KeyFuncs.IsInvalid(Hash[EnumerateStart]))
318 {
319 EnumerateStart = HashSpaceToBucketSpace(EnumerateStart + 1);
320 break;
321 }
322 EnumerateStart = HashSpaceToBucketSpace(EnumerateStart - 1);
323 }
324 }
325
326 // We do not allow the container to become completely full, so we should always find an unused bucket
327 check(CollisionChainCount < HashSize);
328
329 Result.LongestSearch = 0;
331 bool bFirstLoop = true;
335 )
336 {
337 if (KeyFuncs.IsInvalid(Hash[CollisionChainStart]))
338 {
339 CollisionChainStart = HashSpaceToBucketSpace(CollisionChainStart + 1);
340 bFirstLoop = false;
341 continue;
342 }
343
344 uint32 Bucket;
345 for (Bucket = CollisionChainStart;
346 Bucket != EnumerateStart || bFirstLoop;
347 Bucket = HashSpaceToBucketSpace(Bucket + 1), bFirstLoop = false)
348 {
349 const ElementType& Element = Hash[Bucket];
350 if (KeyFuncs.IsInvalid(Element))
351 {
352 break;
353 }
354 uint32 RealBucket = HashSpaceToBucketSpace(KeyFuncs.GetTypeHash(Element));
355 // RealBucket must be on the path from CollisionChainStart to Bucket.
356 // Record the length from RealBucket to Bucket, including Bucket itself.
358 if (CollisionChainStart <= Bucket)
359 {
361 SearchLength = static_cast<int32>(Bucket - RealBucket + 1);
362 }
363 else
364 {
366 if (RealBucket <= Bucket)
367 {
368 SearchLength = static_cast<int32>(Bucket - RealBucket + 1);
369 }
370 else
371 {
372 SearchLength = static_cast<int32>((HashSize - RealBucket) + Bucket + 1);
373 }
374 }
375 Result.LongestSearch = FMath::Max(Result.LongestSearch, SearchLength);
377 }
378 CollisionChainStart = Bucket;
379 }
380
381 Result.AverageSearch = static_cast<float>(SumOfSearches) / static_cast<float>(NumValues);
382 return Result;
383}
384
385template <typename ElementType, typename KeyFuncsType>
386template <typename CompareType>
387const ElementType* TSetKeyFuncs<ElementType, KeyFuncsType>::Find(const CompareType& Key) const
388{
389 return FindByHash(KeyFuncs.GetTypeHash(Key), Key);
390}
391
392template <typename ElementType, typename KeyFuncsType>
393template <typename CompareType>
394const ElementType* TSetKeyFuncs<ElementType, KeyFuncsType>::FindByHash(uint32 TypeHash, const CompareType& Key) const
395{
396 if (Hash == nullptr)
397 {
398 return nullptr;
399 }
400 uint32 Bucket = HashSpaceToBucketSpace(TypeHash);
402 for (CollisionCount = 0; CollisionCount < HashSize; ++CollisionCount)
403 {
404 const ElementType& Element = Hash[Bucket];
405 if (KeyFuncs.IsInvalid(Element))
406 {
407 break;
408 }
409 if (KeyFuncs.Matches(Element, Key))
410 {
411 return &Element;
412 }
413 Bucket = HashSpaceToBucketSpace(Bucket + 1);
414 }
415 // We do not allow the container to become completely full, so we should always find an unused bucket
416 check(CollisionCount < HashSize);
417 return nullptr;
418}
419
420template <typename ElementType, typename KeyFuncsType>
422{
423 AddByHash(KeyFuncs.GetTypeHash(Value), MoveTemp(Value), bAlreadyExists);
424}
425
426template <typename ElementType, typename KeyFuncsType>
428{
429 if (KeyFuncs.IsInvalid(Value))
430 {
431 checkf(false, TEXT("Add called with invalid element."));
432 return;
433 }
434 if (HashSize == 0)
435 {
436 Empty(InitialAllocationSize);
437 }
438 check(Hash != nullptr && HashSize > 0);
439
440 AddByHashNoReallocate(TypeHash, Value, bAlreadyExists);
441
442 float LoadFactor = static_cast<float>(NumValues) / static_cast<float>(HashSize);
443 if (LoadFactor > MaxLoadFactorDuringAdd)
444 {
445 Reallocate(GetTargetHashSize());
446 }
447}
448
449template <typename ElementType, typename KeyFuncsType>
451{
453 uint32 Bucket = HashSpaceToBucketSpace(TypeHash);
455 {
456 const ElementType& ExistingElement = Hash[Bucket];
457 if (KeyFuncs.IsInvalid(ExistingElement))
458 {
459 break;
460 }
461 else if (KeyFuncs.Matches(ExistingElement, Value))
462 {
463 // Already exists
464 if (bAlreadyExists)
465 {
466 *bAlreadyExists = true;
467 return;
468 }
469 }
470 Bucket = HashSpaceToBucketSpace(Bucket + 1);
471 }
472
473 // We do not allow the container to become completely full, so we should always find an unused bucket
474 check(CollisionChainCount < HashSize);
475 Hash[Bucket].~ElementType();
476 new (&Hash[Bucket]) ElementType(MoveTemp(Value));
477 ++NumValues;
478 if (bAlreadyExists)
479 {
480 *bAlreadyExists = false;
481 }
482}
483
484template <typename ElementType, typename KeyFuncsType>
486{
487 return RemoveByHash(KeyFuncs.GetTypeHash(Value), Value);
488}
489
490template <typename ElementType, typename KeyFuncsType>
492{
493 if (KeyFuncs.IsInvalid(Value))
494 {
495 checkf(false, TEXT("Remove called with invalid element."));
496 return 0;
497 }
498 if (NumValues == 0)
499 {
500 return 0;
501 }
502 check(HashSize != 0 && Hash != nullptr);
503
505 uint32 Bucket = HashSpaceToBucketSpace(TypeHash);
507 {
508 const ElementType& ExistingElement = Hash[Bucket];
509 if (KeyFuncs.IsInvalid(ExistingElement))
510 {
511 // Does not exist
512 return 0;
513 }
514 else if (KeyFuncs.Matches(ExistingElement, Value))
515 {
516 break;
517 }
518 Bucket = HashSpaceToBucketSpace(Bucket + 1);
519 }
520 // We do not allow the container to become completely full, so we should always find the end of the collision chain.
521 check(CollisionChainCount < HashSize);
522
523 // If we remove a value from the middle of a collision chain, we have to shift other elements in the chain down to
524 // plug the hole so that Find will be able to find them.
525 uint32 HoleIndex = Bucket;
526 uint32 CurrentBucket = HashSpaceToBucketSpace(HoleIndex + 1);
528 {
529 ElementType& ExistingElement = Hash[CurrentBucket];
530 if (KeyFuncs.IsInvalid(ExistingElement))
531 {
532 // None of the values in between HoleIndex and CurrentBucket needed to be patched into
533 // the hole, and we've reached the end of the collision chain. Leave the hole empty,
534 // which will split the collision chain in two (or will decrease the size of the collision
535 // chain by one if the hole is at the start or end of the chain).
536 break;
537 }
538 uint32 RealBucket = HashSpaceToBucketSpace(KeyFuncs.GetTypeHash(ExistingElement));
539
540 // We are guaranteed that RealBucket comes earlier in the collision chain than CurrentBucket,
541 // because when we resolve collisions during add we only move forward.
542 // If the hole is in between RealBucket and CurrentBucket then we need to move the value back from
543 // CurrentBucket into the Hole so that we find it when we start searching from RealBucket.
544 // But the comparison is complicated because we're searching in a ring; the collision chain might
545 // overlap the end of the bucket array and wrap around to the start, so RealBucket might be more than
546 // Hole and CurrentBucket even though it is earlier in the collision chain.
547 bool bPatchTheHole = false;
548 if (RealBucket == CurrentBucket)
549 {
550 // No need to patch the hole if the value is already assigned to its RealBucket.
551 }
552 else if (RealBucket < CurrentBucket)
553 {
554 // Need to patch if the hole is on or after RealBucket on the path from RealBucket to CurrentBucket:
555 // ################ RealBucket ### Hole #### CurrentBucket ########
556 // No need to patch if the hole is after CurrentBucket on the path from CurrentBucket to RealBucket:
557 // ################ Hole ### RealBucket #### CurrentBucket ########
558 // ################ RealBucket #### CurrentBucket ######## Hole ###
559 bPatchTheHole = RealBucket <= HoleIndex && HoleIndex < CurrentBucket;
560 }
561 else
562 {
563 // Need to patch if the hole is on or after RealBucket on the path from RealBucket to CurrentBucket:
564 // ################ Hole ### CurrentBucket #### RealBucket ########
565 // ################ CurrentBucket ### RealBucket #### Hole ########
566 // No need to patch if the hole is after CurrentBucket on the path from CurrentBucket to RealBucket:
567 // ################ CurrentBucket ### Hole #### RealBucket ########
568 bPatchTheHole = HoleIndex < CurrentBucket || RealBucket <= HoleIndex;
569 }
570
571 if (bPatchTheHole)
572 {
573 // Move Value into the hole, which fills the hole, and creates a new hole at CurrentBucket. We now need
574 // to patch the new hole, so continue iterating.
575 Hash[HoleIndex].~ElementType();
576 new (&Hash[HoleIndex]) ElementType(MoveTemp(ExistingElement));
577 HoleIndex = CurrentBucket;
578 }
579
580 CurrentBucket = HashSpaceToBucketSpace(CurrentBucket + 1);
581 }
582 // We do not allow the container to become completely full, so we should always find the end of the collision chain.
583 check(CollisionChainCount < HashSize);
584
585 // We decided not to fill the last hole we created, so mark it as an unused bucket.
586 Hash[HoleIndex].~ElementType();
587 new (&Hash[HoleIndex]) ElementType(KeyFuncs.GetInvalidElement());
588 --NumValues;
589
590 return 1;
591}
592
593template <typename ElementType, typename KeyFuncsType>
595{
596 check(NumValues == 0 || NewHashSize > NumValues);
597
598 if (NewHashSize == 0)
599 {
600 Empty();
601 }
602 else
603 {
604 ElementType* OldHash = Hash;
605 uint32 OldHashSize = HashSize;
606 uint32 OldNumValues = NumValues;
607
608 HashSize = NewHashSize;
609 ElementType InvalidValue = KeyFuncs.GetInvalidElement();
610 Hash = reinterpret_cast<ElementType*>(FMemory::Malloc(sizeof(ElementType) * HashSize, alignof(ElementType)));
611 for (uint32 Bucket = 0; Bucket < HashSize; ++Bucket)
612 {
613 new (&Hash[Bucket]) ElementType(InvalidValue);
614 }
615
616 NumValues = 0;
618 {
619 ElementType& OldElement = OldHash[OldBucket];
620 if (!KeyFuncs.IsInvalid(OldElement))
621 {
622 AddByHashNoReallocate(KeyFuncs.GetTypeHash(OldElement), MoveTemp(OldElement), nullptr);
623 }
624 OldElement.~ElementType();
625 }
627 check(NumValues == OldNumValues);
628 }
629}
630
631template <typename ElementType, typename KeyFuncsType>
633{
634 return GetTargetHashSize(NumValues);
635}
636
637template <typename ElementType, typename KeyFuncsType>
639{
640 if (TargetNumValues == 0)
641 {
642 return 0;
643 }
644 uint32 TargetHashSize = static_cast<uint32>(FMath::CeilToInt32(
645 static_cast<float>(TargetNumValues) / static_cast<float>(TargetLoadFactor)));
646 TargetHashSize = FMath::Max(TargetHashSize, MinimumNonZeroSize);
647 return TargetHashSize;
648}
649
650template <typename ElementType, typename KeyFuncsType>
652{
653 return HashSize != 0 ? (HashKey % HashSize) : 0;
654}
655
656template <typename ElementType, typename KeyFuncsType>
658{
659 if (Hash)
660 {
661 for (uint32 Bucket = 0; Bucket < HashSize; ++Bucket)
662 {
663 Hash[Bucket].~ElementType();
664 }
666 Hash = nullptr;
667 }
668}
669
670template <typename ElementType, typename KeyFuncsType>
672: Owner(InOwner)
673, Bucket(0)
674{
675 if (Owner.HashSize > 0 && Owner.KeyFuncs.IsInvalid(Owner.Hash[Bucket]))
676 {
677 this->operator++();
678 }
679}
680
681template <typename ElementType, typename KeyFuncsType>
683{
684 return Owner.Hash[Bucket];
685}
686
687template <typename ElementType, typename KeyFuncsType>
690{
691 ++Bucket;
692 while (Bucket < Owner.HashSize && Owner.KeyFuncs.IsInvalid(Owner.Hash[Bucket]))
693 {
694 ++Bucket;
695 }
696 return *this;
697}
698
699template <typename ElementType, typename KeyFuncsType>
701{
702 return Bucket < Owner.HashSize;
703}
#define check(expr)
Definition AssertionMacros.h:314
#define checkf(expr, format,...)
Definition AssertionMacros.h:315
#define TEXT(x)
Definition Platform.h:1272
FPlatformTypes::SIZE_T SIZE_T
An unsigned integer the same size as a pointer, the same as UPTRINT.
Definition Platform.h:1150
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::uint64 uint64
A 64-bit unsigned integer.
Definition Platform.h:1117
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
UE_INTRINSIC_CAST UE_REWRITE constexpr std::remove_reference_t< T > && MoveTemp(T &&Obj) noexcept
Definition UnrealTemplate.h:520
uint32_t uint32
Definition binka_ue_file_header.h:6
Definition SetKeyFuncs.h:44
int32 Remove(const ElementType &Value)
Definition SetKeyFuncs.h:485
FSetKeyFuncsStats GetStats() const
Definition SetKeyFuncs.h:296
~TSetKeyFuncs()
Definition SetKeyFuncs.h:215
friend FIterationSentinel end(const TSetKeyFuncs< ElementType, KeyFuncsType > &Set)
Definition SetKeyFuncs.h:107
int32 RemoveByHash(uint32 TypeHash, const ElementType &Value)
Definition SetKeyFuncs.h:491
void Reserve(int32 ExpectedNumElements)
Definition SetKeyFuncs.h:263
void Add(ElementType Value, bool *bAlreadyExists=nullptr)
Definition SetKeyFuncs.h:421
int32 Num() const
Definition SetKeyFuncs.h:283
TSetKeyFuncs & operator=(const TSetKeyFuncs< ElementType, KeyFuncsType > &Other)
Definition SetKeyFuncs.h:170
const ElementType * Find(const CompareType &Key) const
Definition SetKeyFuncs.h:387
void ResizeToTargetSize()
Definition SetKeyFuncs.h:273
SIZE_T GetAllocatedSize() const
Definition SetKeyFuncs.h:289
const ElementType * FindByHash(uint32 TypeHash, const CompareType &Key) const
Definition SetKeyFuncs.h:394
TSetKeyFuncs(KeyFuncsType KeyFuncs, int32 ExpectedNumElements=0)
Definition SetKeyFuncs.h:150
void Reset()
Definition SetKeyFuncs.h:230
friend FIterator begin(const TSetKeyFuncs< ElementType, KeyFuncsType > &Set)
Definition SetKeyFuncs.h:103
void SetKeyFuncs(KeyFuncsType KeyFuncs)
Definition SetKeyFuncs.h:221
void Empty(int32 ExpectedNumElements=0)
Definition SetKeyFuncs.h:245
void AddByHash(uint32 TypeHash, ElementType Value, bool *bAlreadyExists=nullptr)
Definition SetKeyFuncs.h:427
FNameFuncs< typename TGetKeyType< MapType >::Type > KeyFuncs
Definition PerPlatformProperties.h:107
static FORCENOINLINE CORE_API void Free(void *Original)
Definition UnrealMemory.cpp:685
Definition SetKeyFuncs.h:136
float AverageSearch
Definition SetKeyFuncs.h:138
int32 LongestSearch
Definition SetKeyFuncs.h:140
Definition SetKeyFuncs.h:98
Definition SetKeyFuncs.h:85
FIterator & operator++()
Definition SetKeyFuncs.h:689
FIterator(const TSetKeyFuncs< ElementType, KeyFuncsType > &InOwner)
Definition SetKeyFuncs.h:671
const ElementType & operator*() const
Definition SetKeyFuncs.h:682
bool operator!=(FIterationSentinel) const
Definition SetKeyFuncs.h:700