perf(hash): swap std HashMap/HashSet for rustc-hash FxHash

Phase 4.1. Handle / u64 keys dominate the hot maps (block_defn,
hatch / image / mesh caches, draw-depth map, viewport wire caches);
the default SipHash is wasted work on integer keys. Alias the std
types to rustc_hash::FxHashMap / FxHashSet crate-wide so every map and
set — including the cross-module draw-depth map shared between scene
and the GPU pipelines — uses the faster hasher consistently.

Mechanical: no behavioural change. Constructors moved from ::new() to
::default() (Fx variants carry no RandomState). Build + 13 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-06-05 00:35:53 +03:00
commit 70702d063f
30 changed files with 102 additions and 98 deletions

1
Cargo.lock generated
View file

@ -28,6 +28,7 @@ dependencies = [
"printpdf",
"rayon",
"rfd",
"rustc-hash 2.1.2",
"truck-meshalgo",
"truck-modeling",
"truck-polymesh",

View file

@ -28,6 +28,9 @@ rayon = "1"
ureq = { version = "3", default-features = false, features = ["rustls"] }
inventory = "0.3"
truck-shapeops = "0.4"
# Fast non-cryptographic hasher. Handle/u64 keys dominate the hot block,
# hatch, draw-depth and wire caches; the default SipHash is overkill there.
rustc-hash = "2"
[target.'cfg(target_os = "windows")'.dependencies]
windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }

View file

