solverforge_solver/realtime/
solver_handle.rs1use std::fmt::Debug;
4use std::sync::atomic::{AtomicBool, Ordering};
5use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
6use std::sync::Arc;
7
8use solverforge_core::domain::PlanningSolution;
9
10use super::problem_change::BoxedProblemChange;
11use super::ProblemChange;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ProblemChangeResult {
16 Queued,
18 SolverNotRunning,
20 QueueFull,
22}
23
24pub struct SolverHandle<S: PlanningSolution> {
76 change_tx: Sender<BoxedProblemChange<S>>,
78 solving: Arc<AtomicBool>,
80 terminate_early: Arc<AtomicBool>,
82}
83
84impl<S: PlanningSolution> SolverHandle<S> {
85 pub fn new() -> (Self, ProblemChangeReceiver<S>) {
89 let (tx, rx) = mpsc::channel();
90 let solving = Arc::new(AtomicBool::new(false));
91 let terminate_early = Arc::new(AtomicBool::new(false));
92
93 let handle = Self {
94 change_tx: tx,
95 solving: Arc::clone(&solving),
96 terminate_early: Arc::clone(&terminate_early),
97 };
98
99 let receiver = ProblemChangeReceiver {
100 change_rx: rx,
101 solving,
102 terminate_early,
103 };
104
105 (handle, receiver)
106 }
107
108 pub fn add_problem_change<P: ProblemChange<S> + 'static>(
113 &self,
114 change: P,
115 ) -> ProblemChangeResult {
116 if !self.solving.load(Ordering::SeqCst) {
117 return ProblemChangeResult::SolverNotRunning;
118 }
119
120 match self.change_tx.send(Box::new(change)) {
121 Ok(()) => ProblemChangeResult::Queued,
122 Err(_) => ProblemChangeResult::QueueFull,
123 }
124 }
125
126 pub fn add_problem_change_boxed(&self, change: BoxedProblemChange<S>) -> ProblemChangeResult {
128 if !self.solving.load(Ordering::SeqCst) {
129 return ProblemChangeResult::SolverNotRunning;
130 }
131
132 match self.change_tx.send(change) {
133 Ok(()) => ProblemChangeResult::Queued,
134 Err(_) => ProblemChangeResult::QueueFull,
135 }
136 }
137
138 pub fn is_solving(&self) -> bool {
140 self.solving.load(Ordering::SeqCst)
141 }
142
143 pub fn terminate_early(&self) {
147 self.terminate_early.store(true, Ordering::SeqCst);
148 }
149
150 pub fn set_solving(&self, solving: bool) {
152 self.solving.store(solving, Ordering::SeqCst);
153 }
154}
155
156impl<S: PlanningSolution> Clone for SolverHandle<S> {
157 fn clone(&self) -> Self {
158 Self {
159 change_tx: self.change_tx.clone(),
160 solving: Arc::clone(&self.solving),
161 terminate_early: Arc::clone(&self.terminate_early),
162 }
163 }
164}
165
166impl<S: PlanningSolution> Debug for SolverHandle<S> {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168 f.debug_struct("SolverHandle")
169 .field("solving", &self.solving.load(Ordering::SeqCst))
170 .field(
171 "terminate_early",
172 &self.terminate_early.load(Ordering::SeqCst),
173 )
174 .finish()
175 }
176}
177
178pub struct ProblemChangeReceiver<S: PlanningSolution> {
180 change_rx: Receiver<BoxedProblemChange<S>>,
182 solving: Arc<AtomicBool>,
184 terminate_early: Arc<AtomicBool>,
186}
187
188impl<S: PlanningSolution> ProblemChangeReceiver<S> {
189 pub fn try_recv(&self) -> Option<BoxedProblemChange<S>> {
193 match self.change_rx.try_recv() {
194 Ok(change) => Some(change),
195 Err(TryRecvError::Empty) => None,
196 Err(TryRecvError::Disconnected) => None,
197 }
198 }
199
200 pub fn drain_pending(&self) -> Vec<BoxedProblemChange<S>> {
204 let mut changes = Vec::new();
205 while let Some(change) = self.try_recv() {
206 changes.push(change);
207 }
208 changes
209 }
210
211 pub fn is_terminate_early_requested(&self) -> bool {
213 self.terminate_early.load(Ordering::SeqCst)
214 }
215
216 pub fn set_solving(&self, solving: bool) {
218 self.solving.store(solving, Ordering::SeqCst);
219 }
220
221 pub fn clear_terminate_early(&self) {
223 self.terminate_early.store(false, Ordering::SeqCst);
224 }
225}
226
227impl<S: PlanningSolution> Debug for ProblemChangeReceiver<S> {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
229 f.debug_struct("ProblemChangeReceiver")
230 .field("solving", &self.solving.load(Ordering::SeqCst))
231 .finish()
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238 use solverforge_core::score::SoftScore;
239 use solverforge_scoring::Director;
240
241 #[derive(Clone, Debug)]
242 struct TestSolution {
243 value: i32,
244 score: Option<SoftScore>,
245 }
246
247 impl PlanningSolution for TestSolution {
248 type Score = SoftScore;
249 fn score(&self) -> Option<Self::Score> {
250 self.score
251 }
252 fn set_score(&mut self, score: Option<Self::Score>) {
253 self.score = score;
254 }
255 }
256
257 #[derive(Debug)]
258 struct IncrementValue;
259
260 impl ProblemChange<TestSolution> for IncrementValue {
261 fn apply(&self, score_director: &mut dyn Director<TestSolution>) {
262 score_director.working_solution_mut().value += 1;
263 }
264 }
265
266 #[test]
267 fn handle_creation() {
268 let (handle, _rx) = SolverHandle::<TestSolution>::new();
269 assert!(!handle.is_solving());
270 }
271
272 #[test]
273 fn submit_change_when_solving() {
274 let (handle, rx) = SolverHandle::<TestSolution>::new();
275 handle.set_solving(true);
276
277 let result = handle.add_problem_change(IncrementValue);
278 assert_eq!(result, ProblemChangeResult::Queued);
279
280 let changes = rx.drain_pending();
282 assert_eq!(changes.len(), 1);
283 }
284
285 #[test]
286 fn submit_change_when_not_solving() {
287 let (handle, _rx) = SolverHandle::<TestSolution>::new();
288
289 let result = handle.add_problem_change(IncrementValue);
290 assert_eq!(result, ProblemChangeResult::SolverNotRunning);
291 }
292
293 #[test]
294 fn multiple_changes() {
295 let (handle, rx) = SolverHandle::<TestSolution>::new();
296 handle.set_solving(true);
297
298 handle.add_problem_change(IncrementValue);
299 handle.add_problem_change(IncrementValue);
300 handle.add_problem_change(IncrementValue);
301
302 let changes = rx.drain_pending();
303 assert_eq!(changes.len(), 3);
304 }
305
306 #[test]
307 fn terminate_early() {
308 let (handle, rx) = SolverHandle::<TestSolution>::new();
309
310 assert!(!rx.is_terminate_early_requested());
311 handle.terminate_early();
312 assert!(rx.is_terminate_early_requested());
313
314 rx.clear_terminate_early();
315 assert!(!rx.is_terminate_early_requested());
316 }
317
318 #[test]
319 fn handle_clone() {
320 let (handle1, rx) = SolverHandle::<TestSolution>::new();
321 let handle2 = handle1.clone();
322
323 handle1.set_solving(true);
324 assert!(handle2.is_solving());
325
326 handle2.add_problem_change(IncrementValue);
327 let changes = rx.drain_pending();
328 assert_eq!(changes.len(), 1);
329 }
330}