cplex_rs/
environment.rs

1use std::ffi::{c_void, CString};
2
3use crate::{
4    errors::{self, Result},
5    logging::{
6        get_trampoline, LoggingCallback, LoggingClosure, StreamType, DEFAULT_LOGGING_CLOSURE,
7    },
8    parameters::{Parameter, ParameterValue},
9};
10use ffi::{
11    cpxchannel, cpxenv, CPXaddfuncdest, CPXcloseCPLEX, CPXdelfuncdest, CPXgetchannels,
12    CPXopenCPLEX, CPXsetdblparam, CPXsetintparam, CPXsetlongparam, CPXsetstrparam,
13};
14use log::error;
15
16mod macros {
17    macro_rules! cpx_env_result {
18        ( unsafe { $func:ident ( $env:expr $(, $b:expr)* $(,)? ) } ) => {
19            {
20                let status = unsafe { $func( $env $(,$b)* ) };
21                if status != 0 {
22                    Err(errors::Error::from(errors::Cplex::from_code($env, std::ptr::null(), status)))
23                } else {
24                    Ok(())
25                }
26            }
27        };
28    }
29
30    pub(super) use cpx_env_result;
31}
32
33pub struct Environment {
34    pub(crate) inner: *mut cpxenv,
35    pub(crate) logging_closures: [Option<(LoggingClosure, LoggingCallback)>; 4],
36}
37
38unsafe impl Send for Environment {}
39
40impl Environment {
41    pub fn new() -> Result<Environment> {
42        let mut status = 0;
43        let inner = unsafe { CPXopenCPLEX(&mut status) };
44        if inner.is_null() {
45            Err(errors::Cplex::env_error(status).into())
46        } else {
47            let env = Environment {
48                inner,
49                logging_closures: [DEFAULT_LOGGING_CLOSURE; 4],
50            };
51
52            Ok(env)
53        }
54    }
55
56    pub fn set_parameter<P: Parameter>(&mut self, p: P) -> Result<()> {
57        match p.value() {
58            ParameterValue::Integer(i) => {
59                macros::cpx_env_result!(unsafe { CPXsetintparam(self.inner, p.id() as i32, i) })
60            }
61            ParameterValue::Long(l) => {
62                macros::cpx_env_result!(unsafe { CPXsetlongparam(self.inner, p.id() as i32, l) })
63            }
64            ParameterValue::Double(d) => {
65                macros::cpx_env_result!(unsafe { CPXsetdblparam(self.inner, p.id() as i32, d) })
66            }
67            ParameterValue::String(s) => {
68                let cstr = CString::new(s.as_bytes()).expect("Invalid parameter string");
69                macros::cpx_env_result!(unsafe {
70                    CPXsetstrparam(self.inner, p.id() as i32, cstr.as_ptr())
71                })
72            }
73        }
74    }
75
76    pub fn unset_logging_closure(&mut self, stream_type: StreamType) -> Result<()> {
77        let channel = self.channel_from_stream_type(stream_type)?;
78
79        assert!(!channel.is_null());
80
81        if let Some((mut previous_closure, previous_trampoline)) =
82            self.logging_closures[stream_type.as_index()].take()
83        {
84            macros::cpx_env_result!(unsafe {
85                CPXdelfuncdest(
86                    self.inner,
87                    channel,
88                    &mut *previous_closure as *mut _ as *mut c_void,
89                    previous_trampoline,
90                )
91            })?;
92        }
93
94        Ok(())
95    }
96
97    pub fn set_logging_closure<F: Fn(&str) + Send + 'static>(
98        &mut self,
99        stream_type: StreamType,
100        closure: F,
101    ) -> Result<()> {
102        let channel = self.channel_from_stream_type(stream_type)?;
103
104        assert!(!channel.is_null());
105
106        if let Some((mut previous_closure, previous_trampoline)) =
107            self.logging_closures[stream_type.as_index()].take()
108        {
109            macros::cpx_env_result!(unsafe {
110                CPXdelfuncdest(
111                    self.inner,
112                    channel,
113                    &mut *previous_closure as *mut _ as *mut c_void,
114                    previous_trampoline,
115                )
116            })?;
117        }
118
119        let mut new_closure = Box::new(closure);
120        let new_trampoline = get_trampoline::<F>();
121        macros::cpx_env_result!(unsafe {
122            CPXaddfuncdest(
123                self.inner,
124                channel,
125                &mut *new_closure as *mut F as *mut c_void,
126                new_trampoline,
127            )
128        })?;
129
130        self.logging_closures[stream_type.as_index()] = Some((new_closure, new_trampoline));
131
132        Ok(())
133    }
134
135    fn channel_from_stream_type(&self, stream_type: StreamType) -> Result<*mut cpxchannel> {
136        let mut results_channel = std::ptr::null_mut();
137        let mut warning_channel = std::ptr::null_mut();
138        let mut error_channel = std::ptr::null_mut();
139        let mut log_channel = std::ptr::null_mut();
140        macros::cpx_env_result!(unsafe {
141            CPXgetchannels(
142                self.inner,
143                &mut results_channel,
144                &mut warning_channel,
145                &mut error_channel,
146                &mut log_channel,
147            )
148        })?;
149
150        Ok(match stream_type {
151            StreamType::Error => error_channel,
152            StreamType::Log => log_channel,
153            StreamType::Results => results_channel,
154            StreamType::Warning => warning_channel,
155        })
156    }
157}
158
159impl Drop for Environment {
160    fn drop(&mut self) {
161        self.unset_logging_closure(StreamType::Log).unwrap();
162        self.unset_logging_closure(StreamType::Results).unwrap();
163        self.unset_logging_closure(StreamType::Warning).unwrap();
164        self.unset_logging_closure(StreamType::Error).unwrap();
165        unsafe {
166            let status = CPXcloseCPLEX(&mut self.inner);
167            if status != 0 {
168                error!("Unable to close CPLEX context, got status: '{}'", status)
169            }
170        }
171    }
172}