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}