ludic.base: tb_reserve and imap_reserve size a table up front; HandlePool (a slot and a generation, nothing made after hd_pool_new, past its capacity a panic naming it); kept_push is the one declared grow, and every growth path left says its bound in @alloc_ok
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f2dd27f443
commit
2d1d06899e
17 changed files with 199 additions and 26 deletions
|
|
@ -29,6 +29,7 @@ function grid_moved(g: Grid, from: int, to: int) -> void {
|
||||||
}
|
}
|
||||||
|
|
||||||
# twice the buckets, every filed row linked again where its key now hashes
|
# twice the buckets, every filed row linked again where its key now hashes
|
||||||
|
@alloc_ok("past grid_reserve only: the buckets double once past the most rows filed, and are kept")
|
||||||
function grid_rehash(g: Grid) -> void {
|
function grid_rehash(g: Grid) -> void {
|
||||||
g.mask = g.mask * 2 + 1
|
g.mask = g.mask * 2 + 1
|
||||||
g.head = words(g.mask + 1)
|
g.head = words(g.mask + 1)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ export function tb_within_recs<T>(tb: Table<T>, g: Grid, x: float, z: float, r:
|
||||||
let cells = (ix1 - ix0 + 1) * (iz1 - iz0 + 1)
|
let cells = (ix1 - ix0 + 1) * (iz1 - iz0 + 1)
|
||||||
if cells > g.count or cells < 0 {
|
if cells > g.count or cells < 0 {
|
||||||
for row in 0 .. len(g.key) {
|
for row in 0 .. len(g.key) {
|
||||||
if g.key[row] >= 0 and (mcol == null or mcol[row] == mv) and grid_d2(xs, zs, row, x, z) <= r * r { push(out, tb.rec[row]) }
|
if g.key[row] >= 0 and (mcol == null or mcol[row] == mv) and grid_d2(xs, zs, row, x, z) <= r * r { kept_push(out, tb.rec[row]) }
|
||||||
}
|
}
|
||||||
return len(out)
|
return len(out)
|
||||||
}
|
}
|
||||||
|
|
@ -78,7 +78,7 @@ function grid_recs_cell<T>(tb: Table<T>, g: Grid, xs: floats, zs: floats, mcol:
|
||||||
var r = g.head[grid_bucket(g, ix, iz)]
|
var r = g.head[grid_bucket(g, ix, iz)]
|
||||||
while r > 0 {
|
while r > 0 {
|
||||||
let row = r - 1
|
let row = r - 1
|
||||||
if g.key[row] == k and (mcol == null or mcol[row] == mv) and grid_d2(xs, zs, row, x, z) <= r2 { push(out, tb.rec[row]) }
|
if g.key[row] == k and (mcol == null or mcol[row] == mv) and grid_d2(xs, zs, row, x, z) <= r2 { kept_push(out, tb.rec[row]) }
|
||||||
r = g.next[row]
|
r = g.next[row]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ export function tb_within_of_recs<T>(tb: Table<T>, g: Grid, ix: IntIndex, x: flo
|
||||||
for k in 0 .. n {
|
for k in 0 .. n {
|
||||||
let row = rows[k]
|
let row = rows[k]
|
||||||
if g.gate >= 0 and tb.i[g.gate][row] == 0 { continue }
|
if g.gate >= 0 and tb.i[g.gate][row] == 0 { continue }
|
||||||
if grid_d2(xs, zs, row, x, z) <= r * r { push(out, tb.rec[row]) }
|
if grid_d2(xs, zs, row, x, z) <= r * r { kept_push(out, tb.rec[row]) }
|
||||||
}
|
}
|
||||||
return len(out)
|
return len(out)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ function ix_file(ix: IntIndex, r: int, v: int) -> void {
|
||||||
if s < 0 { return }
|
if s < 0 { return }
|
||||||
ix.pos[r] = len(ix.lists[s])
|
ix.pos[r] = len(ix.lists[s])
|
||||||
ix.val[r] = s
|
ix.val[r] = s
|
||||||
push(ix.lists[s], r)
|
kept_push(ix.lists[s], r)
|
||||||
}
|
}
|
||||||
|
|
||||||
# out of its list: the list's last row takes its place
|
# out of its list: the list's last row takes its place
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
const IX_DENSE: int = 1024 # a value below this is its own slot; above, one is given it
|
const IX_DENSE: int = 1024 # a value below this is its own slot; above, one is given it
|
||||||
const IX_CAP: int = 16 # rows a value's list holds before it grows: a list grown in play is a new block
|
const IX_CAP: int = 16 # rows a value's list holds before it grows: a list grown in play is a new block
|
||||||
|
|
||||||
|
@alloc_ok("once per distinct value: its list is made the first time the value is filed, and kept")
|
||||||
function ix_list_new() -> words {
|
function ix_list_new() -> words {
|
||||||
let l = words(IX_CAP)
|
let l = words(IX_CAP)
|
||||||
List.clear(l)
|
List.clear(l)
|
||||||
|
|
@ -16,15 +17,15 @@ function ix_slot(ix: IntIndex, v: int, make: bool) -> int {
|
||||||
if v < IX_DENSE {
|
if v < IX_DENSE {
|
||||||
if v >= len(ix.lists) {
|
if v >= len(ix.lists) {
|
||||||
if not make { return -1 }
|
if not make { return -1 }
|
||||||
while len(ix.lists) <= v { push(ix.lists, ix_list_new()) }
|
while len(ix.lists) <= v { kept_push(ix.lists, ix_list_new()) }
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
var s = imap_get(ix.sparse, v, -1)
|
var s = imap_get(ix.sparse, v, -1)
|
||||||
if s < 0 and make {
|
if s < 0 and make {
|
||||||
while len(ix.lists) < IX_DENSE { push(ix.lists, ix_list_new()) }
|
while len(ix.lists) < IX_DENSE { kept_push(ix.lists, ix_list_new()) }
|
||||||
s = len(ix.lists)
|
s = len(ix.lists)
|
||||||
push(ix.lists, ix_list_new())
|
kept_push(ix.lists, ix_list_new())
|
||||||
imap_put(ix.sparse, v, s)
|
imap_put(ix.sparse, v, s)
|
||||||
}
|
}
|
||||||
return s
|
return s
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,18 @@ export function imap_del(m: IntMap, k: int) -> bool {
|
||||||
export function imap_len(m: IntMap) -> int { return m.n }
|
export function imap_len(m: IntMap) -> int { return m.n }
|
||||||
export function imap_clear(m: IntMap) -> void { imap_alloc(m, m.mask + 1) }
|
export function imap_clear(m: IntMap) -> void { imap_alloc(m, m.mask + 1) }
|
||||||
|
|
||||||
|
# room for `n` keys before the map grows, made now
|
||||||
|
export function imap_reserve(m: IntMap, n: int) -> void {
|
||||||
|
if (m.mask + 1) * 3 >= n * 4 { return }
|
||||||
|
var cap = m.mask + 1
|
||||||
|
while cap * 3 < n * 4 { cap = cap * 2 }
|
||||||
|
let ok = m.keys
|
||||||
|
let ov = m.vals
|
||||||
|
imap_alloc(m, cap)
|
||||||
|
for i in 0 .. len(ok) { if ok[i] >= 0 { imap_put(m, ok[i], ov[i]) } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@alloc_ok("past imap_reserve only: doubles when half full; the old arrays are not given back")
|
||||||
function imap_grow(m: IntMap) -> void {
|
function imap_grow(m: IntMap) -> void {
|
||||||
let ok = m.keys
|
let ok = m.keys
|
||||||
let ov = m.vals
|
let ov = m.vals
|
||||||
|
|
|
||||||
|
|
@ -66,22 +66,10 @@ export function tb_alive<T>(tb: Table<T>, h: int) -> bool { return tb_row(tb, h)
|
||||||
# a new row holding `r`, every column 0 and filed in no index until a setter says where it is
|
# a new row holding `r`, every column 0 and filed in no index until a setter says where it is
|
||||||
export function tb_add<T>(tb: Table<T>, r: T) -> int {
|
export function tb_add<T>(tb: Table<T>, r: T) -> int {
|
||||||
var s = 0
|
var s = 0
|
||||||
if len(tb.free) > 0 { s = List.pop(tb.free) } else {
|
if len(tb.free) > 0 { s = List.pop(tb.free) } else { s = tb_new_slot(tb) }
|
||||||
s = len(tb.slot_gen)
|
|
||||||
push(tb.slot_gen, 0)
|
|
||||||
push(tb.slot_row, -1)
|
|
||||||
}
|
|
||||||
tb.slot_row[s] = tb.n
|
tb.slot_row[s] = tb.n
|
||||||
let h = s | (tb.slot_gen[s] << 22)
|
let h = s | (tb.slot_gen[s] << 22)
|
||||||
push(tb.rec, r)
|
tb_push_row(tb, r, h)
|
||||||
push(tb.ent, h)
|
|
||||||
push(tb.added, tb.tick)
|
|
||||||
push(tb.changed, tb.tick)
|
|
||||||
for c in 0 .. len(tb.f) { push(tb.f[c], 0.0) }
|
|
||||||
for c in 0 .. len(tb.i) { push(tb.i[c], 0) }
|
|
||||||
tb.n += 1
|
|
||||||
for g in 0 .. len(tb.grids) { grid_grow(tb.grids[g]) }
|
|
||||||
for k in 0 .. len(tb.idx) { ix_grow(tb.idx[k]) }
|
|
||||||
tb_stamp(tb, tb.n - 1)
|
tb_stamp(tb, tb.n - 1)
|
||||||
for k in 0 .. len(tb.on_add) { tb.on_add[k].run(h) }
|
for k in 0 .. len(tb.on_add) { tb.on_add[k].run(h) }
|
||||||
return h
|
return h
|
||||||
|
|
@ -91,6 +79,6 @@ export function tb_add<T>(tb: Table<T>, r: T) -> int {
|
||||||
function tb_stamp<T>(tb: Table<T>, row: int) -> void {
|
function tb_stamp<T>(tb: Table<T>, row: int) -> void {
|
||||||
tb.changed[row] = tb.tick
|
tb.changed[row] = tb.tick
|
||||||
let b = row >> 6
|
let b = row >> 6
|
||||||
while len(tb.blk) <= b { push(tb.blk, 0) }
|
while len(tb.blk) <= b { kept_push(tb.blk, 0) }
|
||||||
tb.blk[b] = tb.tick
|
tb.blk[b] = tb.tick
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export function tb_remove<T>(tb: Table<T>, h: int) -> bool {
|
||||||
let s = h & ECS_SLOT
|
let s = h & ECS_SLOT
|
||||||
tb.slot_row[s] = -1
|
tb.slot_row[s] = -1
|
||||||
tb.slot_gen[s] = (tb.slot_gen[s] + 1) & ECS_GEN
|
tb.slot_gen[s] = (tb.slot_gen[s] + 1) & ECS_GEN
|
||||||
push(tb.free, s)
|
kept_push(tb.free, s)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
43
packages/ludic.base/ecs_table_reserve.ludic
Normal file
43
packages/ludic.base/ecs_table_reserve.ludic
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# ludic.base/ecs_table_reserve.ludic - room made at the start: a table sized for its busiest moment
|
||||||
|
# never grows in play, and nothing that grows is ever given back. What does grow past it is here
|
||||||
|
export function tb_reserve<T>(tb: Table<T>, rows: int) -> void {
|
||||||
|
let k = rows - tb.n
|
||||||
|
if k <= 0 { return }
|
||||||
|
for j in 0 .. k { tb_push_row(tb, null, 0) }
|
||||||
|
for j in 0 .. k { tb_pop(tb) }
|
||||||
|
let s0 = len(tb.slot_gen)
|
||||||
|
for j in s0 .. rows {
|
||||||
|
push(tb.slot_gen, 0)
|
||||||
|
push(tb.slot_row, -1)
|
||||||
|
push(tb.free, 0)
|
||||||
|
}
|
||||||
|
for j in s0 .. rows {
|
||||||
|
List.pop(tb.slot_gen)
|
||||||
|
List.pop(tb.slot_row)
|
||||||
|
List.pop(tb.free)
|
||||||
|
}
|
||||||
|
while len(tb.blk) <= (rows >> 6) { push(tb.blk, 0) }
|
||||||
|
for g in 0 .. len(tb.grids) { grid_reserve(tb.grids[g], rows) }
|
||||||
|
}
|
||||||
|
|
||||||
|
# a row on every column, grid and index
|
||||||
|
@alloc_ok("past tb_reserve's rows only: a table grows to its high-water once and keeps it")
|
||||||
|
function tb_push_row<T>(tb: Table<T>, r: T, h: int) -> void {
|
||||||
|
push(tb.rec, r)
|
||||||
|
push(tb.ent, h)
|
||||||
|
push(tb.added, tb.tick)
|
||||||
|
push(tb.changed, tb.tick)
|
||||||
|
for c in 0 .. len(tb.f) { push(tb.f[c], 0.0) }
|
||||||
|
for c in 0 .. len(tb.i) { push(tb.i[c], 0) }
|
||||||
|
tb.n += 1
|
||||||
|
for g in 0 .. len(tb.grids) { grid_grow(tb.grids[g]) }
|
||||||
|
for k in 0 .. len(tb.idx) { ix_grow(tb.idx[k]) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@alloc_ok("past tb_reserve's rows only: a slot is made once and reused through the free list")
|
||||||
|
function tb_new_slot<T>(tb: Table<T>) -> int {
|
||||||
|
let s = len(tb.slot_gen)
|
||||||
|
push(tb.slot_gen, 0)
|
||||||
|
push(tb.slot_row, -1)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
@ -59,7 +59,7 @@ export function tb_changed_since<T>(tb: Table<T>, since: int, out: words) -> int
|
||||||
if tb.blk[b] <= since { continue }
|
if tb.blk[b] <= since { continue }
|
||||||
var end = b * 64 + 64
|
var end = b * 64 + 64
|
||||||
if end > tb.n { end = tb.n }
|
if end > tb.n { end = tb.n }
|
||||||
for r in b * 64 .. end { if tb.changed[r] > since { push(out, r) } }
|
for r in b * 64 .. end { if tb.changed[r] > since { kept_push(out, r) } }
|
||||||
}
|
}
|
||||||
return len(out)
|
return len(out)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
62
packages/ludic.base/handle.ludic
Normal file
62
packages/ludic.base/handle.ludic
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# ludic.base/handle.ludic - ids a pool hands out, each a slot and a generation (Table's encoding): a
|
||||||
|
# freed id's slot is reused under a new generation, so an id kept past its free misses with -1
|
||||||
|
export property HandlePool {
|
||||||
|
name: string = ""
|
||||||
|
gen: words = null # slot -> generation
|
||||||
|
live: words = null # slot -> 1 while handed out
|
||||||
|
free: words = null
|
||||||
|
n: int = 0 # slots made so far
|
||||||
|
count: int = 0 # handed out now
|
||||||
|
refused: int = 0 # asks past the capacity
|
||||||
|
strict: bool = true # past the capacity is a panic naming the pool, else -1
|
||||||
|
}
|
||||||
|
|
||||||
|
# everything the pool will ever hold, made now: nothing after this allocates
|
||||||
|
export function hd_pool_new(name: string, cap: int) -> HandlePool {
|
||||||
|
let p = new HandlePool
|
||||||
|
p.name = name
|
||||||
|
p.gen = words(cap)
|
||||||
|
p.live = words(cap)
|
||||||
|
p.free = words(cap)
|
||||||
|
List.clear(p.free)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hd_new(p: HandlePool) -> int {
|
||||||
|
var s = -1
|
||||||
|
if len(p.free) > 0 { s = List.pop(p.free) }
|
||||||
|
else if p.n < len(p.gen) {
|
||||||
|
s = p.n
|
||||||
|
p.n += 1
|
||||||
|
} else { return hd_refuse(p) }
|
||||||
|
p.live[s] = 1
|
||||||
|
p.count += 1
|
||||||
|
return s | (p.gen[s] << 22)
|
||||||
|
}
|
||||||
|
|
||||||
|
function hd_refuse(p: HandlePool) -> int {
|
||||||
|
p.refused += 1
|
||||||
|
if p.strict { panic(`handle pool {p.name} is full at {len(p.gen)}`) }
|
||||||
|
return ECS_NONE
|
||||||
|
}
|
||||||
|
|
||||||
|
# the slot a handle names, or -1 when it was freed (or its slot has been handed out again since)
|
||||||
|
export function hd_slot(p: HandlePool, h: int) -> int {
|
||||||
|
if h < 0 { return -1 }
|
||||||
|
let s = h & ECS_SLOT
|
||||||
|
if s >= p.n or p.live[s] == 0 or p.gen[s] != ((h >> 22) & ECS_GEN) { return -1 }
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hd_free(p: HandlePool, h: int) -> bool {
|
||||||
|
let s = hd_slot(p, h)
|
||||||
|
if s < 0 { return false }
|
||||||
|
p.live[s] = 0
|
||||||
|
p.gen[s] = (p.gen[s] + 1) & ECS_GEN
|
||||||
|
kept_push(p.free, s)
|
||||||
|
p.count -= 1
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hd_live(p: HandlePool) -> int { return p.count }
|
||||||
|
export function hd_cap(p: HandlePool) -> int { return len(p.gen) }
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
module ludic_base uses
|
module ludic_base uses
|
||||||
numbers float
|
numbers float
|
||||||
import "tick.ludic"
|
import "tick.ludic"
|
||||||
|
import "kept.ludic"
|
||||||
|
import "handle.ludic"
|
||||||
import "queue.ludic"
|
import "queue.ludic"
|
||||||
import "text_buf.ludic"
|
import "text_buf.ludic"
|
||||||
import "text_intern.ludic"
|
import "text_intern.ludic"
|
||||||
|
|
@ -14,6 +16,7 @@ import "system.ludic"
|
||||||
import "ecs_table.ludic"
|
import "ecs_table.ludic"
|
||||||
import "ecs_hooks.ludic"
|
import "ecs_hooks.ludic"
|
||||||
import "ecs_table_remove.ludic"
|
import "ecs_table_remove.ludic"
|
||||||
|
import "ecs_table_reserve.ludic"
|
||||||
import "ecs_table_set.ludic"
|
import "ecs_table_set.ludic"
|
||||||
import "ecs_grid.ludic"
|
import "ecs_grid.ludic"
|
||||||
import "ecs_grid_file.ludic"
|
import "ecs_grid_file.ludic"
|
||||||
|
|
|
||||||
4
packages/ludic.base/kept.ludic
Normal file
4
packages/ludic.base/kept.ludic
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
# ludic.base/kept.ludic - the one push the frame is allowed: into a list its owner keeps and clears,
|
||||||
|
# which grows to the most it has ever held and stays there (a queue's halves, a query's out list)
|
||||||
|
@alloc_ok("a kept list: it grows to the most it has held once, is cleared and reused, never dropped")
|
||||||
|
export function kept_push<T>(xs: []T, v: T) -> void { push(xs, v) }
|
||||||
|
|
@ -30,7 +30,7 @@ export function queue_new<T>(name: string) -> Queue<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function q_push<T>(q: Queue<T>, v: T) -> void {
|
export function q_push<T>(q: Queue<T>, v: T) -> void {
|
||||||
push(q.items, v)
|
kept_push(q.items, v)
|
||||||
q.tag.pending = len(q.items)
|
q.tag.pending = len(q.items)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +65,7 @@ export function q_unheld<T>(q: Queue<T>, pool: []T, out: []T) -> int {
|
||||||
var held = false
|
var held = false
|
||||||
for j in 0 .. len(q.items) { if q.items[j] == r { held = true } }
|
for j in 0 .. len(q.items) { if q.items[j] == r { held = true } }
|
||||||
for j in 0 .. len(q.spare) { if q.spare[j] == r { held = true } }
|
for j in 0 .. len(q.spare) { if q.spare[j] == r { held = true } }
|
||||||
if not held { push(out, r) }
|
if not held { kept_push(out, r) }
|
||||||
}
|
}
|
||||||
return len(out)
|
return len(out)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ export property System {
|
||||||
export open registry Systems of System as SYS
|
export open registry Systems of System as SYS
|
||||||
|
|
||||||
|
|
||||||
|
# the declared systems; a registry is not filled when a state's defaults are made, so on first ask
|
||||||
|
@alloc_ok("once: the runner's list is made the first time it is asked, and kept")
|
||||||
function cs_all(base_st: mut BaseState) -> []System {
|
function cs_all(base_st: mut BaseState) -> []System {
|
||||||
if base_st.cs_list == null {
|
if base_st.cs_list == null {
|
||||||
base_st.cs_list = new []System
|
base_st.cs_list = new []System
|
||||||
|
|
|
||||||
|
|
@ -179,8 +179,26 @@ program EcsTest {
|
||||||
test "observers hear each add, and a remove once" (hook_st: HookState) { hooks_case(hook_st) }
|
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 "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() }
|
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 grid answers nearest and within, gated, after moves" () { grid_case() }
|
||||||
test "the kind index follows the setters, the gate and removals" () { index_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 "change ticks name the rows written since" () { ticks_case() }
|
||||||
test "IntMap puts, finds, deletes and grows" () { map_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() }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
38
packages/ludic.base/tests/handle_test.ludic
Normal file
38
packages/ludic.base/tests/handle_test.ludic
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
# handle_test.ludic - a pool's ids: a freed one misses once its slot is reused, the generation wraps,
|
||||||
|
# and past the capacity a pool that is not strict refuses rather than grows
|
||||||
|
import "ludic.base"
|
||||||
|
program HandleTest {
|
||||||
|
numbers float
|
||||||
|
test "a freed handle misses, and its slot comes back under a new generation" {
|
||||||
|
let p = hd_pool_new("bufs", 4)
|
||||||
|
p.strict = false
|
||||||
|
let a = hd_new(p)
|
||||||
|
let b = hd_new(p)
|
||||||
|
expect_eq(hd_slot(p, a), 0)
|
||||||
|
expect_eq(hd_slot(p, b), 1)
|
||||||
|
expect(hd_free(p, a))
|
||||||
|
expect_eq(hd_slot(p, a), -1)
|
||||||
|
expect(not hd_free(p, a))
|
||||||
|
let c = hd_new(p)
|
||||||
|
expect_eq(hd_slot(p, c), 0)
|
||||||
|
expect(c != a)
|
||||||
|
expect_eq(hd_slot(p, a), -1)
|
||||||
|
expect_eq(hd_live(p), 2)
|
||||||
|
}
|
||||||
|
test "the generation wraps after 512 reuses and the pool never grows past its capacity" {
|
||||||
|
let p = hd_pool_new("once", 2)
|
||||||
|
p.strict = false
|
||||||
|
var h = hd_new(p)
|
||||||
|
let first = h
|
||||||
|
for i in 0 .. 511 {
|
||||||
|
hd_free(p, h)
|
||||||
|
h = hd_new(p)
|
||||||
|
}
|
||||||
|
hd_free(p, h)
|
||||||
|
expect_eq(hd_new(p), first)
|
||||||
|
hd_new(p)
|
||||||
|
expect_eq(hd_new(p), -1)
|
||||||
|
expect_eq(p.refused, 1)
|
||||||
|
expect_eq(hd_cap(p), 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -34,6 +34,7 @@ function sb_same(sb: StrBuf, s: string) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
# the table's string for the buffer's bytes
|
# the table's string for the buffer's bytes
|
||||||
|
@alloc_ok("intern: one string per distinct text, up to the table's most, then kept")
|
||||||
export function sb_intern(sb: StrBuf, tb: StrTable) -> string {
|
export function sb_intern(sb: StrBuf, tb: StrTable) -> string {
|
||||||
let h = sb_hash(sb)
|
let h = sb_hash(sb)
|
||||||
let mask = len(tb.hs) - 1
|
let mask = len(tb.hs) - 1
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue