From b6eabbf499148486bc2137aa45e228f823672010 Mon Sep 17 00:00:00 2001 From: OptimistOtaku Date: Tue, 27 Oct 2026 03:10:53 +0530 Subject: [PATCH] perf(project): resolve hidden cursor windows without allocation --- crates/project/src/configuration.rs | 39 +-- .../configuration/cursor_visibility_tests.rs | 236 ++++++++++++++++++ 2 files changed, 260 insertions(+), 15 deletions(-) create mode 100644 crates/project/src/configuration/cursor_visibility_tests.rs diff --git a/crates/project/src/configuration.rs b/crates/project/src/configuration.rs index 328cc8da059..66df78749e1 100644 --- a/crates/project/src/configuration.rs +++ b/crates/project/src/configuration.rs @@ -17,6 +17,9 @@ use crate::DisplayNotch; #[cfg(test)] mod style_resolution_tests; +#[cfg(test)] +mod cursor_visibility_tests; + #[derive(Type, Serialize, Deserialize, Clone, Debug, Default)] #[serde(rename_all = "camelCase")] pub enum AspectRatio { @@ -2216,13 +2219,10 @@ impl TimelineConfiguration { } } - /// Gapless-time windows covered by clips that hide the cursor, with - /// touching windows merged so a run of hidden clips fades once at each - /// end rather than at every boundary. A window opens where its clip's - /// incoming transition starts and closes where the next transition - /// starts, matching the frame mapping above. - fn hidden_cursor_windows(&self) -> (Vec<(f64, f64)>, f64) { - let mut windows: Vec<(f64, f64)> = Vec::new(); + fn hidden_cursor_window_at(&self, time: f64) -> (Option<(f64, f64)>, f64) { + let mut window: Option<(f64, f64)> = None; + let mut active_window = None; + let contains_time = |(start, end): &(f64, f64)| !(time < *start || time >= *end); let mut segment_start = 0.0; for (index, segment) in self.segments.iter().enumerate() { let next_duration = self @@ -2230,14 +2230,20 @@ impl TimelineConfiguration { .map_or(0.0, |transition| transition.duration); let end = segment_start + segment.duration() - next_duration; if segment.hides_cursor() { - match windows.last_mut() { + match window.as_mut() { Some(last) if (last.1 - segment_start).abs() < 1e-9 => last.1 = end, - _ => windows.push((segment_start, end)), + _ => { + active_window = active_window.or_else(|| window.filter(contains_time)); + window = Some((segment_start, end)); + } } } segment_start = end; } - (windows, segment_start) + ( + active_window.or_else(|| window.filter(contains_time)), + segment_start, + ) } /// 0..1 multiplier for the cursor (and its click ripples) at an output @@ -2245,17 +2251,20 @@ impl TimelineConfiguration { /// a [`CLIP_CURSOR_FADE_SECS`] smoothstep ramp just inside each end of the /// hidden run. The ramp is skipped at the timeline's own start and end so /// the cursor never blinks in for a hidden first or last clip. + #[inline] pub fn cursor_visibility_at(&self, output_time: f64) -> f64 { if !self.segments.iter().any(TimelineSegment::hides_cursor) { return 1.0; } + self.cursor_visibility_with_hidden_clips(output_time) + } + + #[inline(never)] + fn cursor_visibility_with_hidden_clips(&self, output_time: f64) -> f64 { let holds = self.hold_windows(); let time = output_time - held_time_before(&holds, output_time); - let (windows, total) = self.hidden_cursor_windows(); - for (start, end) in windows { - if time < start || time >= end { - continue; - } + let (window, total) = self.hidden_cursor_window_at(time); + if let Some((start, end)) = window { let from_start = if start <= 1e-9 { f64::INFINITY } else { diff --git a/crates/project/src/configuration/cursor_visibility_tests.rs b/crates/project/src/configuration/cursor_visibility_tests.rs new file mode 100644 index 00000000000..97628904a65 --- /dev/null +++ b/crates/project/src/configuration/cursor_visibility_tests.rs @@ -0,0 +1,236 @@ +use super::*; +use std::{hint::black_box, time::Instant}; + +fn timeline(count: usize, mode: usize, transitions: bool, holds: bool) -> TimelineConfiguration { + let segments: Vec<_> = (0..count) + .map(|index| { + serde_json::json!({ + "start": index as f64 * 4.0, + "end": index as f64 * 4.0 + 4.0, + "timescale": ([0.5, 1.0, 2.0][index % 3]), + "hideCursor": match mode { + 0 => false, + 1 => true, + _ => index % 4 >= 1 && index % 4 <= 2, + } + }) + }) + .collect(); + let transitions: Vec<_> = (1..count) + .filter(|index| transitions && index % 2 == 1) + .map(|index| serde_json::json!({"segmentIndex": index, "type": "cross-fade", "duration": 0.4})) + .collect(); + let text = if holds { + vec![ + serde_json::json!({"start": 1.0, "end": 2.0, "layout": "fullscreen"}), + serde_json::json!({"start": 1.5, "end": 3.0, "layout": "fullscreen"}), + serde_json::json!({"start": 9.0, "end": 10.0, "layout": "fullscreen"}), + ] + } else { + Vec::new() + }; + serde_json::from_value(serde_json::json!({ + "segments": segments, "zoomSegments": [], "transitions": transitions, "textSegments": text + })) + .unwrap() +} + +fn assert_visibility(timeline: &TimelineConfiguration, time: f64) { + let expected = baseline_visibility(timeline, time); + let actual = timeline.cursor_visibility_at(time); + assert_eq!( + actual.to_bits(), + expected.to_bits(), + "time {time}, clips {:?}", + timeline.segments + ); +} + +#[test] +fn cursor_visibility_matches_allocated_windows_at_boundaries_and_holds() { + for count in 0..=6 { + for mask in 0..1_usize << count { + for transitions in [false, true] { + for holds in [false, true] { + let mut timeline = timeline(count, 0, transitions, holds); + for (index, segment) in timeline.segments.iter_mut().enumerate() { + segment.hide_cursor = Some(mask & (1 << index) != 0); + } + let (windows, total) = baseline_windows(&timeline); + let mut times = vec![ + -1.0, + -0.0, + 0.0, + total, + total + 1.0, + f64::NAN, + f64::INFINITY, + f64::NEG_INFINITY, + ]; + for (start, end) in windows.into_iter().chain(timeline.hold_windows()) { + for edge in [start, end] { + times.extend([ + edge.next_down(), + edge, + edge.next_up(), + edge - 0.15, + edge + 0.15, + edge - 0.3, + edge + 0.3, + ]); + } + } + for time in times { + assert_visibility(&timeline, time); + } + } + } + } + } +} + +#[test] +fn cursor_visibility_preserves_degenerate_clip_arithmetic() { + let values = [ + 0.0, + -0.0, + -1.0, + 1e-12, + f64::MAX, + f64::INFINITY, + f64::NEG_INFINITY, + f64::NAN, + ]; + for value in values { + for field in 0..3 { + let mut timeline = timeline(5, 2, false, false); + match field { + 0 => timeline.segments[2].start = value, + 1 => timeline.segments[2].end = value, + _ => timeline.segments[2].timescale = value, + } + for time in values + .into_iter() + .chain([0.1, 4.0, 7.9, 8.0, 10.0, 20.0, 30.0]) + { + assert_visibility(&timeline, time); + } + } + } +} + +fn measure( + timeline: &TimelineConfiguration, + visibility: fn(&TimelineConfiguration, f64) -> f64, +) -> f64 { + let iterations = 10_000; + let duration = timeline.duration(); + let started = Instant::now(); + for index in 0..iterations { + let time = (index % 97) as f64 / 97.0 * duration; + black_box(visibility(black_box(timeline), black_box(time))); + } + started.elapsed().as_secs_f64() * 1e9 / iterations as f64 +} + +#[test] +#[ignore] +fn benchmark_cursor_visibility() { + println!("clips,mode,transitions,holds,before_ns,after_ns,speedup"); + for count in [2, 20, 200, 1_000] { + for (mode, transitions, holds) in [ + (0, false, false), + (1, false, false), + (2, false, false), + (2, true, false), + (2, true, true), + ] { + let timeline = timeline(count, mode, transitions, holds); + for index in 0..97 { + assert_visibility(&timeline, index as f64 / 97.0 * timeline.duration()); + } + for _ in 0..2 { + black_box(measure(&timeline, baseline_visibility)); + black_box(measure( + &timeline, + TimelineConfiguration::cursor_visibility_at, + )); + } + let mut before = Vec::new(); + let mut after = Vec::new(); + for run in 0..9 { + if run % 2 == 0 { + before.push(measure(&timeline, baseline_visibility)); + after.push(measure( + &timeline, + TimelineConfiguration::cursor_visibility_at, + )); + } else { + after.push(measure( + &timeline, + TimelineConfiguration::cursor_visibility_at, + )); + before.push(measure(&timeline, baseline_visibility)); + } + } + before.sort_by(f64::total_cmp); + after.sort_by(f64::total_cmp); + println!( + "{count},{mode},{transitions},{holds},{:.3},{:.3},{:.2}", + before[4], + after[4], + before[4] / after[4] + ); + } + } +} + +fn baseline_windows(timeline: &TimelineConfiguration) -> (Vec<(f64, f64)>, f64) { + let mut windows: Vec<(f64, f64)> = Vec::new(); + let mut segment_start = 0.0; + for (index, segment) in timeline.segments.iter().enumerate() { + let next_duration = timeline + .effective_transition(index + 1) + .map_or(0.0, |transition| transition.duration); + let end = segment_start + segment.duration() - next_duration; + if segment.hides_cursor() { + match windows.last_mut() { + Some(last) if (last.1 - segment_start).abs() < 1e-9 => last.1 = end, + _ => windows.push((segment_start, end)), + } + } + segment_start = end; + } + (windows, segment_start) +} + +fn baseline_visibility(timeline: &TimelineConfiguration, output_time: f64) -> f64 { + if !timeline.segments.iter().any(TimelineSegment::hides_cursor) { + return 1.0; + } + let holds = timeline.hold_windows(); + let time = output_time - held_time_before(&holds, output_time); + let (windows, total) = baseline_windows(timeline); + for (start, end) in windows { + if time < start || time >= end { + continue; + } + let from_start = if start <= 1e-9 { + f64::INFINITY + } else { + time - start + }; + let from_end = if end >= total - 1e-9 { + f64::INFINITY + } else { + end - time + }; + let edge = from_start.min(from_end); + if edge >= CLIP_CURSOR_FADE_SECS { + return 0.0; + } + let t = (edge / CLIP_CURSOR_FADE_SECS).clamp(0.0, 1.0); + return 1.0 - t * t * (3.0 - 2.0 * t); + } + 1.0 +}