Skip to main content

ifc_lite_wasm/api/
parsing.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//! Parsing and entity scanning methods for IFC-Lite API
6
7use super::IfcAPI;
8use ifc_lite_core::EntityScanner;
9use wasm_bindgen::prelude::*;
10
11#[wasm_bindgen]
12impl IfcAPI {
13    /// Fast entity scanning using SIMD-accelerated Rust scanner
14    /// Returns array of entity references for data model parsing
15    /// Much faster than TypeScript byte-by-byte scanning (5-10x speedup)
16    #[wasm_bindgen(js_name = scanEntitiesFast)]
17    pub fn scan_entities_fast(&self, content: &str) -> JsValue {
18        Self::scan_entities_fast_inner(content.as_bytes())
19    }
20
21    /// Fast entity scanning from raw bytes (avoids TextDecoder.decode on JS side).
22    /// Accepts Uint8Array directly — saves ~2-5s for 487MB files by skipping
23    /// JS string creation and UTF-16→UTF-8 conversion.
24    #[wasm_bindgen(js_name = scanEntitiesFastBytes)]
25    pub fn scan_entities_fast_bytes(&self, data: &[u8]) -> JsValue {
26        Self::scan_entities_fast_inner(data)
27    }
28
29    fn scan_entities_fast_inner(content: &[u8]) -> JsValue {
30        use serde::{Deserialize, Serialize};
31        use serde_wasm_bindgen::to_value;
32
33        #[derive(Serialize, Deserialize)]
34        #[serde(rename_all = "camelCase")]
35        struct EntityRefJs {
36            express_id: u32,
37            #[serde(rename = "type")]
38            entity_type: String,
39            byte_offset: usize,
40            byte_length: usize,
41            line_number: usize,
42        }
43
44        let mut scanner = EntityScanner::new(content);
45        let mut refs = Vec::new();
46        let bytes = content;
47
48        // Track line numbers efficiently: count newlines up to each entity start
49        let mut last_position = 0;
50        let mut line_count = 1; // Start at line 1
51
52        // Cache type name strings: ~776 unique types repeated across 8M+ entities
53        let mut type_cache: rustc_hash::FxHashMap<&str, String> = rustc_hash::FxHashMap::default();
54
55        while let Some((id, type_name, start, end)) = scanner.next_entity() {
56            // Count newlines between last position and current start
57            if start > last_position {
58                line_count += bytes[last_position..start]
59                    .iter()
60                    .filter(|&&b| b == b'\n')
61                    .count();
62            }
63
64            let entity_type = type_cache
65                .entry(type_name)
66                .or_insert_with(|| type_name.to_string())
67                .clone();
68
69            refs.push(EntityRefJs {
70                express_id: id,
71                entity_type,
72                byte_offset: start,
73                byte_length: end - start,
74                line_number: line_count,
75            });
76
77            last_position = end;
78        }
79
80        to_value(&refs).unwrap_or_else(|_| js_sys::Array::new().into())
81    }
82
83    /// Fast geometry-only entity scanning
84    /// Scans only entities that have geometry, skipping 99% of non-geometry entities
85    /// Returns array of geometry entity references for parallel processing
86    /// Much faster than scanning all entities (3x speedup for large files)
87    #[wasm_bindgen(js_name = scanGeometryEntitiesFast)]
88    pub fn scan_geometry_entities_fast(&self, content: &str) -> JsValue {
89        use serde::{Deserialize, Serialize};
90        use serde_wasm_bindgen::to_value;
91
92        #[derive(Serialize, Deserialize)]
93        #[serde(rename_all = "camelCase")]
94        struct GeometryEntityRefJs {
95            express_id: u32,
96            #[serde(rename = "type")]
97            entity_type: String,
98            byte_offset: usize,
99            byte_length: usize,
100        }
101
102        let mut scanner = EntityScanner::new(content.as_bytes());
103        let mut refs = Vec::new();
104
105        // Only scan entities that have geometry - skip IFCCARTESIANPOINT, IFCDIRECTION, etc.
106        while let Some((id, type_name, start, end)) = scanner.next_entity() {
107            // Fast filter: only process entities that can have geometry
108            if ifc_lite_core::has_geometry_by_name(type_name) {
109                refs.push(GeometryEntityRefJs {
110                    express_id: id,
111                    entity_type: type_name.to_string(),
112                    byte_offset: start,
113                    byte_length: end - start,
114                });
115            }
116        }
117
118        to_value(&refs).unwrap_or_else(|_| js_sys::Array::new().into())
119    }
120
121}