ifc_lite_geometry/router/diagnostics/processor_failures.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//! Split out of `router/diagnostics.rs` (module-size ratchet): the sweep that
6//! moves a processor's OWN boolean-failure log into the router's map, so
7//! `take_csg_failures` is the single drain point every pipeline already calls.
8
9use super::super::GeometryRouter;
10use super::UNATTRIBUTED_PRODUCT_ID;
11use crate::BoolFailure;
12
13impl GeometryRouter {
14 /// Sweep every registered processor's own boolean-failure log into this
15 /// router's map. Called by [`Self::take_csg_failures`], so a consumer that
16 /// already drains the router — the native pipeline via
17 /// `produce_element_meshes`, the wasm batch path via
18 /// `drain_and_log_csg_diagnostics` — picks these up with no second opt-in.
19 ///
20 /// Before #3821 `BooleanClippingProcessor::take_failures` was called from
21 /// tests ONLY. Everything the boolean processor recorded (an unsupported
22 /// operand, an `EmptyOperand` cutter, an unknown operator) accumulated in a
23 /// `RefCell` no production caller ever read, so a model whose booleans all
24 /// degraded still reported a clean load: absence was indistinguishable from
25 /// success.
26 ///
27 /// Bucketed under [`UNATTRIBUTED_PRODUCT_ID`] — the same "no attribution
28 /// available" bucket `take_csg_failures` already uses for
29 /// `PENDING_MAPPED_BOOL_FAILURES`. A processor is registered once per
30 /// router and reused across every item it meshes, so at drain time its log
31 /// is not attributable to one product from in here.
32 ///
33 /// What that bucket does and does NOT preserve, precisely: the native and
34 /// wasm pipelines both drain PER ELEMENT (`produce_element_meshes` takes
35 /// the router's failures after each job), so a record can never be
36 /// mis-attributed to a DIFFERENT element — it is attributed to none. The
37 /// element it came from is lost, so these records do not appear in the
38 /// per-product `worst_hosts` detail and are excluded from
39 /// `products_with_failures` (see [`super::count_attributed_products`]);
40 /// they do count towards the failure totals and the reason breakdown.
41 /// Real per-product attribution would mean threading the owning product id
42 /// down to every `processor.process` call site; that is a follow-up, not a
43 /// reason to keep dropping the records.
44 ///
45 /// `register` stores ONE `Rc` per custom processor under each supported IFC type
46 /// (`IfcBooleanResult` and `IfcBooleanClippingResult` share an instance),
47 /// so iterating custom registrations can visit the same processor twice; the second visit
48 /// drains an already-emptied log and contributes nothing. No double count.
49 pub fn drain_processor_failures(&self) {
50 let mut swept: Vec<BoolFailure> = Vec::new();
51 for processor in self.processors.values() {
52 swept.extend(processor.take_bool_failures());
53 }
54 if !swept.is_empty() {
55 self.csg_failures
56 .borrow_mut()
57 .entry(UNATTRIBUTED_PRODUCT_ID)
58 .or_default()
59 .extend(swept);
60 }
61 }
62}