Skip to main content

lean_rs_host/host/
progress.rs

1//! Structured progress reporting for host-session operations.
2//!
3//! Progress is host policy layered over the `lean-rs` scoped progress
4//! callback. Public callers provide a borrowed [`LeanProgressSink`]; this
5//! module maps Lean progress ticks into host phases and elapsed time.
6
7use std::panic::{AssertUnwindSafe, catch_unwind};
8use std::time::{Duration, Instant};
9
10use lean_rs::abi::traits::TryFromLean;
11use lean_rs::{LeanCallbackFlow, LeanProgressCallback, LeanResult, Obj};
12
13/// One structured progress observation from a `LeanSession` operation.
14///
15/// Events are delivered synchronously on the Lean-bound worker thread.
16/// `current` is phase-local and monotonically non-decreasing within one
17/// `phase`. `total = None` means the operation can report a phase-local
18/// counter but does not know the final bound cheaply.
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20pub struct LeanProgressEvent {
21    /// Stable phase label for the current operation.
22    pub phase: &'static str,
23    /// Phase-local progress counter.
24    pub current: u64,
25    /// Optional phase-local total.
26    pub total: Option<u64>,
27    /// Time elapsed since this phase started.
28    pub elapsed: Duration,
29}
30
31/// Sink for structured host progress events.
32///
33/// A sink is a one-way callback. It runs synchronously on the thread
34/// currently executing the `LeanSession` method and must not call back
35/// into the same session or re-enter the same Lean call stack. Expensive
36/// work should be queued elsewhere by the sink because progress may be
37/// reported many times for bulk operations.
38///
39/// Rust panics from sinks invoked through Lean progress shims are
40/// contained by the `lean-rs` callback trampoline and returned as a host
41/// internal error. Panics from host-side progress checkpoints are caught
42/// before they escape the session method.
43pub trait LeanProgressSink: Send + Sync {
44    /// Receive one progress event.
45    fn report(&self, event: LeanProgressEvent);
46}
47
48pub(crate) struct ProgressBridge<'a> {
49    callback: LeanProgressCallback<'a>,
50}
51
52impl<'a> ProgressBridge<'a> {
53    pub(crate) fn new(sink: &'a dyn LeanProgressSink, phase: &'static str, total: Option<u64>) -> LeanResult<Self> {
54        let started = Instant::now();
55        let callback = LeanProgressCallback::register(move |event| {
56            sink.report(LeanProgressEvent {
57                phase,
58                current: event.current,
59                total,
60                elapsed: started.elapsed(),
61            });
62            LeanCallbackFlow::Continue
63        })?;
64        Ok(Self { callback })
65    }
66
67    pub(crate) fn abi_parts(&self) -> (usize, usize) {
68        self.callback.abi_parts()
69    }
70
71    pub(crate) fn decode<'lean, T>(&self, obj: Obj<'lean>) -> LeanResult<T>
72    where
73        T: TryFromLean<'lean>,
74    {
75        self.callback.decode_result(obj)
76    }
77}
78
79pub(crate) fn report_progress(
80    sink: Option<&dyn LeanProgressSink>,
81    phase: &'static str,
82    current: u64,
83    total: Option<u64>,
84    started: Instant,
85) -> LeanResult<()> {
86    let Some(sink) = sink else {
87        return Ok(());
88    };
89    let event = LeanProgressEvent {
90        phase,
91        current,
92        total,
93        elapsed: started.elapsed(),
94    };
95    catch_unwind(AssertUnwindSafe(|| sink.report(event)))
96        .map_err(|payload| lean_rs::__host_internals::host_callback_panic(payload.as_ref()))
97}