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}