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}