Skip to main content

limen_core/
runtime.rs

1//! Runtime trait and stop-handle for Limen graph executors.
2//!
3//! [`LimenRuntime`] is the uniform contract that all Limen runtimes implement.
4//! It owns a clock and telemetry after [`LimenRuntime::init`] and drives
5//! execution through [`LimenRuntime::step`] / [`LimenRuntime::run`].
6//!
7//! `RuntimeStopHandle` (`std` only) allows cooperative stop requests from
8//! outside the runtime loop (e.g. from another thread).
9
10#[cfg(any(test, feature = "bench"))]
11pub mod bench;
12
13#[cfg(test)]
14mod tests;
15
16use crate::{
17    edge::EdgeOccupancy,
18    graph::GraphApi,
19    prelude::{PlatformClock, Telemetry},
20};
21
22/// An opaque, cloneable, thread-safe stop handle for external cooperative stop.
23///
24/// Wraps an `Arc<AtomicBool>`. Calling `request_stop()` sets the flag;
25/// `is_stopping()` reads it. Safe to send to another thread and use while
26/// `LimenRuntime::run()` holds `&mut self`.
27#[cfg(feature = "std")]
28#[derive(Clone)]
29pub struct RuntimeStopHandle {
30    flag: std::sync::Arc<core::sync::atomic::AtomicBool>,
31}
32
33#[cfg(feature = "std")]
34impl RuntimeStopHandle {
35    /// Create a new stop handle wrapping the given atomic flag.
36    pub fn new(flag: std::sync::Arc<core::sync::atomic::AtomicBool>) -> Self {
37        Self { flag }
38    }
39
40    /// Request cooperative stop (visible to the runtime's scheduler).
41    pub fn request_stop(&self) {
42        self.flag.store(true, core::sync::atomic::Ordering::Relaxed);
43    }
44
45    /// Return `true` if stop has been requested.
46    pub fn is_stopping(&self) -> bool {
47        self.flag.load(core::sync::atomic::Ordering::Relaxed)
48    }
49}
50
51/// A single, uniform runtime trait that all Limen runtimes (P0, P1, P2, P2Concurrent)
52/// can implement. The API is allocation- and threading-agnostic.
53///
54/// The runtime *owns* its clock & telemetry after `init` and no longer
55/// threads them through `step()` / `run()`.
56pub trait LimenRuntime<Graph, const NODE_COUNT: usize, const EDGE_COUNT: usize>
57where
58    Graph: GraphApi<NODE_COUNT, EDGE_COUNT>,
59    Self::Clock: PlatformClock + Sized,
60    Self::Telemetry: Telemetry + Sized,
61{
62    /// Clock abstraction stored by the runtime. Use `()` if not needed.
63    type Clock;
64
65    /// Telemetry collector stored by the runtime. Use `()` if not needed.
66    type Telemetry;
67
68    /// Error type produced by the runtime. Use `core::convert::Infallible` if none.
69    type Error;
70
71    /// External stop handle type. `Clone + Send + Sync + 'static`.
72    /// Only available under `std`.
73    #[cfg(feature = "std")]
74    type StopHandle: Clone + Send + Sync + 'static;
75
76    /// Initialize internal state and adopt the provided clock & telemetry.
77    fn init(
78        &mut self,
79        graph: &mut Graph,
80        clock: Self::Clock,
81        telemetry: Self::Telemetry,
82    ) -> Result<(), Self::Error>;
83
84    /// Reset internal runtime state (keep graph/node state unless your policy requires otherwise).
85    fn reset(&mut self, graph: &Graph) -> Result<(), Self::Error>;
86
87    /// Request a cooperative stop.
88    fn request_stop(&mut self);
89
90    /// Return an external stop handle, if the runtime supports it.
91    /// Clone before calling `run()` to enable stopping from another thread.
92    #[cfg(feature = "std")]
93    fn stop_handle(&self) -> Option<Self::StopHandle> {
94        None
95    }
96
97    /// Return `true` iff a stop has been requested.
98    fn is_stopping(&self) -> bool;
99
100    /// Borrow the runtime’s persistent edge-occupancy buffer.
101    fn occupancies(&self) -> &[EdgeOccupancy; EDGE_COUNT];
102
103    /// Execute one scheduler tick. Return `Ok(true)` if more work remains, `Ok(false)` to stop.
104    fn step(&mut self, graph: &mut Graph) -> Result<bool, Self::Error>;
105
106    /// Drive the runtime until `step()` returns `false` or a stop is requested.
107    #[inline]
108    fn run(&mut self, graph: &mut Graph) -> Result<(), Self::Error> {
109        while !self.is_stopping() {
110            if !self.step(graph)? {
111                break;
112            }
113        }
114        Ok(())
115    }
116}