use crate::{
Child, Execution, NodeId, Observer, Outcome, Phase, PhaseSpec, PlanError, Pulse, Report,
SharedPulse, Snapshot, Stop, StopReason, Summary, Total,
};
use alloc::{boxed::Box, sync::Arc, vec::Vec};
use core::{
fmt,
sync::atomic::{AtomicU8, Ordering},
};
pub struct Checkpoint<'a> {
root: &'a Observer,
phase: &'a Observer,
}
impl Checkpoint<'_> {
pub fn phase_id(&self) -> NodeId {
self.phase.id()
}
pub fn phase_name(&self) -> &str {
self.phase.name()
}
pub fn summary(&self) -> Summary {
self.root.summary()
}
pub fn try_summary(&self) -> Option<Summary> {
self.root.try_summary()
}
pub fn snapshot(&self) -> Snapshot {
self.root.snapshot()
}
pub fn try_snapshot(&self) -> Option<Snapshot> {
self.root.try_snapshot()
}
}
type Callback = dyn Fn(&Checkpoint<'_>) -> Result<(), StopReason> + Send + Sync;
pub(crate) struct Dispatcher {
callback: Box<Callback>,
root: Observer,
stopped: AtomicU8,
}
impl Dispatcher {
pub(crate) fn check(&self, phase: &Observer) -> Result<(), StopReason> {
let code = self.stopped.load(Ordering::Acquire);
if code != 0 {
return Err(decode(code));
}
if self.root.is_finished() || phase.is_finished() {
return Ok(());
}
let result = (self.callback)(&Checkpoint {
root: &self.root,
phase,
});
if let Err(reason) = result {
let code = match reason {
StopReason::TimedOut => 2,
_ => 1,
};
let _ = self
.stopped
.compare_exchange(0, code, Ordering::AcqRel, Ordering::Acquire);
}
match self.stopped.load(Ordering::Acquire) {
0 => Ok(()),
code => Err(decode(code)),
}
}
}
impl crate::pulse::Policy for Arc<Dispatcher> {
#[track_caller]
fn check(&self, phase: &Observer) -> Result<(), StopReason> {
Dispatcher::check(self, phase)
}
fn may_stop(&self) -> bool {
true
}
}
fn decode(code: u8) -> StopReason {
if code == 2 {
StopReason::TimedOut
} else {
StopReason::Cancelled
}
}
#[must_use = "finish the root after its work joins; dropping it unfinished records Abandoned"]
pub struct FnPulse {
tree: crate::pulse::TreePulse<Arc<Dispatcher>>,
}
impl FnPulse {
pub fn new(
name: &str,
callback: impl Fn(&Checkpoint<'_>) -> Result<(), StopReason> + Send + Sync + 'static,
) -> Self {
Self::with_phase(Phase::new(name, Total::Unknown), callback)
}
pub fn with_phase(
phase: Phase,
callback: impl Fn(&Checkpoint<'_>) -> Result<(), StopReason> + Send + Sync + 'static,
) -> Self {
let root = phase.observer();
let dispatch = Arc::new(Dispatcher {
root,
callback: Box::new(callback),
stopped: AtomicU8::new(0),
});
Self {
tree: crate::pulse::TreePulse::new(phase, dispatch),
}
}
pub fn observer(&self) -> Observer {
self.tree.observer()
}
pub fn finish(self, outcome: Outcome) -> Result<(), PlanError> {
self.tree.finish(outcome)
}
}
impl fmt::Debug for FnPulse {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("FnPulse")
.field("root", &self.tree.observer().id())
.finish()
}
}
impl how_far::Complete for FnPulse {
fn complete_as(self, outcome: Outcome) {
self.tree.complete_as(outcome);
}
}
pulse_view!([] FnPulse, tree);