feat(ecs): chunks as a grouping - IntIndex takes sparse values; ludic.things' chunk column and things_drop_chunk
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>
This commit is contained in:
parent
7834b26ef8
commit
f3fec88888
11 changed files with 186 additions and 65 deletions
|
|
@ -5,10 +5,10 @@ export property IntIndex {
|
|||
gate: int = -1 # an int column: a row is filed only while it is non-zero (-1: always)
|
||||
lists: []words = null # value -> its rows
|
||||
pos: words = null # row -> where it stands in its value's list, -1 not filed
|
||||
val: words = null # row -> the value it is filed under
|
||||
val: words = null # row -> the slot of the value it is filed under
|
||||
empty: words = null # what a value with no rows answers
|
||||
sparse: IntMap = null # a value past IX_DENSE (a chunk's packed cell) to its list's slot
|
||||
}
|
||||
|
||||
export function tb_index<T>(tb: Table<T>, col: int, gate: int) -> IntIndex {
|
||||
let ix = new IntIndex
|
||||
ix.col = col
|
||||
|
|
@ -17,6 +17,7 @@ export function tb_index<T>(tb: Table<T>, col: int, gate: int) -> IntIndex {
|
|||
ix.pos = words(0)
|
||||
ix.val = words(0)
|
||||
ix.empty = words(0)
|
||||
ix.sparse = imap_new()
|
||||
for r in 0 .. tb.n { ix_grow(ix) }
|
||||
push(tb.idx, ix)
|
||||
for r in 0 .. tb.n { tb_ix_refile(tb, ix, r) }
|
||||
|
|
@ -25,17 +26,16 @@ export function tb_index<T>(tb: Table<T>, col: int, gate: int) -> IntIndex {
|
|||
|
||||
# the rows filed under v - the index's own list: read it, never push to it
|
||||
export function ix_rows(ix: IntIndex, v: int) -> words {
|
||||
if v < 0 or v >= len(ix.lists) { return ix.empty }
|
||||
return ix.lists[v]
|
||||
}
|
||||
export function ix_count(ix: IntIndex, v: int) -> int {
|
||||
if v < 0 or v >= len(ix.lists) { return 0 }
|
||||
return len(ix.lists[v])
|
||||
let s = ix_slot(ix, v, false)
|
||||
if s < 0 { return ix.empty }
|
||||
return ix.lists[s]
|
||||
}
|
||||
export function ix_count(ix: IntIndex, v: int) -> int { return len(ix_rows(ix, v)) }
|
||||
# the first row filed under v, or -1
|
||||
export function ix_first(ix: IntIndex, v: int) -> int {
|
||||
if ix_count(ix, v) == 0 { return -1 }
|
||||
return ix.lists[v][0]
|
||||
let rows = ix_rows(ix, v)
|
||||
if len(rows) == 0 { return -1 }
|
||||
return rows[0]
|
||||
}
|
||||
|
||||
function ix_grow(ix: IntIndex) -> void {
|
||||
|
|
@ -49,13 +49,13 @@ function ix_shrink(ix: IntIndex) -> void {
|
|||
}
|
||||
|
||||
function ix_file(ix: IntIndex, r: int, v: int) -> void {
|
||||
if ix.pos[r] >= 0 and ix.val[r] == v { return }
|
||||
let s = ix_slot(ix, v, true)
|
||||
if ix.pos[r] >= 0 and ix.val[r] == s { return }
|
||||
ix_unfile(ix, r)
|
||||
if v < 0 { return }
|
||||
while len(ix.lists) <= v { push(ix.lists, words(0)) }
|
||||
ix.pos[r] = len(ix.lists[v])
|
||||
ix.val[r] = v
|
||||
push(ix.lists[v], r)
|
||||
if s < 0 { return }
|
||||
ix.pos[r] = len(ix.lists[s])
|
||||
ix.val[r] = s
|
||||
push(ix.lists[s], r)
|
||||
}
|
||||
|
||||
# out of its list: the list's last row takes its place
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue