Guide and interactive examples · API reference
Tracking and observation for how-far. This crate records declared work;
applications choose when to observe it. It starts no threads and reads no clocks.
[dependencies]
how-far-along = "0.1.0"
The library accepts &dyn Pulse; the application owns the tree and its final
result. Keep an observer for your display, then hand the result back before ?:
use how_far_along::{prelude::*, Phase, PulseTree, StopReason, Total, Unstoppable};
fn checksum(bytes: &[u8], pulse: &dyn Pulse) -> Result<u64, StopReason> {
pulse.check()?;
let mut sum = 0;
for &byte in bytes {
sum += u64::from(byte);
pulse.step(1)?;
}
Ok(sum)
}
let tree = PulseTree::new(Phase::new("checksum", Total::Exact(3)), Unstoppable);
let observer = tree.observer();
let result = checksum(&[1, 2, 3], &tree).finish_phase(tree)?;
assert_eq!(result, 6);
assert_eq!(observer.snapshot().fraction(), Some(1.0));
Replace Unstoppable with your application's stop token. The library's code
stays the same. The integration guide
shows cancellation callbacks, nested stages and display smoothing.
Every phase starts Status::NotStarted, becomes Running when entered or
reported to, and eventually becomes Finished(Outcome). Outcomes distinguish
Succeeded, Failed, Cancelled, Skipped, NotRun, and Abandoned.
A successful parent may retain a failed attempt: the library's result decides
whether it recovered. completion_inferred identifies outcomes resolved by the
parent or a plan helper, rather than explicitly reported by that child.
A result handoff freezes the parent's observation. Started but uncompleted children become abandoned; untouched children are skipped on success and not run on failure. Retained child owners can close later but cannot rewrite that frozen parent observation. Join workers first. Drop supplies abandonment, never an inferred successful result.
Observer::summary() reads fraction, unresolved share and status without
allocating. snapshot() copies the tree. try_summary / try_snapshot avoid
waiting on standard metadata locks; on no_std they enter the host's critical
section and do not promise wait-free access. Counting never locks metadata.
Weighted parents show known progress even while other stages have unknown
totals. Unknown children contribute zero but keep their assigned weight;
unresolved_fraction exposes their reserved share. A leaf with an unknown or
overflowed denominator still returns None. Fractions can regress after total
revisions. Success discharges a parent's obligation without rewriting failed
attempts.
The only default feature is std. Snapshot JSON, WithStop, FnPulse and lazy
Checkpoint are always available. With defaults disabled, tracking uses
alloc, pointer atomics, and the application's critical-section provider. That
provider must exclude every participating core. Native-width counters saturate
and expose overflow on targets without 64-bit atomics. Callbacks need neither
std nor 64-bit atomics. Callback state belongs only to callback-backed pulses;
ordinary PulseTree nodes have no callback field or dispatch branch.
The stable, opt-in smooth feature adds
ProgressSmoother, which smooths a display's fraction between reports at each
running stage's own pace; it needs neither std nor a clock of its own.
Callbacks run at checkpoints, never from counting, completion or Drop. Use
poll::LocalPoller for UI-thread or borrowed callbacks. Shared workers retain
full capabilities through share(); completion rights remain with the owner.
Only selected how-far author-facing types and its trait prelude are re-exported.
Implementor and checked-runner types are imported directly from how_far.
Profiling and tuning belong in how_far_really::{profile, diagnostics}; neither
that crate nor its types are re-exported here.
use how_far_along::diagnostics::DiagnosticPulse;
Tracking JSON uses schema version 2 (NotStarted and inferred-completion evidence).
See the example application and
design and API boundaries.
The tested integration guide connects library code, caller-owned completion, diagnostics and display smoothing.