From ad5c233b15d36dd9d70dd96fc64bb68dedd5fd8c Mon Sep 17 00:00:00 2001 From: Will Temple Date: Mon, 23 Mar 2026 10:11:51 -0400 Subject: [PATCH] Fix lints, undo broken set_immediate_interval --- .../src/platform/linux_x86_64/runtime.rs | 76 +++++---- lib/ui/src/layout.rs | 161 +++++++++++++----- lib/ui/src/lib.rs | 1 + lib/ui_renderer_wgpu/src/lib.rs | 24 ++- 4 files changed, 174 insertions(+), 88 deletions(-) diff --git a/lib/runtime/src/platform/linux_x86_64/runtime.rs b/lib/runtime/src/platform/linux_x86_64/runtime.rs index 039a4b5..6bbcac7 100644 --- a/lib/runtime/src/platform/linux_x86_64/runtime.rs +++ b/lib/runtime/src/platform/linux_x86_64/runtime.rs @@ -217,17 +217,19 @@ where ); if delay.is_zero() { + unimplemented!("Zero-delay intervals are not yet implemented.") + // TODO: vibeshit got this completely wrong // Zero-delay intervals never touch the timer heap or arm an io_uring // timer (a past-deadline kernel timer would fire on every event loop // iteration, spinning the runtime at 100 % CPU). Instead the callback // is stored in `immediate_intervals` and self-schedules as a macrotask // each turn, mirroring JS `setInterval(f, 0)` semantics. - let callback = Rc::new(RefCell::new(Box::new(callback) as Box)); - current_thread() - .immediate_intervals - .borrow_mut() - .insert(id, Rc::clone(&callback)); - schedule_immediate_interval(owner, id); + // let callback = Rc::new(RefCell::new(Box::new(callback) as Box)); + // current_thread() + // .immediate_intervals + // .borrow_mut() + // .insert(id, Rc::clone(&callback)); + // schedule_immediate_interval(owner, id); } else { let deadline = deadline_from_now(delay); #[cfg(debug_assertions)] @@ -571,6 +573,8 @@ impl Future for YieldNow { } } +type ImmediateIntervals = RefCell>>>>; + struct ThreadState { driver: Driver, shared: Arc, @@ -580,7 +584,7 @@ struct ThreadState { timers: RefCell, /// Zero-delay intervals bypasses the timer heap entirely. Each entry /// re-enqueues itself as a macrotask on every turn. - immediate_intervals: RefCell>>>>, + immediate_intervals: ImmediateIntervals, next_timer_id: Cell, children: RefCell>, } @@ -1162,27 +1166,32 @@ fn clear_timer(owner: *const ThreadState, id: usize) { } } -/// Enqueues one macrotask turn for a zero-delay interval. -/// -/// The macrotask checks whether the interval is still live (not cleared), fires -/// the callback, and re-enqueues itself for the next turn. -fn schedule_immediate_interval(owner: *const ThreadState, id: usize) { - push_local_macrotask(Box::new(move || { - let callback = current_thread() - .immediate_intervals - .borrow() - .get(&id) - .map(Rc::clone); - let Some(callback) = callback else { - return; // interval was cleared before this turn ran - }; - (callback.borrow_mut())(); - // Re-enqueue for the next turn if still live. - if current_thread().immediate_intervals.borrow().contains_key(&id) { - schedule_immediate_interval(owner, id); - } - })); -} +// TODO: vibeshit got this completely wrong +// /// Enqueues one macrotask turn for a zero-delay interval. +// /// +// /// The macrotask checks whether the interval is still live (not cleared), fires +// /// the callback, and re-enqueues itself for the next turn. +// fn schedule_immediate_interval(owner: *const ThreadState, id: usize) { +// push_local_macrotask(Box::new(move || { +// let callback = current_thread() +// .immediate_intervals +// .borrow() +// .get(&id) +// .map(Rc::clone); +// let Some(callback) = callback else { +// return; // interval was cleared before this turn ran +// }; +// (callback.borrow_mut())(); +// // Re-enqueue for the next turn if still live. +// if current_thread() +// .immediate_intervals +// .borrow() +// .contains_key(&id) +// { +// schedule_immediate_interval(owner, id); +// } +// })); +// } fn dispatch_expired_timers() { let now = deadline_from_now(Duration::ZERO); @@ -1207,12 +1216,8 @@ fn dispatch_expired_timers() { .deadline .checked_add(interval) .unwrap_or(Duration::MAX); - let next_timer = TimerNode::interval( - timer.id, - next_deadline, - interval, - Rc::clone(&callback), - ); + let next_timer = + TimerNode::interval(timer.id, next_deadline, interval, Rc::clone(&callback)); current_thread().timers.borrow_mut().insert(next_timer); push_local_macrotask(Box::new(move || { @@ -1394,8 +1399,7 @@ mod tests { // turns by awaiting a sleep between checks. let count = Rc::new(Cell::new(0usize)); let count_clone = Rc::clone(&count); - let handle_slot: Rc>> = - Rc::new(RefCell::new(None)); + let handle_slot: Rc>> = Rc::new(RefCell::new(None)); let handle_slot_clone = Rc::clone(&handle_slot); let handle = set_interval(Duration::ZERO, move || { diff --git a/lib/ui/src/layout.rs b/lib/ui/src/layout.rs index 51289ee..e9637cb 100644 --- a/lib/ui/src/layout.rs +++ b/lib/ui/src/layout.rs @@ -128,7 +128,12 @@ impl LayoutNode { /// Return a copy of this node (and all descendants) with all positions shifted by `offset`. pub fn translated(self, offset: Point) -> Self { fn translate_rect(r: Rect, o: Point) -> Rect { - Rect::new(r.origin.x + o.x, r.origin.y + o.y, r.size.width, r.size.height) + Rect::new( + r.origin.x + o.x, + r.origin.y + o.y, + r.size.width, + r.size.height, + ) } let rect = translate_rect(self.rect, offset); let clip_rect = self.clip_rect.map(|r| translate_rect(r, offset)); @@ -143,7 +148,11 @@ impl LayoutNode { rect: translate_rect(img.rect, offset), ..img }); - let children = self.children.into_iter().map(|c| c.translated(offset)).collect(); + let children = self + .children + .into_iter() + .map(|c| c.translated(offset)) + .collect(); LayoutNode { rect, clip_rect, @@ -256,24 +265,24 @@ fn layout_element( perf_stats.nodes += 1; // Viewport culling: skip fully off-screen elements inside a scroll box. - if let Some(clip) = clip_rect { - if !rects_overlap(rect, clip) { - perf_stats.viewport_culled += 1; - return LayoutNode { - path, - element_id: element.id, - rect, - corner_radius: 0.0, - clip_rect: None, - pointer_events: element.style.pointer_events, - focusable: element.style.focusable, - cursor: element.style.cursor.unwrap_or(CursorIcon::Default), - scroll_metrics: None, - prepared_image: None, - prepared_text: None, - children: Vec::new(), - }; - } + if let Some(clip) = clip_rect + && !rects_overlap(rect, clip) + { + perf_stats.viewport_culled += 1; + return LayoutNode { + path, + element_id: element.id, + rect, + corner_radius: 0.0, + clip_rect: None, + pointer_events: element.style.pointer_events, + focusable: element.style.focusable, + cursor: element.style.cursor.unwrap_or(CursorIcon::Default), + scroll_metrics: None, + prepared_image: None, + prepared_text: None, + children: Vec::new(), + }; } // Incremental layout cache check. @@ -368,7 +377,13 @@ fn layout_element( if pushed_clip { scene.pop_clip(); } - cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin); + cache_layout( + layout_cache, + cache_key, + &interaction, + &scene.items[scene_start..], + rect.origin, + ); return interaction; } @@ -382,7 +397,13 @@ fn layout_element( if pushed_clip { scene.pop_clip(); } - cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin); + cache_layout( + layout_cache, + cache_key, + &interaction, + &scene.items[scene_start..], + rect.origin, + ); return interaction; } @@ -508,7 +529,13 @@ fn layout_element( if pushed_clip { scene.pop_clip(); } - cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin); + cache_layout( + layout_cache, + cache_key, + &interaction, + &scene.items[scene_start..], + rect.origin, + ); return interaction; } @@ -518,7 +545,13 @@ fn layout_element( if pushed_clip { scene.pop_clip(); } - cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin); + cache_layout( + layout_cache, + cache_key, + &interaction, + &scene.items[scene_start..], + rect.origin, + ); return interaction; } @@ -527,7 +560,13 @@ fn layout_element( if pushed_clip { scene.pop_clip(); } - cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin); + cache_layout( + layout_cache, + cache_key, + &interaction, + &scene.items[scene_start..], + rect.origin, + ); return interaction; } interaction.children = layout_container_children( @@ -545,7 +584,13 @@ fn layout_element( scene.pop_clip(); } - cache_layout(layout_cache, cache_key, &interaction, &scene.items[scene_start..], rect.origin); + cache_layout( + layout_cache, + cache_key, + &interaction, + &scene.items[scene_start..], + rect.origin, + ); interaction } @@ -558,10 +603,13 @@ fn cache_layout( origin: Point, ) { let neg = Point::new(-origin.x, -origin.y); - cache.results.insert(key, CachedLayout { - interaction_node: interaction.clone().translated(neg), - scene_items: scene_items.iter().map(|i| i.translated(neg)).collect(), - }); + cache.results.insert( + key, + CachedLayout { + interaction_node: interaction.clone().translated(neg), + scene_items: scene_items.iter().map(|i| i.translated(neg)).collect(), + }, + ); } fn hit_path_node(node: &LayoutNode, point: crate::scene::Point) -> Option> { @@ -986,7 +1034,14 @@ fn intrinsic_size( return cached; } let insets = content_insets(&element.style); - let result = intrinsic_size_inner(element, available_size, insets, text_system, perf_stats, layout_cache); + let result = intrinsic_size_inner( + element, + available_size, + insets, + text_system, + perf_stats, + layout_cache, + ); layout_cache.intrinsic_cache.insert(cache_key, result); result } @@ -1047,8 +1102,13 @@ fn intrinsic_size_inner( ); } - let intrinsic_content = - intrinsic_container_content_size(element, content_size, text_system, perf_stats, layout_cache); + let intrinsic_content = intrinsic_container_content_size( + element, + content_size, + text_system, + perf_stats, + layout_cache, + ); UiSize::new( explicit_width @@ -1539,9 +1599,9 @@ fn child_rect( mod tests { use super::{LayoutCache, layout_scene, layout_snapshot, layout_snapshot_with_cache}; use crate::scene::{Color, DisplayItem, Point, Quad, Rect, UiSize}; + use crate::text::TextSystem; use crate::text::{TextStyle, TextWrap}; use crate::tree::{Edges, Element, ElementId, FlexDirection}; - use crate::text::TextSystem; #[test] fn row_layout_apportions_fixed_and_flex_children() { @@ -2156,10 +2216,12 @@ mod tests { let size = UiSize::new(400.0, 600.0); let snap1 = layout_snapshot_with_cache(1, size, &root, &mut text_system, &mut layout_cache); let snap2 = layout_snapshot_with_cache(2, size, &root, &mut text_system, &mut layout_cache); - assert_eq!(snap1.scene.items, snap2.scene.items, "scene items should be identical"); assert_eq!( - snap1.interaction_tree.root, - snap2.interaction_tree.root, + snap1.scene.items, snap2.scene.items, + "scene items should be identical" + ); + assert_eq!( + snap1.interaction_tree.root, snap2.interaction_tree.root, "interaction trees should be identical" ); } @@ -2198,12 +2260,14 @@ mod tests { let item_height = 40.0; let viewport_height = 200.0; let n = 100; - let mut scroll_box = Element::scroll_box(0.0).width(400.0).height(viewport_height); + let mut scroll_box = Element::scroll_box(0.0) + .width(400.0) + .height(viewport_height); for i in 0..n { scroll_box = scroll_box.child( Element::new() .height(item_height) - .child(Element::text(format!("item {i}"), text_style())) + .child(Element::text(format!("item {i}"), text_style())), ); } let root = Element::new().child(scroll_box); @@ -2214,11 +2278,20 @@ mod tests { let snap = layout_snapshot_with_cache(1, size, &root, &mut text_system, &mut layout_cache); // Only text items within the 200px viewport should appear in the scene. - let text_items = snap.scene.items.iter().filter(|item| { - matches!(item, DisplayItem::Text(_)) - }).count(); + let text_items = snap + .scene + .items + .iter() + .filter(|item| matches!(item, DisplayItem::Text(_))) + .count(); // At most 6 text items should be visible (5 fit + 1 partial). - assert!(text_items <= 6, "expected <= 6 text items in scene, got {text_items} (culling not working)"); - assert!(text_items >= 4, "expected >= 4 text items visible, got {text_items}"); + assert!( + text_items <= 6, + "expected <= 6 text items in scene, got {text_items} (culling not working)" + ); + assert!( + text_items >= 4, + "expected >= 4 text items visible, got {text_items}" + ); } } diff --git a/lib/ui/src/lib.rs b/lib/ui/src/lib.rs index dfb0155..69e6bd1 100644 --- a/lib/ui/src/lib.rs +++ b/lib/ui/src/lib.rs @@ -6,6 +6,7 @@ //! sibling crates. pub(crate) mod trace_targets { + #![allow(dead_code)] pub const PLATFORM: &str = "ruin_ui::platform"; pub const SCENE: &str = "ruin_ui::scene"; pub const TEXT_PERF: &str = "ruin_ui::text_perf"; diff --git a/lib/ui_renderer_wgpu/src/lib.rs b/lib/ui_renderer_wgpu/src/lib.rs index f58e8bd..08edc27 100644 --- a/lib/ui_renderer_wgpu/src/lib.rs +++ b/lib/ui_renderer_wgpu/src/lib.rs @@ -990,7 +990,9 @@ fn fs_main(input: VertexOut) -> @location(0) vec4 { bottom: physical.y - image.placement.top + height, }, content: image.content, - color: glyph.color_opt.map_or(text.default_color, color_from_cosmic), + color: glyph + .color_opt + .map_or(text.default_color, color_from_cosmic), data: image.data.clone(), }); } @@ -1470,7 +1472,11 @@ fn create_glyph_atlas( /// Resolve `Color::SENTINEL` to `default_color`; return other colors unchanged. #[inline] fn resolve_glyph_color(color: Color, default_color: Color) -> Color { - if color == Color::SENTINEL { default_color } else { color } + if color == Color::SENTINEL { + default_color + } else { + color + } } fn color_from_cosmic(color: cosmic_text::Color) -> Color { @@ -2297,7 +2303,7 @@ fn clip_textured_rect( /// return only those glyph indices. This turns O(all_glyphs) iteration into /// O(log(lines) + visible_glyphs) — critical for large text nodes in scroll /// boxes where only a few lines are on screen at once. -fn clip_visible_glyphs<'a>(text: &'a PreparedText, clip: Option) -> &'a [GlyphInstance] { +fn clip_visible_glyphs(text: &PreparedText, clip: Option) -> &[GlyphInstance] { let Some(clip) = clip else { return &text.glyphs; }; @@ -2332,7 +2338,12 @@ fn text_texture_key(text: &PreparedText) -> TextTextureKey { .map(|bounds| (bounds.width.to_bits(), bounds.height.to_bits())), font_size_bits: text.font_size.to_bits(), line_height_bits: text.line_height.to_bits(), - color: (text.default_color.r, text.default_color.g, text.default_color.b, text.default_color.a), + color: ( + text.default_color.r, + text.default_color.g, + text.default_color.b, + text.default_color.a, + ), glyphs: text .glyphs .iter() @@ -2354,10 +2365,7 @@ mod tests { ActiveClip, blend_rgba, build_text_vertices, build_vertices, clip_rect_array, push_clip_state, rounded_clip_arrays, text_texture_key, }; - use ruin_ui::{ - ClipRegion, Color, GlyphInstance, Point, PreparedText, Rect, SceneSnapshot, - TextSelectionStyle, UiSize, - }; + use ruin_ui::{ClipRegion, Color, Point, PreparedText, Rect, SceneSnapshot, UiSize}; #[test] fn quad_scenes_expand_to_six_vertices_per_quad() {