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

Growable allocator adapter that automatically expands capacity. More...

#include <growable_allocator.hpp>

Public Member Functions

 GrowableAllocator (size_t initial_capacity, double growth_factor=kDefaultGrowthFactor, size_t max_allocators=0)
 Constructs a growable allocator with initial capacity.
 
 GrowableAllocator (const GrowableAllocator &other)
 Copy constructor.
 
 GrowableAllocator (GrowableAllocator &&other) noexcept
 Move constructor.
 
 ~GrowableAllocator () noexcept=default
 
GrowableAllocatoroperator= (const GrowableAllocator &other)
 Copy assignment operator.
 
GrowableAllocatoroperator= (GrowableAllocator &&other) noexcept
 Move assignment operator.
 
AllocationResult Allocate (size_t size, size_t alignment=kDefaultAlignment) noexcept
 Allocates memory with specified size and alignment.
 
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 (void *ptr, size_t size=0) noexcept
 Deallocates memory.
 
void Reset () noexcept
 Resets all allocator instances.
 
bool CanGrow () const noexcept
 Checks if the allocator can grow.
 
size_t AllocatorCount () const noexcept
 Gets the number of allocator instances.
 
AllocatorStats Stats () const noexcept
 Gets combined statistics for all allocator instances.
 
size_t TotalCapacity () const noexcept
 Gets the total capacity across all allocator instances.
 
size_t InitialCapacity () const noexcept
 Gets the initial capacity.
 
double GrowthFactor () const noexcept
 Gets the growth factor.
 
size_t MaxAllocators () const noexcept
 Gets the maximum number of allocators.
 

Static Public Attributes

static constexpr double kDefaultGrowthFactor = 2.0
 

Detailed Description

template<typename Allocator>
class helios::memory::GrowableAllocator< Allocator >

Growable allocator adapter that automatically expands capacity.

Wraps another allocator and automatically creates additional allocator instances when capacity is exceeded. Manages multiple allocator instances and delegates allocations to them.

When an allocation fails due to insufficient capacity, a new allocator instance is created with a configurable growth factor applied to the initial capacity.

Supports deallocation by tracking which allocator owns each pointer.

The GrowableAllocator itself is thread-safe, using shared_mutex for optimal concurrent access. Underlying allocators are already thread-safe.

Note
Thread-safe with optimized locking. Growth occurs only when an allocation fails due to capacity constraints. Each growth creates a new allocator instance with expanded capacity. Read operations (Stats, queries) use shared locks for concurrent access. Write operations (Allocate with growth, Deallocate, Reset) use exclusive locks.

The wrapped allocator must be constructible with a single capacity argument. Compatible allocators: FrameAllocator, StackAllocator, FreeListAllocator. Not compatible with: PoolAllocator (requires additional construction parameters).

Copy operations are conditionally available based on the underlying allocator. If a custom copyable allocator is provided, GrowableAllocator will automatically support copy construction and copy assignment.

Template Parameters
AllocatorThe underlying allocator type to wrap

Definition at line 50 of file growable_allocator.hpp.

Constructor & Destructor Documentation

◆ GrowableAllocator() [1/3]

template<typename Allocator >
helios::memory::GrowableAllocator< Allocator >::GrowableAllocator ( size_t  initial_capacity,
double  growth_factor = kDefaultGrowthFactor,
size_t  max_allocators = 0 
)
inlineexplicit

Constructs a growable allocator with initial capacity.

Warning
Triggers assertion in next cases:
  • Initial capacity is 0.
  • Growth factor is less than or equal to 1.0.
Parameters
initial_capacityInitial capacity for the first allocator instance
growth_factorFactor to multiply capacity by when growing (default: kDefaultGrowthFactor)
max_allocatorsMaximum number of allocator instances to create (0 = unlimited)
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 264 of file growable_allocator.hpp.

266 : initial_capacity_(initial_capacity),
267 growth_factor_(growth_factor),
268 max_allocators_(max_allocators),
269 next_capacity_(initial_capacity) {
270 HELIOS_ASSERT(initial_capacity > 0,
271 "Failed to construct GrowableAllocator: initial_capacity must be greater than 0!");
272 HELIOS_ASSERT(growth_factor > 1.0,
273 "Failed to construct GrowableAllocator: growth_factor must be greater than 1.0, got '{}'!",
274 growth_factor);
275
276 // Create the first allocator
277 allocators_.reserve(max_allocators > 0 ? max_allocators : 4);
278 CreateAllocator(initial_capacity_);
279}
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140

◆ GrowableAllocator() [2/3]

