feat(ecs): #80 entity-pool stats (Pool.live/free/reserved/capacity)
Ludic's ECS is already pool-based — the allocator recycles freed entity slots through a freelist (L_alloc pops @L_freen before growing @L_entc), and component storage is fixed per-entity arrays, so spawn/despawn churn (bullet-hell/horde) does no per-spawn heap allocation and cannot fragment. Expose that with a Pool.* namespace so a game can watch reuse: Pool.live (alive now), Pool.free (recycled slots waiting), Pool.reserved (high-water — stays flat across a steady spawn/despawn loop, proving reuse not reallocation), Pool.capacity (the fixed cap). Zero-cost inline reads of the existing counters. Example pool.ludic proves the key property: after despawn+respawn, Pool.reserved() stays 3 (freed slot reused) — prints 0 0 3 3 0 2 1 3 0 3 1. 4 docs pages. Full suite 118/0, goldens byte-identical, fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f2cb3cd7e8
commit
347352cc4c
10 changed files with 25580 additions and 25382 deletions
39
examples/library/pool.ludic
Normal file
39
examples/library/pool.ludic
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# pool.ludic — entity pooling stats (#80). Ludic's ECS already recycles freed
|
||||
# entity slots through a freelist (a despawned slot is reused by the next spawn
|
||||
# before any new slot is taken), so bullet-hell / horde spawn+despawn does no
|
||||
# per-spawn allocation and cannot fragment. Pool.* exposes the counters so a game
|
||||
# can watch the reuse. The key proof: after despawn + respawn, Pool.reserved()
|
||||
# does NOT grow — the freed slot was reused, not reallocated.
|
||||
#
|
||||
# Deterministic; a full run prints: 0 0 3 3 0 2 1 3 0 3 1
|
||||
program PoolDemo {
|
||||
property Mob { hp: int = 0 }
|
||||
model M { Mob }
|
||||
|
||||
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
||||
|
||||
entry {
|
||||
print(Pool.reserved()) # 0 — nothing allocated yet
|
||||
print(Pool.live()) # 0
|
||||
|
||||
spawn M { Mob { hp: 1 } }
|
||||
spawn M { Mob { hp: 2 } }
|
||||
spawn M { Mob { hp: 3 } }
|
||||
print(Pool.reserved()) # 3
|
||||
print(Pool.live()) # 3
|
||||
print(Pool.free()) # 0 — no freed slots yet
|
||||
|
||||
let pm = World.prop_id("Mob")
|
||||
let e = World.query_next(pm, 0)
|
||||
despawn e
|
||||
print(Pool.live()) # 2
|
||||
print(Pool.free()) # 1 — one slot recycled onto the freelist
|
||||
|
||||
spawn M { Mob { hp: 4 } } # reuses the freed slot
|
||||
print(Pool.reserved()) # 3 — NO new slot allocated (pooled, not reallocated)
|
||||
print(Pool.free()) # 0 — the freelist slot was drawn back down
|
||||
print(Pool.live()) # 3
|
||||
|
||||
print(bi(Pool.capacity() >= 3)) # 1 — there is a fixed entity cap to budget against
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue