Files
UnrealEngine/Engine/Source/Runtime/AutoRTFM/Private/BlockAllocator.h
2025-05-18 13:04:45 +08:00

129 lines
3.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#if (defined(__AUTORTFM) && __AUTORTFM)
#include "Utils.h"
#include "ExternAPI.h"
#include <cstddef>
namespace AutoRTFM
{
// A single-threaded, bump memory allocator that sub-allocates out of blocks of
// inline memory and once that's exhausted, from heap allocated memory.
// All allocated memory is freed when the TBlockAllocator is destructed.
// There is no way to free individual allocations.
//
// Template parameters:
// InlineBlockDataSize - The size of the data section of the inline block.
// DataAlignment - The maximum supported alignment of data from this allocator.
// GrowthPercentage - The percentage to grow the block size each reallocation.
template<size_t InlineBlockDataSize = 256, size_t DataAlignment = 16, size_t GrowthPercentage = 200>
class TBlockAllocator
{
public:
// Constructor
TBlockAllocator() = default;
// Destructor - frees all the memory allocated by the allocator.
~TBlockAllocator()
{
FreeAll();
}
// Frees all allocations made by the allocator.
void FreeAll()
{
while (Tail != &InlineBlock)
{
FBlockHeader* Prev = Tail->Prev;
AutoRTFM::Free(Tail);
Tail = Prev;
}
InlineBlock.Remaining = InlineBlockDataSize;
NextBlockSize = InlineBlockDataSize * GrowthPercentage / 100;
}
// Allocates memory from the block allocator.
// Alignment must be a power of two and no larger than DataAlignment.
inline void* Allocate(size_t Size, size_t Alignment)
{
AUTORTFM_ASSERT(Alignment <= DataAlignment);
if (void* Allocation = Tail->TryAllocate(Size, Alignment))
{
return Allocation;
}
size_t NewBlockSize = std::max(NextBlockSize, Size);
NextBlockSize = NextBlockSize * GrowthPercentage / 100;
FBlockHeader* NewBlock = FBlockHeader::New(Tail, NewBlockSize);
Tail = NewBlock;
void* Allocation = NewBlock->TryAllocate(Size, Alignment);
AUTORTFM_ASSERT(Allocation);
return Allocation;
}
// Constructs and returns a new T into the memory returned by calling
// Allocate(sizeof(T), alignof(T)).
template<typename T, typename... ArgTypes> T* New(ArgTypes&&... Args)
{
return new (Allocate(sizeof(T), alignof(T))) T(Forward<ArgTypes>(Args)...);
}
private:
TBlockAllocator(TBlockAllocator&&) = delete;
TBlockAllocator(const TBlockAllocator&) = delete;
TBlockAllocator& operator=(const TBlockAllocator&) = delete;
TBlockAllocator& operator=(TBlockAllocator&&) = delete;
static constexpr size_t BlockAlignment = std::max<size_t>(DataAlignment, 8);
struct alignas(BlockAlignment) FBlockHeader
{
// The previous FBlock in the singly linked-list
FBlockHeader* const Prev = nullptr;
// The size of the block's data
const size_t BlockDataSize = 0;
// The number of unallocated bytes remaining in the block
size_t Remaining = BlockDataSize;
// <data>
// Allocates, constructs and returns a new block from the heap-allocated memory.
static FBlockHeader* New(FBlockHeader* Prev, size_t BlockDataSize)
{
void* Memory = AutoRTFM::Allocate(sizeof(FBlockHeader) + BlockDataSize, BlockAlignment);
return new (Memory) FBlockHeader{Prev, BlockDataSize};
}
// Attempts to sub-allocate with out of this block. Returns a pointer to
// the sub-allocated memory on success, or nullptr on failure.
inline void* TryAllocate(size_t Size, size_t Alignment)
{
size_t RemainingAligned = AlignDown(Remaining, Alignment);
if (RemainingAligned < Size)
{
return nullptr;
}
void* Ptr = reinterpret_cast<std::byte*>(this) + sizeof(FBlockHeader) + BlockDataSize - RemainingAligned;
Remaining = RemainingAligned - Size;
return Ptr;
}
};
static_assert(sizeof(FBlockHeader) % DataAlignment == 0);
size_t NextBlockSize = InlineBlockDataSize * GrowthPercentage / 100;
FBlockHeader* Tail = &InlineBlock;
FBlockHeader InlineBlock{/* Prev */ nullptr, InlineBlockDataSize};
alignas(BlockAlignment) std::byte Data[InlineBlockDataSize];
};
}
#endif // (defined(__AUTORTFM) && __AUTORTFM)