Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
helios::memory::ArenaAllocator Class Referencefinal

Lock-free, thread-safe arena allocator. More...

#include <arena_allocator.hpp>

Public Member Functions

 ArenaAllocator (void *buffer, size_t size) noexcept
 Constructs an arena allocator over an existing buffer.
 
 ArenaAllocator (const ArenaAllocator &)=delete
 
 ArenaAllocator (ArenaAllocator &&other) noexcept
 
 ~ArenaAllocator () noexcept=default
 
ArenaAllocatoroperator= (const ArenaAllocator &)=delete
 
ArenaAllocatoroperator= (ArenaAllocator &&other) noexcept
 
AllocationResult Allocate (size_t size, size_t alignment=kDefaultAlignment) noexcept
 Allocates a block of memory from the arena.
 
template<typename T >
T * Allocate () noexcept
 
template<typename T >
T * Allocate (size_t count) noexcept
 
template<typename T , typename... Args>
requires std::constructible_from<T, Args...>
T * AllocateAndConstruct (Args &&... args) noexcept(std::is_nothrow_constructible_v< T, Args... >)
 
template<typename T >
requires std::default_initializable<T>
T * AllocateAndConstructArray (size_t count) noexcept(std::is_nothrow_default_constructible_v< T >)
 
void Deallocate (const void *) noexcept
 Deallocation is a no-op.
 
void Reset () noexcept
 Resets the arena, freeing all allocations.
 
bool Empty () const noexcept
 Checks if the arena is empty.
 
bool Full () const noexcept
 Checks if the arena is full.
 
AllocatorStats Stats () const noexcept
 Gets current allocator statistics.
 
size_t Capacity () const noexcept
 Gets the total capacity of the arena.
 
size_t CurrentOffset () const noexcept
 Gets the current offset (amount of memory used).
 
size_t FreeSpace () const noexcept
 Gets the amount of free space remaining.
 
void * Data () noexcept
 Gets the raw backing buffer pointer.
 
const void * Data () const noexcept
 Gets the raw backing buffer pointer (const).
 

Detailed Description

Lock-free, thread-safe arena allocator.

Arena allocator that allocates from a pre-allocated buffer using a bump-pointer strategy. All allocations are performed with lock-free atomic operations on an internal offset.

Memory is released only when the arena is reset, which is an O(1) operation. Individual deallocations are not supported.

This allocator is suitable for use as a backing allocator for higher level systems that require fast, thread-safe allocation with predictable lifetime.

All operations that modify the arena state (Allocate, Reset) use atomic operations. Reset is not safe to call concurrently with Allocate and must be externally synchronized when used in that way.

Note
Thread-safe.
Warning
Reset must not be used concurrently with active allocations. The caller is responsible for enforcing this invariant.

Definition at line 36 of file arena_allocator.hpp.

Constructor & Destructor Documentation

◆ ArenaAllocator() [1/3]

helios::memory::ArenaAllocator::ArenaAllocator ( void *  buffer,
size_t  size 
)
inlinenoexcept

Constructs an arena allocator over an existing buffer.

The caller provides a raw buffer and its size. The buffer must remain valid for the entire lifetime of the allocator. Alignment guarantees are provided up to kDefaultAlignment (or higher alignment if the buffer itself is appropriately aligned).

The allocator does not take ownership of the buffer and will not free it.

Warning
Triggers assertion if:
  • buffer is nullptr but size is non-zero.
  • size is 0.
  • buffer is not aligned to kMinAlignment.
Parameters
bufferPointer to the backing memory buffer.
sizeSize of the buffer in bytes.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 226 of file arena_allocator.hpp.

