Skip to main content

ifc_lite_wasm/api/gpu_meshes/
batch_from_source.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//! Session-source batch variants (cold-load lever 1c).
6//!
7//! `setSourceBytes` stores the whole IFC file ONCE per load on the `IfcAPI`
8//! (see the `cached_source_bytes` field doc in `api::mod`). The `*FromSource`
9//! batch entry points then read those held bytes instead of taking a `data`
10//! slice, so the wasm-bindgen glue (`passArray8ToWasm0`) no longer mallocs +
11//! memcpys the entire file into the wasm heap on EVERY call. On a huge CSG-dense
12//! model — which adapts down to 64-job batches and makes 600+ calls/worker —
13//! that per-call copy is 15-25 s/worker of pure memcpy; holding one copy removes
14//! it. The bytes are identical across a worker's calls (one model per `IfcAPI`),
15//! so the meshing reads exactly the same bytes it would have received per-call,
16//! making the output byte-for-byte identical to the `data`-taking twins.
17//!
18//! These are additive: the legacy `data`-taking `processGeometryBatch*` methods
19//! in `batch.rs` are unchanged and remain the fallback for native, non-streaming,
20//! and older-JS callers. Each `*FromSource` method is a thin wrapper that resolves
21//! the held `Arc<Vec<u8>>` and delegates to its legacy twin with that slice — an
22//! internal Rust call, so no marshalling/copy happens at that boundary.
23
24use crate::api::IfcAPI;
25use crate::zero_copy::MeshCollection;
26use wasm_bindgen::prelude::*;
27
28use super::batch::PartitionedBatch;
29
30impl IfcAPI {
31    /// Clone the held session source `Arc` (a cheap refcount bump), or an empty
32    /// `Arc<Vec<u8>>` when none is installed. The empty case is unreachable from
33    /// the JS worker (it gates `*FromSource` on a successful `setSourceBytes`),
34    /// but is handled defensively: the decoder validates every byte span, so an
35    /// empty source simply yields zero meshes instead of panicking.
36    fn source_bytes_arc(&self) -> std::sync::Arc<Vec<u8>> {
37        self.cached_source_bytes
38            .lock()
39            .unwrap_or_else(std::sync::PoisonError::into_inner)
40            .clone()
41            .unwrap_or_default()
42    }
43}
44
45#[wasm_bindgen]
46impl IfcAPI {
47    /// Store the whole IFC source file ONCE per load so the `*FromSource` batch
48    /// variants can read it from the wasm heap instead of re-copying it per call.
49    ///
50    /// Mirrors the `setEntityIndex` lifecycle: called once per worker per load,
51    /// and REPLACES the previous file wholesale (repeated calls swap the bytes),
52    /// so a parser/geometry worker reusing one `IfcAPI` across loads is safe.
53    /// The bytes must be the exact source the batch jobs' byte spans index into
54    /// (the same buffer passed as `data` to the legacy `processGeometryBatch*`),
55    /// or the decoded entities won't match — the JS worker installs its own
56    /// session buffer, so this holds by construction.
57    ///
58    /// Taking `Vec<u8>` (by value) means wasm-bindgen hands us ownership of the
59    /// single JS→wasm copy directly; we wrap it in `Arc` with no second copy.
60    #[wasm_bindgen(js_name = setSourceBytes)]
61    pub fn set_source_bytes(&self, data: Vec<u8>) {
62        let mut slot = self
63            .cached_source_bytes
64            .lock()
65            .unwrap_or_else(std::sync::PoisonError::into_inner);
66        *slot = Some(std::sync::Arc::new(data));
67    }
68
69    /// Like [`IfcAPI::process_geometry_batch`] but reads the source bytes held by
70    /// [`IfcAPI::set_source_bytes`] instead of taking `data`. Byte-for-byte
71    /// identical output — it delegates to the legacy twin with the held slice.
72    #[wasm_bindgen(js_name = processGeometryBatchFromSource)]
73    #[allow(clippy::too_many_arguments)]
74    pub fn process_geometry_batch_from_source(
75        &self,
76        jobs_flat: &[u32],
77        unit_scale: f64,
78        rtc_x: f64,
79        rtc_y: f64,
80        rtc_z: f64,
81        needs_shift: bool,
82        void_keys: &[u32],
83        void_counts: &[u32],
84        void_values: &[u32],
85        style_ids: &[u32],
86        style_colors: &[u8],
87        plane_angle_to_radians: Option<f64>,
88        material_element_ids: Option<Vec<u32>>,
89        material_color_counts: Option<Vec<u32>>,
90        material_colors_rgba: Option<Vec<u8>>,
91    ) -> MeshCollection {
92        let source = self.source_bytes_arc();
93        self.process_geometry_batch(
94            &source,
95            jobs_flat,
96            unit_scale,
97            rtc_x,
98            rtc_y,
99            rtc_z,
100            needs_shift,
101            void_keys,
102            void_counts,
103            void_values,
104            style_ids,
105            style_colors,
106            plane_angle_to_radians,
107            material_element_ids,
108            material_color_counts,
109            material_colors_rgba,
110        )
111    }
112
113    /// Like [`IfcAPI::process_geometry_batch_partitioned`] but reads the source
114    /// bytes held by [`IfcAPI::set_source_bytes`] instead of taking `data`.
115    /// Byte-for-byte identical output — it delegates to the legacy twin.
116    #[wasm_bindgen(js_name = processGeometryBatchPartitionedFromSource)]
117    #[allow(clippy::too_many_arguments)]
118    pub fn process_geometry_batch_partitioned_from_source(
119        &self,
120        jobs_flat: &[u32],
121        unit_scale: f64,
122        rtc_x: f64,
123        rtc_y: f64,
124        rtc_z: f64,
125        needs_shift: bool,
126        void_keys: &[u32],
127        void_counts: &[u32],
128        void_values: &[u32],
129        style_ids: &[u32],
130        style_colors: &[u8],
131        plane_angle_to_radians: Option<f64>,
132        material_element_ids: Option<Vec<u32>>,
133        material_color_counts: Option<Vec<u32>>,
134        material_colors_rgba: Option<Vec<u8>>,
135    ) -> PartitionedBatch {
136        let source = self.source_bytes_arc();
137        self.process_geometry_batch_partitioned(
138            &source,
139            jobs_flat,
140            unit_scale,
141            rtc_x,
142            rtc_y,
143            rtc_z,
144            needs_shift,
145            void_keys,
146            void_counts,
147            void_values,
148            style_ids,
149            style_colors,
150            plane_angle_to_radians,
151            material_element_ids,
152            material_color_counts,
153            material_colors_rgba,
154        )
155    }
156}