1#![deny(unsafe_op_in_unsafe_fn)]
7
8use std::collections::HashMap;
9use std::panic::{catch_unwind, AssertUnwindSafe};
10use std::sync::atomic::{AtomicU64, Ordering};
11use std::sync::{Arc, LazyLock, Mutex, MutexGuard};
12
13use axiolid::application::Application;
14use axiolid::core::Point3;
15use axiolid::mesh::TriMesh;
16
17#[repr(i32)]
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum AxiolidStatus {
21 Ok = 0,
22 NullPointer = 1,
23 InvalidArgument = 2,
24 InvalidHandle = 3,
25 LimitExceeded = 4,
26 Unsupported = 5,
27 UnsupportedExact = 6,
28 BufferTooSmall = 7,
29 OperationFailed = 8,
30 WrongResultKind = 9,
31 NoError = 10,
32 Panic = 255,
33}
34
35#[repr(C)]
37#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
38pub struct AxiolidVersion {
39 pub abi_major: u16,
40 pub abi_minor: u16,
41 pub abi_patch: u16,
42 pub crate_major: u16,
43 pub crate_minor: u16,
44 pub crate_patch: u16,
45}
46
47#[repr(transparent)]
49#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
50pub struct AxiolidContextHandle(pub u64);
51
52impl AxiolidContextHandle {
53 pub const INVALID: Self = Self(0);
54}
55
56pub type AxiolidProviderProfile = i32;
58pub const AXIOLID_PROVIDER_PORTABLE: AxiolidProviderProfile = 1;
59
60#[repr(C)]
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct AxiolidContextConfig {
64 pub provider_profile: AxiolidProviderProfile,
65 pub max_meshes: u32,
66 pub max_results: u32,
67 pub max_vertices_per_mesh: u32,
68 pub max_triangles_per_mesh: u32,
69}
70
71impl Default for AxiolidContextConfig {
72 fn default() -> Self {
73 Self {
74 provider_profile: AXIOLID_PROVIDER_PORTABLE,
75 max_meshes: 1_024,
76 max_results: 1_024,
77 max_vertices_per_mesh: 10_000_000,
78 max_triangles_per_mesh: 20_000_000,
79 }
80 }
81}
82
83struct Context {
84 application: Application,
85 config: AxiolidContextConfig,
86 meshes: HandleTable<TriMesh>,
87 results: HandleTable<StoredResult>,
88 last_error: Option<ErrorRecord>,
89}
90
91enum StoredResult {
92 Mesh(TriMesh),
93 Exact(Box<axiolid::brep::ExactBRep>),
94}
95
96struct ErrorRecord {
97 status: AxiolidStatus,
98 operation: AxiolidOperation,
99 tolerance: AxiolidTolerance,
100 provider: String,
101 message: String,
102}
103
104struct HandleTable<T> {
105 values: HashMap<u64, T>,
106}
107
108impl<T> Default for HandleTable<T> {
109 fn default() -> Self {
110 Self {
111 values: HashMap::new(),
112 }
113 }
114}
115
116static NEXT_HANDLE: AtomicU64 = AtomicU64::new(1);
117
118impl<T> HandleTable<T> {
119 fn insert(&mut self, value: T) -> u64 {
120 let handle = loop {
121 let candidate = NEXT_HANDLE.fetch_add(1, Ordering::Relaxed);
122 if candidate != 0 && !self.values.contains_key(&candidate) {
123 break candidate;
124 }
125 };
126 self.values.insert(handle, value);
127 handle
128 }
129
130 fn get(&self, handle: u64) -> Option<&T> {
131 self.values.get(&handle)
132 }
133
134 fn get_mut(&mut self, handle: u64) -> Option<&mut T> {
135 self.values.get_mut(&handle)
136 }
137
138 fn remove(&mut self, handle: u64) -> Option<T> {
139 self.values.remove(&handle)
140 }
141
142 fn live_count(&self) -> usize {
143 self.values.len()
144 }
145}
146
147static CONTEXTS: LazyLock<Mutex<HandleTable<Arc<Mutex<Context>>>>> =
148 LazyLock::new(|| Mutex::new(HandleTable::default()));
149
150fn lock_unpoisoned<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
151 mutex
152 .lock()
153 .unwrap_or_else(|poisoned| poisoned.into_inner())
154}
155
156fn contexts() -> MutexGuard<'static, HandleTable<Arc<Mutex<Context>>>> {
157 lock_unpoisoned(&CONTEXTS)
158}
159
160fn context_entry(handle: u64) -> Option<Arc<Mutex<Context>>> {
161 contexts().get(handle).cloned()
162}
163
164fn boundary(operation: impl FnOnce() -> AxiolidStatus) -> AxiolidStatus {
165 catch_unwind(AssertUnwindSafe(operation)).unwrap_or(AxiolidStatus::Panic)
166}
167
168#[no_mangle]
174pub unsafe extern "C" fn axiolid_v0_4_version(out_version: *mut AxiolidVersion) -> AxiolidStatus {
175 boundary(|| {
176 if out_version.is_null() {
177 return AxiolidStatus::NullPointer;
178 }
179 let version = AxiolidVersion {
180 abi_major: 0,
181 abi_minor: 4,
182 abi_patch: 0,
183 crate_major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap_or(0),
184 crate_minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap_or(0),
185 crate_patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap_or(0),
186 };
187 unsafe { out_version.write(version) };
189 AxiolidStatus::Ok
190 })
191}
192
193#[no_mangle]
199pub unsafe extern "C" fn axiolid_v0_4_context_create(
200 config: *const AxiolidContextConfig,
201 out_context: *mut AxiolidContextHandle,
202) -> AxiolidStatus {
203 boundary(|| {
204 if config.is_null() || out_context.is_null() {
205 return AxiolidStatus::NullPointer;
206 }
207 let config = unsafe { config.read() };
209 if config.provider_profile != AXIOLID_PROVIDER_PORTABLE
210 || config.max_meshes == 0
211 || config.max_results == 0
212 || config.max_vertices_per_mesh == 0
213 || config.max_triangles_per_mesh == 0
214 {
215 return AxiolidStatus::InvalidArgument;
216 }
217 let Ok(application) = Application::portable() else {
218 return AxiolidStatus::OperationFailed;
219 };
220 let handle = AxiolidContextHandle(contexts().insert(Arc::new(Mutex::new(Context {
221 application,
222 config,
223 meshes: HandleTable::default(),
224 results: HandleTable::default(),
225 last_error: None,
226 }))));
227 unsafe { out_context.write(handle) };
229 AxiolidStatus::Ok
230 })
231}
232
233#[no_mangle]
237pub extern "C" fn axiolid_v0_4_context_destroy(context: AxiolidContextHandle) -> AxiolidStatus {
238 boundary(|| {
239 if contexts().remove(context.0).is_some() {
240 AxiolidStatus::Ok
241 } else {
242 AxiolidStatus::InvalidHandle
243 }
244 })
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn panic_is_contained() {
253 assert_eq!(boundary(|| panic!("contained")), AxiolidStatus::Panic);
254 }
255}
256
257#[repr(transparent)]
259#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
260pub struct AxiolidMeshHandle(pub u64);
261
262impl AxiolidMeshHandle {
263 pub const INVALID: Self = Self(0);
264}
265
266#[repr(i32)]
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub enum AxiolidOperation {
270 CurveEvaluation = 1,
271 SurfaceEvaluation = 2,
272 ProfileTriangulation = 3,
273 Sweep = 4,
274 Tessellation = 5,
275 MeshBoolean = 6,
276 MeshPlaneSection = 7,
277 SpatialQuery = 8,
278 Measurement = 9,
279 Healing = 10,
280 GraphCompilation = 11,
281 Unknown = 255,
282}
283
284#[repr(i32)]
286#[derive(Debug, Clone, Copy, PartialEq, Eq)]
287pub enum AxiolidRepresentation {
288 Scalar = 1,
289 Linear = 2,
290 Profile2d = 3,
291 AnalyticCurve = 4,
292 AnalyticSurface = 5,
293 Topology = 6,
294 ExactBrep = 7,
295 TriangleMesh = 8,
296 MeshHealth = 9,
297 Measurements = 10,
298 RayHit = 11,
299 ModelGraph = 12,
300 SampledField = 13,
301 Unknown = 255,
302}
303
304#[repr(i32)]
306#[derive(Debug, Clone, Copy, PartialEq, Eq)]
307pub enum AxiolidExactness {
308 Exact = 1,
309 ToleranceBounded = 2,
310}
311
312#[repr(C)]
314#[derive(Debug, Clone, Copy)]
315pub struct AxiolidCapabilityDescriptor {
316 pub operation: AxiolidOperation,
317 pub representation: AxiolidRepresentation,
318 pub exactness: AxiolidExactness,
319 pub id: [u8; 64],
320 pub provider: [u8; 64],
321 pub required_feature: [u8; 32],
322 pub id_len: u8,
323 pub provider_len: u8,
324 pub required_feature_len: u8,
325 pub deterministic: u8,
326}
327
328impl Default for AxiolidCapabilityDescriptor {
329 fn default() -> Self {
330 Self {
331 id: [0; 64],
332 id_len: 0,
333 provider: [0; 64],
334 provider_len: 0,
335 required_feature: [0; 32],
336 required_feature_len: 0,
337 operation: AxiolidOperation::Unknown,
338 representation: AxiolidRepresentation::Unknown,
339 exactness: AxiolidExactness::ToleranceBounded,
340 deterministic: 0,
341 }
342 }
343}
344
345fn copy_text<const N: usize>(text: &str, destination: &mut [u8; N]) -> u8 {
346 let count = text.len().min(N);
347 destination[..count].copy_from_slice(&text.as_bytes()[..count]);
348 count as u8
349}
350
351fn operation(value: axiolid::contracts::Operation) -> AxiolidOperation {
352 use axiolid::contracts::Operation;
353 match value {
354 Operation::CurveEvaluation => AxiolidOperation::CurveEvaluation,
355 Operation::SurfaceEvaluation => AxiolidOperation::SurfaceEvaluation,
356 Operation::ProfileTriangulation => AxiolidOperation::ProfileTriangulation,
357 Operation::Sweep => AxiolidOperation::Sweep,
358 Operation::Tessellation => AxiolidOperation::Tessellation,
359 Operation::MeshBoolean => AxiolidOperation::MeshBoolean,
360 Operation::MeshPlaneSection => AxiolidOperation::MeshPlaneSection,
361 Operation::SpatialQuery => AxiolidOperation::SpatialQuery,
362 Operation::Measurement => AxiolidOperation::Measurement,
363 Operation::Healing => AxiolidOperation::Healing,
364 Operation::GraphCompilation => AxiolidOperation::GraphCompilation,
365 _ => AxiolidOperation::Unknown,
366 }
367}
368
369fn representation(value: axiolid::contracts::Representation) -> AxiolidRepresentation {
370 use axiolid::contracts::Representation;
371 match value {
372 Representation::Scalar => AxiolidRepresentation::Scalar,
373 Representation::Linear => AxiolidRepresentation::Linear,
374 Representation::Profile2d => AxiolidRepresentation::Profile2d,
375 Representation::AnalyticCurve => AxiolidRepresentation::AnalyticCurve,
376 Representation::AnalyticSurface => AxiolidRepresentation::AnalyticSurface,
377 Representation::Topology => AxiolidRepresentation::Topology,
378 Representation::ExactBrep => AxiolidRepresentation::ExactBrep,
379 Representation::TriangleMesh => AxiolidRepresentation::TriangleMesh,
380 Representation::MeshHealth => AxiolidRepresentation::MeshHealth,
381 Representation::Measurements => AxiolidRepresentation::Measurements,
382 Representation::RayHit => AxiolidRepresentation::RayHit,
383 Representation::ModelGraph => AxiolidRepresentation::ModelGraph,
384 Representation::SampledField => AxiolidRepresentation::SampledField,
385 }
386}
387
388fn exactness(value: axiolid::contracts::Exactness) -> AxiolidExactness {
389 match value {
390 axiolid::contracts::Exactness::Exact => AxiolidExactness::Exact,
391 axiolid::contracts::Exactness::ToleranceBounded => AxiolidExactness::ToleranceBounded,
392 }
393}
394
395fn capability(value: &axiolid::contracts::CapabilityDescriptor) -> AxiolidCapabilityDescriptor {
396 let mut result = AxiolidCapabilityDescriptor::default();
397 result.id_len = copy_text(value.id.as_str(), &mut result.id);
398 result.provider_len = copy_text(value.provider.as_str(), &mut result.provider);
399 result.required_feature_len = copy_text(value.required_feature, &mut result.required_feature);
400 result.operation = operation(value.operation);
401 result.representation = representation(value.output);
402 result.exactness = exactness(value.exactness);
403 result.deterministic = u8::from(value.deterministic);
404 result
405}
406
407fn c_capabilities(
408 context: &Context,
409) -> impl Iterator<Item = &axiolid::contracts::CapabilityDescriptor> {
410 context
411 .application
412 .descriptor()
413 .capabilities
414 .iter()
415 .filter(|descriptor| {
416 matches!(
417 descriptor.operation,
418 axiolid::contracts::Operation::Healing
419 | axiolid::contracts::Operation::Measurement
420 | axiolid::contracts::Operation::MeshBoolean
421 | axiolid::contracts::Operation::Sweep
422 )
423 })
424}
425
426#[no_mangle]
430pub unsafe extern "C" fn axiolid_v0_4_capability_count(
431 context: AxiolidContextHandle,
432 out_count: *mut usize,
433) -> AxiolidStatus {
434 boundary(|| {
435 if out_count.is_null() {
436 return AxiolidStatus::NullPointer;
437 }
438 let Some(context) = context_entry(context.0) else {
439 return AxiolidStatus::InvalidHandle;
440 };
441 let context = lock_unpoisoned(&context);
442 let count = c_capabilities(&context).count();
443 unsafe { out_count.write(count) };
445 AxiolidStatus::Ok
446 })
447}
448
449#[no_mangle]
453pub unsafe extern "C" fn axiolid_v0_4_capability_get(
454 context: AxiolidContextHandle,
455 index: usize,
456 out_descriptor: *mut AxiolidCapabilityDescriptor,
457) -> AxiolidStatus {
458 boundary(|| {
459 if out_descriptor.is_null() {
460 return AxiolidStatus::NullPointer;
461 }
462 let Some(context) = context_entry(context.0) else {
463 return AxiolidStatus::InvalidHandle;
464 };
465 let context = lock_unpoisoned(&context);
466 let Some(value) = c_capabilities(&context).nth(index) else {
467 return AxiolidStatus::InvalidArgument;
468 };
469 unsafe { out_descriptor.write(capability(value)) };
471 AxiolidStatus::Ok
472 })
473}
474
475fn tolerance(value: AxiolidTolerance) -> Result<axiolid::core::Tolerance, AxiolidStatus> {
476 axiolid::core::Tolerance::new(value.linear, value.angular)
477 .map_err(|_| AxiolidStatus::InvalidArgument)
478}
479
480fn record_error(
481 mut context: MutexGuard<'_, Context>,
482 status: AxiolidStatus,
483 operation: AxiolidOperation,
484 tolerance: AxiolidTolerance,
485 provider: impl Into<String>,
486 message: impl Into<String>,
487) -> AxiolidStatus {
488 context.last_error = Some(ErrorRecord {
489 status,
490 operation,
491 tolerance,
492 provider: provider.into(),
493 message: message.into(),
494 });
495 status
496}
497
498fn record_application_error(
499 context: MutexGuard<'_, Context>,
500 error: axiolid::application::ApplicationError,
501) -> AxiolidStatus {
502 let operation = operation(error.context.operation);
503 let tolerance = AxiolidTolerance {
504 linear: error.context.tolerance.linear(),
505 angular: error.context.tolerance.angular(),
506 };
507 record_error(
508 context,
509 AxiolidStatus::OperationFailed,
510 operation,
511 tolerance,
512 error.context.provider.as_str(),
513 error.to_string(),
514 )
515}
516
517#[repr(C)]
519#[derive(Debug, Clone, Copy)]
520pub struct AxiolidErrorInfo {
521 pub status: AxiolidStatus,
522 pub operation: AxiolidOperation,
523 pub tolerance: AxiolidTolerance,
524 pub provider: [u8; 64],
525 pub message_len: usize,
526 pub provider_len: u8,
527 pub reserved: [u8; 7],
529}
530
531impl Default for AxiolidErrorInfo {
532 fn default() -> Self {
533 Self {
534 status: AxiolidStatus::NoError,
535 operation: AxiolidOperation::Unknown,
536 tolerance: AxiolidTolerance {
537 linear: 0.0,
538 angular: 0.0,
539 },
540 provider: [0; 64],
541 message_len: 0,
542 provider_len: 0,
543 reserved: [0; 7],
544 }
545 }
546}
547
548mod diagnostics;
549mod mesh;
550mod operations;
551
552pub use diagnostics::*;
553pub use mesh::*;
554pub use operations::*;