use iris::prelude::*; use std::{ alloc::{GlobalAlloc, Layout, System}, cell::Cell, time::{Duration, Instant}, }; struct CountingAllocator; thread_local! { static TRACKING: Cell = const { Cell::new(false) }; static ALLOCATIONS: Cell = const { Cell::new(0) }; } fn note_allocation() { TRACKING.with(|tracking| { if tracking.get() { ALLOCATIONS.with(|allocations| allocations.set(allocations.get() + 1)); } }); } unsafe impl GlobalAlloc for CountingAllocator { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { let ptr = unsafe { System.alloc(layout) }; note_allocation(); ptr } unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { let ptr = unsafe { System.alloc_zeroed(layout) }; note_allocation(); ptr } unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { unsafe { System.dealloc(ptr, layout) } } unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { let ptr = unsafe { System.realloc(ptr, layout, new_size) }; note_allocation(); ptr } } #[global_allocator] static ALLOCATOR: CountingAllocator = CountingAllocator; fn allocations_during(run: impl FnOnce()) -> usize { ALLOCATIONS.with(|allocations| allocations.set(0)); TRACKING.with(|tracking| tracking.set(true)); run(); TRACKING.with(|tracking| tracking.set(false)); ALLOCATIONS.with(Cell::get) } struct TestRsc { ui: Ui, } impl UiRsc for TestRsc { fn ui(&self) -> &Ui { &self.ui } fn ui_mut(&mut self) -> &mut Ui { &mut self.ui } } struct FrameRequester; impl Widget for FrameRequester { fn draw(&mut self, painter: &mut Painter) { let paint = painter.paint(&PaintId::WHITE); painter.primitive(RectPrimitive::color(paint)); painter.request_next_frame(); painter.set_size(Size::REST); } } #[test] fn a_warmed_self_redraw_allocates_nothing() { let mut rsc = TestRsc { ui: Ui::default() }; let root = rsc.ui.widgets.add_strong(FrameRequester).any(); let mut render = UiRenderState::new(); render.resize((800.0, 600.0)); let start = Instant::now(); for frame in 0..3 { assert!(render.update_at(&root, &mut rsc, start + Duration::from_millis(frame * 16),)); } let allocations = allocations_during(|| { assert!(render.update_at(&root, &mut rsc, start + Duration::from_millis(48),)); }); assert_eq!(allocations, 0); } #[test] fn refreshing_retained_accessibility_allocates_nothing() { let mut rsc = TestRsc { ui: Ui::default() }; let leaf = rsc.ui.widgets.add_strong(Rect::new(PaintId::WHITE)); rsc.ui.widgets.set_label(&leaf, "named".to_owned()); let offset = rsc.ui.widgets.add_strong(Offset { inner: leaf.any(), amt: UiVec2::ZERO, }); let offset_id = offset.weak(); let root = offset.any(); let mut render = UiRenderState::new(); render.resize((800.0, 600.0)); render.update(&root, &mut rsc); let mut access = AccessTree::new(); assert!(access.refresh(rsc.widgets(), &render, &rsc)); rsc.ui.widgets.get_mut(&offset_id).unwrap().amt = UiVec2::abs(Vec2::new(20.0, 0.0)); render.update(&root, &mut rsc); let moved = allocations_during(|| assert!(access.refresh(rsc.widgets(), &render, &rsc))); assert_eq!(moved, 0); let unchanged = allocations_during(|| assert!(!access.refresh(rsc.widgets(), &render, &rsc))); assert_eq!(unchanged, 0); }