Skip to main content

how_far/
ext.rs

1//! Checkpoint helpers for every value that checks and counts.
2
3use crate::{
4    Child, Execution, Inert, NoPulse, PhaseSpec, PlanError, Pulse, Report, Stop, StopReason,
5};
6
7/// Checkpoints for anything that both checks cancellation and counts work,
8/// including `&dyn Pulse`, [`Child`], and
9/// [`ProgressWithStop`](crate::ProgressWithStop).
10pub trait ProgressExt: Stop + Report {
11    /// Count `completed` finished units, then check for cancellation.
12    ///
13    /// Work finished before a stop request is still counted. Check once
14    /// before a loop and `step` after each finished unit or batch; `check()`
15    /// alone is the stop-only checkpoint. With
16    /// [`NoPulse`](crate::NoPulse), `step` makes no call: the pulse is
17    /// recognized by its address, a comparison a loop moves out of its body.
18    #[inline]
19    #[track_caller]
20    fn step(&self, completed: u64) -> Result<(), StopReason> {
21        if is_no_pulse(self) {
22            return Ok(());
23        }
24        self.advance(completed);
25        self.check()
26    }
27
28    /// The live pulse: this value, or `None` when it can neither stop nor
29    /// report.
30    ///
31    /// [`step`](Self::step) already skips [`NoPulse`](crate::NoPulse)
32    /// without a call. `live` extends that to any pulse whose `may_stop` and
33    /// `may_report` both return `false`, for the price of asking it once. Call
34    /// it once before a hot loop. `Option<&P>` implements `Stop` and `Report`,
35    /// so `check`, `advance` and `step` work on the result: they forward when
36    /// the pulse may stop or report, and cost a branch, which a loop hoists,
37    /// instead of a call when it can do neither. `may_stop` and `may_report`
38    /// return `false` only for permanent no-ops, so gating early never drops a
39    /// report or a stop request.
40    ///
41    /// ```
42    /// use how_far::prelude::*;
43    ///
44    /// fn decode(rows: &[Vec<u8>], pulse: &dyn Pulse) -> Result<u64, how_far::StopReason> {
45    ///     let pulse = pulse.live();
46    ///     pulse.check()?;
47    ///     let mut sum = 0;
48    ///     for row in rows {
49    ///         sum += row.iter().map(|&b| u64::from(b)).sum::<u64>();
50    ///         pulse.step(1)?;
51    ///     }
52    ///     Ok(sum)
53    /// }
54    ///
55    /// assert!(how_far::NoPulse.live().is_none());
56    /// assert_eq!(decode(&[vec![1, 2], vec![3]], &how_far::NoPulse), Ok(6));
57    /// ```
58    #[inline]
59    fn live(&self) -> Option<&Self> {
60        if !is_no_pulse(self) && (self.may_stop() || self.may_report()) {
61            Some(self)
62        } else {
63            None
64        }
65    }
66
67    /// [`Pulse::split`] into exactly `N` children, returned as an array.
68    ///
69    /// ```
70    /// use how_far::{Execution, NoPulse, PhaseSpec, ProgressExt, Total};
71    ///
72    /// let [left, right] = NoPulse.split_array(Execution::ForkJoin, [
73    ///     PhaseSpec::new("left", 1, Total::Exact(10)),
74    ///     PhaseSpec::new("right", 1, Total::Exact(10)),
75    /// ])?;
76    /// # let _ = (left, right);
77    /// # Ok::<(), how_far::PlanError>(())
78    /// ```
79    ///
80    /// # Panics
81    ///
82    /// If the pulse breaks the [`Pulse::split`] contract by returning a
83    /// different number of children than parts.
84    fn split_array<const N: usize>(
85        &self,
86        execution: Execution,
87        parts: [PhaseSpec<'_>; N],
88    ) -> Result<[Child<'_>; N], PlanError>
89    where
90        Self: Pulse,
91    {
92        match self.split(execution, &parts)?.try_into() {
93            Ok(children) => Ok(children),
94            Err(children) => wrong_child_count(children.len(), N),
95        }
96    }
97}
98
99impl<T: Stop + Report + ?Sized> ProgressExt for T {}
100
101/// Whether `value` is the one [`NoPulse`], whatever reference
102/// it arrived as. The size rules out a zero-sized value at the same address,
103/// such as one ending another allocation. For a sized type the size is a
104/// constant, so the test compiles away unless the type is one byte.
105#[inline]
106fn is_no_pulse<T: ?Sized>(value: &T) -> bool {
107    core::ptr::addr_eq(value, &NoPulse)
108        && core::mem::size_of_val(value) == core::mem::size_of::<Inert>()
109}
110
111/// Kept out of `split_array`, which is instantiated for every pulse type and
112/// length it is called with.
113#[cold]
114#[inline(never)]
115fn wrong_child_count(found: usize, parts: usize) -> ! {
116    panic!("Pulse::split returned {found} children for {parts} parts")
117}