code wiki / _hdl_build / nx_entity_store.nx
nx_entity_store.nx
buildroot/runtime/_hdl_build/nx_entity_store.nx
about
nx_entity_store.nx -- the SOVEREIGN ENTITY STORE. nx_gamebench gap-queue rank 1 after save/load and rpg-stats
landed: entity-component-sim was PARTIAL and blocks 7 of 12 benchmarked titles (Doom actors, OpenXcom
units, Diablo monsters, Cataclysm, Veloren, Freeciv, NetHack).
THE HAZARD THIS PART EXISTS TO KILL -- the stale handle. Every naive entity store hands out a raw slot
index. An entity dies, its slot is recycled for a new entity, and any handle still held by old code now
silently addresses a DIFFERENT entity: a homing missile retargets onto a random unit, a dead unit's buff
lands on its replacement. It is one of the nastiest bug classes in game code because it is invisible
until it is a bug report. Here every handle carries a GENERATION stamped into it, bumped on destroy, so a
stale handle is DETECTABLY invalid -- rejection is by construction, not by discipline.
Other certified properties (each a gate tooth):
- O(1) spawn and destroy (free-list + swap-remove), no scanning
- DENSE iteration: visiting the alive set never walks holes
- REPLAY-DETERMINISTIC: the same operation sequence yields the same handles and the same iteration
order, so recorded input replays identically (the determinism exceed carried into simulation)
- CAPACITY-BOUNDED: spawning past capacity returns the null handle, never corrupts the arena
- Component values are plain i64 so entity state drops straight into gs_save/gs_load (nx_gamesave).
Arena layout follows the nx_swgpu base+offset idiom: one mmap block, all offsets derived from cap/ncomp.
LIB ONLY -- no main() by ecosystem convention.
license_tier: ORIGINAL expect_exit: 0
dependencies 1 imports · 7 importers
imports: nx_syscalls.nx
imported by: nx_entity_store_gate.nxnx_game_actor.nxnx_game_companion.nxnx_wasm_craft.nxnx_wire_diablo2_gate.nxnx_wire_nethack_gate.nxnx_wire_probe.nx
structs
| none |
consts
| 25 | const EN_GENMOD: i64 = 1048576 // 2^20 generations per slot before wrap |
| 26 | const EN_NULL: i64 = 0 // the null handle; a real handle is always > 0 |
| 27 | const EN_HDR: i64 = 4 // cap, ncomp, alive_count, fresh_next |
functions
| 30 | func en_cap(a: *i64) -> i64 { return a[0] } |
| 31 | func en_ncomp(a: *i64) -> i64 { return a[1] } |
| 32 | func en_count(a: *i64) -> i64 { return a[2] } |
| 35 | func en_o_gen(a: *i64) -> i64 { return EN_HDR } |
| 36 | func en_o_alive(a: *i64) -> i64 { return EN_HDR + en_cap(a) } |
| 37 | func en_o_free(a: *i64) -> i64 { return EN_HDR + 2*en_cap(a) } |
| 38 | func en_o_dense(a: *i64) -> i64 { return EN_HDR + 3*en_cap(a) } |
| 39 | func en_o_pos(a: *i64) -> i64 { return EN_HDR + 4*en_cap(a) } |
| 40 | func en_o_comp(a: *i64) -> i64 { return EN_HDR + 5*en_cap(a) } |
| 43 | func en_words(cap: i64, ncomp: i64) -> i64 { return EN_HDR + 5*cap + ncomp*cap + 8 } |
| 44 | func en_bytes(cap: i64, ncomp: i64) -> i64 { return en_words(cap, ncomp) * 8 } |
| 48 | func en_init(a: *i64, cap: i64, ncomp: i64) -> i64 |
| 65 | func en_freelen(a: *i64) -> i64 { return a[en_o_free(a)] } |
| 67 | func en_make_handle(idx: i64, gen: i64) -> i64 { return (idx + 1) * EN_GENMOD + gen } |
| 68 | func en_handle_idx(h: i64) -> i64 { return (h / EN_GENMOD) - 1 } |
| 69 | func en_handle_gen(h: i64) -> i64 { return h % EN_GENMOD } called by 1: en_valid |
| 72 | func en_valid(a: *i64, h: i64) -> i64 called by 23: ba_nearest_wildba_ticken_destroyen_seten_getmain+17 calls 5: en_handle_idxen_capen_o_aliveen_o_genen_handle_gen |
| 83 | func en_spawn(a: *i64) -> i64 called by 21: ba_seed_wildsba_runmainmainreplaymain+15 calls 10: en_freelenen_o_freeen_capen_o_aliveen_ncompen_o_comp+4 |
| 108 | func en_destroy(a: *i64, h: i64) -> i64 called by 9: mainreplaymainac_removebrd_capturetm_release+3 calls 8: en_validen_handle_idxen_o_aliveen_o_genen_o_posen_o_dense+2 |
| 131 | func en_set(a: *i64, h: i64, comp: i64, val: i64) -> i64 called by 40: ba_seed_wildsba_duel_wildba_runmainmainreplay+34 calls 5: en_validen_ncompen_o_compen_capen_handle_idx |
| 138 | func en_get(a: *i64, h: i64, comp: i64) -> i64 |
| 146 | func en_nth(a: *i64, k: i64) -> i64 |