From fba10b753c22413bad5dc608da318703d1113bf8 Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 24 Jul 2026 02:04:35 +0300 Subject: [PATCH] fix: optimize performance by wrapping trace logs, adding captured drag event subscriptions, and adding regression tests for adjustment compositing. --- hcie-composite/src/tiled.rs | 54 ++++++++--- hcie-composite/tests/adjustment_fast_path.rs | 64 +++++++++++++ hcie-engine-api/src/partial_composite.rs | 34 ++++--- hcie-engine-api/src/stroke_cache.rs | 16 ++-- hcie-iced-app/crates/hcie-iced-gui/src/app.rs | 45 ++++++++- .../crates/hcie-iced-gui/src/canvas/mod.rs | 17 ++++ .../crates/hcie-iced-gui/src/canvas/perf.rs | 86 +++++++++++++++++ .../hcie-iced-gui/src/canvas/shader_canvas.rs | 92 +++++++++++++++++-- versions/version build 1022.txt | 6 ++ 9 files changed, 364 insertions(+), 50 deletions(-) create mode 100644 hcie-composite/tests/adjustment_fast_path.rs diff --git a/hcie-composite/src/tiled.rs b/hcie-composite/src/tiled.rs index daed378..28351c4 100644 --- a/hcie-composite/src/tiled.rs +++ b/hcie-composite/src/tiled.rs @@ -384,6 +384,16 @@ pub fn composite_tiled_into( } } +/// Applies one adjustment-layer pixel to the current composite row. +/// +/// **Purpose:** Evaluates curves, gradient-map, or hue/saturation data while +/// respecting masks, opacity, and blend mode. **Logic & Workflow:** Transparent +/// or masked-out destinations exit early; Normal blend bypasses the generic +/// blend dispatcher, and fully opaque adjustment pixels are assigned directly. +/// **Arguments:** `row` is the destination row, `x`/`gy` are canvas coordinates, +/// `layer` owns adjustment and mask data, and `blend`/`opacity` control mixing. +/// **Returns:** Nothing. **Side Effects / Dependencies:** Mutates only the RGB +/// channels of one destination pixel and uses `hcie-blend` for non-Normal modes. #[inline] fn blend_adjustment_pixel( row: &mut [u8], @@ -435,20 +445,36 @@ fn blend_adjustment_pixel( }; let eff_opacity = opacity * (mask_val as f32 / 255.0); - let blended_opaque = blend_pixels( - [dst[0], dst[1], dst[2], 255], - [adj_rgb[0], adj_rgb[1], adj_rgb[2], 255], - blend, - 1.0, - ); - row[out_idx] = ((1.0 - eff_opacity) * dst[0] as f32 + eff_opacity * blended_opaque[0] as f32) - .round() as u8; - row[out_idx + 1] = ((1.0 - eff_opacity) * dst[1] as f32 - + eff_opacity * blended_opaque[1] as f32) - .round() as u8; - row[out_idx + 2] = ((1.0 - eff_opacity) * dst[2] as f32 - + eff_opacity * blended_opaque[2] as f32) - .round() as u8; + if eff_opacity <= 0.0 { + return; + } + + let blended_rgb = if blend == hcie_blend::BlendMode::Normal { + adj_rgb + } else { + let blended = blend_pixels( + [dst[0], dst[1], dst[2], 255], + [adj_rgb[0], adj_rgb[1], adj_rgb[2], 255], + blend, + 1.0, + ); + [blended[0], blended[1], blended[2]] + }; + + if eff_opacity >= 1.0 { + row[out_idx] = blended_rgb[0]; + row[out_idx + 1] = blended_rgb[1]; + row[out_idx + 2] = blended_rgb[2]; + return; + } + + let inverse_opacity = 1.0 - eff_opacity; + row[out_idx] = + (inverse_opacity * dst[0] as f32 + eff_opacity * blended_rgb[0] as f32).round() as u8; + row[out_idx + 1] = + (inverse_opacity * dst[1] as f32 + eff_opacity * blended_rgb[1] as f32).round() as u8; + row[out_idx + 2] = + (inverse_opacity * dst[2] as f32 + eff_opacity * blended_rgb[2] as f32).round() as u8; } #[inline] diff --git a/hcie-composite/tests/adjustment_fast_path.rs b/hcie-composite/tests/adjustment_fast_path.rs new file mode 100644 index 0000000..f876791 --- /dev/null +++ b/hcie-composite/tests/adjustment_fast_path.rs @@ -0,0 +1,64 @@ +//! Regression coverage for optimized Normal-blend adjustment compositing. +//! +//! **Purpose:** Verifies that bypassing the generic blend dispatcher preserves +//! exact RGB output for opaque and partially opaque adjustments. +//! **Logic & Workflow:** Builds a one-pixel base and Curves layer, composites +//! through the public tiled path, and compares deterministic channel values. +//! **Side Effects / Dependencies:** Allocates only one-pixel protocol layers. + +use hcie_blend::Adjustment; +use hcie_composite::tiled::composite_tiled; +use hcie_protocol::Layer; +use hcie_tile::TiledLayer; + +/// Builds a Curves adjustment whose lookup tables map selected input channels. +/// +/// **Arguments:** `opacity` controls the layer-level adjustment mix. +/// **Returns:** A one-pixel adjustment layer. **Side Effects / Dependencies:** None. +fn curves_layer(opacity: f32) -> Layer { + let mut lut_r: Vec = (0..=255).map(|value| value as u8).collect(); + let mut lut_g = lut_r.clone(); + let mut lut_b = lut_r.clone(); + lut_r[10] = 100; + lut_g[20] = 110; + lut_b[30] = 120; + + let mut layer = Layer::new_transparent("Curves", 1, 1); + layer.adjustment = Some(Adjustment::Curves { + lut_r, + lut_g, + lut_b, + }); + layer.opacity = opacity; + layer +} + +/// Confirms a fully opaque Normal adjustment assigns the LUT result exactly. +#[test] +fn opaque_normal_adjustment_matches_lookup_result() { + let base = Layer::from_rgba("Base", 1, 1, vec![10, 20, 30, 255]); + let adjustment = curves_layer(1.0); + let tiles = vec![ + Some(TiledLayer::from_dense(&base.pixels, 1, 1)), + Some(TiledLayer::from_dense(&adjustment.pixels, 1, 1)), + ]; + + let output = composite_tiled(&[base, adjustment], &tiles, 1, 1); + + assert_eq!(output, vec![100, 110, 120, 255]); +} + +/// Confirms partial opacity retains the previous rounded interpolation behavior. +#[test] +fn partial_normal_adjustment_preserves_rounded_interpolation() { + let base = Layer::from_rgba("Base", 1, 1, vec![10, 20, 30, 255]); + let adjustment = curves_layer(0.5); + let tiles = vec![ + Some(TiledLayer::from_dense(&base.pixels, 1, 1)), + Some(TiledLayer::from_dense(&adjustment.pixels, 1, 1)), + ]; + + let output = composite_tiled(&[base, adjustment], &tiles, 1, 1); + + assert_eq!(output, vec![55, 65, 75, 255]); +} diff --git a/hcie-engine-api/src/partial_composite.rs b/hcie-engine-api/src/partial_composite.rs index d6cdeef..6b61724 100644 --- a/hcie-engine-api/src/partial_composite.rs +++ b/hcie-engine-api/src/partial_composite.rs @@ -145,25 +145,29 @@ impl Engine { "[render_composite_region] BEFORE composite: active_idx={}, cache_valid={}, below_cache={}, below_cache_active_idx={:?}, tile_layers_len={}, dirty_rect=[{},{},{},{}]", active_idx, cache_valid, self.below_cache.is_some(), self.below_cache_active_idx, self.tile_layers.len(), x0, y0, x1, y1 ); - for (i, l) in self.document.layers.iter().enumerate() { - let tc = self - .tile_layers - .get(i) - .and_then(|t| t.as_ref().map(|tl| tl.tile_count())) - .unwrap_or(0); - log::trace!( - "[render_composite_region] layer[{}] id={} visible={} dirty={} opacity={} blend={:?} tile_count={}", - i, l.id, l.visible, l.dirty, l.opacity, l.blend_mode, tc - ); + if log::log_enabled!(log::Level::Trace) { + for (i, l) in self.document.layers.iter().enumerate() { + let tc = self + .tile_layers + .get(i) + .and_then(|t| t.as_ref().map(|tl| tl.tile_count())) + .unwrap_or(0); + log::trace!( + "[render_composite_region] layer[{}] id={} visible={} dirty={} opacity={} blend={:?} tile_count={}", + i, l.id, l.visible, l.dirty, l.opacity, l.blend_mode, tc + ); + } } if cache_valid { let cache = self.below_cache.as_ref().unwrap(); - let below_non_zero = cache.iter().filter(|&&b| b != 0).count(); - log::trace!( - "[render_composite_region] CACHE HIT: using below_cache ({} non-zero bytes), compositing layers[{}..{}] on top", - below_non_zero, active_idx, self.document.layers.len() - ); + if log::log_enabled!(log::Level::Trace) { + let below_non_zero = cache.iter().filter(|&&b| b != 0).count(); + log::trace!( + "[render_composite_region] CACHE HIT: using below_cache ({} non-zero bytes), compositing layers[{}..{}] on top", + below_non_zero, active_idx, self.document.layers.len() + ); + } for y in y0..y1 { let start = (y as usize * wu + x0u) * 4; let end = start + ((x1 - x0) as usize) * 4; diff --git a/hcie-engine-api/src/stroke_cache.rs b/hcie-engine-api/src/stroke_cache.rs index da84b72..6c708f7 100644 --- a/hcie-engine-api/src/stroke_cache.rs +++ b/hcie-engine-api/src/stroke_cache.rs @@ -470,13 +470,15 @@ impl Engine { ch, &mut cache, ); - let non_zero = cache.iter().filter(|&&b| b != 0).count(); - log::trace!( - "[begin_stroke] below_cache built: {} bytes, non-zero bytes={}, active_idx={}", - cache.len(), - non_zero, - active_idx - ); + if log::log_enabled!(log::Level::Trace) { + let non_zero = cache.iter().filter(|&&b| b != 0).count(); + log::trace!( + "[begin_stroke] below_cache built: {} bytes, non-zero bytes={}, active_idx={}", + cache.len(), + non_zero, + active_idx + ); + } self.below_cache = Some(cache); self.below_cache_active_idx = Some(active_idx); self.below_cache_dirty = false; diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/app.rs b/hcie-iced-app/crates/hcie-iced-gui/src/app.rs index bc0497c..b516d9f 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/app.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/app.rs @@ -3550,6 +3550,7 @@ impl HcieIcedApp { } Message::CanvasCursorPos { x, y } => { + crate::canvas::perf::record_idle_pointer_message(false); self.canvas_cursor_pos = Some((x, y)); } @@ -4707,6 +4708,7 @@ impl HcieIcedApp { self.layer_style_dragging = true; } Message::LayerStyleDialogDragMove(x, y) | Message::DockPointerMoved(x, y) => { + crate::canvas::perf::record_idle_pointer_message(true); self.last_cursor_pos = Some((x, y)); let pointer = iced::Point::new(x, y); if self.dock.drag.is_none() { @@ -9677,13 +9679,19 @@ impl HcieIcedApp { colors, )); } - let _elapsed = view_start.elapsed(); + let elapsed = view_start.elapsed(); + if !self.tool_state.is_drawing { + crate::canvas::perf::record_idle_view(elapsed); + } // Performance log measuring Elm view reconstruction time. // useful to track widget layout allocation and view model reconstruction times. // log::info!("[perf] view(): {:.1}ms", elapsed.as_secs_f64() * 1000.0); stack.width(Length::Fill).height(Length::Fill).into() } else { - let _elapsed = view_start.elapsed(); + let elapsed = view_start.elapsed(); + if !self.tool_state.is_drawing { + crate::canvas::perf::record_idle_view(elapsed); + } // Performance log measuring Elm view reconstruction time. // log::info!("[perf] view(): {:.1}ms", elapsed.as_secs_f64() * 1000.0); content.into() @@ -9933,7 +9941,9 @@ impl HcieIcedApp { iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left) => (status == iced::event::Status::Ignored) .then_some(Message::OverlayOutsideClick), - iced::mouse::Event::CursorMoved { position } => { + iced::mouse::Event::CursorMoved { position } + if status == iced::event::Status::Ignored => + { Some(Message::DockPointerMoved(position.x, position.y)) } iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left) => { @@ -9982,6 +9992,27 @@ impl HcieIcedApp { } }); + // Captured canvas events normally stay local to the retained overlay. + // During a real global drag, forward captured movement so dock/dialog + // interactions continue across the canvas without rebuilding the app + // for every idle canvas movement. + let captured_drag_pointer = if self.layer_style_dragging + || self.dock.header_drag.is_some() + || self.dock.drag.is_some() + || self.dock.floating_drag.is_some() + { + iced::event::listen_with(|event, status, _id| match event { + iced::Event::Mouse(iced::mouse::Event::CursorMoved { position }) + if status == iced::event::Status::Captured => + { + Some(Message::DockPointerMoved(position.x, position.y)) + } + _ => None, + }) + } else { + iced::Subscription::none() + }; + // The marching ants are rendered in the persistent GPU canvas shader. // Use a real timer rather than an immediately-ready async loop: the old // loop generated an unbounded stream of CompositeRefresh messages and @@ -10010,7 +10041,13 @@ impl HcieIcedApp { iced::Subscription::none() }; - iced::Subscription::batch(vec![keyboard, mouse, marching_ants, stroke_frames]) + iced::Subscription::batch(vec![ + keyboard, + mouse, + captured_drag_pointer, + marching_ants, + stroke_frames, + ]) } } diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs b/hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs index a9ab149..b819ca5 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/canvas/mod.rs @@ -87,6 +87,8 @@ struct OverlayProgram { _quick_mask: bool, /// Active brush diameter in canvas pixels for the footprint cursor. brush_size: f32, + /// Whether a raster stroke is active; used to keep idle latency metrics isolated. + is_drawing: bool, /// Normalized bounds of the selected vector shape in canvas-space (x1, y1, x2, y2). selected_vector_bounds: Option<(f32, f32, f32, f32)>, /// Rotation angle (radians) of the selected vector shape. @@ -108,6 +110,10 @@ struct OverlayProgram { struct OverlayState { /// Current cursor position in viewport-local coordinates. cursor_pos: Option, + /// Monotonic timestamp of the latest cursor event for opt-in latency diagnostics. + cursor_event_at: Option, + /// Sequence used to avoid measuring duplicate redraws of one cursor event. + cursor_event_sequence: u64, /// Whether the mouse is over the overlay. is_hovered: bool, /// Current handle being hovered (for cursor changes). @@ -2050,6 +2056,14 @@ impl canvas::Program for OverlayProgram { }, ); geometries.push(crosshair_frame.into_geometry()); + if !self.is_drawing { + if let Some(captured_at) = state.cursor_event_at { + crate::canvas::perf::record_idle_cursor_draw( + state.cursor_event_sequence, + captured_at, + ); + } + } } } @@ -2096,6 +2110,8 @@ impl canvas::Program for OverlayProgram { canvas::Event::Mouse(mouse::Event::CursorMoved { position }) => { let local_pos = Point::new(position.x - bounds.x, position.y - bounds.y); state.cursor_pos = Some(local_pos); + state.cursor_event_at = Some(std::time::Instant::now()); + state.cursor_event_sequence = state.cursor_event_sequence.wrapping_add(1); state.is_hovered = bounds.contains(position); // Update hovered handle @@ -2305,6 +2321,7 @@ pub fn view<'a>( polygon_points: doc.polygon_points.clone(), _quick_mask: doc.quick_mask, brush_size: tool_state.brush_size, + is_drawing: tool_state.is_drawing, selected_vector_bounds: sel_vec_bounds, selected_vector_angle: sel_vec_angle, vector_edit_handle: doc.vector_edit_handle, diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/canvas/perf.rs b/hcie-iced-app/crates/hcie-iced-gui/src/canvas/perf.rs index c6c0974..63c31a3 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/canvas/perf.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/canvas/perf.rs @@ -27,6 +27,92 @@ struct PerfState { latest_render_input: Option, last_prepare: Option, finish_pending: bool, + idle_cursor_samples_ms: Vec, + idle_view_samples_ms: Vec, + idle_canvas_messages: u64, + idle_dock_messages: u64, + last_idle_cursor_sequence: Option, +} + +/// Records one application view reconstruction during cursor diagnostics. +/// +/// **Arguments:** `elapsed` is the time spent constructing the Elm element tree. +/// **Returns:** Nothing. **Side Effects / Dependencies:** Stores a bounded sample +/// only when `HCIE_CANVAS_PERF=1`; reporting is performed by cursor draw samples. +pub fn record_idle_view(elapsed: Duration) { + if !enabled() { + return; + } + with_state(|state| { + if state.idle_view_samples_ms.len() < 240 { + state + .idle_view_samples_ms + .push(elapsed.as_secs_f64() * 1000.0); + } + }); +} + +/// Counts an app-level message generated by idle pointer movement. +/// +/// **Arguments:** `dock_message` is `true` for the global dock subscription and +/// `false` for canvas status coordinates. **Returns:** Nothing. +/// **Side Effects / Dependencies:** Updates diagnostic counters only when enabled. +pub fn record_idle_pointer_message(dock_message: bool) { + if !enabled() { + return; + } + with_state(|state| { + if dock_message { + state.idle_dock_messages = state.idle_dock_messages.saturating_add(1); + } else { + state.idle_canvas_messages = state.idle_canvas_messages.saturating_add(1); + } + }); +} + +/// Records native cursor-event to overlay-draw latency and periodically reports it. +/// +/// **Arguments:** `sequence` identifies a unique retained-overlay event and +/// `captured_at` is its monotonic timestamp. **Returns:** Nothing. +/// **Side Effects / Dependencies:** Emits one summary per 120 unique cursor draws +/// when `HCIE_CANVAS_PERF=1`; duplicate draws of the same event are ignored. +pub fn record_idle_cursor_draw(sequence: u64, captured_at: Instant) { + if !enabled() { + return; + } + with_state(|state| { + if state.last_idle_cursor_sequence == Some(sequence) { + return; + } + state.last_idle_cursor_sequence = Some(sequence); + state + .idle_cursor_samples_ms + .push(captured_at.elapsed().as_secs_f64() * 1000.0); + if state.idle_cursor_samples_ms.len() < 120 { + return; + } + + let mut cursor_samples = std::mem::take(&mut state.idle_cursor_samples_ms); + let mut view_samples = std::mem::take(&mut state.idle_view_samples_ms); + cursor_samples.sort_by(f64::total_cmp); + view_samples.sort_by(f64::total_cmp); + log::info!( + target: "hcie_canvas_perf", + "idle_cursor draws={} event_to_overlay_draw[p50={:.3}ms,p95={:.3}ms,max={:.3}ms] view_rebuild[n={},p50={:.3}ms,p95={:.3}ms,max={:.3}ms] messages[canvas={},dock={}]", + cursor_samples.len(), + percentile(&cursor_samples, 0.50), + percentile(&cursor_samples, 0.95), + cursor_samples.last().copied().unwrap_or(0.0), + view_samples.len(), + percentile(&view_samples, 0.50), + percentile(&view_samples, 0.95), + view_samples.last().copied().unwrap_or(0.0), + state.idle_canvas_messages, + state.idle_dock_messages, + ); + state.idle_canvas_messages = 0; + state.idle_dock_messages = 0; + }); } /// Returns whether diagnostics are enabled for this process. diff --git a/hcie-iced-app/crates/hcie-iced-gui/src/canvas/shader_canvas.rs b/hcie-iced-app/crates/hcie-iced-gui/src/canvas/shader_canvas.rs index de2a53d..da528ac 100644 --- a/hcie-iced-app/crates/hcie-iced-gui/src/canvas/shader_canvas.rs +++ b/hcie-iced-app/crates/hcie-iced-gui/src/canvas/shader_canvas.rs @@ -963,6 +963,30 @@ pub struct CanvasShaderState { pub space_left_pan: bool, /// Immediate local pan offset maintained during active panning to eliminate 1-frame Elm update latency. pub local_pan_offset: Option, + /// Last canvas coordinate published to the status bar. + pub last_status_cursor: Option<(u32, u32)>, + /// Time of the last status-bar coordinate publication. + pub last_status_emit: Option, + /// Last pane size sent to application state, stored as exact float bit patterns. + pub last_reported_pane_size: Option<(u32, u32)>, +} + +/// Minimum interval between status-bar cursor messages during idle movement. +const CURSOR_STATUS_INTERVAL: std::time::Duration = std::time::Duration::from_millis(500); + +/// Decides whether an idle cursor coordinate needs an Elm application message. +/// +/// **Arguments:** `previous` and `current` are integer canvas coordinates; +/// `elapsed` is time since the previous publication, or `None` for the first. +/// **Returns:** `true` only for a changed coordinate whose interval is due. +/// **Side Effects / Dependencies:** None. +fn should_publish_cursor_status( + previous: Option<(u32, u32)>, + current: (u32, u32), + elapsed: Option, +) -> bool { + previous != Some(current) + && elapsed.map_or(true, |duration| duration >= CURSOR_STATUS_INTERVAL) } /// Canvas shader program — implements `iced::widget::shader::Program`. @@ -1070,8 +1094,6 @@ impl shader::Program for CanvasShaderProgram { state.cursor_pos = Some(local_pos); state.is_hovered = bounds.contains(position); - let pane_size_msg = Message::CanvasSize((bounds.width, bounds.height)); - let (canvas_x, canvas_y) = screen_to_canvas_local( local_pos, bounds.width, @@ -1139,16 +1161,38 @@ impl shader::Program for CanvasShaderProgram { // Cursor over canvas → status bar coords if let (Some(cx), Some(cy)) = (canvas_x, canvas_y) { - return ( - iced::event::Status::Captured, - Some(Message::CanvasCursorPos { - x: cx as u32, - y: cy as u32, - }), - ); + let current = (cx as u32, cy as u32); + let now = std::time::Instant::now(); + let elapsed = state + .last_status_emit + .map(|previous| now.duration_since(previous)); + if should_publish_cursor_status( + state.last_status_cursor, + current, + elapsed, + ) { + state.last_status_cursor = Some(current); + state.last_status_emit = Some(now); + return ( + iced::event::Status::Captured, + Some(Message::CanvasCursorPos { + x: current.0, + y: current.1, + }), + ); + } + return (iced::event::Status::Captured, None); } - return (iced::event::Status::Captured, Some(pane_size_msg)); + let pane_size = (bounds.width.to_bits(), bounds.height.to_bits()); + if state.last_reported_pane_size != Some(pane_size) { + state.last_reported_pane_size = Some(pane_size); + return ( + iced::event::Status::Captured, + Some(Message::CanvasSize((bounds.width, bounds.height))), + ); + } + return (iced::event::Status::Captured, None); } mouse::Event::ButtonPressed(button) => { @@ -1365,10 +1409,38 @@ impl shader::Program for CanvasShaderProgram { #[cfg(test)] mod shader_regression_tests { + use super::{should_publish_cursor_status, CURSOR_STATUS_INTERVAL}; + /// Prevents restoration of the nine-sample per-fragment selection border detector. #[test] fn selection_overlay_uses_one_texture_sample() { let shader = include_str!("canvas.wgsl"); assert_eq!(shader.matches("textureSample(selection_texture").count(), 1); } + + /// Confirms status-bar messages are bounded without suppressing the first update. + /// + /// **Purpose:** Protects the retained overlay from accidental per-pointer Elm + /// rebuilds. **Logic & Workflow:** Exercises first, early, due, and unchanged + /// cursor publications. **Arguments & Returns:** None. + /// **Side Effects / Dependencies:** None. + #[test] + fn idle_cursor_status_updates_are_rate_limited() { + assert!(should_publish_cursor_status(None, (10, 20), None)); + assert!(!should_publish_cursor_status( + Some((10, 20)), + (11, 20), + Some(CURSOR_STATUS_INTERVAL / 2), + )); + assert!(should_publish_cursor_status( + Some((10, 20)), + (11, 20), + Some(CURSOR_STATUS_INTERVAL), + )); + assert!(!should_publish_cursor_status( + Some((11, 20)), + (11, 20), + Some(CURSOR_STATUS_INTERVAL * 2), + )); + } } diff --git a/versions/version build 1022.txt b/versions/version build 1022.txt index 82a44f1..4501dc2 100644 --- a/versions/version build 1022.txt +++ b/versions/version build 1022.txt @@ -1,4 +1,10 @@ Build:1022 +Egui ile Iced arasında hız farkı var. Egui hızlı . Ama o kadar büyük değil. Zaman zaman bu gerileme ortaya çıkıyor. O yüzden buraya karşılaştırma için dosyaları bırakıyorum. + +commit : f2cca7e6a19daa9f470680260bfdf24746f214ee +DÜZELTME +feat: implement file opening logic in a new tab and streamline document loading process + commit : a2264b130d47fbbd565226040bc28fd5b9626177 4K MULTI LAYEPERFORMANCE OK : SOL 5.6 HIGH feat(perf): add real-time canvas performance measurements