1use std::sync::OnceLock;
18
19use axiolid_contracts::{
20 capability_ids, BackendId, CapabilityDescriptor, Exactness, ExecutionOptions, GeomError,
21 IntegrationDescriptor, Operation, Representation,
22};
23use axiolid_hidden_lines::{ScalarHiddenLines, ScalarVisibleFaces};
24use axiolid_mesh_hidden_lines_contract::conformance::{
25 faces::FaceConformanceSuite,
26 perspective::{PerspectiveConformanceReport, PerspectiveConformanceSuite},
27 ConformanceFailure, ConformanceReport, ConformanceSuite,
28};
29use axiolid_mesh_hidden_lines_contract::{
30 HiddenLineError, HiddenLineOutcome, HiddenLineRequest, RefusedMesh, ViewProjection,
31 VisibleFaceOutcome, VisibleFaceRequest,
32};
33
34use super::{
35 Application, ApplicationBuilder, ApplicationError, ApplicationErrorSource, CallContext,
36 ProviderConformanceReport, APPLICATION_ID, MESH_INPUT,
37};
38
39type Verdict = OnceLock<Result<(), ProviderConformanceReport>>;
40
41fn hidden_lines_verdict() -> Result<(), &'static ProviderConformanceReport> {
43 static CELL: Verdict = OnceLock::new();
44 verified(&CELL, || {
45 ProviderConformanceReport::MeshHiddenLines(Box::new(ConformanceSuite::run(
46 &ScalarHiddenLines::new(),
47 )))
48 })
49}
50
51fn perspective_hidden_lines_verdict() -> Result<(), &'static ProviderConformanceReport> {
53 static CELL: Verdict = OnceLock::new();
54 verified(&CELL, || {
55 perspective_report(PerspectiveConformanceSuite::run(&ScalarHiddenLines::new()))
56 })
57}
58
59fn perspective_report(report: PerspectiveConformanceReport) -> ProviderConformanceReport {
63 let mut folded = ConformanceReport::default();
64 if !report.is_success() {
65 folded.failures.push(ConformanceFailure::ReturnedError {
66 case: "perspective claim",
67 error: HiddenLineError::request(GeomError::InvalidInput(format!(
68 "perspective suite: {report}"
69 ))),
70 });
71 }
72 ProviderConformanceReport::MeshHiddenLines(Box::new(folded))
73}
74
75fn visible_faces_verdict() -> Result<(), &'static ProviderConformanceReport> {
77 static CELL: Verdict = OnceLock::new();
78 verified(&CELL, || {
79 ProviderConformanceReport::MeshVisibleFaces(Box::new(FaceConformanceSuite::run(
80 &ScalarVisibleFaces::new(),
81 )))
82 })
83}
84
85fn verified(
90 cell: &Verdict,
91 run: impl FnOnce() -> ProviderConformanceReport,
92) -> Result<(), &ProviderConformanceReport> {
93 cell.get_or_init(|| {
94 let report = run();
95 if report.is_success() {
96 Ok(())
97 } else {
98 Err(report)
99 }
100 })
101 .as_ref()
102 .map(|&()| ())
103}
104
105impl ApplicationBuilder {
106 #[must_use]
111 pub fn with_portable_hidden_lines(mut self) -> Self {
112 self.hidden_lines.register(0, ScalarHiddenLines::new());
113 self.hidden_lines_provider = Some(ScalarHiddenLines::ID);
114 self
115 }
116
117 #[must_use]
122 pub fn with_portable_visible_faces(mut self) -> Self {
123 self.visible_faces.register(0, ScalarVisibleFaces::new());
124 self.visible_faces_provider = Some(ScalarVisibleFaces::ID);
125 self
126 }
127}
128
129pub(super) fn push_capabilities(
131 descriptor: &mut IntegrationDescriptor,
132 hidden_lines: Option<BackendId>,
133 visible_faces: Option<BackendId>,
134) {
135 for (id, operation, provider) in [
136 (
137 capability_ids::MESH_HIDDEN_LINES,
138 Operation::MeshHiddenLines,
139 hidden_lines,
140 ),
141 (
142 capability_ids::MESH_VISIBLE_FACES,
143 Operation::MeshVisibleFaces,
144 visible_faces,
145 ),
146 ] {
147 if let Some(provider) = provider {
148 descriptor.capabilities.push(CapabilityDescriptor {
149 id,
150 operation,
151 provider,
152 required_feature: "application",
153 inputs: MESH_INPUT,
154 output: Representation::Profile2d,
155 exactness: Exactness::ToleranceBounded,
156 deterministic: true,
157 });
158 }
159 }
160}
161
162impl Application {
163 pub fn hidden_lines(
168 &self,
169 request: &HiddenLineRequest<'_>,
170 options: &ExecutionOptions,
171 ) -> Result<HiddenLineOutcome, ApplicationError> {
172 let provider = self.hidden_lines_provider.unwrap_or(APPLICATION_ID);
173 options.check_cancelled().map_err(|source| {
174 drawing_error(Operation::MeshHiddenLines, provider, options, source.into())
175 })?;
176 if self.hidden_lines_provider.is_some() {
177 hidden_lines_verdict().map_err(|report| {
178 conformance_error(Operation::MeshHiddenLines, provider, options, report)
179 })?;
180 if request.view.projection == ViewProjection::Perspective {
181 perspective_hidden_lines_verdict().map_err(|report| {
182 conformance_error(Operation::MeshHiddenLines, provider, options, report)
183 })?;
184 }
185 }
186 self.hidden_lines
187 .hidden_lines(request, options)
188 .map_err(|source| drawing_error(Operation::MeshHiddenLines, provider, options, source))
189 }
190
191 pub fn audit_hidden_lines(
194 &self,
195 request: &HiddenLineRequest<'_>,
196 options: &ExecutionOptions,
197 ) -> Result<Vec<RefusedMesh>, ApplicationError> {
198 self.hidden_lines.audit(request, options).map_err(|source| {
199 drawing_error(
200 Operation::MeshHiddenLines,
201 self.hidden_lines_provider.unwrap_or(APPLICATION_ID),
202 options,
203 source,
204 )
205 })
206 }
207
208 pub fn visible_faces(
211 &self,
212 request: &VisibleFaceRequest<'_>,
213 options: &ExecutionOptions,
214 ) -> Result<VisibleFaceOutcome, ApplicationError> {
215 let provider = self.visible_faces_provider.unwrap_or(APPLICATION_ID);
216 options.check_cancelled().map_err(|source| {
217 drawing_error(
218 Operation::MeshVisibleFaces,
219 provider,
220 options,
221 source.into(),
222 )
223 })?;
224 if self.visible_faces_provider.is_some() {
225 visible_faces_verdict().map_err(|report| {
226 conformance_error(Operation::MeshVisibleFaces, provider, options, report)
227 })?;
228 }
229 self.visible_faces
230 .visible_faces(request, options)
231 .map_err(|source| drawing_error(Operation::MeshVisibleFaces, provider, options, source))
232 }
233}
234
235fn drawing_error(
236 operation: Operation,
237 provider: BackendId,
238 options: &ExecutionOptions,
239 source: HiddenLineError,
240) -> ApplicationError {
241 ApplicationError {
242 context: CallContext {
243 operation,
244 provider,
245 tolerance: options.tolerance(),
246 },
247 source: Box::new(ApplicationErrorSource::HiddenLines(source)),
248 }
249}
250
251fn conformance_error(
252 operation: Operation,
253 provider: BackendId,
254 options: &ExecutionOptions,
255 report: &ProviderConformanceReport,
256) -> ApplicationError {
257 ApplicationError {
258 context: CallContext {
259 operation,
260 provider,
261 tolerance: options.tolerance(),
262 },
263 source: Box::new(ApplicationErrorSource::ProviderConformance(report.clone())),
264 }
265}
266
267#[cfg(test)]
268mod tests {
269 use std::cell::Cell;
270
271 use axiolid_mesh_hidden_lines_contract::conformance::{ConformanceFailure, ConformanceReport};
272
273 use super::*;
274
275 fn failing() -> ProviderConformanceReport {
276 let mut report = ConformanceReport::default();
277 report
278 .failures
279 .push(ConformanceFailure::NonDeterministic { case: "memo" });
280 ProviderConformanceReport::MeshHiddenLines(Box::new(report))
281 }
282
283 #[test]
284 fn a_failing_verdict_is_returned_and_the_suite_runs_once() {
285 static CELL: Verdict = OnceLock::new();
286 let runs = Cell::new(0);
287 for _ in 0..3 {
288 let verdict = verified(&CELL, || {
289 runs.set(runs.get() + 1);
290 failing()
291 });
292 let report = verdict.expect_err("the failing report is the refusal");
293 assert!(!report.is_success());
294 }
295 assert_eq!(runs.get(), 1);
296 }
297
298 #[test]
299 fn a_failed_perspective_suite_is_folded_into_a_hidden_line_report() {
300 use axiolid_mesh_hidden_lines_contract::conformance::perspective::PerspectiveConformanceFailure;
301
302 let mut failed = PerspectiveConformanceSuite::run(&ScalarHiddenLines::new());
303 assert!(failed.is_success(), "the shipped provider passes: {failed}");
304 failed
305 .failures
306 .push(PerspectiveConformanceFailure::ClaimUnverifiable {
307 operation: "hidden lines",
308 });
309 let folded = perspective_report(failed);
310 assert!(!folded.is_success());
311 let ProviderConformanceReport::MeshHiddenLines(report) = folded else {
312 panic!("a hidden-line report");
313 };
314 assert!(matches!(
315 report.failures[..],
316 [ConformanceFailure::ReturnedError {
317 case: "perspective claim",
318 ..
319 }]
320 ));
321 let passed =
322 perspective_report(PerspectiveConformanceSuite::run(&ScalarHiddenLines::new()));
323 assert!(passed.is_success());
324 }
325
326 #[test]
327 fn a_passing_verdict_is_kept() {
328 static CELL: Verdict = OnceLock::new();
329 let runs = Cell::new(0);
330 for _ in 0..2 {
331 verified(&CELL, || {
332 runs.set(runs.get() + 1);
333 ProviderConformanceReport::MeshHiddenLines(Box::default())
334 })
335 .expect("an empty report passes");
336 }
337 assert_eq!(runs.get(), 1);
338 }
339}