diff --git a/src/scene/camera_ops.rs b/src/scene/camera_ops.rs index 3ee2e7ce1..1aefcb675 100644 --- a/src/scene/camera_ops.rs +++ b/src/scene/camera_ops.rs @@ -161,7 +161,7 @@ impl Scene { // ── Hit-test convenience: wire name → Handle ────────────────────────── pub fn handle_from_wire_name(name: &str) -> Option { - name.parse::().ok().map(Handle::new) + crate::scene::pipeline::wire_gpu::fast_parse_u64(name).map(Handle::new) } /// Restore camera to a named view from the document view table. diff --git a/src/scene/mod.rs b/src/scene/mod.rs index bc8c98cec..9a86f0392 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -9062,7 +9062,7 @@ impl Scene { let mut changed_slots = HashMap::default(); let mut next_ordinal: HashMap = HashMap::default(); for wire in changed_wires { - if let Ok(handle) = wire.name.parse::() { + if let Some(handle) = pipeline::wire_gpu::fast_parse_u64(&wire.name) { let ordinal = next_ordinal.entry(handle).or_default(); changed_slots.insert((handle, *ordinal), wires.len() as u32); *ordinal += 1; diff --git a/src/scene/pick/interaction_index.rs b/src/scene/pick/interaction_index.rs index 349ccc837..0fb4988ae 100644 --- a/src/scene/pick/interaction_index.rs +++ b/src/scene/pick/interaction_index.rs @@ -841,7 +841,7 @@ impl InteractionIndex { let mut next_ordinal: rustc_hash::FxHashMap = rustc_hash::FxHashMap::with_capacity_and_hasher(n_wires.min(4096), Default::default()); for wire in wires { - let handle = wire.name.parse::().ok(); + let handle = crate::scene::pipeline::wire_gpu::fast_parse_u64(&wire.name); wire_handles.push(handle); wire_ordinals.push(handle.map(|h| { let entry = next_ordinal.entry(h).or_default(); diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index c6bf571a0..6046ade03 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -2615,7 +2615,7 @@ impl Pipeline { wires .par_iter() .enumerate() - .filter_map(|(idx, w)| w.name.parse::().ok().map(|h| (h, idx as u32))) + .filter_map(|(idx, w)| wire_gpu::fast_parse_u64(&w.name).map(|h| (h, idx as u32))) .collect() }; let mut index: rustc_hash::FxHashMap> = rustc_hash::FxHashMap::default(); diff --git a/src/scene/pipeline/wire_arena.rs b/src/scene/pipeline/wire_arena.rs index d0f352579..7938fa467 100644 --- a/src/scene/pipeline/wire_arena.rs +++ b/src/scene/pipeline/wire_arena.rs @@ -290,7 +290,7 @@ pub fn build_handle_index(wires: &[WireModel]) -> std::sync::Arc> = FxHashMap::default(); index.reserve(wires.len()); for (idx, w) in wires.iter().enumerate() { - if let Ok(h) = w.name.parse::() { + if let Some(h) = crate::scene::pipeline::wire_gpu::fast_parse_u64(&w.name) { index.entry(h).or_default().push(idx as u32); } } diff --git a/src/scene/pipeline/wire_gpu.rs b/src/scene/pipeline/wire_gpu.rs index 292db3963..aac7f4a3b 100644 --- a/src/scene/pipeline/wire_gpu.rs +++ b/src/scene/pipeline/wire_gpu.rs @@ -411,6 +411,10 @@ fn pack_color(color: [f32; 4]) -> [u8; 4] { /// non-dash-first patterns keep the legacy centred phase. fn wire_distances(wire: &WireModel) -> (Vec, f32, f32) { let n = wire.points.len(); + let pat_len = wire.pattern_length; + if pat_len <= 1e-6 || n < 2 { + return (Vec::new(), 0.0, 0.0); + } let explicit = wire.pattern_stations.len() >= n + 1; let (mut dists, has_break, total) = if explicit { ( @@ -446,8 +450,7 @@ fn wire_distances(wire: &WireModel) -> (Vec, f32, f32) { (dists, has_break, total) }; - let pat_len = wire.pattern_length; - if pat_len <= 1e-6 || has_break || n < 2 { + if has_break { return (dists, 0.0, 0.0); } if total <= 1e-6 { @@ -589,7 +592,12 @@ pub(crate) fn emit_wire_packed( if seg_count == 0 { return Vec::new(); } - let (dists, align_end, align_total) = wire_distances(wire); + let has_pattern = wire.pattern_length > 1e-6; + let (dists, align_end, align_total) = if has_pattern { + wire_distances(wire) + } else { + (Vec::new(), 0.0, 0.0) + }; let (marker_origin_high, marker_origin_low, marker_normal_scale) = marker_metadata(wire); let low = |i: usize| -> [f32; 3] { wire.points_low.get(i).copied().unwrap_or([0.0; 3]) }; let is_tapered = !wire.taper_widths.is_empty(); @@ -607,14 +615,19 @@ pub(crate) fn emit_wire_packed( if !finite3(a) || !finite3(b) { continue; } + let (dist_a, dist_b) = if has_pattern && dists.len() > i + 1 { + (dists[i], dists[i + 1]) + } else { + (0.0, 0.0) + }; instances.push(PackedWireInstance { pos_a: a, pos_a_low: low(i), pos_b: b, pos_b_low: low(i + 1), color: color_u8, - distance_a: dists[i], - distance_b: dists[i + 1], + distance_a: dist_a, + distance_b: dist_b, half_width, pattern_length: wire.pattern_length, pat0, @@ -641,7 +654,14 @@ pub(crate) fn emit_wire_native( color: [f32; 4], draw_depth: f32, ) -> (Vec, WireConst) { - let (dists, align_end, align_total) = wire_distances(wire); + let n = wire.points.len(); + let seg_count = n.saturating_sub(1); + let has_pattern = wire.pattern_length > 1e-6 && seg_count > 0; + let (dists, align_end, align_total) = if has_pattern { + wire_distances(wire) + } else { + (Vec::new(), 0.0, 0.0) + }; let (marker_origin_high, marker_origin_low, marker_normal_scale) = marker_metadata(wire); let is_tapered = !wire.taper_widths.is_empty(); let cst = WireConst { @@ -660,8 +680,6 @@ pub(crate) fn emit_wire_native( marker_origin_low, marker_normal_scale, }; - let n = wire.points.len(); - let seg_count = n.saturating_sub(1); if seg_count == 0 { return (Vec::new(), cst); } @@ -681,13 +699,18 @@ pub(crate) fn emit_wire_native( if !finite3(a) || !finite3(b) { continue; } + let (dist_a, dist_b) = if has_pattern && dists.len() > i + 1 { + (dists[i], dists[i + 1]) + } else { + (0.0, 0.0) + }; instances.push(WireInstance { pos_a: a, pos_a_low: low(i), pos_b: b, pos_b_low: low(i + 1), - distance_a: dists[i], - distance_b: dists[i + 1], + distance_a: dist_a, + distance_b: dist_b, wire_id, taper_ratio: [taper_ratio(i), taper_ratio(i + 1)], }); @@ -695,6 +718,23 @@ pub(crate) fn emit_wire_native( (instances, cst) } +/// Fast ASCII decimal parsing for handle IDs, avoiding standard library unicode/formatting overhead. +#[inline] +pub fn fast_parse_u64(s: &str) -> Option { + let bytes = s.as_bytes(); + if bytes.is_empty() { + return None; + } + let mut val: u64 = 0; + for &b in bytes { + if !b.is_ascii_digit() { + return None; + } + val = val.checked_mul(10)?.checked_add((b - b'0') as u64)?; + } + Some(val) +} + /// Looks up a wire's draw-order depth from the per-entity map using the /// handle encoded in its `name`. Falls back to 0.0 (transient / preview /// wires that carry no document handle). A wire carrying a block-local @@ -704,10 +744,7 @@ pub(crate) fn wire_draw_depth( wire: &WireModel, depth_map: &rustc_hash::FxHashMap, ) -> f32 { - let base = wire - .name - .parse::() - .ok() + let base = fast_parse_u64(&wire.name) .and_then(|h| depth_map.get(&h).copied()); match (base, wire.depth_override) { (Some([d, half]), Some(local)) => d + local * half,