Skip to main content

bimifc_parser/
decoder.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//! Lazy entity decoder with caching
6
7use crate::scanner::{EntityIndex, EntityScanner};
8use crate::tokenizer::parse_entity_at;
9use bimifc_model::{DecodedEntity, EntityId, ParseError, Result};
10use rustc_hash::FxHashMap;
11use std::sync::Arc;
12
13/// Lazy entity decoder with caching
14///
15/// Decodes entities on-demand and caches them for reuse. Uses an index
16/// for O(1) entity lookup by ID.
17pub struct EntityDecoder<'a> {
18    /// Raw IFC content
19    content: &'a str,
20    /// Entity ID -> (start, end) byte offsets
21    index: EntityIndex,
22    /// Decoded entity cache
23    cache: FxHashMap<u32, Arc<DecodedEntity>>,
24    /// Cached length unit scale
25    unit_scale: Option<f64>,
26}
27
28impl<'a> EntityDecoder<'a> {
29    /// Create a new decoder for the given content
30    pub fn new(content: &'a str) -> Self {
31        let index = EntityScanner::build_index(content);
32
33        Self {
34            content,
35            index,
36            cache: FxHashMap::default(),
37            unit_scale: None,
38        }
39    }
40
41    /// Create decoder with pre-built index
42    pub fn with_index(content: &'a str, index: EntityIndex) -> Self {
43        Self {
44            content,
45            index,
46            cache: FxHashMap::default(),
47            unit_scale: None,
48        }
49    }
50
51    /// Get raw content
52    pub fn content(&self) -> &'a str {
53        self.content
54    }
55
56    /// Get entity index
57    pub fn index(&self) -> &EntityIndex {
58        &self.index
59    }
60
61    /// Get all entity IDs
62    pub fn all_ids(&self) -> Vec<EntityId> {
63        self.index.keys().map(|&id| EntityId(id)).collect()
64    }
65
66    /// Get entity count
67    pub fn entity_count(&self) -> usize {
68        self.index.len()
69    }
70
71    /// Check if entity exists
72    pub fn exists(&self, id: EntityId) -> bool {
73        self.index.contains_key(&id.0)
74    }
75
76    /// Decode entity by ID
77    pub fn decode_by_id(&mut self, id: EntityId) -> Result<Arc<DecodedEntity>> {
78        // Check cache first
79        if let Some(cached) = self.cache.get(&id.0) {
80            return Ok(Arc::clone(cached));
81        }
82
83        // Get byte offsets
84        let (start, end) = self
85            .index
86            .get(&id.0)
87            .ok_or(ParseError::EntityNotFound(id))?;
88
89        // Parse entity
90        let entity = parse_entity_at(self.content, *start, *end)
91            .map_err(|e| ParseError::EntityParse(id, e))?;
92
93        // Cache and return
94        let arc = Arc::new(entity);
95        self.cache.insert(id.0, Arc::clone(&arc));
96        Ok(arc)
97    }
98
99    /// Get raw bytes for an entity (for fast parsing)
100    pub fn raw_bytes(&self, id: EntityId) -> Option<&'a [u8]> {
101        let (start, end) = self.index.get(&id.0)?;
102        Some(&self.content.as_bytes()[*start..*end])
103    }
104
105    /// Get raw string for an entity
106    pub fn raw_str(&self, id: EntityId) -> Option<&'a str> {
107        let (start, end) = self.index.get(&id.0)?;
108        Some(&self.content[*start..*end])
109    }
110
111    /// Get cached unit scale
112    pub fn unit_scale(&self) -> Option<f64> {
113        self.unit_scale
114    }
115
116    /// Set unit scale (called after extraction)
117    pub fn set_unit_scale(&mut self, scale: f64) {
118        self.unit_scale = Some(scale);
119    }
120
121    /// Clear the cache
122    pub fn clear_cache(&mut self) {
123        self.cache.clear();
124    }
125
126    /// Get cache size
127    pub fn cache_size(&self) -> usize {
128        self.cache.len()
129    }
130
131    /// Pre-warm cache with specific entities
132    pub fn preload(&mut self, ids: &[EntityId]) {
133        for id in ids {
134            let _ = self.decode_by_id(*id);
135        }
136    }
137
138    /// Find entities by type name
139    pub fn find_by_type(&mut self, type_name: &str) -> Vec<Arc<DecodedEntity>> {
140        let matches = EntityScanner::find_by_type(self.content, type_name);
141        let mut results = Vec::with_capacity(matches.len());
142
143        for (id, _, _) in matches {
144            if let Ok(entity) = self.decode_by_id(EntityId(id)) {
145                results.push(entity);
146            }
147        }
148
149        results
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    const TEST_IFC: &str = r#"ISO-10303-21;
158HEADER;
159FILE_SCHEMA(('IFC2X3'));
160ENDSEC;
161DATA;
162#1=IFCPROJECT('guid',$,'Project',$,$,$,$,$,#2);
163#2=IFCUNITASSIGNMENT((#3));
164#3=IFCSIUNIT(*,.LENGTHUNIT.,.MILLI.,.METRE.);
165ENDSEC;
166END-ISO-10303-21;
167"#;
168
169    #[test]
170    fn test_decode_by_id() {
171        let mut decoder = EntityDecoder::new(TEST_IFC);
172
173        let entity = decoder.decode_by_id(EntityId(1)).unwrap();
174        assert_eq!(entity.id, EntityId(1));
175        assert_eq!(entity.ifc_type, bimifc_model::IfcType::IfcProject);
176    }
177
178    #[test]
179    fn test_caching() {
180        let mut decoder = EntityDecoder::new(TEST_IFC);
181
182        // First decode
183        let entity1 = decoder.decode_by_id(EntityId(1)).unwrap();
184        assert_eq!(decoder.cache_size(), 1);
185
186        // Second decode should return cached
187        let entity2 = decoder.decode_by_id(EntityId(1)).unwrap();
188        assert_eq!(decoder.cache_size(), 1);
189
190        // Should be same Arc
191        assert!(Arc::ptr_eq(&entity1, &entity2));
192    }
193
194    #[test]
195    fn test_entity_not_found() {
196        let mut decoder = EntityDecoder::new(TEST_IFC);
197
198        let result = decoder.decode_by_id(EntityId(999));
199        assert!(result.is_err());
200    }
201
202    #[test]
203    fn test_find_by_type() {
204        let mut decoder = EntityDecoder::new(TEST_IFC);
205
206        let projects = decoder.find_by_type("IFCPROJECT");
207        assert_eq!(projects.len(), 1);
208        assert_eq!(projects[0].id, EntityId(1));
209    }
210}