11features: local
22
3- // A fixed-capacity mailbox (message queue) implemented in RSS itself, on top of
4- // parallel lists. Each message carries a source id and a monotonic sequence
5- // number; `take` returns the OLDEST pending message (lowest seq), optionally
6- // filtered by source.
7- //
8- // NOTE (language finding): RSS does not allow reassigning a *scalar* struct field
9- // through a `mut` parameter (`m.count = ...` is rejected). `List` fields are
10- // reference types, so `List.set(list: mut m.field, ...)` mutates in place and
11- // propagates. So mutable scalar state (`next_seq`) is held in a 1-element list,
12- // and `count` is computed by scanning. See docs/rss-selfhost-ledger.md SH-007.
3+ // A fixed-capacity ring-buffer mailbox (message queue) implemented in RSS itself.
4+ // `head`/`count` index a circular live window over `values`/`sources` (grown lazily
5+ // up to `capacity`). Common send/take are O(1); only the source-filtered take scans
6+ // (and compacts). Mutable scalar state lives in plain `Int` fields, mutated
7+ // through `mut` parameters (see ledger SH-013) — no `List<Int >`-as-cell.
138struct Mailbox<T > {
149 capacity: Int
15- next_seq: List<Int >
16- msg: List<T >
17- valid: List<Bool >
18- src: List<Int >
19- seq: List<Int >
10+ head: Int
11+ count: Int
12+ values: List<T >
13+ sources: List<Int >
2014}
2115
2216fn mailbox_new<T >(capacity: Int) -> fresh Mailbox<T > {
23- let mut next_seq = List<Int >.new()
24- List.push<Int >(list: mut next_seq, value: read 0)
2517 return Mailbox(
2618 capacity: capacity,
27- next_seq: next_seq,
28- msg: List<T >.new(),
29- valid: List<Bool >.new(),
30- src: List<Int >.new(),
31- seq: List<Int >.new(),
19+ head: 0,
20+ count: 0,
21+ values: List<T >.new(),
22+ sources: List<Int >.new(),
3223 )
3324}
3425
3526fn mailbox_count<T >(m: read Mailbox<T >) -> Int {
36- let mut count = 0
37- let mut i = 0
38- let len = List.len<Bool >(list: read m.valid)
39- while i < len {
40- if List.get<Bool >(list: read m.valid, index: i) {
41- count = count + 1
42- }
43- i = i + 1
44- }
45- return count
27+ return m.count
4628}
4729
48- // Store a message; returns false if the mailbox is full.
30+ // Store a message at the tail. O(1). Returns false if full.
4931fn mailbox_send<T >(m: mut Mailbox<T >, value: read T, source: Int) -> Bool {
50- if mailbox_count< T >(m: read m) >= m.capacity {
32+ if m.count >= m.capacity {
5133 return false
5234 }
53- let seq = List.get<Int >(list: read m.next_seq, index: 0)
54- let next = seq + 1
55- List.set<Int >(list: mut m.next_seq, index: 0, value: read next)
56-
57- let mut i = 0
58- let len = List.len<Bool >(list: read m.valid)
59- while i < len {
60- if List.get<Bool >(list: read m.valid, index: i) == false {
61- List.set<T >(list: mut m.msg, index: i, value: read value)
62- List.set<Bool >(list: mut m.valid, index: i, value: read true)
63- List.set<Int >(list: mut m.src, index: i, value: read source)
64- List.set<Int >(list: mut m.seq, index: i, value: read seq)
65- return true
66- }
67- i = i + 1
35+ let raw = m.head + m.count
36+ let tail = raw % m.capacity
37+ // Slots fill 0..capacity-1 contiguously, so `tail` is either an existing slot
38+ // (reuse) or exactly the next one to allocate (grow). Avoids needing a generic
39+ // placeholder value to pre-size the lists.
40+ if tail < List.len<T >(list: read m.values) {
41+ List.set<T >(list: mut m.values, index: tail, value: read value)
42+ List.set<Int >(list: mut m.sources, index: tail, value: read source)
43+ } else {
44+ List.push<T >(list: mut m.values, value: read value)
45+ List.push<Int >(list: mut m.sources, value: read source)
6846 }
69-
70- List.push<T >(list: mut m.msg, value: read value)
71- List.push<Bool >(list: mut m.valid, value: read true)
72- List.push<Int >(list: mut m.src, value: read source)
73- List.push<Int >(list: mut m.seq, value: read seq)
47+ m.count = m.count + 1
7448 return true
7549}
7650
77- // Slot index of the oldest valid message (optionally only from `source`), or -1.
78- fn oldest_slot<T >(m: read Mailbox<T >, filtered: Bool, source: Int) -> Int {
79- let mut best = -1
80- let mut best_seq = 0
81- let mut i = 0
82- let len = List.len<Bool >(list: read m.valid)
83- while i < len {
84- if List.get<Bool >(list: read m.valid, index: i) {
85- let mut matches = true
86- if filtered {
87- if List.get<Int >(list: read m.src, index: i) != source {
88- matches = false
89- }
90- }
91- if matches {
92- let s = List.get<Int >(list: read m.seq, index: i)
93- if best < 0 {
94- best = i
95- best_seq = s
96- } else {
97- if s < best_seq {
98- best = i
99- best_seq = s
100- }
101- }
102- }
103- }
104- i = i + 1
105- }
106- return best
107- }
108-
109- fn take_at<T >(m: mut Mailbox<T >, slot: Int) -> T {
110- let value = List.get<T >(list: read m.msg, index: slot)
111- List.set<Bool >(list: mut m.valid, index: slot, value: read false)
112- return value
113- }
114-
51+ // Take the oldest message (ring head). O(1).
11552fn mailbox_take<T >(m: mut Mailbox<T >) -> Option<T > {
116- let slot = oldest_slot<T >(m: read m, filtered: false, source: 0)
117- if slot < 0 {
53+ if m.count <= 0 {
11854 return None
11955 }
120- return Some(take_at<T >(m: mut m, slot: slot))
56+ let value = List.get<T >(list: read m.values, index: m.head)
57+ let next = m.head + 1
58+ m.head = next % m.capacity
59+ m.count = m.count - 1
60+ return Some(value)
12161}
12262
63+ // Take the oldest message from `source`. Scans the live window; on a middle
64+ // removal, shifts the older elements forward by one to close the gap (preserving
65+ // FIFO order of the rest).
12366fn mailbox_take_from<T >(m: mut Mailbox<T >, source: Int) -> Option<T > {
124- let slot = oldest_slot<T >(m: read m, filtered: true, source: source)
125- if slot < 0 {
67+ let mut offset = -1
68+ let mut k = 0
69+ while k < m.count {
70+ let raw = m.head + k
71+ let idx = raw % m.capacity
72+ if List.get<Int >(list: read m.sources, index: idx) == source {
73+ offset = k
74+ k = m.count
75+ } else {
76+ k = k + 1
77+ }
78+ }
79+ if offset < 0 {
12680 return None
12781 }
128- return Some(take_at<T >(m: mut m, slot: slot))
82+ let take_raw = m.head + offset
83+ let take_idx = take_raw % m.capacity
84+ let value = List.get<T >(list: read m.values, index: take_idx)
85+ let mut j = offset
86+ while j > 0 {
87+ let dst_raw = m.head + j
88+ let dst = dst_raw % m.capacity
89+ let src_raw = m.head + j - 1
90+ let src = src_raw % m.capacity
91+ let moved_value = List.get<T >(list: read m.values, index: src)
92+ let moved_source = List.get<Int >(list: read m.sources, index: src)
93+ List.set<T >(list: mut m.values, index: dst, value: read moved_value)
94+ List.set<Int >(list: mut m.sources, index: dst, value: read moved_source)
95+ j = j - 1
96+ }
97+ let next = m.head + 1
98+ m.head = next % m.capacity
99+ m.count = m.count - 1
100+ return Some(value)
129101}
130102
131103fn show(label: read String, value: Int) -> Unit {
@@ -142,7 +114,6 @@ fn main() -> Unit {
142114 let _b = mailbox_send<Int >(m: mut box, value: read 20, source: 2)
143115 let _c = mailbox_send<Int >(m: mut box, value: read 30, source: 1)
144116
145- // Match the owned `Option` returned by each take directly (keeps `v` an `Int`).
146117 match mailbox_take<Int >(m: mut box) {
147118 Some(v) => { show(label: read "oldest=", value: v) }
148119 None => { show_none(label: read "oldest=") }
0 commit comments