template<typename Allocator >
requires std::is_copy_constructible_v<Allocator>
helios::memory::GrowableAllocator< Allocator >::GrowableAllocator ( const GrowableAllocator< Allocator > &  other)
inline

Copy constructor.

Note
Only available if Allocator is copy constructible. All built-in Helios allocators are non-copyable, so this will not be available for standard use cases. Provided for custom copyable allocator types.

Definition at line 282 of file growable_allocator.hpp.

284 : initial_capacity_(other.initial_capacity_),
285 growth_factor_(other.growth_factor_),
286 max_allocators_(other.max_allocators_),
287 next_capacity_(other.next_capacity_) {
288 const std::shared_lock lock(other.mutex_);
289 allocators_ = other.allocators_;
290}

◆ GrowableAllocator() [3/3]

template<typename Allocator >
requires std::is_move_constructible_v<Allocator>
helios::memory::GrowableAllocator< Allocator >::GrowableAllocator ( GrowableAllocator< Allocator > &&  other)
inlinenoexcept

Move constructor.

Note
Only available if Allocator is move constructible.

Definition at line 293 of file growable_allocator.hpp.

295 : allocators_(std::move(other.allocators_)),
296 initial_capacity_(other.initial_capacity_),
297 growth_factor_(other.growth_factor_),
298 max_allocators_(other.max_allocators_),
299 next_capacity_(other.next_capacity_) {
300 other.initial_capacity_ = 0;
301 other.growth_factor_ = kDefaultGrowthFactor;
302 other.max_allocators_ = 0;
303 other.next_capacity_ = 0;
304}
static constexpr double kDefaultGrowthFactor

◆ ~GrowableAllocator()

Member Function Documentation

◆ Allocate() [1/3]

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

Definition at line 532 of file growable_allocator.hpp.

532 {
533 constexpr size_t size = sizeof(T);
534 constexpr size_t alignment = std::max(alignof(T), kMinAlignment);
535 auto result = Allocate(size, alignment);
536 return static_cast<T*>(result.ptr);
537}
constexpr size_t kMinAlignment
Minimum alignment for any allocation.

◆ Allocate() [2/3]

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

Definition at line 541 of file growable_allocator.hpp.

541 {
542 if (count == 0) [[unlikely]] {
543 return nullptr;
544 }
545 constexpr size_t alignment = std::max(alignof(T), kMinAlignment);
546 const size_t size = sizeof(T) * count;
547 auto result = Allocate(size, alignment);
548 return static_cast<T*>(result.ptr);
549}

◆ Allocate() [3/3]

template<typename Allocator >
AllocationResult helios::memory::GrowableAllocator< Allocator >::Allocate ( size_t  size,
size_t  alignment = kDefaultAlignment 
)
inlinenoexcept

Allocates memory with specified size and alignment.

Attempts allocation from existing allocators. If all fail, creates a new allocator instance with expanded capacity.

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 actual allocated size, or {nullptr, 0} on failure

Definition at line 352 of file growable_allocator.hpp.

352 {
353 HELIOS_ASSERT(IsPowerOfTwo(alignment), "Failed to allocate memory: alignment must be power of 2, got '{}'!",
354 alignment);
355 HELIOS_ASSERT(alignment >= kMinAlignment, "Failed to allocate memory: alignment must be at least '{}', got '{}'!",
356 kMinAlignment, alignment);
357
358 if (size == 0) [[unlikely]] {
359 return {.ptr = nullptr, .allocated_size = 0};
360 }
361
362 // First, try to allocate from existing allocators with shared lock
363 {
364 const std::shared_lock lock(mutex_);
365
366 for (auto& allocator : allocators_) {
367 AllocationResult result{};
368 if constexpr (requires { allocator.Allocate(size, alignment); }) {
369 result = allocator.Allocate(size, alignment);
370 } else if constexpr (requires { allocator.Allocate(size); }) {
371 result = allocator.Allocate(size);
372 }
373 if (result.ptr != nullptr) {
374 return result;
375 }
376 }
377 }
378
379 // All existing allocators are full, need exclusive lock for growth
380 const std::scoped_lock lock(mutex_);
381
382 // Try again with exclusive lock (another thread might have grown while we waited)
383 for (auto& allocator : allocators_) {
384 AllocationResult result{};
385 if constexpr (requires { allocator.Allocate(size, alignment); }) {
386 result = allocator.Allocate(size, alignment);
387 } else if constexpr (requires { allocator.Allocate(size); }) {
388 result = allocator.Allocate(size);
389 }
390 if (result.ptr != nullptr) {
391 return result;
392 }
393 }
394
395 // Still need to grow, check if we can
396 if (max_allocators_ > 0 && allocators_.size() >= max_allocators_) {
397 return {.ptr = nullptr, .allocated_size = 0};
398 }
399
400 // Calculate next capacity
401 auto new_capacity = static_cast<size_t>(static_cast<double>(next_capacity_) * growth_factor_);
402
403 // Ensure new capacity is at least large enough for the requested allocation
404 if (new_capacity < size) {
405 new_capacity = size + (size / 2); // Add 50% extra space
406 }
407
408 // Create new allocator
409 CreateAllocator(new_capacity);
410 next_capacity_ = new_capacity;
411
412 // Try to allocate from the new allocator
413 auto& new_allocator = allocators_.back();
414 if constexpr (requires { new_allocator.Allocate(size, alignment); }) {
415 return new_allocator.Allocate(size, alignment);
416 } else if constexpr (requires { new_allocator.Allocate(size); }) {
417 return new_allocator.Allocate(size);
418 }
419 return {.ptr = nullptr, .allocated_size = 0};
420}
constexpr bool IsPowerOfTwo(size_t size) noexcept
Helper function to check if a size is a power of 2.

