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