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}