ifc_lite_geometry/router/
brep_signatures.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Completed faceted-BREP signatures shared across one model's routers.
6
7use super::*;
8
9/// Model-scoped memo of completed fast BREP signatures, keyed by express id.
10/// Create a fresh cache for every source/model; identical ids in different
11/// models must never share this cache. Quality, scale and RTC are folded later.
12#[derive(Clone, Default)]
13pub struct SharedBrepSignatureCache(Arc<Mutex<FxHashMap<u32, u128>>>);
14
15impl GeometryRouter {
16    /// Create a fresh signature cache for ONE immutable loaded model.
17    pub fn new_brep_signature_cache() -> SharedBrepSignatureCache {
18        SharedBrepSignatureCache::default()
19    }
20
21    /// Reuse completed BREP signatures across routers of the SAME source model.
22    /// Attach a fresh cache when replacing the source, just like the item cache.
23    pub fn enable_shared_brep_signature_cache(&mut self, cache: SharedBrepSignatureCache) {
24        self.shared_brep_signatures = Some(cache);
25    }
26
27    fn brep_key_with_params(&self, structural: u128, rtc: (f64, f64, f64)) -> u128 {
28        super::content_hash::key_with_params(structural, self.tessellation_quality.to_index(), self.unit_scale, rtc)
29    }
30
31    /// [`Self::item_dedup_key_in_frame`] at the model RTC offset.
32    #[cfg(test)]
33    pub(super) fn item_dedup_key(&self, item: &DecodedEntity, decoder: &mut EntityDecoder) -> Option<u128> {
34        self.item_dedup_key_in_frame(item, decoder, self.rtc_offset)
35    }
36
37    /// Cache key for an item: its structural hash combined with the router params
38    /// that change the meshed output (tessellation quality / unit scale / RTC), or
39    /// `None` when dedup is disabled (skips the hash walk so disabled = zero
40    /// overhead). The quality fold is what keeps `setTessellationQuality` correct —
41    /// the shared cache persists across quality changes on a worker, so the key
42    /// must distinguish them (#976). `rtc` is the offset in the item's own frame
43    /// (metres): an element-frame rebase (#5698) is a different mesh from the
44    /// model-frame one.
45    pub(super) fn item_dedup_key_in_frame(
46        &self,
47        item: &DecodedEntity,
48        decoder: &mut EntityDecoder,
49        rtc: (f64, f64, f64),
50    ) -> Option<u128> {
51        self.item_dedup_cache.as_ref()?;
52        // Keep the proven default types; mapped items have their own cache.
53        let base = matches!(
54            item.ifc_type,
55            IfcType::IfcFacetedBrep
56                | IfcType::IfcBooleanResult
57                | IfcType::IfcBooleanClippingResult
58                | IfcType::IfcExtrudedAreaSolid
59        );
60        // Additive, flagged OFF by default: faceset / surface-model families. Their
61        // generic byte signature (`sig_walk_bytes`) is already complete; gated so a
62        // low-reuse model never pays the hash for no payback (the #1177 trap).
63        let extra = Self::build_dedup_extra_enabled()
64            && matches!(
65                item.ifc_type,
66                IfcType::IfcPolygonalFaceSet
67                    | IfcType::IfcTriangulatedFaceSet
68                    | IfcType::IfcShellBasedSurfaceModel
69                    | IfcType::IfcFaceBasedSurfaceModel
70            );
71        if !(base || extra) {
72            return None;
73        }
74        // #1909: retain the large-BREP bypass. A completed shared hit already
75        // passed this threshold and can skip even the face-count traversal.
76        if item.ifc_type == IfcType::IfcFacetedBrep {
77            if let Some(cache) = &self.shared_brep_signatures {
78                let hit = cache.0.lock().unwrap_or_else(|e| e.into_inner()).get(&item.id).copied();
79                if let Some(signature) = hit {
80                    return Some(self.brep_key_with_params(signature, rtc));
81                }
82            }
83            if let Some(face_count) = super::content_hash::faceted_brep_face_count(decoder, item.id) {
84                if face_count > super::content_hash::FACETED_BREP_DEDUP_FACE_LIMIT {
85                    return None;
86                }
87            }
88        }
89        // Only a completed fast BREP walk is context-independent. Never publish
90        // generic recursive hashes, depth cutoffs, cycle sentinels or refusals.
91        if item.ifc_type == IfcType::IfcFacetedBrep {
92            if let Some(cache) = &self.shared_brep_signatures {
93                if super::content_hash::is_faceted_brep(decoder, item.id) {
94                    if let Some(signature) = super::content_hash::try_faceted_brep_signature(decoder, item.id) {
95                        self.content_sig_memo.borrow_mut().insert(item.id, signature);
96                        cache.0.lock().unwrap_or_else(|e| e.into_inner()).insert(item.id, signature);
97                        return Some(self.brep_key_with_params(signature, rtc));
98                    }
99                }
100                let mut memo = self.content_sig_memo.borrow_mut();
101                let mut refused = self.content_hash_oversized_ref_drops.borrow_mut();
102                let signature = super::content_hash::item_signature_after_failed_brep(decoder, item.id, &mut memo, &mut refused);
103                return Some(self.brep_key_with_params(signature, rtc));
104            }
105        }
106        let structural = {
107            let mut memo = self.content_sig_memo.borrow_mut();
108            let mut refused = self.content_hash_oversized_ref_drops.borrow_mut();
109            super::content_hash::item_signature(decoder, item.id, &mut memo, &mut refused)
110        };
111        Some(super::content_hash::key_with_params(
112            structural,
113            self.tessellation_quality.to_index(),
114            self.unit_scale,
115            rtc,
116        ))
117    }
118
119}
120
121#[cfg(test)]
122#[path = "brep_signatures_tests.rs"]
123mod tests;