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}