use super::GeometryRouter;
use crate::Mesh;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
impl GeometryRouter {
#[inline]
pub(super) fn compute_mesh_hash(mesh: &Mesh) -> u64 {
use rustc_hash::FxHasher;
let mut hasher = FxHasher::default();
let pos_len = mesh.positions.len();
let idx_len = mesh.indices.len();
pos_len.hash(&mut hasher);
idx_len.hash(&mut hasher);
const MAX_HASH_ELEMENTS: usize = 128;
if pos_len <= MAX_HASH_ELEMENTS {
for pos in &mesh.positions {
pos.to_bits().hash(&mut hasher);
}
} else {
let step = pos_len / MAX_HASH_ELEMENTS;
for i in (0..pos_len).step_by(step).take(MAX_HASH_ELEMENTS) {
mesh.positions[i].to_bits().hash(&mut hasher);
}
if pos_len >= 3 {
mesh.positions[pos_len - 1].to_bits().hash(&mut hasher);
mesh.positions[pos_len - 2].to_bits().hash(&mut hasher);
mesh.positions[pos_len - 3].to_bits().hash(&mut hasher);
}
}
if idx_len <= MAX_HASH_ELEMENTS {
for idx in &mesh.indices {
idx.hash(&mut hasher);
}
} else {
let step = idx_len / MAX_HASH_ELEMENTS;
for i in (0..idx_len).step_by(step).take(MAX_HASH_ELEMENTS) {
mesh.indices[i].hash(&mut hasher);
}
if idx_len >= 3 {
mesh.indices[idx_len - 1].hash(&mut hasher);
mesh.indices[idx_len - 2].hash(&mut hasher);
mesh.indices[idx_len - 3].hash(&mut hasher);
}
}
hasher.finish()
}
#[inline]
pub(super) fn get_or_cache_by_hash(&self, mesh: Mesh) -> Arc<Mesh> {
let hash = Self::compute_mesh_hash(&mesh);
{
let cache = self.geometry_hash_cache.borrow();
if let Some(cached) = cache.get(&hash) {
if meshes_equal(cached, &mesh) {
return Arc::clone(cached);
}
return Arc::new(mesh);
}
}
let arc_mesh = Arc::new(mesh);
{
let mut cache = self.geometry_hash_cache.borrow_mut();
cache.insert(hash, Arc::clone(&arc_mesh));
}
arc_mesh
}
}
#[inline]
fn meshes_equal(a: &Mesh, b: &Mesh) -> bool {
a.positions.len() == b.positions.len()
&& a.indices.len() == b.indices.len()
&& a.positions == b.positions
&& a.indices == b.indices
}
#[cfg(test)]
mod tests {
use super::*;
use crate::router::GeometryRouter;
#[test]
fn collision_does_not_silently_swap_meshes() {
let router = GeometryRouter::new();
let mut mesh_a = Mesh::new();
mesh_a.positions = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0];
mesh_a.indices = vec![0, 1, 2];
mesh_a.normals = vec![0.0; 9];
let mut mesh_b = Mesh::new();
mesh_b.positions = vec![0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, -1.0, 0.0];
mesh_b.indices = vec![0, 1, 2];
mesh_b.normals = vec![0.0; 9];
let collision_hash = GeometryRouter::compute_mesh_hash(&mesh_b);
let cached_a = Arc::new(mesh_a.clone());
router
.geometry_hash_cache
.borrow_mut()
.insert(collision_hash, Arc::clone(&cached_a));
let returned = router.get_or_cache_by_hash(mesh_b.clone());
assert_eq!(
returned.positions, mesh_b.positions,
"cache returned mesh_a after hash collision — issue #833 regression",
);
assert!(
!Arc::ptr_eq(&returned, &cached_a),
"cache returned the same Arc as the collision-inserted entry",
);
}
#[test]
fn identical_meshes_still_dedupe() {
let router = GeometryRouter::new();
let mut mesh = Mesh::new();
mesh.positions = vec![0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 2.0, 0.0];
mesh.indices = vec![0, 1, 2];
mesh.normals = vec![0.0; 9];
let first = router.get_or_cache_by_hash(mesh.clone());
let second = router.get_or_cache_by_hash(mesh.clone());
assert!(
Arc::ptr_eq(&first, &second),
"two identical meshes did not share the cached Arc",
);
}
}