Skip to main content

pathland_core/
ring.rs

1//! Producer-side ring helpers operating on the ring region and header cursors.
2//!
3//! The ring is a power-of-two slot array. The guest writes at `writeCursor` and
4//! the host reads from `readCursor`. Both cursors live in the header block.
5
6use alloc::vec::Vec;
7
8use crate::opcode::Opcode;
9use crate::{constants, memory, memory::header};
10
11/// Errors produced by the ring producer.
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum RingError {
14    /// The ring is full (consumer has not caught up). The producer SHOULD
15    /// publish a frame before emitting further opcodes.
16    Full,
17    /// An opcode payload could not be encoded (e.g. an invalid attributed-text
18    /// range or oversized text). The producer should fix the payload.
19    InvalidPayload,
20}
21
22/// Append `op` to the ring at `writeCursor` and advance the cursor.
23///
24/// `slots` is the full ring region; `mask` wraps the cursor. Fails with
25/// `RingError::Full` when the next slot equals `readCursor`.
26#[inline]
27pub(crate) fn push(slots: &mut [u8], header: &mut [u8], mask: usize, op: &Opcode) -> Result<(), RingError> {
28    let write = cursor(header, memory::OFF_WRITE_CURSOR, mask);
29    let next = (write + 1) & mask;
30    if next == cursor(header, memory::OFF_READ_CURSOR, mask) {
31        return Err(RingError::Full);
32    }
33    let bytes = op.to_bytes();
34    let off = write * Opcode::SIZE;
35    slots[off..off + Opcode::SIZE].copy_from_slice(&bytes);
36    header::set_u32(header, memory::OFF_WRITE_CURSOR, next as u32);
37    Ok(())
38}
39
40/// Read the current cursor value for a header offset, masked.
41#[inline]
42pub(crate) fn cursor(header: &[u8], off: usize, mask: usize) -> usize {
43    header::get_u32(header, off) as usize & mask
44}
45
46/// Emit a CREATE_NODE opcode.
47pub(crate) fn push_create_node(
48    slots: &mut [u8],
49    header: &mut [u8],
50    mask: usize,
51    node_id: u32,
52    component_type: u16,
53) -> Result<(), RingError> {
54    push(
55        slots,
56        header,
57        mask,
58        &Opcode::new(
59            constants::category::TREE,
60            constants::tree::CREATE_NODE,
61            0,
62            node_id,
63            component_type as u32,
64            0,
65        ),
66    )
67}
68
69pub(crate) fn push_delete_node(
70    slots: &mut [u8],
71    header: &mut [u8],
72    mask: usize,
73    node_id: u32,
74) -> Result<(), RingError> {
75    push(
76        slots,
77        header,
78        mask,
79        &Opcode::new(constants::category::TREE, constants::tree::DELETE_NODE, 0, node_id, 0, 0),
80    )
81}
82
83pub(crate) fn push_insert_child(
84    slots: &mut [u8],
85    header: &mut [u8],
86    mask: usize,
87    parent: u32,
88    child: u32,
89    index: u32,
90) -> Result<(), RingError> {
91    push(
92        slots,
93        header,
94        mask,
95        &Opcode::new(
96            constants::category::TREE,
97            constants::tree::INSERT_CHILD,
98            0,
99            parent,
100            child,
101            index,
102        ),
103    )
104}
105
106pub(crate) fn push_remove_child(
107    slots: &mut [u8],
108    header: &mut [u8],
109    mask: usize,
110    parent: u32,
111    child: u32,
112) -> Result<(), RingError> {
113    push(
114        slots,
115        header,
116        mask,
117        &Opcode::new(
118            constants::category::TREE,
119            constants::tree::REMOVE_CHILD,
120            0,
121            parent,
122            child,
123            0,
124        ),
125    )
126}
127
128pub(crate) fn push_move_child(
129    slots: &mut [u8],
130    header: &mut [u8],
131    mask: usize,
132    parent: u32,
133    child: u32,
134    new_index: u32,
135) -> Result<(), RingError> {
136    push(
137        slots,
138        header,
139        mask,
140        &Opcode::new(
141            constants::category::TREE,
142            constants::tree::MOVE_CHILD,
143            0,
144            parent,
145            child,
146            new_index,
147        ),
148    )
149}
150
151pub(crate) fn push_set_property(
152    slots: &mut [u8],
153    header: &mut [u8],
154    mask: usize,
155    node_id: u32,
156    property_id: u16,
157    value_type: u8,
158    value: u32,
159) -> Result<(), RingError> {
160    push(
161        slots,
162        header,
163        mask,
164        &Opcode::new(
165            constants::category::PARAMETER,
166            constants::parameter::SET_PROPERTY,
167            0,
168            node_id,
169            ((value_type as u32) << 16) | property_id as u32,
170            value,
171        ),
172    )
173}
174
175pub(crate) fn push_set_text(
176    slots: &mut [u8],
177    header: &mut [u8],
178    mask: usize,
179    node_id: u32,
180    arena_ref: u32,
181) -> Result<(), RingError> {
182    push(
183        slots,
184        header,
185        mask,
186        &Opcode::new(
187            constants::category::PARAMETER,
188            constants::parameter::SET_TEXT,
189            0,
190            node_id,
191            arena_ref,
192            0,
193        ),
194    )
195}
196
197pub(crate) fn push_set_attributed_text(
198    slots: &mut [u8],
199    header: &mut [u8],
200    mask: usize,
201    node_id: u32,
202    arena_ref: u32,
203) -> Result<(), RingError> {
204    push(
205        slots,
206        header,
207        mask,
208        &Opcode::new(
209            constants::category::PARAMETER,
210            constants::parameter::SET_ATTRIBUTED_TEXT,
211            0,
212            node_id,
213            arena_ref,
214            0,
215        ),
216    )
217}
218
219pub(crate) fn push_reset(
220    slots: &mut [u8],
221    header: &mut [u8],
222    mask: usize,
223) -> Result<(), RingError> {
224    push(
225        slots,
226        header,
227        mask,
228        &Opcode::new(constants::category::META, constants::meta::RESET, 0, 0, 0, 0),
229    )
230}
231
232// ---------------------------------------------------------------------------
233// Host → guest event ring
234// ---------------------------------------------------------------------------
235
236/// Append an `EVENT`-category opcode to the host → guest event ring.
237///
238/// The host (renderer) writes at `eventWriteCursor`; the guest reads from
239/// `eventReadCursor`. Same 16-byte slot layout as the guest → host ring.
240#[inline]
241pub(crate) fn push_event(
242    event_slots: &mut [u8],
243    header: &mut [u8],
244    mask: usize,
245    op: &Opcode,
246) -> Result<(), RingError> {
247    let write = cursor(header, memory::OFF_EVENT_WRITE_CURSOR, mask);
248    let next = (write + 1) & mask;
249    if next == cursor(header, memory::OFF_EVENT_READ_CURSOR, mask) {
250        return Err(RingError::Full);
251    }
252    let bytes = op.to_bytes();
253    let off = write * Opcode::SIZE;
254    event_slots[off..off + Opcode::SIZE].copy_from_slice(&bytes);
255    header::set_u32(header, memory::OFF_EVENT_WRITE_CURSOR, next as u32);
256    Ok(())
257}
258
259/// Drain pending `EVENT`-category opcodes from the host → guest event ring,
260/// advancing `eventReadCursor`. The guest (app/engine) reads raw inputs here.
261///
262/// The masked cursors wrap independently — the writer can lap the reader
263/// numerically — so the pending count is computed modulo the ring size. A
264/// plain `write < read` guard would permanently deadlock the ring the moment
265/// the writer wraps past the reader (the reader never advances, the writer is
266/// stuck "full", and every drain returns empty). `max` bounds how many opcodes
267/// are drained (the rest stay pending for the next call).
268#[inline]
269pub(crate) fn read_events(
270    event_slots: &[u8],
271    header: &mut [u8],
272    mask: usize,
273    max: usize,
274) -> Vec<Opcode> {
275    let read = cursor(header, memory::OFF_EVENT_READ_CURSOR, mask);
276    let write = cursor(header, memory::OFF_EVENT_WRITE_CURSOR, mask);
277    let count = write.wrapping_sub(read) & mask;
278    let n = count.min(max);
279    if n == 0 {
280        return Vec::new();
281    }
282    let mut out = Vec::with_capacity(n);
283    for i in 0..n {
284        let slot = (read + i) & mask;
285        let off = slot * Opcode::SIZE;
286        let bytes: [u8; Opcode::SIZE] =
287            event_slots[off..off + Opcode::SIZE].try_into().unwrap();
288        out.push(Opcode::from_bytes(&bytes));
289    }
290    header::set_u32(header, memory::OFF_EVENT_READ_CURSOR, ((read + n) & mask) as u32);
291    out
292}
293
294#[cfg(test)]
295mod tests {
296    use super::*;
297    use crate::memory::header;
298    use alloc::vec;
299
300    /// A tiny initialized linear memory with `slot_count` slots and a bare
301    /// arena.
302    fn tiny_mem(slot_count: usize) -> Vec<u8> {
303        let layout = crate::memory::MemoryLayout {
304            slot_count,
305            arena_bytes: 64,
306            event_arena_bytes: 64,
307        };
308        let mut mem = vec![0u8; layout.total_bytes()];
309        crate::init_memory(&mut mem, &layout);
310        mem
311    }
312
313    #[test]
314    fn push_reports_full_at_exact_capacity() {
315        let slot_count = 4usize;
316        let mut mem = tiny_mem(slot_count);
317        let mask = slot_count - 1;
318
319        // With a 4-slot ring, capacity is slot_count - 1 = 3 opcodes.
320        let (head, rest) = mem.split_at_mut(crate::memory::HEADER_SIZE);
321        let (slots, _rest) = rest.split_at_mut(slot_count * Opcode::SIZE);
322
323        for i in 0..3 {
324            push(slots, head, mask, &Opcode::new(crate::category::TREE, 1, 0, i, 0, 0)).unwrap();
325        }
326        assert_eq!(
327            push(slots, head, mask, &Opcode::new(crate::category::TREE, 1, 0, 9, 0, 0)),
328            Err(RingError::Full)
329        );
330        // writeCursor stopped at slot_count - 1, never wrapping.
331        assert_eq!(header::get_u32(head, crate::memory::OFF_WRITE_CURSOR), 3);
332    }
333
334    #[test]
335    fn cursor_masks_out_of_range_header_values() {
336        let mut mem = tiny_mem(8);
337        let (head, _) = mem.split_at_mut(crate::memory::HEADER_SIZE);
338        header::set_u32(head, crate::memory::OFF_WRITE_CURSOR, 17);
339        // mask = 7, so 17 & 7 = 1.
340        assert_eq!(cursor(head, crate::memory::OFF_WRITE_CURSOR, 7), 1);
341    }
342
343    #[test]
344    fn push_event_and_read_events_round_trip() {
345        let slot_count = 4usize;
346        let mut mem = tiny_mem(slot_count);
347        let mask = slot_count - 1;
348
349        let (head, rest) = mem.split_at_mut(crate::memory::HEADER_SIZE);
350        let (_slots, rest) = rest.split_at_mut(slot_count * Opcode::SIZE);
351        let (event_slots, _arena) = rest.split_at_mut(slot_count * Opcode::SIZE);
352
353        let op = Opcode::new(crate::category::EVENT, crate::event::POINTER_UP, 0, 5, 0, 0);
354        push_event(event_slots, head, mask, &op).unwrap();
355
356        let drained = read_events(event_slots, head, mask, usize::MAX);
357        assert_eq!(drained, vec![op]);
358        // Second read is empty (readCursor advanced).
359        assert!(read_events(event_slots, head, mask, usize::MAX).is_empty());
360        assert_eq!(
361            header::get_u32(head, crate::memory::OFF_EVENT_READ_CURSOR),
362            header::get_u32(head, crate::memory::OFF_EVENT_WRITE_CURSOR)
363        );
364    }
365
366    #[test]
367    fn push_event_reports_full_and_recovers() {
368        let slot_count = 2usize;
369        let mut mem = tiny_mem(slot_count);
370        let mask = slot_count - 1;
371
372        let (head, rest) = mem.split_at_mut(crate::memory::HEADER_SIZE);
373        let (_slots, rest) = rest.split_at_mut(slot_count * Opcode::SIZE);
374        let (event_slots, _arena) = rest.split_at_mut(slot_count * Opcode::SIZE);
375
376        let op = Opcode::new(crate::category::EVENT, crate::event::POINTER_UP, 0, 5, 0, 0);
377        assert!(push_event(event_slots, head, mask, &op).is_ok());
378        assert_eq!(
379            push_event(event_slots, head, mask, &op),
380            Err(RingError::Full)
381        );
382
383        // Guest drains, freeing the slot; the host can write again.
384        let drained = read_events(event_slots, head, mask, usize::MAX);
385        assert_eq!(drained.len(), 1);
386        assert!(push_event(event_slots, head, mask, &op).is_ok());
387    }
388
389    #[test]
390    fn read_events_drains_a_wrapped_producer() {
391        let slot_count = 4usize;
392        let mut mem = tiny_mem(slot_count);
393        let mask = slot_count - 1;
394
395        let (head, rest) = mem.split_at_mut(crate::memory::HEADER_SIZE);
396        let (_slots, rest) = rest.split_at_mut(slot_count * Opcode::SIZE);
397        let (event_slots, _arena) = rest.split_at_mut(slot_count * Opcode::SIZE);
398
399        let op = Opcode::new(crate::category::EVENT, crate::event::POINTER_UP, 0, 5, 0, 0);
400        // Write 3 events into slots 0,1,2 (capacity is slot_count - 1 = 3).
401        for _ in 0..3 {
402            push_event(event_slots, head, mask, &op).unwrap();
403        }
404        // Reader drains ONE (bounded), so readCursor advances to 1.
405        assert_eq!(read_events(event_slots, head, mask, 1).len(), 1);
406        // Writer can write again: slot 3, writeCursor wraps 3 -> 0. Now the
407        // masked write(0) is numerically BELOW the masked read(1) — the wrap
408        // that used to deadlock the ring.
409        push_event(event_slots, head, mask, &op).unwrap();
410        assert_eq!(
411            header::get_u32(head, crate::memory::OFF_EVENT_READ_CURSOR),
412            1
413        );
414        assert_eq!(
415            header::get_u32(head, crate::memory::OFF_EVENT_WRITE_CURSOR),
416            0
417        );
418
419        // The wrapped run [1..4) + [0..0) = slots 1,2,3 must drain in order,
420        // and readCursor must advance to the (masked) write cursor.
421        let drained = read_events(event_slots, head, mask, usize::MAX);
422        assert_eq!(drained, vec![op; 3]);
423        assert_eq!(
424            header::get_u32(head, crate::memory::OFF_EVENT_READ_CURSOR),
425            0
426        );
427        // And the writer can wrap again without ever deadlocking.
428        assert!(push_event(event_slots, head, mask, &op).is_ok());
429        assert_eq!(
430            read_events(event_slots, head, mask, usize::MAX).len(),
431            1
432        );
433    }
434}