Fix lints, undo broken set_immediate_interval

This commit is contained in:
2026-03-23 10:11:51 -04:00
parent 4193457fc4
commit ad5c233b15
4 changed files with 174 additions and 88 deletions

View File

@@ -217,17 +217,19 @@ where
); );
if delay.is_zero() { 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 // 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 // timer (a past-deadline kernel timer would fire on every event loop
// iteration, spinning the runtime at 100 % CPU). Instead the callback // iteration, spinning the runtime at 100 % CPU). Instead the callback
// is stored in `immediate_intervals` and self-schedules as a macrotask // is stored in `immediate_intervals` and self-schedules as a macrotask
// each turn, mirroring JS `setInterval(f, 0)` semantics. // each turn, mirroring JS `setInterval(f, 0)` semantics.
let callback = Rc::new(RefCell::new(Box::new(callback) as Box<dyn FnMut()>)); // let callback = Rc::new(RefCell::new(Box::new(callback) as Box<dyn FnMut()>));
current_thread() // current_thread()
.immediate_intervals // .immediate_intervals
.borrow_mut() // .borrow_mut()
.insert(id, Rc::clone(&callback)); // .insert(id, Rc::clone(&callback));
schedule_immediate_interval(owner, id); // schedule_immediate_interval(owner, id);
} else { } else {
let deadline = deadline_from_now(delay); let deadline = deadline_from_now(delay);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@@ -571,6 +573,8 @@ impl Future for YieldNow {
} }
} }
type ImmediateIntervals = RefCell<HashMap<usize, Rc<RefCell<Box<dyn FnMut()>>>>>;
struct ThreadState { struct ThreadState {
driver: Driver, driver: Driver,
shared: Arc<ThreadShared>, shared: Arc<ThreadShared>,
@@ -580,7 +584,7 @@ struct ThreadState {
timers: RefCell<TimerHeap>, timers: RefCell<TimerHeap>,
/// Zero-delay intervals bypasses the timer heap entirely. Each entry /// Zero-delay intervals bypasses the timer heap entirely. Each entry
/// re-enqueues itself as a macrotask on every turn. /// re-enqueues itself as a macrotask on every turn.
immediate_intervals: RefCell<HashMap<usize, Rc<RefCell<Box<dyn FnMut()>>>>>, immediate_intervals: ImmediateIntervals,
next_timer_id: Cell<usize>, next_timer_id: Cell<usize>,
children: RefCell<Vec<ChildWorker>>, children: RefCell<Vec<ChildWorker>>,
} }
@@ -1162,27 +1166,32 @@ fn clear_timer(owner: *const ThreadState, id: usize) {
} }
} }
/// Enqueues one macrotask turn for a zero-delay interval. // 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. // /// The macrotask checks whether the interval is still live (not cleared), fires
fn schedule_immediate_interval(owner: *const ThreadState, id: usize) { // /// the callback, and re-enqueues itself for the next turn.
push_local_macrotask(Box::new(move || { // fn schedule_immediate_interval(owner: *const ThreadState, id: usize) {
let callback = current_thread() // push_local_macrotask(Box::new(move || {
.immediate_intervals // let callback = current_thread()
.borrow() // .immediate_intervals
.get(&id) // .borrow()
.map(Rc::clone); // .get(&id)
let Some(callback) = callback else { // .map(Rc::clone);
return; // interval was cleared before this turn ran // 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. // (callback.borrow_mut())();
if current_thread().immediate_intervals.borrow().contains_key(&id) { // // Re-enqueue for the next turn if still live.
schedule_immediate_interval(owner, id); // if current_thread()
} // .immediate_intervals
})); // .borrow()
} // .contains_key(&id)
// {
// schedule_immediate_interval(owner, id);
// }
// }));
// }
fn dispatch_expired_timers() { fn dispatch_expired_timers() {
let now = deadline_from_now(Duration::ZERO); let now = deadline_from_now(Duration::ZERO);
@@ -1207,12 +1216,8 @@ fn dispatch_expired_timers() {
.deadline .deadline
.checked_add(interval) .checked_add(interval)
.unwrap_or(Duration::MAX); .unwrap_or(Duration::MAX);
let next_timer = TimerNode::interval( let next_timer =
timer.id, TimerNode::interval(timer.id, next_deadline, interval, Rc::clone(&callback));
next_deadline,
interval,
Rc::clone(&callback),
);
current_thread().timers.borrow_mut().insert(next_timer); current_thread().timers.borrow_mut().insert(next_timer);
push_local_macrotask(Box::new(move || { push_local_macrotask(Box::new(move || {
@@ -1394,8 +1399,7 @@ mod tests {
// turns by awaiting a sleep between checks. // turns by awaiting a sleep between checks.
let count = Rc::new(Cell::new(0usize)); let count = Rc::new(Cell::new(0usize));
let count_clone = Rc::clone(&count); let count_clone = Rc::clone(&count);
let handle_slot: Rc<RefCell<Option<super::IntervalHandle>>> = let handle_slot: Rc<RefCell<Option<super::IntervalHandle>>> = Rc::new(RefCell::new(None));
Rc::new(RefCell::new(None));
let handle_slot_clone = Rc::clone(&handle_slot); let handle_slot_clone = Rc::clone(&handle_slot);
let handle = set_interval(Duration::ZERO, move || { let handle = set_interval(Duration::ZERO, move || {

View File

@@ -128,7 +128,12 @@ impl LayoutNode {
/// Return a copy of this node (and all descendants) with all positions shifted by `offset`. /// Return a copy of this node (and all descendants) with all positions shifted by `offset`.
pub fn translated(self, offset: Point) -> Self { pub fn translated(self, offset: Point) -> Self {
fn translate_rect(r: Rect, o: Point) -> Rect { 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 rect = translate_rect(self.rect, offset);
let clip_rect = self.clip_rect.map(|r| translate_rect(r, 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), rect: translate_rect(img.rect, offset),
..img ..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 { LayoutNode {
rect, rect,
clip_rect, clip_rect,
@@ -256,24 +265,24 @@ fn layout_element(
perf_stats.nodes += 1; perf_stats.nodes += 1;
// Viewport culling: skip fully off-screen elements inside a scroll box. // Viewport culling: skip fully off-screen elements inside a scroll box.
if let Some(clip) = clip_rect { if let Some(clip) = clip_rect
if !rects_overlap(rect, clip) { && !rects_overlap(rect, clip)
perf_stats.viewport_culled += 1; {
return LayoutNode { perf_stats.viewport_culled += 1;
path, return LayoutNode {
element_id: element.id, path,
rect, element_id: element.id,
corner_radius: 0.0, rect,
clip_rect: None, corner_radius: 0.0,
pointer_events: element.style.pointer_events, clip_rect: None,
focusable: element.style.focusable, pointer_events: element.style.pointer_events,
cursor: element.style.cursor.unwrap_or(CursorIcon::Default), focusable: element.style.focusable,
scroll_metrics: None, cursor: element.style.cursor.unwrap_or(CursorIcon::Default),
prepared_image: None, scroll_metrics: None,
prepared_text: None, prepared_image: None,
children: Vec::new(), prepared_text: None,
}; children: Vec::new(),
} };
} }
// Incremental layout cache check. // Incremental layout cache check.
@@ -368,7 +377,13 @@ fn layout_element(
if pushed_clip { if pushed_clip {
scene.pop_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; return interaction;
} }
@@ -382,7 +397,13 @@ fn layout_element(
if pushed_clip { if pushed_clip {
scene.pop_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; return interaction;
} }
@@ -508,7 +529,13 @@ fn layout_element(
if pushed_clip { if pushed_clip {
scene.pop_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; return interaction;
} }
@@ -518,7 +545,13 @@ fn layout_element(
if pushed_clip { if pushed_clip {
scene.pop_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; return interaction;
} }
@@ -527,7 +560,13 @@ fn layout_element(
if pushed_clip { if pushed_clip {
scene.pop_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; return interaction;
} }
interaction.children = layout_container_children( interaction.children = layout_container_children(
@@ -545,7 +584,13 @@ fn layout_element(
scene.pop_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,
);
interaction interaction
} }
@@ -558,10 +603,13 @@ fn cache_layout(
origin: Point, origin: Point,
) { ) {
let neg = Point::new(-origin.x, -origin.y); let neg = Point::new(-origin.x, -origin.y);
cache.results.insert(key, CachedLayout { cache.results.insert(
interaction_node: interaction.clone().translated(neg), key,
scene_items: scene_items.iter().map(|i| i.translated(neg)).collect(), 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<Vec<HitTarget>> { fn hit_path_node(node: &LayoutNode, point: crate::scene::Point) -> Option<Vec<HitTarget>> {
@@ -986,7 +1034,14 @@ fn intrinsic_size(
return cached; return cached;
} }
let insets = content_insets(&element.style); 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); layout_cache.intrinsic_cache.insert(cache_key, result);
result result
} }
@@ -1047,8 +1102,13 @@ fn intrinsic_size_inner(
); );
} }
let intrinsic_content = let intrinsic_content = intrinsic_container_content_size(
intrinsic_container_content_size(element, content_size, text_system, perf_stats, layout_cache); element,
content_size,
text_system,
perf_stats,
layout_cache,
);
UiSize::new( UiSize::new(
explicit_width explicit_width
@@ -1539,9 +1599,9 @@ fn child_rect(
mod tests { mod tests {
use super::{LayoutCache, layout_scene, layout_snapshot, layout_snapshot_with_cache}; use super::{LayoutCache, layout_scene, layout_snapshot, layout_snapshot_with_cache};
use crate::scene::{Color, DisplayItem, Point, Quad, Rect, UiSize}; use crate::scene::{Color, DisplayItem, Point, Quad, Rect, UiSize};
use crate::text::TextSystem;
use crate::text::{TextStyle, TextWrap}; use crate::text::{TextStyle, TextWrap};
use crate::tree::{Edges, Element, ElementId, FlexDirection}; use crate::tree::{Edges, Element, ElementId, FlexDirection};
use crate::text::TextSystem;
#[test] #[test]
fn row_layout_apportions_fixed_and_flex_children() { fn row_layout_apportions_fixed_and_flex_children() {
@@ -2156,10 +2216,12 @@ mod tests {
let size = UiSize::new(400.0, 600.0); let size = UiSize::new(400.0, 600.0);
let snap1 = layout_snapshot_with_cache(1, size, &root, &mut text_system, &mut layout_cache); 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); 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!( assert_eq!(
snap1.interaction_tree.root, snap1.scene.items, snap2.scene.items,
snap2.interaction_tree.root, "scene items should be identical"
);
assert_eq!(
snap1.interaction_tree.root, snap2.interaction_tree.root,
"interaction trees should be identical" "interaction trees should be identical"
); );
} }
@@ -2198,12 +2260,14 @@ mod tests {
let item_height = 40.0; let item_height = 40.0;
let viewport_height = 200.0; let viewport_height = 200.0;
let n = 100; 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 { for i in 0..n {
scroll_box = scroll_box.child( scroll_box = scroll_box.child(
Element::new() Element::new()
.height(item_height) .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); 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); 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. // Only text items within the 200px viewport should appear in the scene.
let text_items = snap.scene.items.iter().filter(|item| { let text_items = snap
matches!(item, DisplayItem::Text(_)) .scene
}).count(); .items
.iter()
.filter(|item| matches!(item, DisplayItem::Text(_)))
.count();
// At most 6 text items should be visible (5 fit + 1 partial). // 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!(
assert!(text_items >= 4, "expected >= 4 text items visible, got {text_items}"); 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}"
);
} }
} }

View File

@@ -6,6 +6,7 @@
//! sibling crates. //! sibling crates.
pub(crate) mod trace_targets { pub(crate) mod trace_targets {
#![allow(dead_code)]
pub const PLATFORM: &str = "ruin_ui::platform"; pub const PLATFORM: &str = "ruin_ui::platform";
pub const SCENE: &str = "ruin_ui::scene"; pub const SCENE: &str = "ruin_ui::scene";
pub const TEXT_PERF: &str = "ruin_ui::text_perf"; pub const TEXT_PERF: &str = "ruin_ui::text_perf";

View File

@@ -990,7 +990,9 @@ fn fs_main(input: VertexOut) -> @location(0) vec4<f32> {
bottom: physical.y - image.placement.top + height, bottom: physical.y - image.placement.top + height,
}, },
content: image.content, 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(), data: image.data.clone(),
}); });
} }
@@ -1470,7 +1472,11 @@ fn create_glyph_atlas(
/// Resolve `Color::SENTINEL` to `default_color`; return other colors unchanged. /// Resolve `Color::SENTINEL` to `default_color`; return other colors unchanged.
#[inline] #[inline]
fn resolve_glyph_color(color: Color, default_color: Color) -> Color { 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 { 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 /// return only those glyph indices. This turns O(all_glyphs) iteration into
/// O(log(lines) + visible_glyphs) — critical for large text nodes in scroll /// O(log(lines) + visible_glyphs) — critical for large text nodes in scroll
/// boxes where only a few lines are on screen at once. /// boxes where only a few lines are on screen at once.
fn clip_visible_glyphs<'a>(text: &'a PreparedText, clip: Option<Rect>) -> &'a [GlyphInstance] { fn clip_visible_glyphs(text: &PreparedText, clip: Option<Rect>) -> &[GlyphInstance] {
let Some(clip) = clip else { let Some(clip) = clip else {
return &text.glyphs; return &text.glyphs;
}; };
@@ -2332,7 +2338,12 @@ fn text_texture_key(text: &PreparedText) -> TextTextureKey {
.map(|bounds| (bounds.width.to_bits(), bounds.height.to_bits())), .map(|bounds| (bounds.width.to_bits(), bounds.height.to_bits())),
font_size_bits: text.font_size.to_bits(), font_size_bits: text.font_size.to_bits(),
line_height_bits: text.line_height.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: text
.glyphs .glyphs
.iter() .iter()
@@ -2354,10 +2365,7 @@ mod tests {
ActiveClip, blend_rgba, build_text_vertices, build_vertices, clip_rect_array, ActiveClip, blend_rgba, build_text_vertices, build_vertices, clip_rect_array,
push_clip_state, rounded_clip_arrays, text_texture_key, push_clip_state, rounded_clip_arrays, text_texture_key,
}; };
use ruin_ui::{ use ruin_ui::{ClipRegion, Color, Point, PreparedText, Rect, SceneSnapshot, UiSize};
ClipRegion, Color, GlyphInstance, Point, PreparedText, Rect, SceneSnapshot,
TextSelectionStyle, UiSize,
};
#[test] #[test]
fn quad_scenes_expand_to_six_vertices_per_quad() { fn quad_scenes_expand_to_six_vertices_per_quad() {