226 : buffer_(buffer), capacity_(size) {
227 HELIOS_ASSERT(size > 0, "Failed to construct ArenaAllocator: size must be greater than 0!");
228 HELIOS_ASSERT(buffer != nullptr, "Failed to construct ArenaAllocator: buffer must not be nullptr!");
230 "Failed to construct ArenaAllocator: buffer must be at least {}-byte aligned!", kMinAlignment);
231
232 offset_.store(0, std::memory_order_release);
233 peak_offset_.store(0, std::memory_order_release);
234 allocation_count_.store(0, std::memory_order_release);
235 alignment_waste_.store(0, std::memory_order_release);
236}
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
size_t CalculatePadding(const void *ptr, size_t alignment) noexcept
Calculate padding needed for alignment.
constexpr size_t kMinAlignment
Minimum alignment for any allocation.

◆ ArenaAllocator() [2/3]

helios::memory::ArenaAllocator::ArenaAllocator ( const ArenaAllocator )
delete

◆ ArenaAllocator() [3/3]

helios::memory::ArenaAllocator::ArenaAllocator ( ArenaAllocator &&  other)
inlinenoexcept

Definition at line 238 of file arena_allocator.hpp.

239 : buffer_(other.buffer_),
240 capacity_(other.capacity_),
241 offset_(other.offset_.load(std::memory_order_acquire)),
242 peak_offset_(other.peak_offset_.load(std::memory_order_acquire)),
243 allocation_count_(other.allocation_count_.load(std::memory_order_acquire)),
244 alignment_waste_(other.alignment_waste_.load(std::memory_order_acquire)) {
245 other.buffer_ = nullptr;
246 other.capacity_ = 0;
247 other.offset_.store(0, std::memory_order_release);
248 other.peak_offset_.store(0, std::memory_order_release);
249 other.allocation_count_.store(0, std::memory_order_release);
250 other.alignment_waste_.store(0, std::memory_order_release);
251}

◆ ~ArenaAllocator()

helios::memory::ArenaAllocator::~ArenaAllocator ( )
defaultnoexcept

Member Function Documentation

◆ Allocate() [1/3]

template<typename T >
T * helios::memory::ArenaAllocator::Allocate ( )
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 352 of file arena_allocator.hpp.

352 {
353 constexpr size_t size = sizeof(T);
354 constexpr size_t alignment = std::max(alignof(T), kMinAlignment);
355 auto result = Allocate(size, alignment);
356 return static_cast<T*>(result.ptr);
357}

◆ Allocate() [2/3]

template<typename T >
T * helios::memory::ArenaAllocator::Allocate ( size_t  count)
inlinenoexcept

Definition at line 360 of file arena_allocator.hpp.

360 {
361 if (count == 0) [[unlikely]] {
362 return nullptr;
363 }
364 constexpr size_t alignment = std::max(alignof(T), kMinAlignment);
365 const size_t size = sizeof(T) * count;
366 auto result = Allocate(size, alignment);
367 return static_cast<T*>(result.ptr);
368}

◆ Allocate() [3/3]

AllocationResult helios::memory::ArenaAllocator::Allocate ( size_t  size,
size_t  alignment = kDefaultAlignment 
)
inlinenoexcept

Allocates a block of memory from the arena.

Uses a lock-free bump-pointer with compare_exchange_weak to reserve space from the backing buffer. The returned memory is not initialized.

Warning
Triggers assertion in next cases:
  • Alignment is not a power of 2.
  • Alignment is less than kMinAlignment.
Parameters
sizeNumber of bytes to allocate.
alignmentAlignment requirement (must be power of 2).
Returns
AllocationResult with pointer and size, or {nullptr, 0} on failure.

Definition at line 275 of file arena_allocator.hpp.