◆ AllocateAndConstruct()

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

Definition at line 554 of file growable_allocator.hpp.

555 {
556 T* ptr = Allocate<T>();
557 if (ptr != nullptr) [[likely]] {
558 std::construct_at(ptr, std::forward<Args>(args)...);
559 }
560 return ptr;
561}

◆ AllocateAndConstructArray()

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

Definition at line 566 of file growable_allocator.hpp.

567 {
568 T* ptr = Allocate<T>(count);
569 if (ptr != nullptr) [[likely]] {
570 for (size_t i = 0; i < count; ++i) {
571 std::construct_at(ptr + i);
572 }
573 }
574 return ptr;
575}

◆ AllocatorCount()

template<typename Allocator >
size_t helios::memory::GrowableAllocator< Allocator >::AllocatorCount ( ) const
inlinenoexcept

Gets the number of allocator instances.

Returns
Number of allocator instances
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 473 of file growable_allocator.hpp.

473 {
474 const std::shared_lock lock(mutex_);
475 return allocators_.size();
476}

◆ CanGrow()

template<typename Allocator >
bool helios::memory::GrowableAllocator< Allocator >::CanGrow ( ) const
inlinenoexcept

Checks if the allocator can grow.

Returns
True if more allocator instances can be created
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 467 of file growable_allocator.hpp.

467 {
468 const std::shared_lock lock(mutex_);
469 return max_allocators_ == 0 || allocators_.size() < max_allocators_;
470}

◆ Deallocate()

template<typename Allocator >
void helios::memory::GrowableAllocator< Allocator >::Deallocate ( void *  ptr,
size_t  size = 0 
)
inlinenoexcept

Deallocates memory.

Finds the allocator that owns the pointer and delegates deallocation.

Warning
Triggers assertion if pointer does not belong to any allocator instance.
Parameters
ptrPointer to deallocate
sizeSize of allocation (for allocators that require it)
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 423 of file growable_allocator.hpp.

423 {
424 if (ptr == nullptr) [[unlikely]] {
425 return;
426 }
427
428 // Check if allocator supports deallocation at compile time
429 if constexpr (
430 requires { std::declval<Allocator>().Deallocate(ptr, size); } ||
431 requires { std::declval<Allocator>().Deallocate(ptr); }) {
432 const std::shared_lock lock(mutex_);
433
434 auto* allocator = FindOwningAllocator(ptr);
435 HELIOS_ASSERT(allocator != nullptr, "Failed to deallocate memory: pointer does not belong to any allocator!");
436
437 // Delegate deallocation to the owning allocator
438 if constexpr (requires { allocator->Deallocate(ptr, size); }) {
439 allocator->Deallocate(ptr, size);
440 } else if constexpr (requires { allocator->Deallocate(ptr); }) {
441 allocator->Deallocate(ptr);
442 }
443 }
444 // else: No-op for allocators that don't support individual deallocation (e.g., FrameAllocator)
445}

◆ GrowthFactor()

template<typename Allocator >
double helios::memory::GrowableAllocator< Allocator >::GrowthFactor ( ) const
inlinenoexcept

Gets the growth factor.

Returns
Growth factor
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 233 of file growable_allocator.hpp.

233{ return growth_factor_; }

◆ InitialCapacity()

template<typename Allocator >
size_t helios::memory::GrowableAllocator< Allocator >::InitialCapacity ( ) const
inlinenoexcept

Gets the initial capacity.

Returns
Initial capacity in bytes
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 227 of file growable_allocator.hpp.

227{ return initial_capacity_; }

◆ MaxAllocators()

