204 lines
7.1 KiB
Text
204 lines
7.1 KiB
Text
# ecs_test.ludic - Table<T>: handles that go stale, dense rows after a removal, the setters keeping
|
|
# the grid and the kind index current, change ticks, and IntMap. Each case is a function because a
|
|
# generic call is not resolved inside a `test` body yet.
|
|
import "ludic.base"
|
|
program EcsTest {
|
|
numbers float
|
|
property Toy { name: string = "" }
|
|
const CX: int = 0
|
|
const CZ: int = 1
|
|
const KIND: int = 0
|
|
const ON: int = 1
|
|
|
|
state HookState {
|
|
added: int = 0
|
|
gone: int = 0
|
|
last: int = -1
|
|
}
|
|
function seen_add(hook_st: mut HookState, h: int) -> void {
|
|
hook_st.added += 1
|
|
hook_st.last = h
|
|
}
|
|
function seen_gone(hook_st: mut HookState, h: int) -> void { hook_st.gone += 1 }
|
|
|
|
function toy(name: string) -> Toy {
|
|
let t = new Toy
|
|
t.name = name
|
|
return t
|
|
}
|
|
|
|
function put(tb: Table<Toy>, name: string, x: float, z: float, kind: int) -> int {
|
|
let h = tb_add(tb, toy(name))
|
|
let r = tb_row(tb, h)
|
|
tb_set_xz(tb, CX, CZ, r, x, z)
|
|
tb_set_i(tb, KIND, r, kind)
|
|
tb_set_i(tb, ON, r, 1)
|
|
return h
|
|
}
|
|
|
|
function handles_case() -> void {
|
|
let tb: Table<Toy> = table_new(2, 2)
|
|
let a = put(tb, "a", 0.0, 0.0, 1)
|
|
let b = put(tb, "b", 1.0, 0.0, 1)
|
|
let c = put(tb, "c", 2.0, 0.0, 2)
|
|
expect_eq(tb_len(tb), 3)
|
|
expect(tb_remove(tb, a))
|
|
expect(not tb_alive(tb, a))
|
|
expect(not tb_remove(tb, a))
|
|
expect_eq(tb_len(tb), 2)
|
|
expect_eq(tb_rec(tb, tb_row(tb, c)).name, "c")
|
|
expect_eq(tb_rec(tb, tb_row(tb, b)).name, "b")
|
|
let d = put(tb, "d", 3.0, 0.0, 1)
|
|
expect(d != a)
|
|
expect_eq(d & ECS_SLOT, a & ECS_SLOT)
|
|
expect(not tb_alive(tb, a))
|
|
expect_eq(tb_rec(tb, tb_row(tb, d)).name, "d")
|
|
}
|
|
|
|
function grid_case() -> void {
|
|
let tb: Table<Toy> = table_new(2, 2)
|
|
let g = tb_grid(tb, CX, CZ, ON, 16.0)
|
|
let near = put(tb, "near", 5.0, 5.0, 1)
|
|
put(tb, "far", 500.0, -300.0, 1)
|
|
let fire = put(tb, "fire", -40.0, 2.0, 2)
|
|
expect_eq(tb_handle(tb, tb_nearest(tb, g, 0.0, 0.0, 0.0, -1, 0)), near)
|
|
expect_eq(tb_handle(tb, tb_nearest(tb, g, 0.0, 0.0, 0.0, KIND, 2)), fire)
|
|
expect_eq(tb_nearest(tb, g, 0.0, 0.0, 10.0, KIND, 2), -1)
|
|
tb_set_i(tb, ON, tb_row(tb, near), 0)
|
|
expect_eq(tb_handle(tb, tb_nearest(tb, g, 0.0, 0.0, 0.0, KIND, 1)), tb_handle(tb, tb_nearest(tb, g, 499.0, -300.0, 0.0, -1, 0)))
|
|
let out = words(0)
|
|
expect_eq(tb_within(tb, g, 0.0, 0.0, 50.0, -1, 0, out), 1)
|
|
tb_set_i(tb, ON, tb_row(tb, near), 1)
|
|
expect_eq(tb_within(tb, g, 0.0, 0.0, 50.0, -1, 0, out), 2)
|
|
tb_set_xz(tb, CX, CZ, tb_row(tb, fire), 900.0, 900.0)
|
|
expect_eq(tb_within(tb, g, 0.0, 0.0, 50.0, -1, 0, out), 1)
|
|
}
|
|
|
|
function index_case() -> void {
|
|
let tb: Table<Toy> = table_new(2, 2)
|
|
let ix = tb_index(tb, KIND, ON)
|
|
let a = put(tb, "a", 0.0, 0.0, 3)
|
|
put(tb, "b", 0.0, 0.0, 3)
|
|
put(tb, "c", 0.0, 0.0, 5)
|
|
expect_eq(ix_count(ix, 3), 2)
|
|
expect_eq(ix_count(ix, 5), 1)
|
|
expect_eq(ix_count(ix, 99), 0)
|
|
tb_set_i(tb, ON, tb_row(tb, a), 0)
|
|
expect_eq(ix_count(ix, 3), 1)
|
|
tb_remove(tb, a)
|
|
tb_set_i(tb, KIND, ix_first(ix, 3), 5)
|
|
expect_eq(ix_count(ix, 3), 0)
|
|
expect_eq(ix_count(ix, 5), 2)
|
|
}
|
|
|
|
function ticks_case() -> void {
|
|
let tb: Table<Toy> = table_new(2, 2)
|
|
let a = put(tb, "a", 0.0, 0.0, 1)
|
|
put(tb, "b", 0.0, 0.0, 1)
|
|
let seen = tb_tick(tb)
|
|
tb_advance(tb)
|
|
let out = words(0)
|
|
expect_eq(tb_changed_since(tb, seen, out), 0)
|
|
tb_set_f(tb, CX, tb_row(tb, a), 4.0)
|
|
expect_eq(tb_changed_since(tb, seen, out), 1)
|
|
expect_eq(out[0], tb_row(tb, a))
|
|
expect_eq(tb_added_since(tb, seen, out), 0)
|
|
}
|
|
|
|
function map_case() -> void {
|
|
let m = imap_new()
|
|
for k in 0 .. 5000 { imap_put(m, k * 7, k) }
|
|
expect_eq(imap_len(m), 5000)
|
|
expect_eq(imap_get(m, 700, -1), 100)
|
|
expect_eq(imap_get(m, 701, -1), -1)
|
|
for k in 0 .. 2500 { imap_del(m, k * 14) }
|
|
expect_eq(imap_len(m), 2500)
|
|
expect_eq(imap_get(m, 14, -1), -1)
|
|
expect_eq(imap_get(m, 21, -1), 3)
|
|
imap_put(m, 14, 9)
|
|
expect_eq(imap_get(m, 14, -1), 9)
|
|
}
|
|
|
|
function hooks_case(hook_st: HookState) -> void {
|
|
let tb: Table<Toy> = table_new(2, 2)
|
|
tb_on_add(tb, fn seen_add)
|
|
tb_on_remove(tb, fn seen_gone)
|
|
let a = put(tb, "a", 0.0, 0.0, 1)
|
|
expect_eq(hook_st.last, a)
|
|
put(tb, "b", 0.0, 0.0, 1)
|
|
tb_remove(tb, a)
|
|
tb_remove(tb, a)
|
|
expect_eq(hook_st.added, 2)
|
|
expect_eq(hook_st.gone, 1)
|
|
}
|
|
|
|
# 300 rows over five blocks; a change in one block is found, the untouched blocks skipped
|
|
function blocks_case() -> void {
|
|
let tb: Table<Toy> = table_new(2, 2)
|
|
for i in 0 .. 300 { put(tb, "x", float(i), 0.0, 1) }
|
|
let seen = tb_tick(tb)
|
|
tb_advance(tb)
|
|
let out = words(0)
|
|
expect_eq(tb_changed_since(tb, seen, out), 0)
|
|
tb_set_f(tb, CX, 200, 9.0)
|
|
tb_set_f(tb, CX, 7, 9.0)
|
|
expect_eq(tb_changed_since(tb, seen, out), 2)
|
|
expect_eq(out[0], 7)
|
|
expect_eq(out[1], 200)
|
|
let before = tb_tick(tb)
|
|
tb_advance(tb)
|
|
tb_remove(tb, tb_handle(tb, 3))
|
|
expect_eq(tb_changed_since(tb, before, out), 0) # a row moved into a gap was not written
|
|
expect_eq(tb_changed_since(tb, seen, out), 2) # a removal is told by tb_on_remove
|
|
}
|
|
|
|
# values far apart (a chunk's packed cell) each take one list, not every list up to them
|
|
function sparse_case() -> void {
|
|
let tb: Table<Toy> = table_new(2, 2)
|
|
let ix = tb_index(tb, KIND, -1)
|
|
let a = chunk_of(9000.0, -9000.0, 256.0)
|
|
let b = chunk_of(-512.0, 700.0, 256.0)
|
|
expect(a > 100000 and b > 100000)
|
|
put(tb, "a", 0.0, 0.0, a)
|
|
put(tb, "b", 0.0, 0.0, b)
|
|
let c = put(tb, "c", 0.0, 0.0, a)
|
|
put(tb, "d", 0.0, 0.0, 3)
|
|
expect_eq(ix_count(ix, a), 2)
|
|
expect_eq(ix_count(ix, b), 1)
|
|
expect_eq(ix_count(ix, 3), 1)
|
|
expect(len(ix.lists) < 1100)
|
|
tb_set_i(tb, KIND, tb_row(tb, c), b)
|
|
expect_eq(ix_count(ix, a), 1)
|
|
expect_eq(ix_count(ix, b), 2)
|
|
expect_eq(tb_remove_all(tb, ix, b), 2)
|
|
expect_eq(ix_count(ix, b), 0)
|
|
expect_eq(tb_len(tb), 2)
|
|
}
|
|
|
|
test "a sparse value takes one list" () { sparse_case() }
|
|
test "observers hear each add, and a remove once" (hook_st: HookState) { hooks_case(hook_st) }
|
|
test "change detection skips the blocks nobody wrote" () { blocks_case() }
|
|
test "a removed handle misses, rows stay dense, a reused slot is a new handle" () { handles_case() }
|
|
function reserve_case() -> void {
|
|
let tb: Table<Toy> = table_new(2, 2)
|
|
let g = tb_grid(tb, CX, CZ, ON, 8.0)
|
|
let ix = tb_index(tb, KIND, ON)
|
|
put(tb, "a", 1.0, 1.0, 3)
|
|
tb_reserve(tb, 200)
|
|
expect_eq(tb_len(tb), 1)
|
|
var hs = new []int
|
|
for i in 0 .. 150 { push(hs, put(tb, "t", float(i), 2.0, i % 4)) }
|
|
expect_eq(tb_len(tb), 151)
|
|
expect_eq(ix_count(ix, 3), 38)
|
|
expect(tb_row(tb, hs[10]) >= 0)
|
|
expect(tb_remove(tb, hs[10]))
|
|
expect_eq(tb_row(tb, hs[10]), -1)
|
|
expect_eq(grid_count(g), 150)
|
|
}
|
|
|
|
test "the grid answers nearest and within, gated, after moves" () { grid_case() }
|
|
test "the kind index follows the setters, the gate and removals" () { index_case() }
|
|
test "change ticks name the rows written since" () { ticks_case() }
|
|
test "IntMap puts, finds, deletes and grows" () { map_case() }
|
|
test "a reserved table holds its rows and indexes as an unreserved one does" () { reserve_case() }
|
|
}
|