diff --git a/packages/ludic.base/queue.ludic b/packages/ludic.base/queue.ludic index 55e5205a..521a85e2 100644 --- a/packages/ludic.base/queue.ludic +++ b/packages/ludic.base/queue.ludic @@ -4,6 +4,7 @@ export property Queue { items: []T = null 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 } # what a queue says about itself, whatever it holds: its name and how many facts wait in it export property QueueTag { @@ -24,6 +25,7 @@ export function queue_new(name: string) -> Queue { q.tag = new QueueTag q.tag.name = name q.none = new []T + q.spare = new []T return q } @@ -32,13 +34,16 @@ export function q_push(q: Queue, v: T) -> void { q.tag.pending = len(q.items) } -# the facts in the order they were pushed; the queue is empty afterwards. Most queues are empty -# most frames, and Ludic frees nothing: an empty drain hands back one shared empty list rather -# than a new one a frame. Read what a drain returns; never push to it. +# the facts in the order they were pushed; the queue is empty afterwards. Ludic frees nothing, so +# a drain allocates nothing: the queue keeps two lists and hands one out while the other fills, and +# an empty drain hands back one shared empty list. What a drain returns is good until the NEXT +# drain of the same queue - read it, never push to it, never keep it. export function q_drain(q: Queue) -> []T { if len(q.items) == 0 { return q.none } let out = q.items - q.items = new []T + List.clear(q.spare) + q.items = q.spare + q.spare = out q.tag.pending = 0 return out } diff --git a/packages/ludic.base/tests/queue_test.ludic b/packages/ludic.base/tests/queue_test.ludic index 15631343..cbf46091 100644 --- a/packages/ludic.base/tests/queue_test.ludic +++ b/packages/ludic.base/tests/queue_test.ludic @@ -87,6 +87,23 @@ program QueueTest { } test "a queue drains in the order it was pushed" () { fifo_case() } + # two lists, reused: a drained list is the caller's until the next drain of that queue + function reuse_case() -> void { + let q: Queue = queue_new("reused") + q_push(q, 1) + let a = q_drain(q) + q_push(q, 2) + expect_eq(a[0], 1) + let b = q_drain(q) + expect_eq(b[0], 2) + q_push(q, 3) + let c = q_drain(q) + expect(c == a) + expect_eq(len(c), 1) + expect_eq(c[0], 3) + } + + test "a drain reuses the queue's two lists" () { reuse_case() } test "an empty drain allocates nothing, and a drained list is the caller's" () { empty_case() } test "a queue holds records" () { record_case() } test "clear drops what is waiting" () { clear_case() }