cplex-rs 0.1.10

Binding of IBM Cplex library
Documentation
use std::ffi::{c_void, CString};

use crate::{
    errors::{self, Result},
    logging::{
        get_trampoline, LoggingCallback, LoggingClosure, StreamType, DEFAULT_LOGGING_CLOSURE,
    },
    parameters::{Parameter, ParameterValue},
};
use ffi::{
    cpxchannel, cpxenv, CPXaddfuncdest, CPXcloseCPLEX, CPXdelfuncdest, CPXgetchannels,
    CPXopenCPLEX, CPXsetdblparam, CPXsetintparam, CPXsetlongparam, CPXsetstrparam,
};
use log::error;

mod macros {
    macro_rules! cpx_env_result {
        ( unsafe { $func:ident ( $env:expr $(, $b:expr)* $(,)? ) } ) => {
            {
                let status = unsafe { $func( $env $(,$b)* ) };
                if status != 0 {
                    Err(errors::Error::from(errors::Cplex::from_code($env, std::ptr::null(), status)))
                } else {
                    Ok(())
                }
            }
        };
    }

    pub(super) use cpx_env_result;
}

pub struct Environment {
    pub(crate) inner: *mut cpxenv,
    pub(crate) logging_closures: [Option<(LoggingClosure, LoggingCallback)>; 4],
}

unsafe impl Send for Environment {}

impl Environment {
    pub fn new() -> Result<Environment> {
        let mut status = 0;
        let inner = unsafe { CPXopenCPLEX(&mut status) };
        if inner.is_null() {
            Err(errors::Cplex::env_error(status).into())
        } else {
            let env = Environment {
                inner,
                logging_closures: [DEFAULT_LOGGING_CLOSURE; 4],
            };

            Ok(env)
        }
    }

    pub fn set_parameter<P: Parameter>(&mut self, p: P) -> Result<()> {
        match p.value() {
            ParameterValue::Integer(i) => {
                macros::cpx_env_result!(unsafe { CPXsetintparam(self.inner, p.id() as i32, i) })
            }
            ParameterValue::Long(l) => {
                macros::cpx_env_result!(unsafe { CPXsetlongparam(self.inner, p.id() as i32, l) })
            }
            ParameterValue::Double(d) => {
                macros::cpx_env_result!(unsafe { CPXsetdblparam(self.inner, p.id() as i32, d) })
            }
            ParameterValue::String(s) => {
                let cstr = CString::new(s.as_bytes()).expect("Invalid parameter string");
                macros::cpx_env_result!(unsafe {
                    CPXsetstrparam(self.inner, p.id() as i32, cstr.as_ptr())
                })
            }
        }
    }

    pub fn unset_logging_closure(&mut self, stream_type: StreamType) -> Result<()> {
        let channel = self.channel_from_stream_type(stream_type)?;

        assert!(!channel.is_null());

        if let Some((mut previous_closure, previous_trampoline)) =
            self.logging_closures[stream_type.as_index()].take()
        {
            macros::cpx_env_result!(unsafe {
                CPXdelfuncdest(
                    self.inner,
                    channel,
                    &mut *previous_closure as *mut _ as *mut c_void,
                    previous_trampoline,
                )
            })?;
        }

        Ok(())
    }

    pub fn set_logging_closure<F: Fn(&str) + Send + 'static>(
        &mut self,
        stream_type: StreamType,
        closure: F,
    ) -> Result<()> {
        let channel = self.channel_from_stream_type(stream_type)?;

        assert!(!channel.is_null());

        if let Some((mut previous_closure, previous_trampoline)) =
            self.logging_closures[stream_type.as_index()].take()
        {
            macros::cpx_env_result!(unsafe {
                CPXdelfuncdest(
                    self.inner,
                    channel,
                    &mut *previous_closure as *mut _ as *mut c_void,
                    previous_trampoline,
                )
            })?;
        }

        let mut new_closure = Box::new(closure);
        let new_trampoline = get_trampoline::<F>();
        macros::cpx_env_result!(unsafe {
            CPXaddfuncdest(
                self.inner,
                channel,
                &mut *new_closure as *mut F as *mut c_void,
                new_trampoline,
            )
        })?;

        self.logging_closures[stream_type.as_index()] = Some((new_closure, new_trampoline));

        Ok(())
    }

    fn channel_from_stream_type(&self, stream_type: StreamType) -> Result<*mut cpxchannel> {
        let mut results_channel = std::ptr::null_mut();
        let mut warning_channel = std::ptr::null_mut();
        let mut error_channel = std::ptr::null_mut();
        let mut log_channel = std::ptr::null_mut();
        macros::cpx_env_result!(unsafe {
            CPXgetchannels(
                self.inner,
                &mut results_channel,
                &mut warning_channel,
                &mut error_channel,
                &mut log_channel,
            )
        })?;

        Ok(match stream_type {
            StreamType::Error => error_channel,
            StreamType::Log => log_channel,
            StreamType::Results => results_channel,
            StreamType::Warning => warning_channel,
        })
    }
}

impl Drop for Environment {
    fn drop(&mut self) {
        self.unset_logging_closure(StreamType::Log).unwrap();
        self.unset_logging_closure(StreamType::Results).unwrap();
        self.unset_logging_closure(StreamType::Warning).unwrap();
        self.unset_logging_closure(StreamType::Error).unwrap();
        unsafe {
            let status = CPXcloseCPLEX(&mut self.inner);
            if status != 0 {
                error!("Unable to close CPLEX context, got status: '{}'", status)
            }
        }
    }
}