template<typename Allocator >
size_t helios::memory::GrowableAllocator< Allocator >::MaxAllocators ( ) const
inlinenoexcept

Gets the maximum number of allocators.

Returns
Maximum allocator count (0 = unlimited)
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 239 of file growable_allocator.hpp.

239{ return max_allocators_; }

◆ operator=() [1/2]

template<typename Allocator >
requires std::is_copy_assignable_v<Allocator>
auto helios::memory::GrowableAllocator< Allocator >::operator= ( const GrowableAllocator< Allocator > &  other)
inline

Copy assignment operator.

Note
Only available if Allocator is copy assignable. All built-in Helios allocators are non-copyable, so this will not be available for standard use cases. Provided for custom copyable allocator types.
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 307 of file growable_allocator.hpp.

309{
310 if (this == &other) [[unlikely]] {
311 return *this;
312 }
313
314 const std::scoped_lock lock(mutex_);
315 const std::shared_lock other_lock(other.mutex_);
316
317 allocators_ = other.allocators_;
318 initial_capacity_ = other.initial_capacity_;
319 growth_factor_ = other.growth_factor_;
320 max_allocators_ = other.max_allocators_;
321 next_capacity_ = other.next_capacity_;
322
323 return *this;
324}

◆ operator=() [2/2]

template<typename Allocator >
requires std::is_move_assignable_v<Allocator>
auto helios::memory::GrowableAllocator< Allocator >::operator= ( GrowableAllocator< Allocator > &&  other)
inlinenoexcept

Move assignment operator.

Note
Only available if Allocator is move assignable.

Definition at line 327 of file growable_allocator.hpp.

329{
330 if (this == &other) [[unlikely]] {
331 return *this;
332 }
333
334 const std::scoped_lock lock(mutex_);
335 const std::scoped_lock other_lock(other.mutex_);
336
337 allocators_ = std::move(other.allocators_);
338 initial_capacity_ = other.initial_capacity_;
339 growth_factor_ = other.growth_factor_;
340 max_allocators_ = other.max_allocators_;
341 next_capacity_ = other.next_capacity_;
342
343 other.initial_capacity_ = 0;
344 other.growth_factor_ = kDefaultGrowthFactor;
345 other.max_allocators_ = 0;
346 other.next_capacity_ = 0;
347
348 return *this;
349}

◆ Reset()

template<typename Allocator >
void helios::memory::GrowableAllocator< Allocator >::Reset ( )
inlinenoexcept

Resets all allocator instances.

Resets all allocators and removes all but the first one.

Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 448 of file growable_allocator.hpp.

448 {
449 const std::scoped_lock lock(mutex_);
450
451 // Reset all allocators
452 for (auto& allocator : allocators_) {
453 allocator.Reset();
454 }
455
456 // Keep only the first allocator
457 if (!allocators_.empty()) {
458 auto first = std::move(allocators_.front());
459 allocators_.clear();
460 allocators_.push_back(std::move(first));
461 }
462
463 next_capacity_ = initial_capacity_;
464}

◆ Stats()

template<typename Allocator >
AllocatorStats helios::memory::GrowableAllocator< Allocator >::Stats ( ) const
inlinenoexcept

Gets combined statistics for all allocator instances.

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

Definition at line 479 of file growable_allocator.hpp.

479 {
480 const std::shared_lock lock(mutex_);
481
482 AllocatorStats combined{};
483
484 for (const auto& allocator : allocators_) {
485 const auto stats = allocator.Stats();
486 combined.total_allocated += stats.total_allocated;
487 combined.total_freed += stats.total_freed;
488 combined.peak_usage = std::max(combined.peak_usage, stats.peak_usage);
489 combined.allocation_count += stats.allocation_count;
490 combined.total_allocations += stats.total_allocations;
491 combined.total_deallocations += stats.total_deallocations;
492 combined.alignment_waste += stats.alignment_waste;
493 }
494
495 return combined;
496}

◆ TotalCapacity()

template<typename Allocator >
size_t helios::memory::GrowableAllocator< Allocator >::TotalCapacity ( ) const
inlinenoexcept

Gets the total capacity across all allocator instances.

Returns
Total capacity in bytes
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/growable_allocator.hpp.

Definition at line 499 of file growable_allocator.hpp.

499 {
500 const std::shared_lock lock(mutex_);
501
502 size_t total = 0;
503 for (const auto& allocator : allocators_) {
504 total += allocator.Capacity();
505 }
506 return total;
507}

Member Data Documentation

◆ kDefaultGrowthFactor

template<typename Allocator >
constexpr double helios::memory::GrowableAllocator< Allocator >::kDefaultGrowthFactor = 2.0
staticconstexpr