ludic.base: a queue's fact records come round again (q_rec / q_ring_add), used by effects, clock, wallet, weather, tracks, needs, fire, settings, update, steps, crafting, shop, gear, hints, session and photo instead of a new record per fact; the minimap's marks cleared and rubbed out in place, its scale steps without a list; the clock's dice seeded again, not made again; once-per-join, per-save and pool-miss paths declared

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-28 20:03:46 +03:00
parent 5be2422c84
commit d9612ba2a6
31 changed files with 215 additions and 25 deletions

View file

@ -12,6 +12,7 @@ export property HandlePool {
}
# everything the pool will ever hold, made now: nothing after this allocates
@alloc_ok("a pool made at start: its owner keeps it")
export function hd_pool_new(name: string, cap: int) -> HandlePool {
let p = new HandlePool
p.name = name

View file

@ -6,6 +6,7 @@ import "tick.ludic"
import "kept.ludic"
import "handle.ludic"
import "queue.ludic"
import "queue_ring.ludic"
import "text_buf.ludic"
import "text_intern.ludic"
import "text_ring.ludic"

View file

@ -5,6 +5,8 @@ export property Queue<T> {
tag: QueueTag = null
none: []T = null # what draining an empty queue hands back, never pushed to
spare: []T = null # the list the last drain handed out: cleared and reused by the next
ring: []T = null # the facts' records, used again once no list holds them (queue_ring.ludic)
ring_at: int = 0
}
# what a queue says about itself, whatever it holds: its name and how many facts wait in it
export property QueueTag {
@ -26,6 +28,7 @@ export function queue_new<T>(name: string) -> Queue<T> {
q.tag.name = name
q.none = new []T
q.spare = new []T
q.ring = new []T
return q
}

View file

@ -0,0 +1,21 @@
# ludic.base/queue_ring.ludic - a fact's record made again rather than new: Ludic frees nothing, and a
# mechanic reports all day. q_rec hands out a record neither waiting in the queue nor handed out by
# its last drain, or null when every one is: the caller makes one more and q_ring_add keeps it, so
# the ring grows to the busiest frame and stays there. The caller sets every field it reads
export function q_rec<T>(q: Queue<T>) -> T {
let n = len(q.ring)
for t in 0 .. n {
let r = q.ring[q.ring_at]
q.ring_at = (q.ring_at + 1) % n
if not q_holds(q, r) { return r }
}
return null
}
export function q_ring_add<T>(q: Queue<T>, r: T) -> void { kept_push(q.ring, r) }
function q_holds<T>(q: Queue<T>, r: T) -> bool {
for i in 0 .. len(q.items) { if q.items[i] == r { return true } }
for i in 0 .. len(q.spare) { if q.spare[i] == r { return true } }
return false
}

View file

@ -134,6 +134,32 @@ program QueueTest {
test "a queue holds records" () { record_case() }
test "clear drops what is waiting" () { clear_case() }
test "a pool's records are free once no drain holds them" () { unheld_case() }
function rec_of(q: Queue<Caught>) -> Caught {
var c = q_rec(q)
if c == null {
c = new Caught
q_ring_add(q, c)
}
return c
}
function ring_case() -> void {
let q: Queue<Caught> = queue_new("caught")
let a = rec_of(q)
q_push(q, a)
let b = rec_of(q)
expect(a != b)
q_push(q, b)
q_drain(q)
expect_eq(len(q.ring), 2)
let c = rec_of(q)
expect(c != a and c != b)
q_push(q, c)
q_drain(q)
let d = rec_of(q)
expect(d == a or d == b)
expect_eq(len(q.ring), 3)
}
test "a fact's record comes round again once no list holds it" () { ring_case() }
test "a queue's element type is told by the queue alone" () { infer_case() }
test "the undrained queues are named, and a drain clears the report" () { undrained_case() }
}