Skip to main content

ifc_lite_core/decoder/
caches.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//! `EntityDecoder`'s cache management.
6//!
7//! Split from `decoder.rs` under the house rule (AGENTS.md). These are one
8//! cohesive concern — what is retained, what is handed across rayon workers,
9//! and what a caller may drop to bound memory — and they read better together
10//! than scattered among the decode paths that populate them.
11
12use rustc_hash::FxHashMap;
13use std::sync::Arc;
14
15use super::EntityDecoder;
16use crate::DecodedEntity;
17
18impl EntityDecoder<'_> {
19    /// Drain the populated cache out of this decoder for sharing across
20    /// rayon tasks. After calling this, the decoder is empty (cache
21    /// moved out); callers typically then drop the decoder.
22    pub fn drain_cache(&mut self) -> FxHashMap<u32, Arc<DecodedEntity>> {
23        std::mem::take(&mut self.cache)
24    }
25
26    /// Clear all caches to free memory
27    pub fn clear_cache(&mut self) {
28        self.cache.clear();
29        self.point_cache.clear();
30        self.placement_transform_cache.clear();
31    }
32
33    /// Clear only the decoded-entity cache, keeping the placement memo.
34    ///
35    /// For a caller that drains on a size trigger while resolving placements.
36    /// The two caches have very different value per byte: an entry in the
37    /// entity cache saves one re-decode of one entity, while an entry in the
38    /// placement memo can save re-walking a chain that thousands of elements
39    /// share — a site or building transform is resolved once and read by every
40    /// product under it. Dropping the memo to bound the entity cache trades the
41    /// expensive one away to bound the cheap one, and does it invisibly: output
42    /// stays correct and large models simply get slower.
43    pub fn clear_entity_cache(&mut self) {
44        self.cache.clear();
45    }
46
47    /// Clear only the point coordinate cache (used after BREP preprocessing).
48    /// The entity cache is preserved for subsequent geometry processing.
49    pub fn clear_point_cache(&mut self) {
50        self.point_cache.clear();
51    }
52
53    /// Move the CartesianPoint coordinate cache OUT of this decoder, leaving it
54    /// empty. Paired with [`Self::set_point_cache`] to hoist the cache across the
55    /// per-element decoders a single worker builds within one batch: the cache is
56    /// pure memoization of `content` + point id -> coords, so reusing it across
57    /// elements is byte-identical (speed only). Cheap: moves the map header, not
58    /// its contents.
59    pub fn take_point_cache(&mut self) -> FxHashMap<u32, (f64, f64, f64)> {
60        std::mem::take(&mut self.point_cache)
61    }
62
63    /// Install a previously-accumulated point cache (see [`Self::take_point_cache`]).
64    /// Does not reset the hit/miss counters, which stay per-decoder so each job's
65    /// [`Self::point_cache_stats`] reflect only that job's activity.
66    pub fn set_point_cache(&mut self, cache: FxHashMap<u32, (f64, f64, f64)>) {
67        self.point_cache = cache;
68    }
69
70    /// `(hits, misses)` served by [`Self::get_polyloop_coords_cached`] over this
71    /// decoder's lifetime. A hit is a CartesianPoint served from the cache; a miss
72    /// is one parsed for the first time. Non-zero hits after processing more than
73    /// one faceted part with a shared point list prove cross-element memoization.
74    pub fn point_cache_stats(&self) -> (u64, u64) {
75        (self.point_cache_hits, self.point_cache_misses)
76    }
77
78    /// Move the placement-transform memo OUT of this decoder, leaving it empty.
79    /// Paired with [`Self::set_placement_transform_cache`] to hoist the cache
80    /// across the per-element decoders a single worker builds within one batch,
81    /// exactly like [`Self::take_point_cache`]. The memo is a pure function of
82    /// `content` + placement id (deterministic `parent * local` composition), so
83    /// reusing it across elements is byte-identical (speed only). Cheap: moves
84    /// the map header, not its contents.
85    pub fn take_placement_transform_cache(&mut self) -> FxHashMap<u32, [f64; 16]> {
86        std::mem::take(&mut self.placement_transform_cache)
87    }
88
89    /// Install a previously-accumulated placement-transform memo (see
90    /// [`Self::take_placement_transform_cache`]).
91    pub fn set_placement_transform_cache(&mut self, cache: FxHashMap<u32, [f64; 16]>) {
92        self.placement_transform_cache = cache;
93    }
94
95    /// Read a memoized placement world transform by placement id. Returns a copy
96    /// (`[f64; 16]` is `Copy`) so the caller can drop the borrow before
97    /// reconstructing its `Matrix4`. The array is the opaque column-major layout
98    /// written by [`Self::cache_placement_transform`].
99    pub fn get_placement_transform_cached(&self, id: u32) -> Option<[f64; 16]> {
100        self.placement_transform_cache.get(&id).copied()
101    }
102
103    /// Memoize a resolved placement world transform under its placement id. Only
104    /// the geometry router's real computed transforms (IfcLocalPlacement /
105    /// linear / grid) are stored here; identity/depth-guard fallbacks are not, so
106    /// the memo stays a pure function of the placement id (byte-identical reuse).
107    pub fn cache_placement_transform(&mut self, id: u32, transform: [f64; 16]) {
108        self.placement_transform_cache.insert(id, transform);
109    }
110
111    /// Get cache size
112    pub fn cache_size(&self) -> usize {
113        self.cache.len()
114    }
115}