# ecs_test.ludic - Table: 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, 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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 = 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() } }