fix: optimize performance by wrapping trace logs, adding captured drag event subscriptions, and adding regression tests for adjustment compositing.

This commit is contained in:
Your Name
2026-07-24 02:04:35 +03:00
parent f2cca7e6a1
commit fba10b753c
9 changed files with 364 additions and 50 deletions
+35 -9
View File
@@ -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] #[inline]
fn blend_adjustment_pixel( fn blend_adjustment_pixel(
row: &mut [u8], row: &mut [u8],
@@ -435,20 +445,36 @@ fn blend_adjustment_pixel(
}; };
let eff_opacity = opacity * (mask_val as f32 / 255.0); let eff_opacity = opacity * (mask_val as f32 / 255.0);
let blended_opaque = blend_pixels( 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], [dst[0], dst[1], dst[2], 255],
[adj_rgb[0], adj_rgb[1], adj_rgb[2], 255], [adj_rgb[0], adj_rgb[1], adj_rgb[2], 255],
blend, blend,
1.0, 1.0,
); );
row[out_idx] = ((1.0 - eff_opacity) * dst[0] as f32 + eff_opacity * blended_opaque[0] as f32) [blended[0], blended[1], blended[2]]
.round() as u8; };
row[out_idx + 1] = ((1.0 - eff_opacity) * dst[1] as f32
+ eff_opacity * blended_opaque[1] as f32) if eff_opacity >= 1.0 {
.round() as u8; row[out_idx] = blended_rgb[0];
row[out_idx + 2] = ((1.0 - eff_opacity) * dst[2] as f32 row[out_idx + 1] = blended_rgb[1];
+ eff_opacity * blended_opaque[2] as f32) row[out_idx + 2] = blended_rgb[2];
.round() as u8; 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] #[inline]
@@ -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<u8> = (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]);
}
+4
View File
@@ -145,6 +145,7 @@ impl Engine {
"[render_composite_region] BEFORE composite: active_idx={}, cache_valid={}, below_cache={}, below_cache_active_idx={:?}, tile_layers_len={}, dirty_rect=[{},{},{},{}]", "[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 active_idx, cache_valid, self.below_cache.is_some(), self.below_cache_active_idx, self.tile_layers.len(), x0, y0, x1, y1
); );
if log::log_enabled!(log::Level::Trace) {
for (i, l) in self.document.layers.iter().enumerate() { for (i, l) in self.document.layers.iter().enumerate() {
let tc = self let tc = self
.tile_layers .tile_layers
@@ -156,14 +157,17 @@ impl Engine {
i, l.id, l.visible, l.dirty, l.opacity, l.blend_mode, tc i, l.id, l.visible, l.dirty, l.opacity, l.blend_mode, tc
); );
} }
}
if cache_valid { if cache_valid {
let cache = self.below_cache.as_ref().unwrap(); let cache = self.below_cache.as_ref().unwrap();
if log::log_enabled!(log::Level::Trace) {
let below_non_zero = cache.iter().filter(|&&b| b != 0).count(); let below_non_zero = cache.iter().filter(|&&b| b != 0).count();
log::trace!( log::trace!(
"[render_composite_region] CACHE HIT: using below_cache ({} non-zero bytes), compositing layers[{}..{}] on top", "[render_composite_region] CACHE HIT: using below_cache ({} non-zero bytes), compositing layers[{}..{}] on top",
below_non_zero, active_idx, self.document.layers.len() below_non_zero, active_idx, self.document.layers.len()
); );
}
for y in y0..y1 { for y in y0..y1 {
let start = (y as usize * wu + x0u) * 4; let start = (y as usize * wu + x0u) * 4;
let end = start + ((x1 - x0) as usize) * 4; let end = start + ((x1 - x0) as usize) * 4;
+2
View File
@@ -470,6 +470,7 @@ impl Engine {
ch, ch,
&mut cache, &mut cache,
); );
if log::log_enabled!(log::Level::Trace) {
let non_zero = cache.iter().filter(|&&b| b != 0).count(); let non_zero = cache.iter().filter(|&&b| b != 0).count();
log::trace!( log::trace!(
"[begin_stroke] below_cache built: {} bytes, non-zero bytes={}, active_idx={}", "[begin_stroke] below_cache built: {} bytes, non-zero bytes={}, active_idx={}",
@@ -477,6 +478,7 @@ impl Engine {
non_zero, non_zero,
active_idx active_idx
); );
}
self.below_cache = Some(cache); self.below_cache = Some(cache);
self.below_cache_active_idx = Some(active_idx); self.below_cache_active_idx = Some(active_idx);
self.below_cache_dirty = false; self.below_cache_dirty = false;
+41 -4
View File
@@ -3550,6 +3550,7 @@ impl HcieIcedApp {
} }
Message::CanvasCursorPos { x, y } => { Message::CanvasCursorPos { x, y } => {
crate::canvas::perf::record_idle_pointer_message(false);
self.canvas_cursor_pos = Some((x, y)); self.canvas_cursor_pos = Some((x, y));
} }
@@ -4707,6 +4708,7 @@ impl HcieIcedApp {
self.layer_style_dragging = true; self.layer_style_dragging = true;
} }
Message::LayerStyleDialogDragMove(x, y) | Message::DockPointerMoved(x, y) => { Message::LayerStyleDialogDragMove(x, y) | Message::DockPointerMoved(x, y) => {
crate::canvas::perf::record_idle_pointer_message(true);
self.last_cursor_pos = Some((x, y)); self.last_cursor_pos = Some((x, y));
let pointer = iced::Point::new(x, y); let pointer = iced::Point::new(x, y);
if self.dock.drag.is_none() { if self.dock.drag.is_none() {
@@ -9677,13 +9679,19 @@ impl HcieIcedApp {
colors, 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. // Performance log measuring Elm view reconstruction time.
// useful to track widget layout allocation and view model reconstruction times. // useful to track widget layout allocation and view model reconstruction times.
// log::info!("[perf] view(): {:.1}ms", elapsed.as_secs_f64() * 1000.0); // log::info!("[perf] view(): {:.1}ms", elapsed.as_secs_f64() * 1000.0);
stack.width(Length::Fill).height(Length::Fill).into() stack.width(Length::Fill).height(Length::Fill).into()
} else { } 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. // Performance log measuring Elm view reconstruction time.
// log::info!("[perf] view(): {:.1}ms", elapsed.as_secs_f64() * 1000.0); // log::info!("[perf] view(): {:.1}ms", elapsed.as_secs_f64() * 1000.0);
content.into() content.into()
@@ -9933,7 +9941,9 @@ impl HcieIcedApp {
iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left) => (status iced::mouse::Event::ButtonPressed(iced::mouse::Button::Left) => (status
== iced::event::Status::Ignored) == iced::event::Status::Ignored)
.then_some(Message::OverlayOutsideClick), .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)) Some(Message::DockPointerMoved(position.x, position.y))
} }
iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left) => { 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. // The marching ants are rendered in the persistent GPU canvas shader.
// Use a real timer rather than an immediately-ready async loop: the old // Use a real timer rather than an immediately-ready async loop: the old
// loop generated an unbounded stream of CompositeRefresh messages and // loop generated an unbounded stream of CompositeRefresh messages and
@@ -10010,7 +10041,13 @@ impl HcieIcedApp {
iced::Subscription::none() 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,
])
} }
} }
@@ -87,6 +87,8 @@ struct OverlayProgram {
_quick_mask: bool, _quick_mask: bool,
/// Active brush diameter in canvas pixels for the footprint cursor. /// Active brush diameter in canvas pixels for the footprint cursor.
brush_size: f32, 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). /// Normalized bounds of the selected vector shape in canvas-space (x1, y1, x2, y2).
selected_vector_bounds: Option<(f32, f32, f32, f32)>, selected_vector_bounds: Option<(f32, f32, f32, f32)>,
/// Rotation angle (radians) of the selected vector shape. /// Rotation angle (radians) of the selected vector shape.
@@ -108,6 +110,10 @@ struct OverlayProgram {
struct OverlayState { struct OverlayState {
/// Current cursor position in viewport-local coordinates. /// Current cursor position in viewport-local coordinates.
cursor_pos: Option<Point>, cursor_pos: Option<Point>,
/// Monotonic timestamp of the latest cursor event for opt-in latency diagnostics.
cursor_event_at: Option<std::time::Instant>,
/// Sequence used to avoid measuring duplicate redraws of one cursor event.
cursor_event_sequence: u64,
/// Whether the mouse is over the overlay. /// Whether the mouse is over the overlay.
is_hovered: bool, is_hovered: bool,
/// Current handle being hovered (for cursor changes). /// Current handle being hovered (for cursor changes).
@@ -2050,6 +2056,14 @@ impl canvas::Program<Message> for OverlayProgram {
}, },
); );
geometries.push(crosshair_frame.into_geometry()); 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<Message> for OverlayProgram {
canvas::Event::Mouse(mouse::Event::CursorMoved { position }) => { canvas::Event::Mouse(mouse::Event::CursorMoved { position }) => {
let local_pos = Point::new(position.x - bounds.x, position.y - bounds.y); let local_pos = Point::new(position.x - bounds.x, position.y - bounds.y);
state.cursor_pos = Some(local_pos); 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); state.is_hovered = bounds.contains(position);
// Update hovered handle // Update hovered handle
@@ -2305,6 +2321,7 @@ pub fn view<'a>(
polygon_points: doc.polygon_points.clone(), polygon_points: doc.polygon_points.clone(),
_quick_mask: doc.quick_mask, _quick_mask: doc.quick_mask,
brush_size: tool_state.brush_size, brush_size: tool_state.brush_size,
is_drawing: tool_state.is_drawing,
selected_vector_bounds: sel_vec_bounds, selected_vector_bounds: sel_vec_bounds,
selected_vector_angle: sel_vec_angle, selected_vector_angle: sel_vec_angle,
vector_edit_handle: doc.vector_edit_handle, vector_edit_handle: doc.vector_edit_handle,
@@ -27,6 +27,92 @@ struct PerfState {
latest_render_input: Option<Instant>, latest_render_input: Option<Instant>,
last_prepare: Option<Instant>, last_prepare: Option<Instant>,
finish_pending: bool, finish_pending: bool,
idle_cursor_samples_ms: Vec<f64>,
idle_view_samples_ms: Vec<f64>,
idle_canvas_messages: u64,
idle_dock_messages: u64,
last_idle_cursor_sequence: Option<u64>,
}
/// 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. /// Returns whether diagnostics are enabled for this process.
@@ -963,6 +963,30 @@ pub struct CanvasShaderState {
pub space_left_pan: bool, pub space_left_pan: bool,
/// Immediate local pan offset maintained during active panning to eliminate 1-frame Elm update latency. /// Immediate local pan offset maintained during active panning to eliminate 1-frame Elm update latency.
pub local_pan_offset: Option<Vector>, pub local_pan_offset: Option<Vector>,
/// 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<std::time::Instant>,
/// 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<std::time::Duration>,
) -> bool {
previous != Some(current)
&& elapsed.map_or(true, |duration| duration >= CURSOR_STATUS_INTERVAL)
} }
/// Canvas shader program — implements `iced::widget::shader::Program`. /// Canvas shader program — implements `iced::widget::shader::Program`.
@@ -1070,8 +1094,6 @@ impl shader::Program<Message> for CanvasShaderProgram {
state.cursor_pos = Some(local_pos); state.cursor_pos = Some(local_pos);
state.is_hovered = bounds.contains(position); 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( let (canvas_x, canvas_y) = screen_to_canvas_local(
local_pos, local_pos,
bounds.width, bounds.width,
@@ -1139,16 +1161,38 @@ impl shader::Program<Message> for CanvasShaderProgram {
// Cursor over canvas → status bar coords // Cursor over canvas → status bar coords
if let (Some(cx), Some(cy)) = (canvas_x, canvas_y) { if let (Some(cx), Some(cy)) = (canvas_x, canvas_y) {
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 ( return (
iced::event::Status::Captured, iced::event::Status::Captured,
Some(Message::CanvasCursorPos { Some(Message::CanvasCursorPos {
x: cx as u32, x: current.0,
y: cy as u32, 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) => { mouse::Event::ButtonPressed(button) => {
@@ -1365,10 +1409,38 @@ impl shader::Program<Message> for CanvasShaderProgram {
#[cfg(test)] #[cfg(test)]
mod shader_regression_tests { mod shader_regression_tests {
use super::{should_publish_cursor_status, CURSOR_STATUS_INTERVAL};
/// Prevents restoration of the nine-sample per-fragment selection border detector. /// Prevents restoration of the nine-sample per-fragment selection border detector.
#[test] #[test]
fn selection_overlay_uses_one_texture_sample() { fn selection_overlay_uses_one_texture_sample() {
let shader = include_str!("canvas.wgsl"); let shader = include_str!("canvas.wgsl");
assert_eq!(shader.matches("textureSample(selection_texture").count(), 1); 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),
));
}
} }
+6
View File
@@ -1,4 +1,10 @@
Build:1022 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 commit : a2264b130d47fbbd565226040bc28fd5b9626177
4K MULTI LAYEPERFORMANCE OK : SOL 5.6 HIGH feat(perf): add real-time canvas performance measurements 4K MULTI LAYEPERFORMANCE OK : SOL 5.6 HIGH feat(perf): add real-time canvas performance measurements