A streamed world loads and lets go of a chunk's entities together, so a chunk is a first-class group: chunk_of(x, z, size) packs a chunk's cell into an int, tb_remove_all(tb, ix, v) removes every row an index files under one value (the observers told of each). IntIndex kept one list per value up to the largest, which a packed chunk number (hundreds of millions) turned into hundreds of millions of empty lists and a 12 GB test run; a value past 1024 now gets its slot through an IntMap, so a sparse value costs one list. ludic.things files every Thing under its 256 m chunk (TH_CHUNK), kept by every move; things_chunk_count and things_drop_chunk (the port and the facts told of each removal, as thing_remove tells them) are what the memory budget's streamed chunks ask. The moves are their own file (moves.ludic). Tests: ludic.base 35, ludic.things 8, all under a 2 GB cap (24 MB peak). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
20 lines
812 B
Text
20 lines
812 B
Text
# ludic.base/ecs_group.ludic - a group is every row an index files under one value: a kind, or a
|
|
# chunk of the world, loaded and let go together. A chunk's number packs its two cell coordinates.
|
|
export function chunk_of(x: float, z: float, size: float) -> int {
|
|
let cx = int(Math.floor(x / size))
|
|
let cz = int(Math.floor(z / size))
|
|
return ((cx & 32767) << 15) | (cz & 32767)
|
|
}
|
|
|
|
# every row filed under v in ix removed (the observers told of each); how many went. The index's
|
|
# own list shrinks as each goes, so it is read from its end until it is empty.
|
|
export function tb_remove_all<T>(tb: Table<T>, ix: IntIndex, v: int) -> int {
|
|
var n = 0
|
|
let rows = ix_rows(ix, v)
|
|
while len(rows) > 0 {
|
|
let h = tb.ent[rows[len(rows) - 1]]
|
|
if not tb_remove(tb, h) { return n }
|
|
n += 1
|
|
}
|
|
return n
|
|
}
|