UDocumentation UE5.7 10.02.2026 (Source)
API documentation for Unreal Engine 5.7
ObjectPool.h
Go to the documentation of this file.
1// Copyright Epic Games, Inc. All Rights Reserved.
2
3#pragma once
4
5#include <type_traits>
6#include "HAL/UnrealMemory.h"
10
11namespace Chaos
12{
13 // SFINAE helpers to clean up the appearance in the class definition.
14
15 // The type needs to have a destructor run when the pool pointer is freed
16 template<typename T>
17 using TRequiresDestructor = std::enable_if_t<!std::is_trivially_destructible_v<T>>;
18
19 // The type needs no destruction and can just be abandoned on free
20 template<typename T>
21 using TTrivialDestruct = std::enable_if_t<std::is_trivially_destructible_v<T>>;
22
23 template<typename ObjectType>
25 {
26 public:
27
28 // Alignment of the stored type
29 constexpr static int32 ItemAlign = alignof(ObjectType);
30
31 using FObject = ObjectType;
32 using FPtr = ObjectType*;
33
39
41 {
42 Reset();
43 }
44
45 // No copy, no move, no default
46 // #TODO possibly add move and copy if the internal type is copy constructible but likely better
47 // to discourage copying. Moving to another pool should be fine.
48 TObjectPool() = delete;
49 TObjectPool(const TObjectPool&) = delete;
53
63 template<typename... TArgs>
64 FPtr Alloc(TArgs&&... Args)
65 {
66 // Need a new Block
67 if(FreeCount == 0)
68 {
69 // Need a new block
70 ObjectType* NewPtr = AddBlock().GetNextFree();
72 --FreeCount;
73 return NewPtr;
74 }
75
76 // We know there's a free item somewhere, find one - shuffling full blocks to the end of the list
77 check(Blocks.Num() > 0);
78
79 if (Blocks[0].IsFull())
80 {
81 // Find the fullest block that is not completely full
82 int32 SelectedIndex = INDEX_NONE;
84 for (int32 Index = 0; Index < Blocks.Num(); ++Index)
85 {
86 const int32 BlockNumFree = Blocks[Index].NumFree;
88 {
90 SelectedIndex = Index;
91 }
92 }
93
94 // Move the selected block to the front
95 checkf(SelectedIndex != INDEX_NONE, TEXT("Could not find an empty block"));
96 Swap(Blocks[0], Blocks[SelectedIndex]);
97 }
98
99 // Ensure any writes to Blocks[0] cannot be reordered so that the GetNextFree
100 // call is definitely handled by the right block
102
103 // Blocks[0] is now a block with at least one free element
104 ObjectType* NewPtr = Blocks[0].GetNextFree();
106 --FreeCount;
107 return NewPtr;
108 }
109
120 {
121 int32 BlockIndex = FindBlock(Object);
122 Blocks[BlockIndex].Free(Object);
123 ++FreeCount;
124 }
125
132 void Reset()
133 {
134 for(FBlock& B : Blocks)
135 {
136 B.Reset();
137 }
138
139 FreeCount = Blocks.Num() * NumPerBlock;
140 }
141
146 {
147 return Blocks.Num();
148 }
149
154 {
155 return NumPerBlock;
156 }
157
162 {
163 return (PaddedItemSize * NumPerBlock) * Blocks.Num();
164 }
165
173 {
175 int32 Index = 0;
176 while(Index < Blocks.Num())
177 {
178 if(Blocks[Index].IsEmpty())
179 {
181 {
183 }
184 else
185 {
186 Blocks.RemoveAtSwap(Index);
187 continue;
188 }
189 }
190 ++Index;
191 }
192 }
193
200 void ReserveBlocks(int32 NumBlocks)
201 {
202 while(NumBlocks > Blocks.Num())
203 {
204 AddBlock();
205 }
206 }
207
217 void ReserveItems(int32 NumItems)
218 {
219 const int32 ItemsSize = NumItems * PaddedItemSize;
220 const int32 BlockSize = NumPerBlock * PaddedItemSize;
221 const int32 NumBlocks = int32(FMath::CeilToInt(static_cast<double>(ItemsSize) / BlockSize));
222 ReserveBlocks(NumBlocks);
223 }
224
230 float GetRatio() const
231 {
232 return static_cast<float>(sizeof(ObjectType)) / PaddedItemSize;
233 }
234
239 {
240 return GetCapacity() - GetNumFree();
241 }
242
248 {
249 return FreeCount;
250 }
251
257 {
258 return Capacity;
259 }
260
261 private:
262
263 // Storage for an item, and an index of the next free item if it's currently on the free list
264 template<typename T_ = ObjectType, typename Destructible = void>
265 struct alignas(ItemAlign) TItem
266 {
267 TItem()
268 : NextFree(INDEX_NONE)
269 {}
270
271 // Object must always remain as the first item to enable casting between Item and Object
272 ObjectType Object;
273
274 // Free-list tracking, index of the next free item
275 int32 NextFree;
276
277 // Set whether the object is live - encapsulated in a function as the base template performs no action
278 void SetLive(bool)
279 {}
280 };
281
282 // Specialized type for types that require a destructor as we need to add an extra
283 // flag for live/dead objects (calling Free will destruct the object but Reset/DestroyAll
284 // will need to destruct any non-free objects). This is specialized so that if the type
285 // is trivially destructible we can avoid adding the extra member.
286 template<typename T_>
288 {
289 TItem()
290 : NextFree(INDEX_NONE)
291 {}
292
293 // Object must always remain as the first item to enable casting between Item and Object
294 ObjectType Object;
295
296 // Free-list tracking, index of the next free item
297 int32 NextFree;
298
299 // Whether the object is live (constructed) and will require destruction
300 bool bLive : 1;
301
302 // Set whether the object is live - encapsulated in a function as the base template performs no action
303 // @param bInLive - new live flag value
304 void SetLive(bool bInLive)
305 {
306 bLive = bInLive;
307 }
308 };
309
310 // Helper alias for the item in this pool
311 using FItem = TItem<ObjectType>;
312
313 // Size of the item, plus padding up to the correct alignment so we can allocate the whole
314 // block with the correct size.
315 constexpr static int32 PaddedItemSize = Align(int32(sizeof(FItem)), ItemAlign);
316
317 // A block is an area of memory large enough to hold NumInBlock items, correctly aligned and
318 // provide items on demand to the pool
319 struct FBlock
320 {
321 FBlock() = delete;
322
323 explicit FBlock(int32 InNum)
324 : Begin(static_cast<FItem*>(FMemory::Malloc(PaddedItemSize * InNum, ItemAlign)))
325 , Next(Begin)
326 , NumInBlock(InNum)
327 , NumFree(NumInBlock)
328 {
329 }
330
331 ~FBlock()
332 {
333 check(Begin);
334
335 // Return our memory block
337 }
338
339 FPtr GetNextFree()
340 {
341 if(FreeList != INDEX_NONE)
342 {
343 // Something in the free list, prefer this
344 FItem* NewItem = Begin + FreeList;
345 FreeList = NewItem->NextFree;
346 NewItem->NextFree = INDEX_NONE;
347 --NumFree;
348
349 return &NewItem->Object;
350 }
351
352 if(NumValid < NumInBlock)
353 {
354 FItem* NewItem = Next++;
355 NewItem->NextFree = INDEX_NONE;
356 NewItem->SetLive(false);
357
358 --NumFree;
359 ++NumValid;
360
361 return &NewItem->Object;
362 }
363
364 checkf(false, TEXT("Attempt to request a free item from a full block (Freelist is empty, NumValid is %d, NumInBlock is %d, NumFree is %d)"), NumValid, NumInBlock, NumFree);
365
366 return nullptr;
367 }
368
369 void Free(ObjectType* Object)
370 {
371 checkf(Owns(Object), TEXT("A pointer was passed to an object pool block that it does not own."));
372
373 // If required, call the object destructor
375
376 // Get the offset into this block (check above ensures this is correct)
377 FItem* const AsItem = reinterpret_cast<FItem*>(Object);
378 const UPTRINT Offset = reinterpret_cast<UPTRINT>(AsItem) - reinterpret_cast<UPTRINT>(Begin);
379 const UPTRINT IndexInBlock = Offset / PaddedItemSize;
380
381 // If there's already a freelist, add it to our next item
382 if(FreeList != INDEX_NONE)
383 {
384 AsItem->NextFree = FreeList;
385 }
386
387 FreeList = (int32)IndexInBlock;
388 ++NumFree;
389 }
390
391 void Reset()
392 {
393 DestructAll();
394
395 Next = Begin;
396 NumFree = NumInBlock;
397 FreeList = INDEX_NONE;
398 NumValid = 0;
399 }
400
401 void DestructAll()
402 {
403 for(int32 i = 0; i < NumValid; ++i)
404 {
405 FItem* ToDestroy = Begin + i;
407 }
408 }
409
410 bool Owns(ObjectType* InPtr) const
411 {
412 FItem* AsItem = reinterpret_cast<FItem*>(InPtr);
413
414 return AsItem >= Begin && AsItem < Begin + NumInBlock;
415 }
416
417 bool IsEmpty() const
418 {
419 return NumFree == NumInBlock;
420 }
421
422 bool IsFull() const
423 {
424 return NumFree == 0;
425 }
426
427 // Ptr to the beginning of the block
428 FItem* Begin = nullptr;
429 // Ptr to the next free item when block is filling initially
430 FItem* Next = nullptr;
431
432 // Number of items the block can hold
433 const int32 NumInBlock;
434 // Count of free items in the block
435 int32 NumFree = 0;
436 // The maximum number of valid items in the block (items are valid if they were ever allocated / constructed)
437 int32 NumValid = 0;
438 // Head of the free list for this block
439 int32 FreeList = INDEX_NONE;
440 };
441
446 FBlock& AddBlock()
447 {
448 Blocks.Emplace(NumPerBlock);
449
450 // Update pool capacity and free count
451 Capacity += NumPerBlock;
452 FreeCount += NumPerBlock;
453
454 return Blocks.Last();
455 }
456
463 int32 FindBlock(ObjectType* InPtr)
464 {
465 for(int32 i = 0; i < Blocks.Num(); ++i)
466 {
467 if(Blocks[i].Owns(InPtr))
468 {
469 return i;
470 }
471 }
472
473 checkf(false, TEXT("A pointer was passed to an object pool method that it does not own."));
474 return INDEX_NONE;
475 }
476
485 template<
486 typename ObjectType_ = ObjectType,
488 typename... TArgs>
489 static ObjectType* Construct(ObjectType* At, TArgs&&... Args)
490 {
491 new (At) ObjectType(Forward<TArgs>(Args)...);
492
493 return At;
494 }
495
504 template<
505 typename ObjectType_ = ObjectType,
507 typename... TArgs>
508 static ObjectType* Construct(ObjectType* At, TArgs&&... Args)
509 {
510 new (At) ObjectType(Forward<TArgs>(Args)...);
511
512 FItem* AsItem = reinterpret_cast<FItem*>(At);
513 AsItem->bLive = true;
514
515 return At;
516 }
517
525 template<
526 typename ObjectType_ = ObjectType,
528 static void ConditionalDestruct(ObjectType* At)
529 {
530 checkSlow(At);
531
532 // Check the live flag, items on the free list will already have been destructed
533 // and we should avoid calling the destructor twice
534 FItem* AsItem = reinterpret_cast<FItem*>(At);
535 if(AsItem->bLive)
536 {
537 At->~ObjectType();
538 AsItem->bLive = false;
539 }
540 }
541
547 template<
548 typename ObjectType_ = ObjectType,
550 static void ConditionalDestruct(ObjectType*)
551 {}
552
553 TArray<FBlock> Blocks;
554
555 int32 NumPerBlock = 0;
556 int32 Capacity = 0;
557 int32 FreeCount = 0;
558 };
559
560
564 template<typename ObjectPoolType>
566 {
567 public:
569 using FObject = typename FObjectPool::FObject;
570
572 : Pool(nullptr)
573 {
574 }
575
577 : Pool(&InPool)
578 {
579 }
580
582 {
583 if (Object != nullptr)
584 {
585 check(Pool != nullptr);
586 Pool->Free(Object);
587 }
588 }
589
590 private:
591 FObjectPool* Pool;
592 };
593}
594
595#if UE_ENABLE_INCLUDE_ORDER_DEPRECATED_IN_5_5
597#endif
constexpr T Align(T Val, uint64 Alignment)
Definition AlignmentTemplates.h:18
#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 TEXT(x)
Definition Platform.h:1272
FPlatformTypes::int32 int32
A 32-bit signed integer.
Definition Platform.h:1125
FPlatformTypes::UPTRINT UPTRINT
An unsigned integer the same size as a pointer.
Definition Platform.h:1146
UE_FORCEINLINE_HINT TSharedRef< CastToType, Mode > StaticCastSharedRef(TSharedRef< CastFromType, Mode > const &InSharedRef)
Definition SharedPointer.h:127
void Construct(const FArguments &InArgs)
uint32 Offset
Definition VulkanMemory.cpp:4033
int BlockIndex
Definition binka_ue_decode_test.cpp:38
Definition ObjectPool.h:566
TObjectPoolDeleter()
Definition ObjectPool.h:571
typename FObjectPool::FObject FObject
Definition ObjectPool.h:569
ObjectPoolType FObjectPool
Definition ObjectPool.h:568
TObjectPoolDeleter(FObjectPool &InPool)
Definition ObjectPool.h:576
void operator()(FObject *Object)
Definition ObjectPool.h:581
Definition ObjectPool.h:25
int32 GetCapacity() const
Definition ObjectPool.h:256
FPtr Alloc(TArgs &&... Args)
Definition ObjectPool.h:64
void ShrinkTo(int32 NumDesiredEmptyBlocks)
Definition ObjectPool.h:172
~TObjectPool()
Definition ObjectPool.h:40
void Free(FPtr Object)
Definition ObjectPool.h:119
ObjectType FObject
Definition ObjectPool.h:31
void Reset()
Definition ObjectPool.h:132
int32 GetNumPerBlock() const
Definition ObjectPool.h:153
static constexpr int32 ItemAlign
Definition ObjectPool.h:29
TObjectPool(TObjectPool &&)=delete
TObjectPool(const TObjectPool &)=delete
int32 GetNumAllocatedBlocks() const
Definition ObjectPool.h:145
int32 GetNumFree() const
Definition ObjectPool.h:247
ObjectType * FPtr
Definition ObjectPool.h:32
int32 GetAllocatedSize() const
Definition ObjectPool.h:161
int32 GetNumAllocated() const
Definition ObjectPool.h:238
TObjectPool & operator=(TObjectPool &&)=delete
void ReserveBlocks(int32 NumBlocks)
Definition ObjectPool.h:200
TObjectPool(int32 InNumPerBlock, int32 InitialBlocks=1)
Definition ObjectPool.h:34
float GetRatio() const
Definition ObjectPool.h:230
TObjectPool & operator=(const TObjectPool &)=delete
void ReserveItems(int32 NumItems)
Definition ObjectPool.h:217
Definition Array.h:670
UE_REWRITE SizeType Num() const
Definition Array.h:1144
UE_FORCEINLINE_HINT void RemoveAtSwap(SizeType Index, EAllowShrinking AllowShrinking=UE::Core::Private::AllowShrinkingByDefault< AllocatorType >())
Definition Array.h:2185
Definition SkeletalMeshComponent.h:307
std::enable_if_t<!std::is_trivially_destructible_v< T > > TRequiresDestructor
Definition ObjectPool.h:17
std::enable_if_t< std::is_trivially_destructible_v< T > > TTrivialDestruct
Definition ObjectPool.h:21
U16 Index
Definition radfft.cpp:71
static UE_FORCEINLINE_HINT void MemoryBarrier()
Definition AndroidPlatformMisc.h:249
Definition UnrealMemory.h:94
static FORCENOINLINE CORE_API void Free(void *Original)
Definition UnrealMemory.cpp:685
Definition NumericLimits.h:41