1use std::cell::Cell;
2use web_time::Instant;
3
4#[derive(Default, Clone, Copy)]
5pub(crate) struct MassProfile {
6 pub enabled: bool,
7 pub faces: u64,
8 pub trimmed_faces: u64,
9 pub route_biperiodic: u64,
10 pub route_winding: u64,
11 pub route_seam_band: u64,
12 pub route_cap: u64,
13 pub route_wall: u64,
14 pub route_hopped_raw: u64,
15 pub route_hopped_unwrapped: u64,
16 pub route_straddle: u64,
17 pub route_periodic_raw: u64,
18 pub route_raw: u64,
19 pub route_untrimmed: u64,
20 pub route_affine: u64,
21 pub polygon_chords: u64,
22 pub base_cells: u64,
23 pub full_cells: u64,
24 pub full_stations: u64,
25 pub leaf_cells: u64,
26 pub tri_stations: u64,
27 pub corr_chords: u64,
28 pub corr_stations: u64,
29 pub polygons_ms: f64,
30 pub base_clip_ms: f64,
31 pub cells_ms: f64,
32 pub child_clip_ms: f64,
34 pub full_ms: f64,
36 pub leaf_ms: f64,
38 pub corr_ms: f64,
39 pub untrimmed_ms: f64,
40 pub gate_ms: f64,
41 pub rule_ms: f64,
45 pub rule_spans: u64,
46 pub rule_sampled: u64,
47 pub face_lines: bool,
49 pub sign_reused: u64,
51 pub sign_reread: u64,
52 pub sign_hash_ms: f64,
53}
54
55thread_local! {
56 static PROFILE: Cell<MassProfile> = const { Cell::new(MassProfile {
57 enabled: false, faces: 0, trimmed_faces: 0, route_biperiodic: 0, route_winding: 0,
58 route_seam_band: 0, route_cap: 0, route_wall: 0, route_hopped_raw: 0,
59 route_hopped_unwrapped: 0, route_straddle: 0, route_periodic_raw: 0, route_raw: 0,
60 route_untrimmed: 0, route_affine: 0, polygon_chords: 0, base_cells: 0, full_cells: 0,
61 full_stations: 0, leaf_cells: 0, tri_stations: 0, corr_chords: 0, corr_stations: 0,
62 polygons_ms: 0.0, base_clip_ms: 0.0, cells_ms: 0.0, child_clip_ms: 0.0, full_ms: 0.0,
63 leaf_ms: 0.0, corr_ms: 0.0, untrimmed_ms: 0.0, gate_ms: 0.0, rule_ms: 0.0,
64 rule_spans: 0, rule_sampled: 0, face_lines: false,
65 sign_reused: 0, sign_reread: 0, sign_hash_ms: 0.0,
66 }) };
67}
68
69pub(crate) fn mass_profile(update: impl FnOnce(&mut MassProfile)) {
72 PROFILE.with(|cell| {
73 let mut profile = cell.get();
74 if profile.enabled {
75 update(&mut profile);
76 cell.set(profile);
77 }
78 });
79}
80
81pub(crate) fn mass_profile_enabled() -> bool {
82 PROFILE.with(|cell| cell.get().enabled)
83}
84
85pub(crate) fn snapshot() -> MassProfile {
87 PROFILE.with(|cell| cell.get())
88}
89
90pub(crate) fn face_lines_enabled() -> bool {
91 PROFILE.with(|cell| cell.get().face_lines)
92}
93
94fn route_name(delta: &MassProfile) -> &'static str {
96 for (count, name) in [
97 (delta.route_affine, "affine"),
98 (delta.route_untrimmed, "untrimmed"),
99 (delta.route_biperiodic, "biperiodic"),
100 (delta.route_winding, "winding"),
101 (delta.route_seam_band, "seam_band"),
102 (delta.route_cap, "cap"),
103 (delta.route_wall, "wall"),
104 (delta.route_hopped_raw, "hopped_raw"),
105 (delta.route_hopped_unwrapped, "hopped_unwrapped"),
106 (delta.route_straddle, "straddle"),
107 (delta.route_periodic_raw, "periodic_raw"),
108 (delta.route_raw, "raw"),
109 ] {
110 if count > 0 {
111 return name;
112 }
113 }
114 "none"
115}
116
117#[allow(clippy::too_many_arguments)]
122pub(crate) fn emit_face(
123 call: &str,
124 shell: usize,
125 face: usize,
126 kind: &str,
127 degree_u: usize,
128 degree_v: usize,
129 closed_u: bool,
130 closed_v: bool,
131 loops: usize,
132 coedges: usize,
133 before: MassProfile,
134 wall_ms: f64,
135) {
136 let now = PROFILE.with(|cell| cell.get());
137 let d = MassProfile {
138 route_biperiodic: now.route_biperiodic - before.route_biperiodic,
139 route_winding: now.route_winding - before.route_winding,
140 route_seam_band: now.route_seam_band - before.route_seam_band,
141 route_cap: now.route_cap - before.route_cap,
142 route_wall: now.route_wall - before.route_wall,
143 route_hopped_raw: now.route_hopped_raw - before.route_hopped_raw,
144 route_hopped_unwrapped: now.route_hopped_unwrapped - before.route_hopped_unwrapped,
145 route_straddle: now.route_straddle - before.route_straddle,
146 route_periodic_raw: now.route_periodic_raw - before.route_periodic_raw,
147 route_raw: now.route_raw - before.route_raw,
148 route_untrimmed: now.route_untrimmed - before.route_untrimmed,
149 route_affine: now.route_affine - before.route_affine,
150 polygon_chords: now.polygon_chords - before.polygon_chords,
151 base_cells: now.base_cells - before.base_cells,
152 full_cells: now.full_cells - before.full_cells,
153 full_stations: now.full_stations - before.full_stations,
154 leaf_cells: now.leaf_cells - before.leaf_cells,
155 tri_stations: now.tri_stations - before.tri_stations,
156 corr_chords: now.corr_chords - before.corr_chords,
157 corr_stations: now.corr_stations - before.corr_stations,
158 polygons_ms: now.polygons_ms - before.polygons_ms,
159 base_clip_ms: now.base_clip_ms - before.base_clip_ms,
160 cells_ms: now.cells_ms - before.cells_ms,
161 child_clip_ms: now.child_clip_ms - before.child_clip_ms,
162 full_ms: now.full_ms - before.full_ms,
163 leaf_ms: now.leaf_ms - before.leaf_ms,
164 corr_ms: now.corr_ms - before.corr_ms,
165 untrimmed_ms: now.untrimmed_ms - before.untrimmed_ms,
166 gate_ms: now.gate_ms - before.gate_ms,
167 rule_ms: now.rule_ms - before.rule_ms,
168 rule_spans: now.rule_spans - before.rule_spans,
169 rule_sampled: now.rule_sampled - before.rule_sampled,
170 ..MassProfile::default()
171 };
172 eprintln!(
173 "mass.face call={call} shell={shell} face={face} kind={kind} deg={degree_u}x{degree_v} closed={}{} loops={loops} coedges={coedges} route={} chords={} base_cells={} full_cells={} full_stations={} leaf_cells={} tri_stations={} corr_chords={} corr_stations={} ms={:.3} gate_ms={:.3} polygons_ms={:.3} base_clip_ms={:.3} child_clip_ms={:.3} full_ms={:.3} leaf_ms={:.3} corr_ms={:.3} untrimmed_ms={:.3} cells_ms={:.3}",
174 if closed_u { "u" } else { "-" },
175 if closed_v { "v" } else { "-" },
176 route_name(&d), d.polygon_chords, d.base_cells, d.full_cells, d.full_stations,
177 d.leaf_cells, d.tri_stations, d.corr_chords, d.corr_stations, wall_ms, d.gate_ms,
178 d.polygons_ms, d.base_clip_ms, d.child_clip_ms, d.full_ms, d.leaf_ms, d.corr_ms,
179 d.untrimmed_ms, d.cells_ms,
180 );
181}
182
183pub(crate) fn profile_started() -> Option<Instant> {
185 mass_profile_enabled().then(Instant::now)
186}
187
188pub(crate) fn elapsed_ms(started: Option<Instant>) -> f64 {
189 started.map_or(0.0, |s| s.elapsed().as_secs_f64() * 1_000.0)
190}
191
192pub(crate) fn begin(call: &str) -> Option<(String, Instant)> {
195 let enabled = std::env::var("BREP_PROFILE").is_ok();
196 let face_lines = enabled && std::env::var("BREP_PROFILE_MASS_FACES").is_ok();
197 PROFILE.with(|cell| {
198 cell.set(MassProfile {
199 enabled,
200 face_lines,
201 ..MassProfile::default()
202 })
203 });
204 enabled.then(|| (call.to_string(), Instant::now()))
205}
206
207pub(crate) fn end(token: Option<(String, Instant)>) {
209 let Some((call, started)) = token else {
210 return;
211 };
212 let p = PROFILE.with(|cell| cell.get());
213 eprintln!(
214 "mass.profile call={call} ms={:.2} faces={} trimmed={} routes: affine={} untrimmed={} biperiodic={} winding={} seam_band={} cap={} wall={} hopped_raw={} hopped_unwrapped={} straddle={} periodic_raw={} raw={} | chords={} base_cells={} full_cells={} full_stations={} leaf_cells={} tri_stations={} corr_chords={} corr_stations={} | gate_ms={:.2} polygons_ms={:.2} base_clip_ms={:.2} cells_ms={:.2} child_clip_ms={:.2} full_ms={:.2} leaf_ms={:.2} corr_ms={:.2} untrimmed_ms={:.2} rule_ms={:.2} rule_spans={} rule_sampled={}",
215 started.elapsed().as_secs_f64() * 1_000.0,
216 p.faces, p.trimmed_faces, p.route_affine, p.route_untrimmed, p.route_biperiodic,
217 p.route_winding, p.route_seam_band, p.route_cap, p.route_wall, p.route_hopped_raw,
218 p.route_hopped_unwrapped, p.route_straddle, p.route_periodic_raw, p.route_raw,
219 p.polygon_chords, p.base_cells, p.full_cells, p.full_stations, p.leaf_cells,
220 p.tri_stations, p.corr_chords, p.corr_stations, p.gate_ms, p.polygons_ms,
221 p.base_clip_ms, p.cells_ms, p.child_clip_ms, p.full_ms, p.leaf_ms, p.corr_ms,
222 p.untrimmed_ms,
223 p.rule_ms,
224 p.rule_spans,
225 p.rule_sampled,
226 );
227 if call == "solid_signed_volume_for_sign" {
228 eprintln!("mass.sign_query caller={} faces={} reused={} reread={} hash_ms={:.3} ms={:.3}",
229 current_caller(), p.faces, p.sign_reused, p.sign_reread, p.sign_hash_ms,
230 started.elapsed().as_secs_f64() * 1000.0);
231 }
232 PROFILE.with(|cell| cell.set(MassProfile::default()));
233}
234
235thread_local! {
240 static CALLER: Cell<Option<&'static str>> = const { Cell::new(None) };
242}
243
244pub struct MassCaller(Option<&'static str>);
254
255impl Drop for MassCaller {
256 fn drop(&mut self) {
257 CALLER.with(|cell| cell.set(self.0));
258 }
259}
260
261pub fn mass_caller(name: &'static str) -> MassCaller {
262 MassCaller(CALLER.with(|cell| cell.replace(Some(name))))
263}
264
265fn current_caller() -> &'static str {
266 CALLER.with(|cell| cell.get()).unwrap_or("unlabelled")
267}
268
269pub(crate) struct IdentityDump {
284 directory: Option<String>,
286}
287
288thread_local! {
289 static DUMP_SEQUENCE: Cell<u64> = const { Cell::new(0) };
293}
294
295pub(crate) fn identity_dump() -> Option<IdentityDump> {
298 let directory = std::env::var("BREP_PROFILE_MASS_DUMP").ok();
299 if directory.is_none() && std::env::var("BREP_PROFILE_MASS_IDENTITY").is_err() {
300 return None;
301 }
302 Some(IdentityDump { directory })
303}
304
305pub(crate) fn emit_identity(
309 dump: IdentityDump,
310 call: &str,
311 solid: &crate::BrepSolid,
312 values: &[(&str, f64)],
313) {
314 let started = Instant::now();
315 let identity = super::identity::face_set_identity(solid);
316 let hash_ms = started.elapsed().as_secs_f64() * 1_000.0;
317 let sequence = DUMP_SEQUENCE.with(|cell| {
318 let next = cell.get() + 1;
319 cell.set(next);
320 next
321 });
322 let printed: Vec<String> = values
323 .iter()
324 .map(|(name, value)| format!("{name}={:016x}", value.to_bits()))
325 .collect();
326 let mut path = String::new();
327 if let Some(directory) = dump.directory {
328 let file = format!("{directory}/{sequence:05}-{call}.json");
329 match serde_json::to_string(solid) {
330 Ok(text) => {
331 if let Err(error) = std::fs::write(&file, text) {
332 eprintln!("mass.dump write failed for {file}: {error}");
333 } else {
334 path = file;
335 }
336 }
337 Err(error) => eprintln!("mass.dump serialize failed for {file}: {error}"),
338 }
339 let meta = format!(
343 "{{\"call\":\"{call}\",\"seq\":{sequence},\"faces\":{},\"hash\":\"{}\",\"values\":\"{}\"}}",
344 identity.faces,
345 identity.hex(),
346 printed.join(" "),
347 );
348 let meta_file = format!("{directory}/{sequence:05}-{call}.meta.json");
349 if let Err(error) = std::fs::write(&meta_file, meta) {
350 eprintln!("mass.dump write failed for {meta_file}: {error}");
351 }
352 }
353 eprintln!(
354 "mass.identity call={call} caller={} seq={sequence} faces={} hash={} hash_ms={hash_ms:.3} {}{}",
355 current_caller(),
356 identity.faces,
357 identity.hex(),
358 printed.join(" "),
359 if path.is_empty() {
360 String::new()
361 } else {
362 format!(" path={path}")
363 },
364 );
365}
366
367pub(crate) fn sign_face(shell: usize, face: &crate::FaceRecord, reread: bool, hash_ms: f64) {
369 mass_profile(|p| {
370 p.sign_reread += u64::from(reread);
371 p.sign_reused += u64::from(!reread);
372 p.sign_hash_ms += hash_ms;
373 });
374 if face_lines_enabled() {
375 eprintln!("mass.sign_face caller={} shell={shell} id={} reused={} hash_ms={hash_ms:.3}",
376 current_caller(), face.id, !reread);
377 }
378}
379
380pub(crate) fn emit_sign_oracle(solid: &crate::BrepSolid, reading: f64, exact: f64) -> Result<(), String> {
383 let identity = crate::mass_properties::face_set_identity(solid);
384 let error = (reading - exact).abs();
385 let relative = if exact == 0.0 { 0.0 } else { error / exact.abs() };
386 eprintln!("mass.sign_oracle caller={} faces={} relative={relative:.3e}", current_caller(), identity.faces);
387 if let Some(path) = std::env::var_os("BREP_SIGN_TERMS_ORACLE_LOG") {
388 use std::io::Write;
389 let row = serde_json::json!({
390 "faces": identity.faces, "identity": identity.hex(),
391 "retained": reading, "whole_body": exact, "relative_error": relative,
392 "caller": current_caller(),
393 });
394 let mut file = std::fs::OpenOptions::new().create(true).append(true).open(path)
395 .map_err(|error| format!("sign terms oracle log: {error}"))?;
396 writeln!(file, "{row}").map_err(|error| format!("sign terms oracle log: {error}"))?;
397 }
398 Ok(())
399}