Skip to main content

proof_stream/
sink.rs

1//! `ProofEventSink` trait + `ProofSink` wrapper.
2//!
3//! `ProofSink` is the handle that stwo-ml stores in a thread-local and calls
4//! into during proving. It wraps an optional boxed sink so that the inactive
5//! (no visualization) path is a single `Option::is_some()` branch with no
6//! heap allocation.
7
8use crate::events::ProofEvent;
9
10// ─── Trait ───────────────────────────────────────────────────────────────────
11
12/// A consumer of proof events.
13///
14/// Implementations must be `Send + Sync + 'static` so they can be stored in a
15/// thread-local and potentially forwarded across threads.
16pub trait ProofEventSink: Send + Sync + 'static {
17    /// Handle one event. Must not block the calling thread.
18    fn emit(&self, event: ProofEvent);
19
20    /// Called at the end of a proof session. Default: no-op.
21    fn flush(&self) {}
22}
23
24// ─── NullSink ────────────────────────────────────────────────────────────────
25
26/// A sink that discards every event. Used as the default when visualization is
27/// disabled. The compiler should inline and eliminate all calls through this.
28pub struct NullSink;
29
30impl ProofEventSink for NullSink {
31    #[inline(always)]
32    fn emit(&self, _: ProofEvent) {}
33}
34
35// ─── ProofSink ───────────────────────────────────────────────────────────────
36
37/// Wrapper around an optional `ProofEventSink`.
38///
39/// When `inner` is `None` (the common, non-visualization path), all hot-path
40/// calls go through a single branch and generate zero allocations.
41pub struct ProofSink {
42    inner: Option<Box<dyn ProofEventSink>>,
43}
44
45impl ProofSink {
46    /// Create an inactive sink (no visualization).
47    pub fn none() -> Self {
48        Self { inner: None }
49    }
50
51    /// Create an active sink wrapping `s`.
52    pub fn new(s: impl ProofEventSink) -> Self {
53        Self {
54            inner: Some(Box::new(s)),
55        }
56    }
57
58    /// Returns `true` if a sink is installed (visualization is active).
59    #[inline]
60    pub fn is_active(&self) -> bool {
61        self.inner.is_some()
62    }
63
64    /// Emit an event. No-op when inactive.
65    #[inline]
66    pub fn emit(&self, event: ProofEvent) {
67        if let Some(s) = &self.inner {
68            s.emit(event);
69        }
70    }
71
72    /// Emit the result of `f()` only when active.
73    ///
74    /// The closure is **never called** when no sink is installed, so constructing
75    /// an event in a hot loop (e.g. sumcheck rounds) costs nothing when disabled.
76    #[inline]
77    pub fn emit_if(&self, f: impl FnOnce() -> ProofEvent) {
78        if self.inner.is_some() {
79            self.emit(f());
80        }
81    }
82
83    /// Flush the inner sink. Called at the end of a proof session.
84    pub fn flush(&self) {
85        if let Some(s) = &self.inner {
86            s.flush();
87        }
88    }
89}
90
91// ─── ChannelSink ─────────────────────────────────────────────────────────────
92
93/// A sink backed by a `crossbeam` bounded channel.
94///
95/// Events are `try_send`'d — if the channel is full the event is silently
96/// dropped rather than blocking the prover.
97pub struct ChannelSink {
98    tx: crossbeam::channel::Sender<ProofEvent>,
99}
100
101impl ChannelSink {
102    /// Create a new `ChannelSink`/receiver pair with `capacity` slots.
103    pub fn new(capacity: usize) -> (Self, crossbeam::channel::Receiver<ProofEvent>) {
104        let (tx, rx) = crossbeam::channel::bounded(capacity);
105        (Self { tx }, rx)
106    }
107}
108
109impl ProofEventSink for ChannelSink {
110    #[inline]
111    fn emit(&self, event: ProofEvent) {
112        // Silently drop events if the consumer is falling behind.
113        let _ = self.tx.try_send(event);
114    }
115}
116
117// ─── CollectingSink (useful in tests) ────────────────────────────────────────
118
119use std::sync::{Arc, Mutex};
120
121/// A sink that collects all emitted events into a `Vec`. Useful in integration
122/// tests that need to assert on the sequence of events.
123#[derive(Clone)]
124pub struct CollectingSink {
125    events: Arc<Mutex<Vec<ProofEvent>>>,
126}
127
128impl CollectingSink {
129    pub fn new() -> Self {
130        Self {
131            events: Arc::new(Mutex::new(Vec::new())),
132        }
133    }
134
135    /// Drain and return all collected events.
136    pub fn drain(&self) -> Vec<ProofEvent> {
137        self.events.lock().unwrap().drain(..).collect()
138    }
139
140    /// Return a snapshot of collected events (clone).
141    pub fn snapshot(&self) -> Vec<ProofEvent> {
142        self.events.lock().unwrap().clone()
143    }
144}
145
146impl Default for CollectingSink {
147    fn default() -> Self {
148        Self::new()
149    }
150}
151
152impl ProofEventSink for CollectingSink {
153    fn emit(&self, event: ProofEvent) {
154        self.events.lock().unwrap().push(event);
155    }
156}