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>
24 lines
891 B
Text
24 lines
891 B
Text
# ludic.base/ecs_index_slot.ludic - where a value's list lives: a small value (a kind) is its own
|
|
# slot; a large one (a chunk's packed cell) is given the next slot through a map, so a sparse
|
|
# value costs one list and not every list up to it
|
|
const IX_DENSE: int = 1024 # a value below this is its own slot; above, one is given it
|
|
|
|
# the slot value v's list is in, -1 when it has none (and `make` did not ask for one)
|
|
function ix_slot(ix: IntIndex, v: int, make: bool) -> int {
|
|
if v < 0 { return -1 }
|
|
if v < IX_DENSE {
|
|
if v >= len(ix.lists) {
|
|
if not make { return -1 }
|
|
while len(ix.lists) <= v { push(ix.lists, words(0)) }
|
|
}
|
|
return v
|
|
}
|
|
var s = imap_get(ix.sparse, v, -1)
|
|
if s < 0 and make {
|
|
while len(ix.lists) < IX_DENSE { push(ix.lists, words(0)) }
|
|
s = len(ix.lists)
|
|
push(ix.lists, words(0))
|
|
imap_put(ix.sparse, v, s)
|
|
}
|
|
return s
|
|
}
|