qhull 0.4.0

Rust bindings to Qhull
Documentation
use std::{error::Error, fmt::Display, os::raw::c_void};

use crate::{helpers::QhTypeRef, sys, tmp_file::TmpFile, Facet, Ridge, Vertex};

macro_rules! define_error_kinds {
    (
        $(
            $(#[$attr:meta])*
            $name:ident => $code:literal,
        ),*$(,)?
    ) => {
        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
        pub enum QhErrorKind {
            $(
                $(#[$attr])*
                ///
                #[doc = concat!("Error code ", $code)]
                $name,
            )*

            /// An error code that is not part of the enum.
            Other(i32),
        }

        impl QhErrorKind {
            pub fn from_code(code: i32) -> Self {
                match code {
                    0 => panic!("0 is not an error code"),
                    $(
                        $code => Self::$name,
                    )*
                    _ => Self::Other(code),
                }
            }
            pub fn error_code(&self) -> i32 {
                match self {
                    $(
                        Self::$name => $code,
                    )*
                    Self::Other(code) => *code,
                }
            }
        }
    };
}

define_error_kinds! {
    // TODO ...
}

/// A Qhull error
///
/// This structure represents error generated by Qhull.  
/// Qhull errors can contain references to the problematic elements (face, ridge, vertex).
#[derive(Debug, Clone)]
pub struct QhError<'a> {
    pub kind: QhErrorKind,
    pub error_message: Option<String>,
    pub face: Option<Facet<'a>>,
    pub ridge: Option<Ridge<'a>>,
    pub vertex: Option<Vertex<'a>>,
}

impl<'a> Display for QhError<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "Qhull error: {:?} (#{})",
            self.kind,
            self.kind.error_code()
        )?;
        if let Some(msg) = &self.error_message {
            write!(f, "\n{}", msg)?;
        }
        if let Some(face) = &self.face {
            write!(f, "\nFace: {:?}", face)?;
        }
        if let Some(ridge) = &self.ridge {
            write!(f, "\nRidge: {:?}", ridge)?;
        }
        if let Some(vertex) = &self.vertex {
            write!(f, "\nVertex: {:?}", vertex)?;
        }
        Ok(())
    }
}

impl<'a> Error for QhError<'a> {}

impl<'a> QhError<'a> {
    /// Convert the error to a `'static` error.
    ///
    /// This is useful when you want to return the error from a function that is not tied to a [`Qh`](crate::Qh) reference.
    ///
    /// # Example
    /// ```
    /// # use qhull::*;
    /// fn my_function() -> QhError<'static> {
    ///     // We create a Qh with 10 aligned points,
    ///     // Qhull will fail to compute the convex hull of these points.
    ///     let mut qh = Qh::builder()
    ///         .compute(false)
    ///         .build_from_iter((0..10).map(|i| [0.0, i as f64]))
    ///         .unwrap();
    ///
    ///     // we cannot return this error since it may contain some references
    ///     // to the Qh instance that will be dropped at the end of the function
    ///     // (for example to problematic vertices)...
    ///     let error = qh.compute().unwrap_err();
    ///
    ///     // ... so we convert it to a static error,
    ///     // but we will lose the references to the problematic vertices.
    ///     error.into_static()
    /// }
    ///
    /// let error = my_function();
    /// assert!(error.face.is_none());
    /// assert!(error.ridge.is_none());
    /// assert!(error.vertex.is_none());
    /// ```
    pub fn into_static(self) -> QhError<'static> {
        let QhError {
            kind,
            error_message,
            face,
            ridge,
            vertex,
        } = self;
        if let Some(face) = face {
            eprintln!(
                "During conversion to static, a face was discarded: {:?}",
                face
            );
        }
        if let Some(ridge) = ridge {
            eprintln!(
                "During conversion to static, a ridge was discarded: {:?}",
                ridge
            );
        }
        if let Some(vertex) = vertex {
            eprintln!(
                "During conversion to static, a vertex was discarded: {:?}",
                vertex
            );
        }
        QhError {
            kind,
            error_message,
            face: None,
            ridge: None,
            vertex: None,
        }
    }

    unsafe fn try_impl<'b>(
        qh: *mut sys::qhT,
        err_file: &mut Option<TmpFile>,
        f: unsafe extern "C" fn(*mut c_void),
        data: *mut c_void,
    ) -> Result<(), QhError<'b>> {

        let err_code = unsafe {
            sys::qhull_sys__try_on_qh(
                &mut *qh,
                Some(f),
                data,
            )
        };

        let qh = &mut *qh;

        if err_code == 0 {
            Ok(())
        } else {
            let kind = QhErrorKind::from_code(err_code);
            let file = err_file
                .replace(TmpFile::new().expect("Failed to create a replacement temporary file"));
            qh.ferr = err_file.as_ref().unwrap().file_handle();
            let msg = file.map(|file| file.read_as_string_and_close().unwrap());
            Err(QhError {
                kind,
                error_message: msg,
                face: Facet::from_ptr(qh.tracefacet, qh.input_dim as _),
                ridge: Ridge::from_ptr(qh.traceridge, qh.input_dim as _),
                vertex: Vertex::from_ptr(qh.tracevertex, qh.input_dim as _),
            })
        }
    }
}



