first commit
This commit is contained in:
71
docs/gaming/games/ecs/src/entity-pool/entity-pool.md
Normal file
71
docs/gaming/games/ecs/src/entity-pool/entity-pool.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# EntityPool
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`EntityPool` is the entity-ID allocator. It hands out positive integer IDs on `allocate()`, recycles freed IDs on `free()`, and tracks a per-ID generation counter so callers can detect stale references to a recycled slot. The pool keeps the live-ID range dense, which is important because every alive entity occupies a bitset slot and dense IDs limit the working-set footprint.
|
||||
|
||||
The pool is owned by `World`; the class is exported only so debug tooling can read its state.
|
||||
|
||||
## Features
|
||||
|
||||
- Recycle-first allocation: freed IDs are returned ahead of fresh ones.
|
||||
- Per-ID generation counter — increments on every allocation, including reuse.
|
||||
- O(1) `allocate`, `free`, `isAlive`, and `generation`.
|
||||
- `peekNextId` / `setNextId` enable snapshot/restore of the underlying counter.
|
||||
- `clear()` resets every counter and free-list back to the initial state.
|
||||
- Defensive `setNextId` — rejects non-positive integers.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { EntityPool } from "@suaveplan/ecs";
|
||||
|
||||
const pool = new EntityPool();
|
||||
const a = pool.allocate(); // 1
|
||||
const b = pool.allocate(); // 2
|
||||
pool.free(a);
|
||||
const recycled = pool.allocate(); // 1 — freed id comes back first
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { EntityPool } from "@suaveplan/ecs";
|
||||
|
||||
const pool = new EntityPool();
|
||||
const a = pool.allocate();
|
||||
const generationAtSpawn = pool.generation(a); // 1
|
||||
|
||||
pool.free(a);
|
||||
pool.allocate(); // recycles `a`
|
||||
const generationNow = pool.generation(a); // 2
|
||||
|
||||
// External handle compares its captured generation against the current one
|
||||
// to detect stale references to a slot that has since been recycled.
|
||||
const handleIsStale = generationAtSpawn !== generationNow;
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
| Member | Description |
|
||||
|---|---|
|
||||
| `allocate()` | Return a fresh or recycled positive integer. Increments the entity's generation. |
|
||||
| `free(entity)` | Move `entity` to the recycled list. Returns `false` for non-alive IDs. |
|
||||
| `isAlive(entity)` | True when `entity` is currently allocated. |
|
||||
| `generation(entity)` | Generation counter. Zero for never-allocated IDs. |
|
||||
| `aliveCount` / `recycledCount` | Inspectable counts. |
|
||||
| `peekNextId` | Next fresh ID that would be assigned. |
|
||||
| `setNextId(id)` | Restore the counter from a snapshot. Rejects non-positive integers. |
|
||||
| `getAlive()` | Read-only snapshot of the live set. |
|
||||
| `clear()` | Reset every counter. |
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- The free-list is a plain `Entity[]` used as a LIFO stack. LIFO maximises cache reuse: the most recently freed slot is the first one re-handed, so it is still likely to be hot.
|
||||
- The generation map is keyed by entity ID, not slot index, because IDs are themselves dense — using the ID as the key keeps the data structure simple.
|
||||
- `peekNextId` returns the counter even after `clear()` (which resets it to `1`). The restore path uses this to rebuild the world's counter before re-spawning entities.
|
||||
- The pool never reuses an ID that was returned by a fresh allocation but never freed — so an `aliveCount` of zero plus `recycledCount` of zero is enough to prove no stale handles exist.
|
||||
Reference in New Issue
Block a user