275 {
276 HELIOS_ASSERT(IsPowerOfTwo(alignment), "ArenaAllocator::Allocate failed: alignment must be power of 2, got '{}'!",
277 alignment);
278 HELIOS_ASSERT(alignment >= kMinAlignment,
279 "ArenaAllocator::Allocate failed: alignment must be at least '{}', got '{}'!", kMinAlignment,
280 alignment);
281
282 if (size == 0) [[unlikely]] {
283 return {.ptr = nullptr, .allocated_size = 0};
284 }
285
286 // Lock-free bump-pointer allocation.
287 size_t current_offset = offset_.load(std::memory_order_acquire);
288 size_t aligned_offset = 0;
289 size_t new_offset = 0;
290 size_t padding = 0;
291
292 do {
293 auto* current_ptr = static_cast<uint8_t*>(buffer_) + current_offset;
294 padding = CalculatePadding(current_ptr, alignment);
295 aligned_offset = current_offset + padding;
296
297 if (aligned_offset + size > capacity_) {
298 return {.ptr = nullptr, .allocated_size = 0};
299 }
300
301 new_offset = aligned_offset + size;
302 } while (
303 !offset_.compare_exchange_weak(current_offset, new_offset, std::memory_order_release, std::memory_order_acquire));
304
305 allocation_count_.fetch_add(1, std::memory_order_relaxed);
306 alignment_waste_.fetch_add(padding, std::memory_order_relaxed);
307
308 size_t current_peak = peak_offset_.load(std::memory_order_acquire);
309 while (new_offset > current_peak) {
310 if (peak_offset_.compare_exchange_weak(current_peak, new_offset, std::memory_order_release,
311 std::memory_order_acquire)) {
312 break;
313 }
314 }
315
316 auto* result = static_cast<uint8_t*>(buffer_) + aligned_offset;
317 return {.ptr = result, .allocated_size = size};
318}
constexpr bool IsPowerOfTwo(size_t size) noexcept
Helper function to check if a size is a power of 2.

◆ AllocateAndConstruct()

template<typename T , typename... Args>
requires std::constructible_from<T, Args...>
T * helios::memory::ArenaAllocator::AllocateAndConstruct ( Args &&...  args)
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 372 of file arena_allocator.hpp.

372 {
373 T* ptr = Allocate<T>();
374 if (ptr != nullptr) [[likely]] {
375 std::construct_at(ptr, std::forward<Args>(args)...);
376 }
377 return ptr;
378}

◆ AllocateAndConstructArray()

template<typename T >
requires std::default_initializable<T>
T * helios::memory::ArenaAllocator::AllocateAndConstructArray ( size_t  count)
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 382 of file arena_allocator.hpp.

382 {
383 T* ptr = Allocate<T>(count);
384 if (ptr != nullptr) [[likely]] {
385 for (size_t i = 0; i < count; ++i) {
386 std::construct_at(ptr + i);
387 }
388 }
389 return ptr;
390}

◆ Capacity()

size_t helios::memory::ArenaAllocator::Capacity ( ) const
inlinenoexcept

Gets the total capacity of the arena.

Returns
Capacity in bytes.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 191 of file arena_allocator.hpp.

191{ return capacity_; }

◆ CurrentOffset()

size_t helios::memory::ArenaAllocator::CurrentOffset ( ) const
inlinenoexcept

Gets the current offset (amount of memory used).

Returns
Current offset in bytes.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 197 of file arena_allocator.hpp.

197{ return offset_.load(std::memory_order_relaxed); }

◆ Data() [1/2]

const void * helios::memory::ArenaAllocator::Data ( ) const
inlinenoexcept

Gets the raw backing buffer pointer (const).

Returns
Const pointer to the beginning of the backing buffer.

Definition at line 215 of file arena_allocator.hpp.

215{ return buffer_; }

◆ Data() [2/2]

void * helios::memory::ArenaAllocator::Data ( )
inlinenoexcept

Gets the raw backing buffer pointer.

Returns
Pointer to the beginning of the backing buffer.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 209 of file arena_allocator.hpp.

209{ return buffer_; }

◆ Deallocate()

void helios::memory::ArenaAllocator::Deallocate ( const void *  )
inlinenoexcept

Deallocation is a no-op.

Arena allocators do not support individual deallocation. Memory is released only via Reset. This function exists to satisfy generic allocator interfaces.

Parameters
ptrPointer previously returned by Allocate (may be nullptr).
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 156 of file arena_allocator.hpp.

156{}

◆ Empty()

bool helios::memory::ArenaAllocator::Empty ( ) const
inlinenoexcept

Checks if the arena is empty.

Returns
True if no allocations have been made since the last reset.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 171 of file arena_allocator.hpp.

171{ return offset_.load(std::memory_order_relaxed) == 0; }

◆ FreeSpace()

size_t helios::memory::ArenaAllocator::FreeSpace ( ) const
inlinenoexcept

Gets the amount of free space remaining.

Returns
Free space in bytes.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 346 of file arena_allocator.hpp.

346 {
347 const auto current = offset_.load(std::memory_order_relaxed);
348 return current < capacity_ ? capacity_ - current : 0;
349}

◆ Full()

bool helios::memory::ArenaAllocator::Full ( ) const
inlinenoexcept

Checks if the arena is full.

Returns
True if no more allocations can be made without reset.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 177 of file arena_allocator.hpp.

177{ return offset_.load(std::memory_order_relaxed) >= capacity_; }

◆ operator=() [1/2]

ArenaAllocator & helios::memory::ArenaAllocator::operator= ( ArenaAllocator &&  other)
inlinenoexcept

Definition at line 253 of file arena_allocator.hpp.

253 {
254 if (this == &other) [[unlikely]] {
255 return *this;
256 }
257
258 buffer_ = other.buffer_;
259 capacity_ = other.capacity_;
260 offset_.store(other.offset_.load(std::memory_order_acquire), std::memory_order_release);
261 peak_offset_.store(other.peak_offset_.load(std::memory_order_acquire), std::memory_order_release);
262 allocation_count_.store(other.allocation_count_.load(std::memory_order_acquire), std::memory_order_release);
263 alignment_waste_.store(other.alignment_waste_.load(std::memory_order_acquire), std::memory_order_release);
264
265 other.buffer_ = nullptr;
266 other.capacity_ = 0;
267 other.offset_.store(0, std::memory_order_release);
268 other.peak_offset_.store(0, std::memory_order_release);
269 other.allocation_count_.store(0, std::memory_order_release);
270 other.alignment_waste_.store(0, std::memory_order_release);
271
272 return *this;
273}

◆ operator=() [2/2]

◆ Reset()

void helios::memory::ArenaAllocator::Reset ( )
inlinenoexcept

Resets the arena, freeing all allocations.

Sets the internal offset to zero and clears accounting statistics. This does not modify the contents of the underlying buffer.

Warning
Must not be called concurrently with Allocate. The caller must ensure there are no ongoing or future allocations that expect previous pointers to remain valid.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 320 of file arena_allocator.hpp.

320 {
321 offset_.store(0, std::memory_order_release);
322 alignment_waste_.store(0, std::memory_order_release);
323 allocation_count_.store(0, std::memory_order_release);
324 // We intentionally keep peak_offset_ to track high-water mark over lifetime.
325}

◆ Stats()

AllocatorStats helios::memory::ArenaAllocator::Stats ( ) const
inlinenoexcept

Gets current allocator statistics.

Statistics are updated in a relaxed manner and are not guaranteed to be perfectly precise under heavy contention, but are sufficient for profiling and diagnostics.

Returns
AllocatorStats with current usage information.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/arena_allocator.hpp.

Definition at line 327 of file arena_allocator.hpp.

327 {
328 const auto current_offset = offset_.load(std::memory_order_relaxed);
329 const auto peak = peak_offset_.load(std::memory_order_relaxed);
330 const auto alloc_count = allocation_count_.load(std::memory_order_relaxed);
331 const auto waste = alignment_waste_.load(std::memory_order_relaxed);
332
333 // In an arena, we conceptually "free" everything on reset, but we do not track per-block frees.
334 // total_freed is modeled as 0; deallocations count remains 0 because `Deallocate` is a no-op.
335 return {
336 .total_allocated = current_offset,
337 .total_freed = 0,
338 .peak_usage = peak,
339 .allocation_count = alloc_count,
340 .total_allocations = alloc_count,
341 .total_deallocations = 0,
342 .alignment_waste = waste,
343 };
344}