struct CCBData<F, Args, R> {
    pub f: F,
    pub args: Args,
    pub result: Option<R>,
}

macro_rules! impl_try {
    (
        $name:ident($($arg:ident: $Arg:ident),*)
    ) => {
        /// Try to run a function on a raw qhT instance and handle errors.
        ///
        /// # Safety
        /// * shall not be nested
        /// * shall not be called when errors are already being handled
        ///
        /// # Implementation details
        ///
        /// Qhull uses [`setjmp`/`longjmp`](https://en.cppreference.com/w/c/program/longjmp) for error handling, this is not currently supported in Rust.
        /// For this reason, the actual error handling is done in C and this function is just a wrapper around the C function [`qhull_sys__try_on_qh`](sys::qhull_sys__try_on_qh).
        ///
        /// Relevant links:
        /// - <https://github.com/rust-lang/rfcs/issues/2625>: RFC for adding support for `setjmp`/`longjmp` to Rust, describes the current problems with `setjmp`/`longjmp` in Rust.
        /// - <https://docs.rs/setjmp/0.1.4/setjmp/index.html>
        /// - <https://en.cppreference.com/w/c/program/longjmp>
        /// - <https://learn.microsoft.com/en-en/cpp/cpp/using-setjmp-longjmp?view=msvc-170>
        /// - <http://groups.di.unipi.it/~nids/docs/longjump_try_trow_catch.html>
        impl<'a> QhError<'a> {
            pub unsafe fn $name<'b, R, $($Arg: Copy),*>(
                qh: *mut sys::qhT,
                err_file: &mut Option<TmpFile>,
                f: unsafe extern "C" fn($($Arg,)*) -> R,
                ($($arg,)*): ($($Arg,)*),
            ) -> Result<R, QhError<'b>> {
                let mut data = CCBData {
                    f,
                    args: ($($arg,)*),
                    result: None,
                };
        
                unsafe extern "C" fn cb<R, $($Arg: Copy),*>(data: *mut c_void) {
                    let CCBData {
                        f,
                        args: ($($arg,)*),
                        result,
                    } = &mut *(data as *mut CCBData<unsafe extern "C" fn($($Arg,)*) -> R, ($($Arg,)*), R>);
                    let r = f($(*$arg,)*);
                    *result = Some(r);
                }
        
                Self::try_impl(
                    qh,
                    err_file,
                    cb::<R, $($Arg,)*>,
                    &mut data as *mut _ as *mut c_void,
                ).map(|_| data.result.expect("Result not set"))
            }
        }
    };
}

impl_try!(try_1(a: A));
impl_try!(try_2(a: A, b: B));
impl_try!(try_3(a: A, b: B, c: C));
impl_try!(try_4(a: A, b: B, c: C, d: D));
impl_try!(try_5(a: A, b: B, c: C, d: D, e: E));