use std::fmt::Debug;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{self, Receiver, Sender, TryRecvError};
use std::sync::Arc;
use solverforge_core::domain::PlanningSolution;
use super::problem_change::BoxedProblemChange;
use super::ProblemChange;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProblemChangeResult {
Queued,
SolverNotRunning,
QueueFull,
}
pub struct SolverHandle<S: PlanningSolution> {
change_tx: Sender<BoxedProblemChange<S>>,
solving: Arc<AtomicBool>,
terminate_early: Arc<AtomicBool>,
}
impl<S: PlanningSolution> SolverHandle<S> {
pub fn new() -> (Self, ProblemChangeReceiver<S>) {
let (tx, rx) = mpsc::channel();
let solving = Arc::new(AtomicBool::new(false));
let terminate_early = Arc::new(AtomicBool::new(false));
let handle = Self {
change_tx: tx,
solving: Arc::clone(&solving),
terminate_early: Arc::clone(&terminate_early),
};
let receiver = ProblemChangeReceiver {
change_rx: rx,
solving,
terminate_early,
};
(handle, receiver)
}
pub fn add_problem_change<P: ProblemChange<S> + 'static>(
&self,
change: P,
) -> ProblemChangeResult {
if !self.solving.load(Ordering::SeqCst) {
return ProblemChangeResult::SolverNotRunning;
}
match self.change_tx.send(Box::new(change)) {
Ok(()) => ProblemChangeResult::Queued,
Err(_) => ProblemChangeResult::QueueFull,
}
}
pub fn add_problem_change_boxed(&self, change: BoxedProblemChange<S>) -> ProblemChangeResult {
if !self.solving.load(Ordering::SeqCst) {
return ProblemChangeResult::SolverNotRunning;
}
match self.change_tx.send(change) {
Ok(()) => ProblemChangeResult::Queued,
Err(_) => ProblemChangeResult::QueueFull,
}
}
pub fn is_solving(&self) -> bool {
self.solving.load(Ordering::SeqCst)
}
pub fn terminate_early(&self) {
self.terminate_early.store(true, Ordering::SeqCst);
}
pub fn set_solving(&self, solving: bool) {
self.solving.store(solving, Ordering::SeqCst);
}
}
impl<S: PlanningSolution> Clone for SolverHandle<S> {
fn clone(&self) -> Self {
Self {
change_tx: self.change_tx.clone(),
solving: Arc::clone(&self.solving),
terminate_early: Arc::clone(&self.terminate_early),
}
}
}
impl<S: PlanningSolution> Debug for SolverHandle<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SolverHandle")
.field("solving", &self.solving.load(Ordering::SeqCst))
.field(
"terminate_early",
&self.terminate_early.load(Ordering::SeqCst),
)
.finish()
}
}
pub struct ProblemChangeReceiver<S: PlanningSolution> {
change_rx: Receiver<BoxedProblemChange<S>>,
solving: Arc<AtomicBool>,
terminate_early: Arc<AtomicBool>,
}
impl<S: PlanningSolution> ProblemChangeReceiver<S> {
pub fn try_recv(&self) -> Option<BoxedProblemChange<S>> {
match self.change_rx.try_recv() {
Ok(change) => Some(change),
Err(TryRecvError::Empty) => None,
Err(TryRecvError::Disconnected) => None,
}
}
pub fn drain_pending(&self) -> Vec<BoxedProblemChange<S>> {
let mut changes = Vec::new();
while let Some(change) = self.try_recv() {
changes.push(change);
}
changes
}
pub fn is_terminate_early_requested(&self) -> bool {
self.terminate_early.load(Ordering::SeqCst)
}
pub fn set_solving(&self, solving: bool) {
self.solving.store(solving, Ordering::SeqCst);
}
pub fn clear_terminate_early(&self) {
self.terminate_early.store(false, Ordering::SeqCst);
}
}
impl<S: PlanningSolution> Debug for ProblemChangeReceiver<S> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ProblemChangeReceiver")
.field("solving", &self.solving.load(Ordering::SeqCst))
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use solverforge_core::score::SoftScore;
use solverforge_scoring::Director;
#[derive(Clone, Debug)]
struct TestSolution {
value: i32,
score: Option<SoftScore>,
}
impl PlanningSolution for TestSolution {
type Score = SoftScore;
fn score(&self) -> Option<Self::Score> {
self.score
}
fn set_score(&mut self, score: Option<Self::Score>) {
self.score = score;
}
}
#[derive(Debug)]
struct IncrementValue;
impl ProblemChange<TestSolution> for IncrementValue {
fn apply(&self, score_director: &mut dyn Director<TestSolution>) {
score_director.working_solution_mut().value += 1;
}
}
#[test]
fn handle_creation() {
let (handle, _rx) = SolverHandle::<TestSolution>::new();
assert!(!handle.is_solving());
}
#[test]
fn submit_change_when_solving() {
let (handle, rx) = SolverHandle::<TestSolution>::new();
handle.set_solving(true);
let result = handle.add_problem_change(IncrementValue);
assert_eq!(result, ProblemChangeResult::Queued);
let changes = rx.drain_pending();
assert_eq!(changes.len(), 1);
}
#[test]
fn submit_change_when_not_solving() {
let (handle, _rx) = SolverHandle::<TestSolution>::new();
let result = handle.add_problem_change(IncrementValue);
assert_eq!(result, ProblemChangeResult::SolverNotRunning);
}
#[test]
fn multiple_changes() {
let (handle, rx) = SolverHandle::<TestSolution>::new();
handle.set_solving(true);
handle.add_problem_change(IncrementValue);
handle.add_problem_change(IncrementValue);
handle.add_problem_change(IncrementValue);
let changes = rx.drain_pending();
assert_eq!(changes.len(), 3);
}
#[test]
fn terminate_early() {
let (handle, rx) = SolverHandle::<TestSolution>::new();
assert!(!rx.is_terminate_early_requested());
handle.terminate_early();
assert!(rx.is_terminate_early_requested());
rx.clear_terminate_early();
assert!(!rx.is_terminate_early_requested());
}
#[test]
fn handle_clone() {
let (handle1, rx) = SolverHandle::<TestSolution>::new();
let handle2 = handle1.clone();
handle1.set_solving(true);
assert!(handle2.is_solving());
handle2.add_problem_change(IncrementValue);
let changes = rx.drain_pending();
assert_eq!(changes.len(), 1);
}
}