use std::fmt;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use crate::Thermodynamics::ChemEquilibrium::equilibrium_nonlinear::ReactionExtentError;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EquilibriumProgressStage {
RepositoryLookup,
FormulationPreparation,
PointStarted,
PointAccepted,
TemperatureTrialStarted,
TemperatureTrialAccepted,
TemperatureTrialRejected,
InnerSolveStarted,
InnerSolveCompleted,
PhaseTransitionAccepted,
PublicationStarted,
PublicationCompleted,
InnerBackendAttemptStarted,
InnerBackendAttemptFinished,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct EquilibriumProgressEvent {
stage: EquilibriumProgressStage,
point_index: Option<usize>,
point_count: Option<usize>,
temperature: Option<f64>,
}
impl EquilibriumProgressEvent {
pub fn new(
stage: EquilibriumProgressStage,
point_index: Option<usize>,
point_count: Option<usize>,
temperature: Option<f64>,
) -> Self {
Self {
stage,
point_index,
point_count,
temperature,
}
}
pub fn stage(self) -> EquilibriumProgressStage {
self.stage
}
pub fn point_index(self) -> Option<usize> {
self.point_index
}
pub fn point_count(self) -> Option<usize> {
self.point_count
}
pub fn temperature(self) -> Option<f64> {
self.temperature
}
}
type ProgressSink = Arc<dyn Fn(EquilibriumProgressEvent) + Send + Sync + 'static>;
#[derive(Clone, Default)]
pub struct EquilibriumExecutionControl {
cancelled: Arc<AtomicBool>,
progress_sink: Option<ProgressSink>,
}
impl fmt::Debug for EquilibriumExecutionControl {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("EquilibriumExecutionControl")
.field("cancelled", &self.is_cancel_requested())
.field("has_progress_sink", &self.progress_sink.is_some())
.finish()
}
}
impl EquilibriumExecutionControl {
pub fn new() -> Self {
Self::default()
}
pub fn with_progress_sink<F>(mut self, sink: F) -> Self
where
F: Fn(EquilibriumProgressEvent) + Send + Sync + 'static,
{
self.progress_sink = Some(Arc::new(sink));
self
}
pub fn request_cancel(&self) {
self.cancelled.store(true, Ordering::Release);
}
pub fn is_cancel_requested(&self) -> bool {
self.cancelled.load(Ordering::Acquire)
}
pub(crate) fn check_cancelled(&self) -> Result<(), ReactionExtentError> {
if self.is_cancel_requested() {
Err(ReactionExtentError::Cancelled)
} else {
Ok(())
}
}
pub(crate) fn report(&self, event: EquilibriumProgressEvent) {
if let Some(sink) = &self.progress_sink {
sink(event);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::mpsc;
#[test]
fn cloned_controls_share_cancellation() {
let first = EquilibriumExecutionControl::new();
let second = first.clone();
second.request_cancel();
assert!(first.is_cancel_requested());
assert!(matches!(
first.check_cancelled(),
Err(ReactionExtentError::Cancelled)
));
}
#[test]
fn progress_sink_receives_typed_events() {
let (sender, receiver) = mpsc::channel();
let control = EquilibriumExecutionControl::new().with_progress_sink(move |event| {
sender.send(event).expect("progress receiver remains alive");
});
control.report(EquilibriumProgressEvent::new(
EquilibriumProgressStage::PointAccepted,
Some(2),
Some(5),
Some(800.0),
));
let event = receiver.recv().expect("event must be delivered");
assert_eq!(event.stage(), EquilibriumProgressStage::PointAccepted);
assert_eq!(event.point_index(), Some(2));
assert_eq!(event.point_count(), Some(5));
assert_eq!(event.temperature(), Some(800.0));
}
#[test]
fn rejected_temperature_trial_is_a_distinct_progress_stage() {
let event = EquilibriumProgressEvent::new(
EquilibriumProgressStage::TemperatureTrialRejected,
Some(4),
Some(9),
Some(1_250.0),
);
assert_eq!(
event.stage(),
EquilibriumProgressStage::TemperatureTrialRejected
);
assert_eq!(event.point_index(), Some(4));
assert_eq!(event.point_count(), Some(9));
assert_eq!(event.temperature(), Some(1_250.0));
}
}