solverforge_solver/realtime/
solver_handle.rs

1//! Solver handle for submitting problem changes during solving.
2
3use 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/// Result of a problem change submission.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum ProblemChangeResult {
16    /// Change was successfully queued.
17    Queued,
18    /// Solver is not running, change was not queued.
19    SolverNotRunning,
20    /// Change queue is full (solver is processing slowly).
21    QueueFull,
22}
23
24/// Handle for interacting with a running solver.
25///
26/// The solver handle allows submitting problem changes to a solver
27/// while it is running. Changes are queued and processed at step
28/// boundaries.
29///
30/// # Example
31///
32/// ```
33/// use solverforge_solver::realtime::{SolverHandle, ProblemChange, ProblemChangeResult};
34/// use solverforge_scoring::ScoreDirector;
35/// use solverforge_core::domain::PlanningSolution;
36/// use solverforge_core::score::SimpleScore;
37///
38/// #[derive(Clone, Debug)]
39/// struct Task { id: usize }
40///
41/// #[derive(Clone, Debug)]
42/// struct Solution {
43///     tasks: Vec<Task>,
44///     score: Option<SimpleScore>,
45/// }
46///
47/// impl PlanningSolution for Solution {
48///     type Score = SimpleScore;
49///     fn score(&self) -> Option<Self::Score> { self.score }
50///     fn set_score(&mut self, score: Option<Self::Score>) { self.score = score; }
51/// }
52///
53/// #[derive(Debug)]
54/// struct AddTask { id: usize }
55///
56/// impl ProblemChange<Solution> for AddTask {
57///     fn apply(&self, sd: &mut dyn ScoreDirector<Solution>) {
58///         sd.working_solution_mut().tasks.push(Task { id: self.id });
59///         sd.trigger_variable_listeners();
60///     }
61/// }
62///
63/// // Create a handle (normally obtained from RealtimeSolver)
64/// let (handle, _rx) = SolverHandle::<Solution>::new();
65///
66/// // Submit a change while solver is "running"
67/// handle.set_solving(true);
68/// let result = handle.add_problem_change(AddTask { id: 42 });
69/// assert_eq!(result, ProblemChangeResult::Queued);
70///
71/// // When solver stops, changes are rejected
72/// handle.set_solving(false);
73/// let result = handle.add_problem_change(AddTask { id: 43 });
74/// assert_eq!(result, ProblemChangeResult::SolverNotRunning);
75/// ```
76pub struct SolverHandle<S: PlanningSolution> {
77    /// Channel for sending problem changes to the solver.
78    change_tx: Sender<BoxedProblemChange<S>>,
79    /// Flag indicating whether solver is currently running.
80    solving: Arc<AtomicBool>,
81    /// Flag to request early termination.
82    terminate_early: Arc<AtomicBool>,
83}
84
85impl<S: PlanningSolution> SolverHandle<S> {
86    /// Creates a new solver handle and its corresponding receiver.
87    ///
88    /// The receiver should be passed to the solver to receive changes.
89    pub fn new() -> (Self, ProblemChangeReceiver<S>) {
90        let (tx, rx) = mpsc::channel();
91        let solving = Arc::new(AtomicBool::new(false));
92        let terminate_early = Arc::new(AtomicBool::new(false));
93
94        let handle = Self {
95            change_tx: tx,
96            solving: Arc::clone(&solving),
97            terminate_early: Arc::clone(&terminate_early),
98        };
99
100        let receiver = ProblemChangeReceiver {
101            change_rx: rx,
102            solving,
103            terminate_early,
104        };
105
106        (handle, receiver)
107    }
108
109    /// Submits a problem change to the solver.
110    ///
111    /// The change is queued and will be processed at the next step boundary.
112    /// Returns the result of the submission.
113    pub fn add_problem_change<P: ProblemChange<S> + 'static>(
114        &self,
115        change: P,
116    ) -> ProblemChangeResult {
117        if !self.solving.load(Ordering::SeqCst) {
118            return ProblemChangeResult::SolverNotRunning;
119        }
120
121        match self.change_tx.send(Box::new(change)) {
122            Ok(()) => ProblemChangeResult::Queued,
123            Err(_) => ProblemChangeResult::QueueFull,
124        }
125    }
126
127    /// Submits a boxed problem change to the solver.
128    pub fn add_problem_change_boxed(&self, change: BoxedProblemChange<S>) -> ProblemChangeResult {
129        if !self.solving.load(Ordering::SeqCst) {
130            return ProblemChangeResult::SolverNotRunning;
131        }
132
133        match self.change_tx.send(change) {
134            Ok(()) => ProblemChangeResult::Queued,
135            Err(_) => ProblemChangeResult::QueueFull,
136        }
137    }
138
139    /// Returns true if the solver is currently running.
140    pub fn is_solving(&self) -> bool {
141        self.solving.load(Ordering::SeqCst)
142    }
143
144    /// Requests early termination of the solver.
145    ///
146    /// The solver will stop at the next step boundary.
147    pub fn terminate_early(&self) {
148        self.terminate_early.store(true, Ordering::SeqCst);
149    }
150
151    /// Sets the solving flag (used internally by the solver).
152    pub fn set_solving(&self, solving: bool) {
153        self.solving.store(solving, Ordering::SeqCst);
154    }
155}
156
157impl<S: PlanningSolution> Clone for SolverHandle<S> {
158    fn clone(&self) -> Self {
159        Self {
160            change_tx: self.change_tx.clone(),
161            solving: Arc::clone(&self.solving),
162            terminate_early: Arc::clone(&self.terminate_early),
163        }
164    }
165}
166
167impl<S: PlanningSolution> Debug for SolverHandle<S> {
168    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
169        f.debug_struct("SolverHandle")
170            .field("solving", &self.solving.load(Ordering::SeqCst))
171            .field(
172                "terminate_early",
173                &self.terminate_early.load(Ordering::SeqCst),
174            )
175            .finish()
176    }
177}
178
179/// Receiver for problem changes, used by the solver.
180pub struct ProblemChangeReceiver<S: PlanningSolution> {
181    /// Channel for receiving problem changes.
182    change_rx: Receiver<BoxedProblemChange<S>>,
183    /// Shared solving flag.
184    solving: Arc<AtomicBool>,
185    /// Shared terminate early flag.
186    terminate_early: Arc<AtomicBool>,
187}
188
189impl<S: PlanningSolution> ProblemChangeReceiver<S> {
190    /// Tries to receive a pending problem change without blocking.
191    ///
192    /// Returns `Some(change)` if a change is available, `None` otherwise.
193    pub fn try_recv(&self) -> Option<BoxedProblemChange<S>> {
194        match self.change_rx.try_recv() {
195            Ok(change) => Some(change),
196            Err(TryRecvError::Empty) => None,
197            Err(TryRecvError::Disconnected) => None,
198        }
199    }
200
201    /// Receives all pending problem changes without blocking.
202    ///
203    /// Returns a vector of all queued changes.
204    pub fn drain_pending(&self) -> Vec<BoxedProblemChange<S>> {
205        let mut changes = Vec::new();
206        while let Some(change) = self.try_recv() {
207            changes.push(change);
208        }
209        changes
210    }
211
212    /// Returns true if early termination has been requested.
213    pub fn is_terminate_early_requested(&self) -> bool {
214        self.terminate_early.load(Ordering::SeqCst)
215    }
216
217    /// Sets the solving flag.
218    pub fn set_solving(&self, solving: bool) {
219        self.solving.store(solving, Ordering::SeqCst);
220    }
221
222    /// Clears the terminate early flag.
223    pub fn clear_terminate_early(&self) {
224        self.terminate_early.store(false, Ordering::SeqCst);
225    }
226}
227
228impl<S: PlanningSolution> Debug for ProblemChangeReceiver<S> {
229    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
230        f.debug_struct("ProblemChangeReceiver")
231            .field("solving", &self.solving.load(Ordering::SeqCst))
232            .finish()
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239    use solverforge_core::score::SimpleScore;
240    use solverforge_scoring::ScoreDirector;
241
242    #[derive(Clone, Debug)]
243    struct TestSolution {
244        value: i32,
245        score: Option<SimpleScore>,
246    }
247
248    impl PlanningSolution for TestSolution {
249        type Score = SimpleScore;
250        fn score(&self) -> Option<Self::Score> {
251            self.score
252        }
253        fn set_score(&mut self, score: Option<Self::Score>) {
254            self.score = score;
255        }
256    }
257
258    #[derive(Debug)]
259    struct IncrementValue;
260
261    impl ProblemChange<TestSolution> for IncrementValue {
262        fn apply(&self, score_director: &mut dyn ScoreDirector<TestSolution>) {
263            score_director.working_solution_mut().value += 1;
264        }
265    }
266
267    #[test]
268    fn handle_creation() {
269        let (handle, _rx) = SolverHandle::<TestSolution>::new();
270        assert!(!handle.is_solving());
271    }
272
273    #[test]
274    fn submit_change_when_solving() {
275        let (handle, rx) = SolverHandle::<TestSolution>::new();
276        handle.set_solving(true);
277
278        let result = handle.add_problem_change(IncrementValue);
279        assert_eq!(result, ProblemChangeResult::Queued);
280
281        // Verify change is received
282        let changes = rx.drain_pending();
283        assert_eq!(changes.len(), 1);
284    }
285
286    #[test]
287    fn submit_change_when_not_solving() {
288        let (handle, _rx) = SolverHandle::<TestSolution>::new();
289
290        let result = handle.add_problem_change(IncrementValue);
291        assert_eq!(result, ProblemChangeResult::SolverNotRunning);
292    }
293
294    #[test]
295    fn multiple_changes() {
296        let (handle, rx) = SolverHandle::<TestSolution>::new();
297        handle.set_solving(true);
298
299        handle.add_problem_change(IncrementValue);
300        handle.add_problem_change(IncrementValue);
301        handle.add_problem_change(IncrementValue);
302
303        let changes = rx.drain_pending();
304        assert_eq!(changes.len(), 3);
305    }
306
307    #[test]
308    fn terminate_early() {
309        let (handle, rx) = SolverHandle::<TestSolution>::new();
310
311        assert!(!rx.is_terminate_early_requested());
312        handle.terminate_early();
313        assert!(rx.is_terminate_early_requested());
314
315        rx.clear_terminate_early();
316        assert!(!rx.is_terminate_early_requested());
317    }
318
319    #[test]
320    fn handle_clone() {
321        let (handle1, rx) = SolverHandle::<TestSolution>::new();
322        let handle2 = handle1.clone();
323
324        handle1.set_solving(true);
325        assert!(handle2.is_solving());
326
327        handle2.add_problem_change(IncrementValue);
328        let changes = rx.drain_pending();
329        assert_eq!(changes.len(), 1);
330    }
331}