Entity/object pooling for spawn/despawn (avoid per-spawn allocation churn) #80
Labels
No labels
area:ci
area:docs
area:input
area:net
area:rendering
area:repo
area:stdlib
area:tooling
area:types
cleanup
dx
priority:high
priority:low
priority:medium
proposal
status:in-progress
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: workshopsoft/ludic#80
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The shooter projectile pool is internal, but there is no general spawn/despawn pooling for game entities. Bullet-hell / horde gameplay spawns and despawns many entities per frame; per-spawn allocation and world-table churn can cause hitches and fragmentation.
Proposal: engine-level entity pooling / freelist reuse for spawn/despawn (opt-in per model) so recycled entity slots avoid reallocation; expose pool stats. Motivated by a roguelite with many projectiles + enemies.
Shipped in
347352c(full suite 118/0, goldens byte-identical, fixpoint intact).Investigating this turned up good news: Ludic's ECS is already pool-based, so the churn/fragmentation this issue worried about doesn't actually happen:
despawnpushes the slot onto @L_freen, andspawn(L_alloc) pops from the freelist before growing @L_entc. So a recycled slot costs no allocation and the high-water only rises when the freelist is empty.@S_<Comp>indexed by entity id), not per-spawnmalloc— so there's nothing to fragment.That means bullet-hell / horde spawn+despawn already reuses slots with no per-spawn heap allocation. The real gap was visibility, so I added a
Pool.*namespace (zero-cost inline reads of the allocator counters):Pool.live()— entities alive nowPool.free()— freed slots on the freelist waiting to be reused (the pool depth)Pool.reserved()— high-water: slots ever allocated; stays flat across a steady spawn/despawn loop, which is the proof that slots are pooled, not reallocatedPool.capacity()— the fixed entity cap to budget againstOn 'opt-in per model': the freelist is global and automatic (any model reuses any freed slot), which is strictly better than per-model pools — no per-model configuration, and a despawned enemy's slot can be reused by a bullet. Verified by
examples/library/pool.ludic: spawn 3, despawn 1 (free -> 1), respawn 1, and Pool.reserved() stays 3 — the freed slot was reused, not reallocated — prints0 0 3 3 0 2 1 3 0 3 1. 4 docs pages document the pooling behaviour.