1#![no_std]
46#![forbid(unsafe_op_in_unsafe_fn)]
47
48#[cfg(test)]
49extern crate std;
50
51extern crate alloc;
52
53mod arena;
54pub mod attributed;
55mod conformance;
56mod constants;
57mod events;
58pub mod fit;
59mod memory;
60mod opcode;
61mod ring;
62pub mod tokens;
63mod window;
64
65pub use arena::ArenaError;
66pub use attributed::{
67 AttributedError, AttributedSegment, AttributedString, LinkRun, StyleRun, TextAttribute,
68};
69pub use constants::*;
70pub use events::{Event, EventError};
71pub use memory::MemoryLayout;
72pub use opcode::{Opcode, OPCODE_SIZE};
73pub use ring::RingError;
74pub use window::WindowMeta;
75
76use alloc::vec;
77use alloc::vec::Vec;
78use arena as arena_fn;
79use memory::header;
80use memory::{OFF_FRAME_COUNT, OFF_READ_CURSOR, OFF_SLOT_COUNT, OFF_WRITE_CURSOR};
81use ring as ring_fn;
82
83pub fn init_memory(mem: &mut [u8], layout: &MemoryLayout) {
86 header::init(mem, layout);
87}
88
89pub struct SharedHeader<'a> {
96 mem: &'a [u8],
97}
98
99impl<'a> SharedHeader<'a> {
100 pub fn new(mem: &'a [u8]) -> Self {
102 Self { mem }
103 }
104
105 fn u32(&self, off: usize) -> u32 {
106 u32::from_le_bytes(self.mem[off..off + 4].try_into().unwrap())
107 }
108
109 pub fn frame_count(&self) -> u32 {
111 self.u32(memory::OFF_FRAME_COUNT)
112 }
113
114 pub fn read_cursor(&self) -> u32 {
116 self.u32(memory::OFF_READ_CURSOR)
117 }
118
119 pub fn write_cursor(&self) -> u32 {
121 self.u32(memory::OFF_WRITE_CURSOR)
122 }
123
124 pub fn slot_count(&self) -> u32 {
126 self.u32(memory::OFF_SLOT_COUNT)
127 }
128
129 pub fn ring_offset(&self) -> usize {
131 self.u32(memory::OFF_RING_OFFSET) as usize
132 }
133
134 pub fn ring_bytes(&self) -> usize {
136 self.u32(memory::OFF_RING_BYTES) as usize
137 }
138
139 pub fn arena_offset(&self) -> usize {
141 self.u32(memory::OFF_ARENA_OFFSET) as usize
142 }
143
144 pub fn arena_cursor(&self) -> u32 {
146 self.u32(memory::OFF_ARENA_CURSOR)
147 }
148}
149
150fn slot_count(header: &[u8]) -> usize {
152 header::get_u32(header, OFF_SLOT_COUNT) as usize
153}
154
155fn slot_mask(header: &[u8]) -> usize {
157 let slots = slot_count(header);
158 debug_assert!(slots.is_power_of_two());
159 slots - 1
160}
161
162pub struct Guest<'a> {
169 header: &'a mut [u8],
170 slots: &'a mut [u8],
171 event_slots: &'a mut [u8],
173 arena: &'a mut [u8],
174 event_arena: &'a mut [u8],
177 frame_start: u32,
179}
180
181impl<'a> Guest<'a> {
182 pub fn new(mem: &'a mut [u8], layout: &MemoryLayout) -> Self {
185 let (header, rest) = mem.split_at_mut(memory::HEADER_SIZE);
186 let (slots, rest) = rest.split_at_mut(layout.ring_bytes());
187 let (event_slots, rest) = rest.split_at_mut(layout.event_ring_bytes());
188 let (arena, event_arena) = rest.split_at_mut(layout.arena_bytes);
189 let frame_start = header::get_u32(header, OFF_WRITE_CURSOR);
190 Self {
191 header,
192 slots,
193 event_slots,
194 arena,
195 event_arena,
196 frame_start,
197 }
198 }
199
200 fn mask(&self) -> usize {
201 slot_mask(self.header)
202 }
203
204 pub fn begin_frame(&mut self) {
206 self.frame_start = header::get_u32(self.header, OFF_WRITE_CURSOR);
207 }
208
209 pub fn pending(&self) -> usize {
211 let write = header::get_u32(self.header, OFF_WRITE_CURSOR);
212 let mask = self.mask() as u32;
213 (write.wrapping_sub(self.frame_start) & mask) as usize
214 }
215
216 pub fn end_frame(&mut self) {
218 let count = header::get_u32(self.header, OFF_FRAME_COUNT);
219 header::set_u32(self.header, OFF_FRAME_COUNT, count.wrapping_add(1));
220 }
221
222 pub fn frame_count(&self) -> u32 {
224 header::get_u32(self.header, OFF_FRAME_COUNT)
225 }
226
227 pub fn create_node(&mut self, node_id: u32, component_type: u16) -> Result<(), RingError> {
230 ring_fn::push_create_node(self.slots, self.header, self.mask(), node_id, component_type)
231 }
232
233 pub fn delete_node(&mut self, node_id: u32) -> Result<(), RingError> {
234 ring_fn::push_delete_node(self.slots, self.header, self.mask(), node_id)
235 }
236
237 pub fn insert_child(&mut self, parent: u32, child: u32, index: u32) -> Result<(), RingError> {
238 ring_fn::push_insert_child(self.slots, self.header, self.mask(), parent, child, index)
239 }
240
241 pub fn remove_child(&mut self, parent: u32, child: u32) -> Result<(), RingError> {
242 ring_fn::push_remove_child(self.slots, self.header, self.mask(), parent, child)
243 }
244
245 pub fn move_child(
246 &mut self,
247 parent: u32,
248 child: u32,
249 new_index: u32,
250 ) -> Result<(), RingError> {
251 ring_fn::push_move_child(self.slots, self.header, self.mask(), parent, child, new_index)
252 }
253
254 pub fn push_opcode(&mut self, op: Opcode) -> Result<(), RingError> {
260 ring_fn::push(self.slots, self.header, self.mask(), &op)
261 }
262
263 pub fn set_property(
266 &mut self,
267 node_id: u32,
268 property_id: u16,
269 value_type: u8,
270 value: u32,
271 ) -> Result<(), RingError> {
272 ring_fn::push_set_property(
273 self.slots,
274 self.header,
275 self.mask(),
276 node_id,
277 property_id,
278 value_type,
279 value,
280 )
281 }
282
283 pub fn set_text(&mut self, node_id: u32, text: &str) -> Result<u32, RingError> {
285 let arena_ref = arena_fn::alloc_str(self.arena, self.header, text).map_err(|_| RingError::Full)?;
286 ring_fn::push_set_text(self.slots, self.header, self.mask(), node_id, arena_ref)?;
287 Ok(arena_ref)
288 }
289
290 pub fn set_attributed_text(
295 &mut self,
296 node_id: u32,
297 text: &crate::AttributedString,
298 ) -> Result<u32, RingError> {
299 let payload = text.encode().map_err(|_| RingError::InvalidPayload)?;
300 let arena_ref = arena_fn::alloc(self.arena, self.header, &payload).map_err(|_| RingError::Full)?;
301 ring_fn::push_set_attributed_text(self.slots, self.header, self.mask(), node_id, arena_ref)?;
302 Ok(arena_ref)
303 }
304
305 pub fn set_date(&mut self, node_id: u32, days: i32, millis: u32) -> Result<(), RingError> {
308 let op = Opcode::new(
309 category::PARAMETER,
310 crate::parameter::SET_DATE,
311 0,
312 node_id,
313 days as u32,
314 millis,
315 );
316 ring_fn::push(self.slots, self.header, self.mask(), &op)
317 }
318
319 pub fn set_design_token(
324 &mut self,
325 path: &str,
326 value_type: u8,
327 value: u32,
328 ) -> Result<u32, RingError> {
329 let arena_ref = arena_fn::alloc_str(self.arena, self.header, path)
330 .map_err(|_| RingError::Full)?;
331 let op = Opcode::new(
332 category::PARAMETER,
333 crate::parameter::SET_DESIGN_TOKEN,
334 0,
335 arena_ref,
336 u32::from(value_type),
337 value,
338 );
339 ring_fn::push(self.slots, self.header, self.mask(), &op)?;
340 Ok(arena_ref)
341 }
342
343 pub fn set_design_token_string(
348 &mut self,
349 path: &str,
350 value: &str,
351 ) -> Result<u32, RingError> {
352 let arena_ref = arena_fn::alloc_str(self.arena, self.header, path)
353 .map_err(|_| RingError::Full)?;
354 let value_ref = arena_fn::alloc_str(self.arena, self.header, value)
355 .map_err(|_| RingError::Full)?;
356 let op = Opcode::new(
357 category::PARAMETER,
358 crate::parameter::SET_DESIGN_TOKEN,
359 0,
360 arena_ref,
361 u32::from(crate::value_type::STRING),
362 value_ref,
363 );
364 ring_fn::push(self.slots, self.header, self.mask(), &op)?;
365 Ok(arena_ref)
366 }
367
368 pub fn alloc(&mut self, bytes: &[u8]) -> Result<u32, ArenaError> {
370 arena_fn::alloc(self.arena, self.header, bytes)
371 }
372
373 pub fn alloc_str(&mut self, s: &str) -> Result<u32, ArenaError> {
375 arena_fn::alloc_str(self.arena, self.header, s)
376 }
377
378 pub fn reset(&mut self) -> Result<(), RingError> {
381 ring_fn::push_reset(self.slots, self.header, self.mask())
382 }
383
384 pub fn set_color_scheme(&mut self, mode: u32) -> Result<(), RingError> {
387 let op = Opcode::new(category::META, crate::meta::SET_COLOR_SCHEME, 0, mode, 0, 0);
388 ring_fn::push(self.slots, self.header, self.mask(), &op)
389 }
390
391 pub fn reset_theme(&mut self) -> Result<(), RingError> {
393 let op = Opcode::new(category::META, crate::meta::RESET_THEME, 0, 0, 0, 0);
394 ring_fn::push(self.slots, self.header, self.mask(), &op)
395 }
396
397 pub fn drain_events(&mut self) -> Vec<Event> {
404 let mask = self.mask();
405 let ops = ring_fn::read_events(&self.event_slots, self.header, mask, usize::MAX);
406 ops.into_iter()
407 .filter_map(|op| crate::events::decode_event(&op, self.event_arena))
408 .collect()
409 }
410
411 pub fn drain_event_opcodes(&mut self) -> Vec<Opcode> {
415 let mask = self.mask();
416 ring_fn::read_events(&self.event_slots, self.header, mask, usize::MAX)
417 }
418
419 pub fn drain_event_opcodes_max(&mut self, max: usize) -> Vec<Opcode> {
423 let mask = self.mask();
424 ring_fn::read_events(&self.event_slots, self.header, mask, max)
425 }
426
427 pub fn event_arena_cursor(&self) -> u32 {
429 header::get_u32(self.header, memory::OFF_EVENT_ARENA_CURSOR)
430 }
431
432 pub fn push_event_opcode(&mut self, op: Opcode) -> Result<(), RingError> {
438 ring_fn::push_event(self.event_slots, self.header, self.mask(), &op)
439 }
440}
441
442pub struct Frame<'a> {
453 slots: &'a [u8],
454 arena: &'a [u8],
455 start: usize,
456 end: usize,
457}
458
459impl<'a> Frame<'a> {
460 pub fn from_parts(slots: &'a [u8], arena: &'a [u8], start: usize, end: usize) -> Self {
466 Self {
467 slots,
468 arena,
469 start,
470 end,
471 }
472 }
473
474 pub fn opcodes(&self) -> FrameIter<'a> {
476 FrameIter {
477 slots: self.slots,
478 cursor: self.start,
479 end: self.end,
480 }
481 }
482
483 pub fn len(&self) -> usize {
485 (self.end - self.start) / Opcode::SIZE
486 }
487
488 pub fn is_empty(&self) -> bool {
489 self.len() == 0
490 }
491
492 pub fn arena_get(&self, offset: u32) -> Result<&'a [u8], ArenaError> {
494 arena_fn::get(self.arena, offset)
495 }
496
497 pub fn arena_str(&self, offset: u32) -> Result<&'a str, ArenaError> {
499 arena_fn::get_str(self.arena, offset)
500 }
501
502 pub fn arena(&self) -> &'a [u8] {
504 self.arena
505 }
506}
507
508pub struct FrameIter<'a> {
510 slots: &'a [u8],
511 cursor: usize,
512 end: usize,
513}
514
515impl<'a> Iterator for FrameIter<'a> {
516 type Item = Opcode;
517
518 fn next(&mut self) -> Option<Opcode> {
519 if self.cursor >= self.end {
520 return None;
521 }
522 let bytes: [u8; 16] = self.slots[self.cursor..self.cursor + 16].try_into().unwrap();
523 self.cursor += 16;
524 Some(Opcode::from_bytes(&bytes))
525 }
526}
527
528pub struct Host<'a> {
535 header: &'a mut [u8],
536 slots: &'a [u8],
537 event_slots: &'a mut [u8],
539 arena: &'a [u8],
540 event_arena: &'a mut [u8],
543 consumed: u32,
545}
546
547impl<'a> Host<'a> {
548 pub fn new(mem: &'a mut [u8], layout: &MemoryLayout) -> Self {
550 let (header, rest) = mem.split_at_mut(memory::HEADER_SIZE);
551 let (slots, rest) = rest.split_at_mut(layout.ring_bytes());
552 let (event_slots, rest) = rest.split_at_mut(layout.event_ring_bytes());
553 let (arena, event_arena) = rest.split_at_mut(layout.arena_bytes);
554 let slots: &'a [u8] = slots;
555 let arena: &'a [u8] = arena;
556 Self {
557 header,
558 slots,
559 event_slots,
560 arena,
561 event_arena,
562 consumed: 0,
563 }
564 }
565
566 pub fn frame_count(&self) -> u32 {
568 header::get_u32(self.header, OFF_FRAME_COUNT)
569 }
570
571 pub fn frames(&mut self) -> Vec<Frame<'a>> {
576 let frame_count = header::get_u32(self.header, OFF_FRAME_COUNT);
577 if self.consumed >= frame_count {
578 return Vec::new();
579 }
580 let mask = slot_mask(self.header);
581 let start = ring_fn::cursor(self.header, OFF_READ_CURSOR, mask);
582 let end = ring_fn::cursor(self.header, OFF_WRITE_CURSOR, mask);
583
584 let count = end.wrapping_sub(start) & mask;
592 if count == 0 {
593 return Vec::new();
594 }
595 let run_end = (start + count) & mask; let (drain_to_slot, frame_byte_end) = if run_end > start {
597 (run_end, run_end * Opcode::SIZE)
598 } else {
599 (0, self.slots.len()) };
601
602 let frames = vec![Frame {
603 slots: self.slots,
604 arena: self.arena,
605 start: start * Opcode::SIZE,
606 end: frame_byte_end,
607 }];
608
609 header::set_u32(self.header, OFF_READ_CURSOR, drain_to_slot as u32);
610 self.consumed = frame_count;
611
612 let drained_end_byte = if drain_to_slot > start {
616 drain_to_slot * Opcode::SIZE
617 } else {
618 self.slots.len() };
620 let reset_seen = self.slots[start * Opcode::SIZE..drained_end_byte]
621 .chunks_exact(Opcode::SIZE)
622 .any(|slot| {
623 let bytes: [u8; 16] = slot.try_into().unwrap();
624 let op = Opcode::from_bytes(&bytes);
625 op.category() == category::META && op.command() == crate::meta::RESET
626 });
627 if reset_seen {
628 memory::header::set_u32(self.header, memory::OFF_EVENT_ARENA_CURSOR, 0);
629 }
630
631 frames
632 }
633
634 pub fn send_event(&mut self, event: &Event) -> Result<(), RingError> {
644 let mask = slot_mask(self.header);
645 let op = match event {
646 Event::TextChanged { target, value } => {
647 let offset = crate::arena::alloc_event(&mut self.event_arena, self.header, value.as_bytes())
648 .map_err(|_| RingError::Full)?;
649 Opcode::new(category::EVENT, crate::event::TEXT_CHANGED, 0, *target, offset, 0)
650 }
651 Event::Navigate { url: Some(url) } => {
652 let offset = crate::arena::alloc_event(&mut self.event_arena, self.header, url.as_bytes())
653 .map_err(|_| RingError::Full)?;
654 Opcode::new(
655 category::EVENT,
656 crate::event::NAVIGATE,
657 crate::flag::NAVIGATE_URL,
658 0,
659 offset,
660 0,
661 )
662 }
663 _ => event.encode(),
664 };
665 ring_fn::push_event(self.event_slots, self.header, mask, &op)
666 }
667}
668
669#[cfg(test)]
670mod tests {
671 use super::*;
672 use crate::constants::{category, component_type, property_id, value_type};
673 use alloc::vec;
674
675 #[test]
676 fn guest_host_round_trip() {
677 let layout = MemoryLayout::default();
678 let mut mem = vec![0u8; layout.total_bytes()];
679 init_memory(&mut mem, &layout);
680
681 let mut guest = Guest::new(&mut mem, &layout);
682 guest.begin_frame();
683 guest.create_node(1, component_type::VSTACK).unwrap();
684 guest.insert_child(0, 1, APPEND).unwrap();
685 guest.set_property(1, property_id::SPACING, value_type::F32, 4.0f32.to_bits()).unwrap();
686 guest
687 .set_property(1, property_id::PADDING, value_type::F32, 8.0f32.to_bits())
688 .unwrap();
689 let arena_ref = guest.set_text(1, "Hello").unwrap();
690 assert_eq!(guest.pending(), 5);
691 guest.end_frame();
692 assert_eq!(guest.frame_count(), 1);
693
694 let mut host = Host::new(&mut mem, &layout);
695 assert_eq!(host.frame_count(), 1);
696 let frames = host.frames();
697 assert_eq!(frames.len(), 1);
698 let frame = &frames[0];
699 let ops: Vec<Opcode> = frame.opcodes().collect();
700 assert_eq!(ops.len(), 5);
701 assert_eq!(ops[0].category(), category::TREE);
702 assert_eq!(ops[0].a(), 1);
703 assert_eq!(ops[1].a(), 0); assert_eq!(ops[2].b() as u16, property_id::SPACING);
705 assert_eq!(ops[2].c_f32(), 4.0);
706 assert_eq!(frame.arena_str(arena_ref).unwrap(), "Hello");
707 assert!(host.frames().is_empty());
709 }
710
711 #[test]
712 fn set_design_token_round_trips() {
713 let layout = MemoryLayout::default();
714 let mut mem = vec![0u8; layout.total_bytes()];
715 init_memory(&mut mem, &layout);
716
717 let mut guest = Guest::new(&mut mem, &layout);
718 guest.begin_frame();
719 let arena_ref = guest
720 .set_design_token("color.primary", value_type::COLOR, 0xFF_2563EB)
721 .unwrap();
722 let dark_ref = guest
723 .set_design_token("dark.color.primary", value_type::COLOR, 0xFF_60A5FA)
724 .unwrap();
725 guest.end_frame();
726
727 let mut host = Host::new(&mut mem, &layout);
728 let frame = &host.frames()[0];
729 let ops: Vec<Opcode> = frame.opcodes().collect();
730 assert_eq!(ops.len(), 2);
731 assert_eq!(ops[0].category(), category::PARAMETER);
732 assert_eq!(ops[0].command(), parameter::SET_DESIGN_TOKEN);
733 assert_eq!(ops[0].b(), u32::from(value_type::COLOR));
734 assert_eq!(ops[0].c(), 0xFF_2563EB);
735 assert_eq!(frame.arena_str(arena_ref).unwrap(), "color.primary");
736 assert_eq!(frame.arena_str(dark_ref).unwrap(), "dark.color.primary");
737 }
738
739 #[test]
740 fn set_design_token_string_round_trips() {
741 let layout = MemoryLayout::default();
742 let mut mem = vec![0u8; layout.total_bytes()];
743 init_memory(&mut mem, &layout);
744
745 let mut guest = Guest::new(&mut mem, &layout);
746 guest.begin_frame();
747 let path_ref = guest.set_design_token_string("font.body.family", "Inter").unwrap();
748 guest.end_frame();
749
750 let mut host = Host::new(&mut mem, &layout);
751 let frame = &host.frames()[0];
752 let ops: Vec<Opcode> = frame.opcodes().collect();
753 assert_eq!(ops.len(), 1);
754 assert_eq!(ops[0].category(), category::PARAMETER);
755 assert_eq!(ops[0].command(), parameter::SET_DESIGN_TOKEN);
756 assert_eq!(ops[0].b(), u32::from(value_type::STRING));
757 assert_eq!(frame.arena_str(ops[0].a()).unwrap(), "font.body.family");
758 assert_eq!(frame.arena_str(ops[0].c()).unwrap(), "Inter");
759 assert_eq!(path_ref, ops[0].a());
760 }
761
762 #[test]
763 fn ring_full_is_reported() {
764 let mut layout = MemoryLayout::default();
765 layout.slot_count = 4; let mut mem = vec![0u8; layout.total_bytes()];
767 init_memory(&mut mem, &layout);
768
769 let mut guest = Guest::new(&mut mem, &layout);
770 assert!(guest.create_node(1, component_type::VSTACK).is_ok());
772 assert!(guest.create_node(2, component_type::HSTACK).is_ok());
773 assert!(guest.create_node(3, component_type::TEXT).is_ok());
774 assert_eq!(
775 guest.create_node(4, component_type::TEXT).unwrap_err(),
776 RingError::Full
777 );
778 }
779
780 #[test]
781 fn host_event_round_trips_to_guest() {
782 let layout = MemoryLayout::default();
783 let mut mem = vec![0u8; layout.total_bytes()];
784 init_memory(&mut mem, &layout);
785
786 let mut host = Host::new(&mut mem, &layout);
788 host.send_event(&Event::PointerUp {
789 target: 4,
790 x: 10.0,
791 y: 20.0,
792 secondary: false,
793 })
794 .unwrap();
795 drop(host);
796
797 let mut guest = Guest::new(&mut mem, &layout);
799 let events = guest.drain_events();
800 assert_eq!(
801 events,
802 vec![Event::PointerUp {
803 target: 4,
804 x: 10.0,
805 y: 20.0,
806 secondary: false,
807 }]
808 );
809 assert!(guest.drain_events().is_empty());
811 }
812
813 #[test]
814 fn drain_events_skips_unknown_event_commands() {
815 let layout = MemoryLayout::default();
816 let mut mem = vec![0u8; layout.total_bytes()];
817 init_memory(&mut mem, &layout);
818
819 {
821 let mut host = Host::new(&mut mem, &layout);
822 host.send_event(&Event::PointerUp {
823 target: 4,
824 x: 10.0,
825 y: 20.0,
826 secondary: false,
827 })
828 .unwrap();
829 }
830
831 let mask = slot_mask(&mem[..memory::HEADER_SIZE]);
833 let (header, rest) = mem.split_at_mut(memory::HEADER_SIZE);
834 let (_slots, rest) = rest.split_at_mut(layout.ring_bytes());
835 let (event_slots, _arena) = rest.split_at_mut(layout.event_ring_bytes());
836 let bogus = Opcode::new(crate::category::EVENT, 0xFF, 0, 1, 0, 0);
837 ring_fn::push_event(event_slots, header, mask, &bogus).unwrap();
838
839 let mut guest = Guest::new(&mut mem, &layout);
841 let events = guest.drain_events();
842 assert_eq!(events.len(), 1);
843 assert_eq!(
844 events[0],
845 Event::PointerUp {
846 target: 4,
847 x: 10.0,
848 y: 20.0,
849 secondary: false,
850 }
851 );
852 }
853
854 #[test]
855 fn host_frames_survives_a_wrapped_write_cursor() {
856 let layout = MemoryLayout {
857 slot_count: 4,
858 arena_bytes: MemoryLayout::default().arena_bytes,
859 event_arena_bytes: MemoryLayout::default().event_arena_bytes,
860 };
861 let mut mem = vec![0u8; layout.total_bytes()];
862 init_memory(&mut mem, &layout);
863
864 {
866 let mut guest = Guest::new(&mut mem, &layout);
867 guest.begin_frame();
868 guest.create_node(1, component_type::VSTACK).unwrap();
869 guest.create_node(2, component_type::HSTACK).unwrap();
870 guest.create_node(3, component_type::TEXT).unwrap();
871 guest.end_frame();
872 }
873 {
875 let mut host = Host::new(&mut mem, &layout);
876 assert_eq!(host.frames().len(), 1);
877 }
878 {
882 let mut guest = Guest::new(&mut mem, &layout);
883 guest.begin_frame();
884 guest.create_node(4, component_type::TEXT).unwrap();
885 guest.end_frame();
886 }
887
888 let mut host = Host::new(&mut mem, &layout);
891 let frames = host.frames();
892 assert_eq!(frames.len(), 1);
893 assert_eq!(frames[0].opcodes().count(), 1);
894 assert_eq!(frames[0].opcodes().next().unwrap().a(), 4);
895 assert!(host.frames().is_empty());
897 {
899 let mut guest = Guest::new(&mut mem, &layout);
900 guest.begin_frame();
901 guest.create_node(5, component_type::TEXT).unwrap();
902 guest.end_frame();
903 }
904 let mut host = Host::new(&mut mem, &layout);
905 assert_eq!(host.frames()[0].opcodes().next().unwrap().a(), 5);
906 }
907
908 #[test]
909 fn icon_vocabulary_names_are_canonical_and_unique() {
910 use crate::constants::icon;
911 use alloc::collections::BTreeSet;
912
913 let mut seen = BTreeSet::new();
914 for name in icon::NAMES {
915 assert!(icon::is_canonical(name), "{} not canonical", name);
916 assert!(seen.insert(name), "duplicate canonical icon {}", name);
917 }
918 for name in [
920 icon::HOME,
921 icon::SETTINGS,
922 icon::PLAY,
923 icon::SKIP_FORWARD,
924 icon::VOLUME_MUTE,
925 icon::FILTER,
926 ] {
927 assert!(icon::is_canonical(name));
928 }
929 assert!(!icon::is_canonical("definitely-not-an-icon"));
930 }
931}