ludic.things: a removed Thing's record placed again, oldest first past a lag of 32; things_reserve sizes the grid once
thing_put and thing_spawn made a new record for every Thing placed, and the world places and removes them all day (an animal's sign and bed, a drop, a fish), so play grew by a record per placement. Removed records wait in a queue compacted in place; once 32 wait, the oldest is set back as new with a new uid. ludic.base's grid_reserve makes the buckets for n rows now, since a rehash in play leaves the old bucket array behind. Tests: a record comes back only past the lag, as new, with a new uid, the indexes agree, and the spare queue stays bounded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
5d71892b7c
commit
1af62d0879
7 changed files with 101 additions and 2 deletions
|
|
@ -38,3 +38,9 @@ function grid_rehash(g: Grid) -> void {
|
||||||
if k >= 0 { grid_link(g, r, k, grid_bucket(g, grid_key_x(k), grid_key_z(k))) }
|
if k >= 0 { grid_link(g, r, k, grid_bucket(g, grid_key_x(k), grid_key_z(k))) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# buckets enough for `rows` filed rows before a rehash, made now: a rehash in play makes a new
|
||||||
|
# bucket array and nothing gives the old one back
|
||||||
|
export function grid_reserve(g: Grid, rows: int) -> void {
|
||||||
|
while (g.mask + 1) * 2 < rows { grid_rehash(g) }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,11 @@ def ThingKinds lure { name_of: fn bait_name, tick: fn bait_wear, put_down: true,
|
||||||
| `put_down` | set down by someone rather than grown by the world: a restock leaves it (the port has the last word) |
|
| `put_down` | set down by someone rather than grown by the world: a restock leaves it (the port has the last word) |
|
||||||
| `keeps` | keeps its `used` through a restock |
|
| `keeps` | keeps its `used` through a restock |
|
||||||
|
|
||||||
|
**A removed Thing's record is placed again.** Ludic frees nothing, and a world places and takes
|
||||||
|
away Things all day, so `thing_remove` keeps the record and `thing_put` / `thing_spawn` make the
|
||||||
|
oldest one again - set back as new, with a new `uid` - once 32 are waiting. Hold a Thing across a
|
||||||
|
removal only by its `uid` (`thing_by_uid`), never by the record.
|
||||||
|
|
||||||
## The port
|
## The port
|
||||||
|
|
||||||
Every member has a default, so any may be left out of the `bind`:
|
Every member has a default, so any may be left out of the `bind`:
|
||||||
|
|
@ -73,6 +78,7 @@ the actor the moment the Thing exists (a caller often scales or tints it on the
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| `thing_spawn(kind, look, x, y, z, yaw, id) -> Thing`, `thing_put(kind, look, x, z, yaw, sink, reach, id) -> Thing` | a new Thing, at a height or on the ground set into it by `sink` |
|
| `thing_spawn(kind, look, x, y, z, yaw, id) -> Thing`, `thing_put(kind, look, x, z, yaw, sink, reach, id) -> Thing` | a new Thing, at a height or on the ground set into it by `sink` |
|
||||||
| `thing_remove(t)`, `things_drop(gone: fn(Thing) -> bool)`, `things_clear()` | gone for good (told to the port); a new world, quietly |
|
| `thing_remove(t)`, `things_drop(gone: fn(Thing) -> bool)`, `things_clear()` | gone for good (told to the port); a new world, quietly |
|
||||||
|
| `things_reserve(n)`, `things_spare_count()` | room in the grid for `n` Things, made once at the start; how many removed records wait to be placed again |
|
||||||
| `thing_move(t, x, z, yaw)`, `thing_move_to(t, x, y, z, yaw)` | on the ground there, or at a height |
|
| `thing_move(t, x, z, yaw)`, `thing_move_to(t, x, y, z, yaw)` | on the ground there, or at a height |
|
||||||
| `thing_hide(t)`, `thing_show(t)`, `thing_set_active(t, on)` | `active`, told to the port |
|
| `thing_hide(t)`, `thing_show(t)`, `thing_set_active(t, on)` | `active`, told to the port |
|
||||||
| `things_all()`, `things_count()`, `things_ready()`, `thing_by_uid(uid)` | every Thing, in the order placed; whether a world was ever set out |
|
| `things_all()`, `things_count()`, `things_ready()`, `thing_by_uid(uid)` | every Thing, in the order placed; whether a world was ever set out |
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,4 @@ import "near.ludic"
|
||||||
import "chunks.ludic"
|
import "chunks.ludic"
|
||||||
import "dispatch.ludic"
|
import "dispatch.ludic"
|
||||||
import "system.ludic"
|
import "system.ludic"
|
||||||
|
import "spare.ludic"
|
||||||
|
|
|
||||||
57
packages/ludic.things/spare.ludic
Normal file
57
packages/ludic.things/spare.ludic
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
# spare.ludic - a removed Thing's record is made again rather than a new one: Ludic frees nothing,
|
||||||
|
# and the world places and takes away Things all day (a sign an animal leaves, a bed, a drop).
|
||||||
|
# Oldest first, and only once TH_SPARE_LAG are waiting, so anything still holding one sees its
|
||||||
|
# uid change long after the removal; everything else about it is set back as new
|
||||||
|
const TH_SPARE_LAG: int = 32
|
||||||
|
|
||||||
|
function th_spare_put(things_st: mut ThingsState, t: Thing) -> void { push(things_st.th_spare, t) }
|
||||||
|
|
||||||
|
# a record for a Thing about to be placed: the oldest spare past the lag, or a new one
|
||||||
|
function th_record(things_st: mut ThingsState) -> Thing {
|
||||||
|
let l = things_st.th_spare
|
||||||
|
if len(l) - things_st.th_spare_at <= TH_SPARE_LAG { return new Thing }
|
||||||
|
let t = l[things_st.th_spare_at]
|
||||||
|
things_st.th_spare_at += 1
|
||||||
|
if things_st.th_spare_at >= 64 { th_spare_pack(things_st) }
|
||||||
|
return th_clean(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
# the waiting ones moved to the front, in place
|
||||||
|
function th_spare_pack(things_st: mut ThingsState) -> void {
|
||||||
|
let l = things_st.th_spare
|
||||||
|
let at = things_st.th_spare_at
|
||||||
|
let n = len(l) - at
|
||||||
|
for i in 0 .. n { l[i] = l[i + at] }
|
||||||
|
for i in 0 .. at { List.pop(l) }
|
||||||
|
things_st.th_spare_at = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function th_clean(t: Thing) -> Thing {
|
||||||
|
t.uid = 0
|
||||||
|
t.kind = 0
|
||||||
|
t.id = 0
|
||||||
|
t.look = ""
|
||||||
|
t.x = 0.0
|
||||||
|
t.y = 0.0
|
||||||
|
t.z = 0.0
|
||||||
|
t.yaw = 0.0
|
||||||
|
t.owner = -1
|
||||||
|
t.active = true
|
||||||
|
t.reach = 0.0
|
||||||
|
t.timer = 0.0
|
||||||
|
t.used = 0
|
||||||
|
t.n = 0
|
||||||
|
t.sx = 0.0
|
||||||
|
t.sz = 0.0
|
||||||
|
t.syaw = 0.0
|
||||||
|
t.shared = false
|
||||||
|
t.ent = -1
|
||||||
|
t.tab = null
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
# room for `n` Things in the grid before it has to grow: a world that will place more in play
|
||||||
|
# than it placed at the start sizes it once, at the start
|
||||||
|
export function things_reserve(things_st: mut ThingsState, n: int) -> void { grid_reserve(things_st.th.grid, n) }
|
||||||
|
|
||||||
|
export function things_spare_count(things_st: ThingsState) -> int { return len(things_st.th_spare) - things_st.th_spare_at }
|
||||||
|
|
@ -24,6 +24,8 @@ export state ThingsState {
|
||||||
th_uid: int = 0
|
th_uid: int = 0
|
||||||
th_facts: Queue<ThingFact> = th_facts__new()
|
th_facts: Queue<ThingFact> = th_facts__new()
|
||||||
th_hs: words = words(0) # things_tick's handles, taken before any tick runs
|
th_hs: words = words(0) # things_tick's handles, taken before any tick runs
|
||||||
|
th_spare: []Thing = new []Thing # removed records, made again oldest first (spare.ludic)
|
||||||
|
th_spare_at: int = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
function th_facts__new() -> Queue<ThingFact> { return queue_new("things.facts") }
|
function th_facts__new() -> Queue<ThingFact> { return queue_new("things.facts") }
|
||||||
|
|
|
||||||
|
|
@ -215,4 +215,30 @@ program ThingsTest {
|
||||||
expect(out[0] == a)
|
expect(out[0] == a)
|
||||||
expect_eq(things_changed(things_st, seen, rows, out), 2)
|
expect_eq(things_changed(things_st, seen, rows, out), 2)
|
||||||
}
|
}
|
||||||
|
test "a removed Thing's record is placed again once enough wait, as new, with a new uid" (things_st: mut ThingsState) {
|
||||||
|
things_reset(things_st)
|
||||||
|
let first = thing_put(things_st, TH_LURE, "lure", 1.0, 1.0, 0.0, 0.0, 2.0, 7)
|
||||||
|
first.used = 5
|
||||||
|
first.owner = 3
|
||||||
|
thing_hide(first)
|
||||||
|
let old_uid = first.uid
|
||||||
|
thing_remove(things_st, first)
|
||||||
|
for i in 0 .. 32 { thing_remove(things_st, thing_put(things_st, TH_BRANCH, "branch", float(i), 0.0, 0.0, 0.0, 1.5, 0)) }
|
||||||
|
expect_eq(things_spare_count(things_st), 33)
|
||||||
|
let again = thing_put(things_st, TH_SIGN, "sign", 4.0, 4.0, 0.0, 0.0, 2.0, 1)
|
||||||
|
let ap: pointer = again
|
||||||
|
let fp: pointer = first
|
||||||
|
expect(ap == fp)
|
||||||
|
expect(again.uid != old_uid)
|
||||||
|
expect_eq(again.kind, TH_SIGN)
|
||||||
|
expect_eq(again.used, 0)
|
||||||
|
expect_eq(again.owner, -1)
|
||||||
|
expect(again.active)
|
||||||
|
expect_eq(thing_nearest(things_st, TH_SIGN, 4.0, 4.0).uid, again.uid)
|
||||||
|
expect_eq(things_verify(things_st), 0)
|
||||||
|
for i in 0 .. 500 { thing_remove(things_st, thing_put(things_st, TH_BRANCH, "branch", 0.0, float(i), 0.0, 0.0, 1.5, 0)) }
|
||||||
|
expect(things_spare_count(things_st) <= 33)
|
||||||
|
things_reserve(things_st, 5000)
|
||||||
|
expect_eq(things_verify(things_st), 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# verbs.ludic - the only ways a Thing is made, removed, moved, shown or hidden: each writes the
|
# verbs.ludic - the only ways a Thing is made, removed, moved, shown or hidden: each writes the
|
||||||
# record and the table's columns together, so the grid and the kind indexes never fall behind
|
# record and the table's columns together, so the grid and the kind indexes never fall behind
|
||||||
export function thing_spawn(things_st: mut ThingsState, kind: int, look: string, x: float, y: float, z: float, yaw: float, id: int) -> Thing {
|
export function thing_spawn(things_st: mut ThingsState, kind: int, look: string, x: float, y: float, z: float, yaw: float, id: int) -> Thing {
|
||||||
return th_place(things_st, new Thing, kind, look, x, y, z, yaw, id)
|
return th_place(things_st, th_record(things_st), kind, look, x, y, z, yaw, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
function th_place(things_st: mut ThingsState, t: Thing, kind: int, look: string, x: float, y: float, z: float, yaw: float, id: int) -> Thing {
|
function th_place(things_st: mut ThingsState, t: Thing, kind: int, look: string, x: float, y: float, z: float, yaw: float, id: int) -> Thing {
|
||||||
|
|
@ -41,7 +41,7 @@ function th_columns(t: Thing) -> void {
|
||||||
|
|
||||||
# a Thing on the ground (the port's), set into it by `sink`, used from `reach`
|
# a Thing on the ground (the port's), set into it by `sink`, used from `reach`
|
||||||
export function thing_put(things_st: mut ThingsState, kind: int, look: string, x: float, z: float, yaw: float, sink: float, reach: float, id: int) -> Thing {
|
export function thing_put(things_st: mut ThingsState, kind: int, look: string, x: float, z: float, yaw: float, sink: float, reach: float, id: int) -> Thing {
|
||||||
let t = new Thing
|
let t = th_record(things_st)
|
||||||
t.reach = reach
|
t.reach = reach
|
||||||
return th_place(things_st, t, kind, look, x, ThingsWorld.ground(x, z) - sink, z, yaw, id)
|
return th_place(things_st, t, kind, look, x, ThingsWorld.ground(x, z) - sink, z, yaw, id)
|
||||||
}
|
}
|
||||||
|
|
@ -54,6 +54,7 @@ export function thing_remove(things_st: mut ThingsState, t: Thing) -> void {
|
||||||
imap_del(things_st.th_uids, t.uid)
|
imap_del(things_st.th_uids, t.uid)
|
||||||
tb_remove(t.tab, t.ent)
|
tb_remove(t.tab, t.ent)
|
||||||
t.ent = -1
|
t.ent = -1
|
||||||
|
th_spare_put(things_st, t)
|
||||||
}
|
}
|
||||||
|
|
||||||
# every Thing `gone` says so is removed for good; from the last row down, so the row a removal
|
# every Thing `gone` says so is removed for good; from the last row down, so the row a removal
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue