axiolid_mesh_compile_contract/
contract.rs

1//! Complete graph compilation capability.
2
3use crate::CompileOutcome;
4use axiolid_mesh::TriMesh;
5use axiolid_model::{GeometryGraph, NodeId};
6
7use axiolid_contracts::{Backend, ExecutionOptions, GeomResult, OutputBound, ScratchRequirement};
8
9/// Backend/orchestrator capable of lowering any advertised graph node to mesh.
10///
11/// Implementations must return [`axiolid_contracts::GeomError::Unsupported`] for a node they
12/// do not support. They must not silently omit it or approximate exact geometry
13/// unless the execution policy explicitly permits that precision.
14pub trait MeshCompiler: Backend {
15    /// Scratch this compiler needs beyond the graph and the produced meshes.
16    ///
17    /// Defaults to [`ScratchRequirement::Unbounded`]: an unaudited compiler is
18    /// treated as unbudgetable rather than silently assumed cheap.
19    fn scratch_requirement(&self) -> ScratchRequirement {
20        ScratchRequirement::Unbounded
21    }
22
23    /// Outputs produced per requested root.
24    ///
25    /// Compilation is one mesh per root, so the destination size is known
26    /// before the batch runs and workers can write disjoint slots.
27    fn output_bound(&self) -> OutputBound {
28        OutputBound::OneToOne
29    }
30
31    /// Compile one root.
32    fn compile_mesh(
33        &self,
34        graph: &GeometryGraph,
35        root: NodeId,
36        options: &ExecutionOptions,
37    ) -> GeomResult<TriMesh>;
38
39    /// Compile one root and report what happened to each attribute channel.
40    ///
41    /// The default wraps [`Self::compile_mesh`] and reports
42    /// [`CompileOutcome::attribute_fates`] as `None` (not tracked). A
43    /// compiler that carries channels overrides this so a caller can tell a
44    /// dropped channel from one that was never there.
45    fn compile_mesh_reported(
46        &self,
47        graph: &GeometryGraph,
48        root: NodeId,
49        options: &ExecutionOptions,
50    ) -> GeomResult<CompileOutcome> {
51        self.compile_mesh(graph, root, options)
52            .map(CompileOutcome::untracked)
53    }
54
55    /// Compile roots into a caller-provided buffer.
56    ///
57    /// This is the seam a batching implementation should override: the caller
58    /// owns the destination, so a provider can reserve once from
59    /// [`Self::output_bound`] and have workers write disjoint slots instead of
60    /// growing a vector under a lock. `destination` is appended to, never
61    /// cleared, so results can be accumulated across calls.
62    ///
63    /// The default is a serial loop over [`Self::compile_mesh`], which stays the
64    /// only required primitive.
65    fn compile_mesh_batch_into(
66        &self,
67        graph: &GeometryGraph,
68        roots: &[NodeId],
69        options: &ExecutionOptions,
70        destination: &mut Vec<TriMesh>,
71    ) -> GeomResult<()> {
72        destination.reserve(roots.len());
73        for &root in roots {
74            destination.push(self.compile_mesh(graph, root, options)?);
75        }
76        Ok(())
77    }
78
79    /// Compile roots as a batch.
80    ///
81    /// Convenience over [`Self::compile_mesh_batch_into`]; overriding that instead
82    /// gives both call shapes the batched behaviour.
83    fn compile_mesh_batch(
84        &self,
85        graph: &GeometryGraph,
86        roots: &[NodeId],
87        options: &ExecutionOptions,
88    ) -> GeomResult<Vec<TriMesh>> {
89        let mut destination = Vec::with_capacity(roots.len());
90        self.compile_mesh_batch_into(graph, roots, options, &mut destination)?;
91        Ok(destination)
92    }
93}