Memory & threading
Games allocate and free a lot of small things every frame; predictable allocation and safe parallel work keep the frame time steady.
The concept
General-purpose new/delete can be slow and fragment memory. Game engines replace it, where it
matters, with allocators that match an allocation pattern: bump pointers for per-frame scratch data,
pools for many same-sized objects, stacks for strictly nested lifetimes. For CPU parallelism, a thread pool
reuses a fixed set of worker threads instead of spawning one per task.
Why general allocation hurts
A general-purpose allocator has to handle any size and any lifetime, so it searches for free blocks, takes locks in multithreaded programs and leaves gaps behind as blocks of different sizes come and go (fragmentation). Its cost is also unpredictable, which shows up as an occasional slow frame. Knowing the allocation pattern lets you replace all that with a few instructions.
Matching pattern to allocator
A linear (arena) allocator just bumps a pointer and frees everything at once: ideal for scratch data that dies at the end of a frame. A pool keeps same-sized slots on a free list: ideal for many identical objects that come and go in any order. A stack allows frees only in reverse order, matching strictly nested lifetimes. The restriction is the point; each gives up some flexibility in exchange for speed and predictability.
Threads in a game
Creating a thread costs far more than calling a function, so spawning one per small task loses the benefit. A thread pool starts its workers once and feeds them jobs from a queue. The hard part of parallel code is not starting work but sharing data safely: jobs should either own their data or touch disjoint parts of it, since locks reintroduce the stalls parallelism was meant to remove.
How ASGE does it
Allocators (asge::mem)
| Type | Pattern | Notes |
|---|---|---|
LinearAllocator | Arena / bump pointer | O(1) alloc over a buffer you provide; Reset() frees everything, GetMarker() + Rewind() roll back; no individual frees. |
PoolAllocator<T, N> | Fixed-size slots | O(1) alloc and free from a free-list; returns an owning pointer whose deleter gives the slot back. Created through Create() as a shared_ptr. |
StackAllocator<N> | LIFO | Frees must be in reverse order (asserted in debug); GetMarker() / FreeToMarker() unwind and run destructors. |
FreeList<N> | Index allocator | Hands out indices in [0, N) with double-free detection. The ECS uses it to issue entity indices. |
Thread pool (asge::concurrent)
ThreadPool owns a fixed set of Worker threads and a shared lock-free queue. Submit()
wraps a callable and its arguments in a job and returns a std::future<T>, or nothing for a callable that
returns void. The pool is neither copyable nor movable because workers hold references to its queue.
asge::concurrent::ThreadPool pool; // defaults to the hardware concurrency
auto result = pool.Submit( []( int n ) { return n * 2; }, 21 ); // std::future<int>
int value = result.get(); // 42
pool.Submit( [] { /* void work: no future is returned */ } );
See also
Source: Core/Memory/, Core/Concurrent/ThreadPool.hpp.
ECS shows the free-list in use.