@ -203,7 +203,7 @@ matters.
## Phase 4 — Allocation & Memory
### 4.1 Swap `HashMap` for `rustc-hash::FxHashMap`
### 4.1 Swap `HashMap` for `rustc-hash::FxHashMap` ✅ DONE
`Handle` is an integer wrapper; the default `SipHash` is overkill.
`FxHashMap` gives 20-40 % in hash-heavy sites (block_cache, hatches /

View file

@ -118,7 +118,7 @@ impl OpenCADStudio {
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
let layers: std::collections::HashSet<String> = self.tabs[i]
let layers: rustc_hash::FxHashSet<String> = self.tabs[i]
.scene
.selected_entities()
.into_iter()
@ -153,7 +153,7 @@ impl OpenCADStudio {
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
let layers: std::collections::HashSet<String> = self.tabs[i]
let layers: rustc_hash::FxHashSet<String> = self.tabs[i]
.scene
.selected_entities()
.into_iter()
@ -188,7 +188,7 @@ impl OpenCADStudio {
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
let layers: std::collections::HashSet<String> = self.tabs[i]
let layers: rustc_hash::FxHashSet<String> = self.tabs[i]
.scene
.selected_entities()
.into_iter()
@ -278,7 +278,7 @@ impl OpenCADStudio {
self.command_line.push_info(&cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(cmd));
} else {
let layers: std::collections::HashSet<String> = self.tabs[i]
let layers: rustc_hash::FxHashSet<String> = self.tabs[i]
.scene
.selected_entities()
.into_iter()
@ -299,7 +299,7 @@ impl OpenCADStudio {
// LAYISO — turn off all layers except those used by selected entities
"LAYISO" => {
let sel_layers: std::collections::HashSet<String> = self.tabs[i]
let sel_layers: rustc_hash::FxHashSet<String> = self.tabs[i]
.scene
.selected_entities()
.into_iter()
@ -3090,10 +3090,10 @@ impl OpenCADStudio {
// including ones not yet in the table, which sort by
// their own handle. (min_eff, max_eff) over siblings.
let fb_baseline: Option<(u64, u64)> = if to_front_opt.is_some() {
let selected_set: std::collections::HashSet<u64> =
let selected_set: rustc_hash::FxHashSet<u64> =
selected.iter().map(|h| h.value()).collect();
let doc_ref = &self.tabs[i].scene.document;
let overrides: std::collections::HashMap<u64, u64> = doc_ref
let overrides: rustc_hash::FxHashMap<u64, u64> = doc_ref
.objects
.values()
.find_map(|obj| {
@ -4358,7 +4358,7 @@ impl OpenCADStudio {
let all = sub == "ALL" || sub.is_empty();
// Collect names in use (immutable borrows — done in their own scope)
let used_layers: std::collections::HashSet<String> = self.tabs[i]
let used_layers: rustc_hash::FxHashSet<String> = self.tabs[i]
.scene
.document
.entities()
@ -4371,7 +4371,7 @@ impl OpenCADStudio {
}
})
.collect();
let used_text_styles: std::collections::HashSet<String> = self.tabs[i]
let used_text_styles: rustc_hash::FxHashSet<String> = self.tabs[i]
.scene
.document
.entities()
@ -4382,7 +4382,7 @@ impl OpenCADStudio {
})
.filter(|s| !s.is_empty())
.collect();
let used_linetypes: std::collections::HashSet<String> = self.tabs[i]
let used_linetypes: rustc_hash::FxHashSet<String> = self.tabs[i]
.scene
.document
.entities()

View file

@ -171,7 +171,7 @@ pub(super) fn entities_centroid(wires: &[WireModel]) -> glam::Vec3 {
/// Generate the next available auto group name ("*A1", "*A2", …).
pub(super) fn next_group_auto_name(scene: &crate::scene::Scene) -> String {
let existing: std::collections::HashSet<String> =
let existing: rustc_hash::FxHashSet<String> =
scene.groups().map(|g| g.name.clone()).collect();
for n in 1..=9999 {
let name = format!("*A{n}");

View file

@ -1,5 +1,5 @@
use super::{document::HistorySnapshot, OpenCADStudio};
use std::collections::HashSet;
use rustc_hash::FxHashSet as HashSet;
impl OpenCADStudio {
pub(super) fn history_label_from_active_cmd(&self, i: usize, fallback: &'static str) -> String {

View file

@ -378,7 +378,7 @@ pub(super) struct OpenCADStudio {
// ── Keyboard Shortcut Editor ──────────────────────────────────────────
/// User-defined function-key overrides: "F3" → command string.
shortcut_overrides: std::collections::HashMap<String, String>,
shortcut_overrides: rustc_hash::FxHashMap<String, String>,
// ── Layout Manager Panel ──────────────────────────────────────────────
layout_manager_selected: String,
@ -1324,7 +1324,7 @@ impl OpenCADStudio {
// Color scheme (default: dark CAD-style)
active_theme: Theme::Dark,
// Keyboard shortcuts
shortcut_overrides: std::collections::HashMap::new(),
shortcut_overrides: rustc_hash::FxHashMap::default(),
// Layout Manager
layout_manager_selected: "Model".to_string(),
layout_manager_rename_buf: String::new(),

View file

@ -214,7 +214,7 @@ impl OpenCADStudio {
self.tabs[i].scene.meshes = caches.meshes;
// Invalidate the wire cache so the new document is tessellated.
self.tabs[i].scene.bump_geometry();
self.tabs[i].scene.selected = std::collections::HashSet::new();
self.tabs[i].scene.selected = rustc_hash::FxHashSet::default();
self.tabs[i].scene.preview_wires = vec![];
self.tabs[i].scene.current_layout = "Model".to_string();
crate::linetypes::populate_document(&mut self.tabs[i].scene.document);

View file

@ -808,7 +808,7 @@ pub trait DimensionTess {
line_weight_px: f32,
world_offset: [f64; 3],
anno_scale: f32,
selected_set: &std::collections::HashSet<acadrust::Handle>,
selected_set: &rustc_hash::FxHashSet<acadrust::Handle>,
active_viewport: Option<acadrust::Handle>,
bg_color: [f32; 4],
view_aabb: Option<[f32; 4]>,
@ -826,7 +826,7 @@ impl DimensionTess for Dimension {
line_weight_px: f32,
world_offset: [f64; 3],
anno_scale: f32,
selected_set: &std::collections::HashSet<acadrust::Handle>,
selected_set: &rustc_hash::FxHashSet<acadrust::Handle>,
active_viewport: Option<acadrust::Handle>,
bg_color: [f32; 4],
view_aabb: Option<[f32; 4]>,
@ -862,7 +862,7 @@ fn tessellate_dimension_inner(
// LOD hints — when present, synthesised dim text routes through the
// top-level LOD ladder (baseline / greek / full) instead of the truck
// path so far-out drawings collapse to a colored rect or baseline.
selected_set: &std::collections::HashSet<acadrust::Handle>,
selected_set: &rustc_hash::FxHashSet<acadrust::Handle>,
active_viewport: Option<acadrust::Handle>,
bg_color: [f32; 4],
view_aabb: Option<[f32; 4]>,

View file

@ -59,8 +59,8 @@ impl TruckConvertible for Table {
// hidden borders disappear from the grid. Cells with no style still
// emit the standard four borders. To avoid drawing each shared edge
// twice we coalesce the segments by their (start, end) coordinates.
use std::collections::HashSet;
let mut emitted: HashSet<(i32, i32, i32, i32)> = HashSet::new();
use rustc_hash::FxHashSet as HashSet;
let mut emitted: HashSet<(i32, i32, i32, i32)> = HashSet::default();
let try_add = |a: Vec3, b: Vec3, vis: bool, emitted: &mut HashSet<(i32, i32, i32, i32)>, pts: &mut Vec<[f32; 3]>| {
if !vis {
return;
@ -334,7 +334,7 @@ pub fn tessellate_table(
use crate::scene::tess_util::aci_to_rgba;
use crate::scene::wire_model::WireModel;
use acadrust::types::Color;
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
if tab.rows.is_empty() || tab.columns.is_empty() {
return Vec::new();
@ -438,10 +438,10 @@ pub fn tessellate_table(
};
// Accumulators keyed by quantised colour (+ weight for borders).
let mut fills: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::new();
let mut texts: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::new();
let mut borders: HashMap<([u8; 4], u32), ([f32; 4], f32, Vec<[f32; 3]>)> = HashMap::new();
let mut emitted: std::collections::HashSet<(i32, i32, i32, i32)> = std::collections::HashSet::new();
let mut fills: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::default();
let mut texts: HashMap<[u8; 4], ([f32; 4], Vec<[f32; 3]>)> = HashMap::default();
let mut borders: HashMap<([u8; 4], u32), ([f32; 4], f32, Vec<[f32; 3]>)> = HashMap::default();
let mut emitted: rustc_hash::FxHashSet<(i32, i32, i32, i32)> = rustc_hash::FxHashSet::default();
let sel_col = WireModel::SELECTED;
let mut add_edge =

View file

@ -9,7 +9,7 @@
//! STB files follow the same format but use named styles instead of
//! ACI indices; they are read into a `Vec<NamedPlotStyle>`.
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
use std::io::Read;
use std::path::Path;
@ -72,7 +72,7 @@ impl PlotStyleTable {
name: name.into(),
is_stb: false,
aci_entries: (0..=255).map(|_| PlotStyleEntry::default()).collect(),
named_entries: HashMap::new(),
named_entries: HashMap::default(),
}
}
@ -202,7 +202,7 @@ fn compress_ctb(text: &[u8]) -> Result<Vec<u8>, String> {
fn parse_plot_style_text(text: &str, name: String, is_stb: bool) -> Result<PlotStyleTable, String> {
let mut aci_entries: Vec<PlotStyleEntry> =
(0..=255).map(|_| PlotStyleEntry::default()).collect();
let mut named_entries: HashMap<String, PlotStyleEntry> = HashMap::new();
let mut named_entries: HashMap<String, PlotStyleEntry> = HashMap::default();
let mut style_index: usize = 1; // CTB: 1-based ACI index
let mut current: Option<PlotStyleEntry> = None;
let mut current_name: String = String::new();

View file

@ -5,7 +5,7 @@ use acadrust::entities::{Block, BlockEnd};
use acadrust::tables::TableEntry;
use acadrust::types::{Handle, Vector3};
use acadrust::{CadDocument, EntityType};
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
use std::path::{Path, PathBuf};
/// Status of an external reference block.
@ -160,7 +160,7 @@ fn merge_xref_into_block(
// Prefix every xref layer (including "0"). Entity layer references are
// remapped below so the resolver finds the merged copy. Host layers
// (incl. its own "0") are untouched — no collisions.
let mut layer_map: HashMap<String, String> = HashMap::new();
let mut layer_map: HashMap<String, String> = HashMap::default();
for layer in xref_doc.layers.iter() {
let old = layer.name.clone();
let new = format!("{}|{}", prefix, old);
@ -174,7 +174,7 @@ fn merge_xref_into_block(
// ── Linetypes ───────────────────────────────────────────────────────
// Skip the three sentinel names — "ByLayer" / "ByBlock" / "Continuous"
// are magic strings the resolver matches verbatim in both docs.
let mut linetype_map: HashMap<String, String> = HashMap::new();
let mut linetype_map: HashMap<String, String> = HashMap::default();
for lt in xref_doc.line_types.iter() {
let old = lt.name.clone();
if is_sentinel_linetype(&old) {
@ -195,8 +195,8 @@ fn merge_xref_into_block(
//
// Tracks (xref-doc BR handle → host BR handle) so entities owned by
// a nested xref block can be routed to the right host block_record.
let mut br_handle_map: HashMap<Handle, Handle> = HashMap::new();
let mut block_name_map: HashMap<String, String> = HashMap::new();
let mut br_handle_map: HashMap<Handle, Handle> = HashMap::default();
let mut block_name_map: HashMap<String, String> = HashMap::default();
for br in xref_doc.block_records.iter() {
// Skip layout block records (*Model_Space, *Paper_Space, *Paper_Space0…)
// and any further-nested xrefs (we don't recurse into xref-of-xref).

View file

@ -5,7 +5,7 @@
//!
//! [`complex_lt`] returns the complex segment catalog for CPU-side rendering.
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
use std::sync::OnceLock;
use acadrust::tables::linetype::{LineType, LineTypeElement};
@ -217,7 +217,7 @@ fn push_element(token: &str, out: &mut Vec<LineTypeElement>) {
/// A linetype is "complex" when its A-line contains at least one `[SHAPE,...]`
/// element. Simple (dash-only) linetypes are excluded.
fn parse_complex(src: &str) -> HashMap<String, ComplexLt> {
let mut catalog: HashMap<String, ComplexLt> = HashMap::new();
let mut catalog: HashMap<String, ComplexLt> = HashMap::default();
let mut current: Option<(String, String)> = None; // (name, description)
for raw in src.lines() {

View file

@ -15,7 +15,7 @@
// a visited set so a self-referential block produces a marker rather than
// recursing forever.
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
use std::sync::Arc;
use acadrust::types::{Color as AcadColor, LineWeight, Transform, Vector3};
@ -206,8 +206,8 @@ impl BlockCache {
/// Walk all entities + all block_record contents collecting every distinct
/// `block_name` that appears in an Insert (transitively).
fn collect_referenced_blocks(doc: &CadDocument) -> Vec<String> {
use std::collections::HashSet;
let mut seen: HashSet<String> = HashSet::new();
use rustc_hash::FxHashSet as HashSet;
let mut seen: HashSet<String> = HashSet::default();
let mut queue: Vec<String> = Vec::new();
for entity in doc.entities() {

View file

@ -4,7 +4,7 @@
//! to 2-D pixel coordinates, then compared against the cursor or selection box.
//! This matches the visual result the user sees.
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
use acadrust::Handle;
use glam::{Mat4, Vec3};

View file

@ -16,7 +16,7 @@
// Cap height is 9 glyph units, matching the `height / 9.0` text scale used
// throughout the renderer.
use std::collections::{HashMap, HashSet};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use std::sync::{Mutex, OnceLock};
// ── Embedded fonts ─────────────────────────────────────────────────────────
@ -154,7 +154,7 @@ fn warn_missing_glyph(font_name: &str, ch: char) {
if ch.is_ascii() {
return;
}
let set = WARNED_GLYPHS.get_or_init(|| Mutex::new(HashSet::new()));
let set = WARNED_GLYPHS.get_or_init(|| Mutex::new(HashSet::default()));
if let Ok(mut guard) = set.lock() {
if guard.insert((font_name.to_string(), ch)) {
eprintln!(
@ -167,7 +167,7 @@ fn warn_missing_glyph(font_name: &str, ch: char) {
fn fonts_map() -> &'static HashMap<String, Font> {
FONTS.get_or_init(|| {
let mut map = HashMap::new();
let mut map = HashMap::default();
// Register every font under its stem and its `# Name:` header.
for (stem, src) in FONTS_SRC {
let f = parse_lff(src);
@ -461,12 +461,12 @@ fn parse_lff(src: &str) -> Font {
letter_spacing: 3.0,
word_spacing: 6.75,
line_spacing: 1.0,
glyphs: HashMap::new(),
shapes: HashMap::new(),
glyphs: HashMap::default(),
shapes: HashMap::default(),
};
let mut raw: HashMap<char, RawGlyph> = HashMap::new();
let mut raw_shapes: HashMap<String, RawGlyph> = HashMap::new();
let mut raw: HashMap<char, RawGlyph> = HashMap::default();
let mut raw_shapes: HashMap<String, RawGlyph> = HashMap::default();
let mut cur: Option<char> = None;
let mut cur_name: Option<String> = None;
let mut cur_glyph = RawGlyph::default();

View file

@ -62,7 +62,7 @@ use truck_modeling::{
use iced::time::Duration;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use std::rc::Rc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
@ -256,9 +256,9 @@ struct OffsetPrep {
/// `Some` when the model BlockRecord enumerates its entities; the offset
/// scan uses this set directly. `None` falls back to the legacy
/// permissive owner-based interpretation.
mspace_set: Option<std::collections::HashSet<Handle>>,
mspace_set: Option<rustc_hash::FxHashSet<Handle>>,
any_enumerated: bool,
owned_by_other_block: std::collections::HashSet<Handle>,
owned_by_other_block: rustc_hash::FxHashSet<Handle>,
}
fn offset_prep(doc: &acadrust::CadDocument, model_block: Handle) -> OffsetPrep {
@ -266,21 +266,21 @@ fn offset_prep(doc: &acadrust::CadDocument, model_block: Handle) -> OffsetPrep {
.block_records
.iter()
.find(|br| br.handle == model_block);
let mspace_set: Option<std::collections::HashSet<Handle>> = model_br
let mspace_set: Option<rustc_hash::FxHashSet<Handle>> = model_br
.filter(|br| !br.entity_handles.is_empty())
.map(|br| br.entity_handles.iter().copied().collect());
let any_enumerated = doc
.block_records
.iter()
.any(|br| !br.entity_handles.is_empty());
let owned_by_other_block: std::collections::HashSet<Handle> = if mspace_set.is_none() {
let owned_by_other_block: rustc_hash::FxHashSet<Handle> = if mspace_set.is_none() {
doc.block_records
.iter()
.filter(|br| br.handle != model_block)
.flat_map(|br| br.entity_handles.iter().copied())
.collect()
} else {
std::collections::HashSet::new()
rustc_hash::FxHashSet::default()
};
OffsetPrep { mspace_set, any_enumerated, owned_by_other_block }
}
@ -718,31 +718,31 @@ impl Scene {
active_model_tile: std::cell::Cell::new(0),
selection: Rc::new(RefCell::new(SelectionState::default())),
document: CadDocument::new(),
selected: HashSet::new(),
hidden: HashSet::new(),
selected: HashSet::default(),
hidden: HashSet::default(),
hover_highlight: None,
transparency_display: true,
selection_filter: HashSet::new(),
selection_filter: HashSet::default(),
preview_wires: vec![],
interim_wire: None,
camera_generation: 0,
geometry_epoch: GEOMETRY_EPOCH.fetch_add(1, Ordering::Relaxed),
wire_cache: RefCell::new(None),
model_tile_wire_cache: RefCell::new(HashMap::new()),
model_tile_wire_cache: RefCell::new(HashMap::default()),
sort_cache: RefCell::new(None),
draw_depth_cache: RefCell::new(None),
hatch_cache: RefCell::new(None),
wipeout_cache: RefCell::new(None),
image_cache: RefCell::new(None),
mesh_cache: RefCell::new(None),
viewport_wire_cache: RefCell::new(HashMap::new()),
viewport_wire_cache: RefCell::new(HashMap::default()),
paper_sheet_cache: RefCell::new(None),
paper_projected_cache: RefCell::new(HashMap::new()),
paper_projected_cache: RefCell::new(HashMap::default()),
current_layout: "Model".to_string(),
hatches: HashMap::new(),
meshes: HashMap::new(),
model_solids: HashMap::new(),
images: HashMap::new(),
hatches: HashMap::default(),
meshes: HashMap::default(),
model_solids: HashMap::default(),
images: HashMap::default(),
active_viewport: None,
bg_color: [0.11, 0.11, 0.11, 1.0],
paper_bg_color: [1.0, 1.0, 1.0, 1.0],
@ -1455,7 +1455,7 @@ impl Scene {
// Deduplicate by name: prefer the entry with a non-null block_record (the
// real layout from the file) over the default placeholder created by
// CadDocument::new().
let mut by_name: std::collections::HashMap<String, (i16, Handle)> = Default::default();
let mut by_name: rustc_hash::FxHashMap<String, (i16, Handle)> = Default::default();
for obj in self.document.objects.values() {
if let ObjectType::Layout(l) = obj {
if l.name == "Model" || l.name.is_empty() {
@ -1628,7 +1628,7 @@ impl Scene {
}
use acadrust::objects::ObjectType;
// Per-block SortEntitiesTable overrides: block -> (entity_val -> sort_val).
let mut overrides: HashMap<Handle, HashMap<u64, u64>> = HashMap::new();
let mut overrides: HashMap<Handle, HashMap<u64, u64>> = HashMap::default();
for obj in self.document.objects.values() {
if let ObjectType::SortEntitiesTable(t) = obj {
if !t.is_empty() {
@ -1643,7 +1643,7 @@ impl Scene {
}
let ms = self.model_space_block_handle();
// Group entities by owning block, carrying each entity's effective key.
let mut by_block: HashMap<Handle, Vec<(u64, u64)>> = HashMap::new();
let mut by_block: HashMap<Handle, Vec<(u64, u64)>> = HashMap::default();
for e in self.document.entities() {
let c = e.common();
// 3D meshes keep real geometric depth — exclude them from
@ -1667,7 +1667,7 @@ impl Scene {
.unwrap_or(hv);
by_block.entry(block).or_default().push((hv, eff));
}
let mut depth_map: HashMap<u64, f32> = HashMap::new();
let mut depth_map: HashMap<u64, f32> = HashMap::default();
for (_block, mut v) in by_block {
v.sort_by_key(|(_, eff)| *eff);
let denom = (v.len() as f32) + 1.0;
@ -1928,7 +1928,7 @@ impl Scene {
.unwrap_or(true);
if needs_rebuild {
let mut idx: HashMap<Handle, HashMap<u64, u64>> = HashMap::new();
let mut idx: HashMap<Handle, HashMap<u64, u64>> = HashMap::default();
for obj in self.document.objects.values() {
if let ObjectType::SortEntitiesTable(t) = obj {
if !t.is_empty() {
@ -2187,7 +2187,7 @@ impl Scene {
}
}
}
let mut map: HashMap<Handle, Handle> = HashMap::new();
let mut map: HashMap<Handle, Handle> = HashMap::default();
for br in self.document.block_records.iter() {
for &eh in &br.entity_handles {
map.insert(eh, br.handle);
@ -4098,11 +4098,11 @@ impl Scene {
pub fn clear(&mut self) {
self.document = CadDocument::new();
self.selected = HashSet::new();
self.selected = HashSet::default();
self.preview_wires = vec![];
self.current_layout = "Model".to_string();
self.hatches = HashMap::new();
self.meshes = HashMap::new();
self.hatches = HashMap::default();
self.meshes = HashMap::default();
*self.camera.borrow_mut() = Camera::default();
self.camera_generation += 1;
self.bump_geometry();
@ -4201,7 +4201,7 @@ impl Scene {
if self.selected.is_empty() {
return 0;
}
let pairs: std::collections::HashSet<(&'static str, String)> = self
let pairs: rustc_hash::FxHashSet<(&'static str, String)> = self
.selected
.iter()
.filter_map(|h| self.document.get_entity(*h))
@ -6276,7 +6276,7 @@ impl Scene {
vp_handle: Handle,
screen_height_px: f32,
) -> Vec<WireModel> {
use std::collections::HashSet as HSet;
use rustc_hash::FxHashSet as HSet;
let (frozen, vp_anno_scale, vp_aspect) = match self.document.get_entity(vp_handle) {
Some(EntityType::Viewport(vp)) => {
@ -6295,7 +6295,7 @@ impl Scene {
};
(f, anno, aspect)
}
_ => (HSet::new(), 1.0_f32, 1.0_f32),
_ => (HSet::default(), 1.0_f32, 1.0_f32),
};
// Drive the per-viewport view_aabb / wpp from the *effective* camera

View file

@ -93,7 +93,7 @@ impl Face3DGpu {
face3d_wires: &[WireModel],
all_wires: &[WireModel],
keep_3d_mesh_fills: bool,
depth_map: &std::collections::HashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, f32>,
) -> Self {
let depth_of = |w: &WireModel| -> f32 {
w.name

View file

@ -891,7 +891,7 @@ impl Pipeline {
&mut self,
device: &wgpu::Device,
wires: &[WireModel],
depth_map: &std::collections::HashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, f32>,
) {
let depth_of = |w: &WireModel| -> f32 {
w.name
@ -987,7 +987,7 @@ impl Pipeline {
face3d_wires: &[WireModel],
all_wires: &[WireModel],
wireframe_only: bool,
depth_map: &std::collections::HashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, f32>,
) {
// Edge buffer is always built from `face3d_wires`, so 3DFACE
// outlines stay on the screen regardless of mode.

View file

@ -267,7 +267,7 @@ fn emit_wire_instances(wire: &WireModel, color: [f32; 4], draw_depth: f32) -> Ve
/// 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).
fn wire_draw_depth(wire: &WireModel, depth_map: &std::collections::HashMap<u64, f32>) -> f32 {
fn wire_draw_depth(wire: &WireModel, depth_map: &rustc_hash::FxHashMap<u64, f32>) -> f32 {
wire
.name
.parse::<u64>()
@ -290,7 +290,7 @@ impl WireGpu {
pub fn from_batch(
device: &wgpu::Device,
wires: &[WireModel],
depth_map: &std::collections::HashMap<u64, f32>,
depth_map: &rustc_hash::FxHashMap<u64, f32>,
) -> Vec<Self> {
let total_segs: usize = wires.iter().map(|w| w.points.len().saturating_sub(1)).sum();
if total_segs == 0 {

View file

@ -57,7 +57,7 @@ pub struct QuadTree {
nodes: Vec<Node>,
/// `handle → (node_idx, item_idx_within_node)` so removal/update
/// is O(1) without walking the tree.
locator: std::collections::HashMap<Handle, (u32, u32)>,
locator: rustc_hash::FxHashMap<Handle, (u32, u32)>,
/// Items whose AABB falls outside the root bounds. Surfaced on
/// every query — small set in practice (typically empty for
/// well-bounded drawings).
@ -74,7 +74,7 @@ impl QuadTree {
};
Self {
nodes: vec![root],
locator: std::collections::HashMap::new(),
locator: rustc_hash::FxHashMap::default(),
overflow: Vec::new(),
}
}

View file

@ -39,7 +39,7 @@ pub struct ViewportData {
/// by the wire / face3d pipelines as a clip-z bias. WireModels carry no
/// depth field (84 construction sites); the bias is looked up by handle
/// at GPU-upload time from this map instead.
pub(super) draw_depths: Arc<std::collections::HashMap<u64, f32>>,
pub(super) draw_depths: Arc<rustc_hash::FxHashMap<u64, f32>>,
pub(super) hatches: Arc<Vec<HatchModel>>,
/// Wipeout fills — rendered in a separate pass AFTER wires.
pub(super) wipeout_hatches: Arc<Vec<HatchModel>>,

View file

@ -10,7 +10,7 @@
// All other surface types are silently skipped; partial results are still
// returned so the solid renders with at least its planar faces.
use std::collections::HashSet;
use rustc_hash::FxHashSet as HashSet;
use std::f64::consts::TAU;
use acadrust::entities::acis::types::Sense;
@ -237,7 +237,7 @@ fn collect_face_polygon(sat: &SatDocument, face: &SatFace) -> Vec<[f64; 3]> {
let first_ptr = sat_loop.first_coedge();
let mut cur = first_ptr;
let mut pts: Vec<[f64; 3]> = Vec::new();
let mut visited: HashSet<i32> = HashSet::new();
let mut visited: HashSet<i32> = HashSet::default();
loop {
if cur.is_null() {

View file

@ -63,7 +63,7 @@ pub struct SnapResult {
// ── Snapper ───────────────────────────────────────────────────────────────
use std::collections::HashSet;
use rustc_hash::FxHashSet as HashSet;
pub struct Snapper {
/// Global snap on/off toggle. When false, all snapping is bypassed
@ -87,7 +87,7 @@ pub struct Snapper {
impl Default for Snapper {
fn default() -> Self {
let mut enabled = HashSet::new();
let mut enabled = HashSet::default();
enabled.insert(SnapType::Endpoint);
enabled.insert(SnapType::Midpoint);
enabled.insert(SnapType::Center);
@ -257,7 +257,7 @@ impl Snapper {
let tmp = Snapper {
snap_enabled: true,
enabled: {
let mut s = HashSet::new();
let mut s = HashSet::default();
s.insert(SnapType::Tangent);
s
},

View file

@ -7,7 +7,7 @@
//! • Linetype → read-only for now
//! • Geometry → text_input per coordinate / dimension field
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
use std::fmt;
use crate::ui::ROW_H;
@ -142,12 +142,12 @@ impl Default for PropertiesPanel {
selected_group: None,
linetype_items: vec![],
selection_group_combo: combo_box::State::new(vec![]),
choice_combos: HashMap::new(),
choice_combos: HashMap::default(),
layer_combo: combo_box::State::new(vec![]),
lineweight_combo: combo_box::State::new(lw_options()),
linetype_combo: combo_box::State::new(vec![]),
hatch_pattern_combo: combo_box::State::new(crate::scene::hatch_patterns::names()),
edit_buf: HashMap::new(),
edit_buf: HashMap::default(),
color_picker_open: false,
color_palette_open: false,
}

View file

@ -6,7 +6,7 @@
//
// Dropdown items within a group are collected into columns of 3 rows.
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
use acadrust::types::{Color as AcadColor, LineWeight};
use iced::widget::{button, column, container, mouse_area, row, scrollable, svg, text};
@ -73,7 +73,7 @@ impl Ribbon {
wireframe: false,
ortho_mode: true,
open_dropdown: None,
last_cmd: HashMap::new(),
last_cmd: HashMap::default(),
layer_names: vec!["0".to_string()],
active_layer: "0".to_string(),
layer_infos: vec![LayerInfo {

View file

@ -1,7 +1,7 @@
// Shared rendering helpers, button styles, colours, layout constants, and
// free functions used by the Ribbon view/overlay methods.
use std::collections::HashMap;
use rustc_hash::FxHashMap as HashMap;
use std::time::Duration;
use acadrust::types::{Color as AcadColor, LineWeight};

View file

@ -2,7 +2,7 @@
//! A checked row means that type can be picked; unchecking it excludes the
//! type from interactive selection. Opened from the FILTER status pill.
use std::collections::HashSet;
use rustc_hash::FxHashSet as HashSet;
use iced::widget::{button, column, container, mouse_area, row, text};
use iced::{Background, Border, Color, Element, Fill, Length, Padding, Theme};

View file

@ -79,7 +79,7 @@ fn section<'a>(title: &'static str) -> Element<'a, Message> {
}
pub fn view_window<'a>(
overrides: &'a std::collections::HashMap<String, String>,
overrides: &'a rustc_hash::FxHashMap<String, String>,
) -> Element<'a, Message> {
// ── Toolbar ───────────────────────────────────────────────────────────
let toolbar = container(

View file

@ -5,7 +5,7 @@
//! adds or removes that pill from the bar. The choice is persisted so it
//! survives across sessions.
use std::collections::HashSet;
use rustc_hash::FxHashSet as HashSet;
use std::path::PathBuf;
/// Identifies a toggleable status-bar pill.
@ -121,7 +121,7 @@ pub struct StatusBarConfig {
impl StatusBarConfig {
/// Load the saved customization, or all-visible when none exists.
pub fn load() -> Self {
let mut hidden = HashSet::new();
let mut hidden = HashSet::default();
if let Some(path) = config_path() {
if let Ok(body) = std::fs::read_to_string(path) {
for line in body.lines() {