//! Composite engine and cloneable scheduler handle. use std::sync::{Arc, Weak}; use std::time::Duration; use crate::backend::{Capabilities, EngineError, ExecutionBackend}; use crate::time::{EngineInstant, Interval, Timer, Timeout}; /// The composite engine: retains a configured core runtime and its execution /// backend, and owns the sole core-driving loop for that runtime. /// /// Construct with [`Engine::new`]; obtain a scheduler handle with /// [`Engine::handle`]. pub struct Engine { /// Retained so the engine owns the runtime it drives for its full lifetime. /// The core driver holds its own clone; this field anchors ownership (and /// future admin/shutdown surfaces) even though it is not read directly. #[allow(dead_code)] runtime: Arc, backend: Arc, } impl Engine { /// Construct an engine over `runtime` driven by `backend`. /// /// The runtime must be fully configured beforehand; after construction the /// engine is its sole driver. Construction fails if `backend` does not /// advertise a capability the engine requires (at minimum, `tasks`). pub fn new( runtime: Arc, backend: impl ExecutionBackend, ) -> Result { let backend: Arc = Arc::new(backend); if !backend.capabilities().tasks { return Err(EngineError::MissingRequiredCapability); } // Install exactly one core-driving loop; the engine is now the sole // driver of `runtime`. This is substrate-neutral — no Tokio feature // gate — so core progression does not silently disappear when an // alternate backend is used (ENGINE_SPEC.md). crate::core_driver::install(runtime.clone(), &backend); Ok(Engine { runtime, backend }) } /// Return a clonable handle for scheduling engine work. /// /// The handle holds a *weak* backend reference, so handles — and engine /// work that captures them — never keep the backend alive. Dropping the /// [`Engine`] releases the backend (and its owned runtime / core-driver /// task) once no other strong reference remains (ENGINE_SPEC.md). pub fn handle(&self) -> EngineHandle { EngineHandle { backend: Arc::downgrade(&self.backend), } } } /// A cloneable scheduler handle. /// /// Schedules work and reads engine time without exposing the underlying /// backend; in particular it never hands out a raw `tokio::runtime::Handle`. /// The handle holds a **weak** backend reference: it does not keep the engine /// or its backend alive. Using a handle after its engine has been dropped /// degrades gracefully — scheduled work is dropped, timers never fire, and /// capability checks report no capabilities — rather than retaining the /// backend (ENGINE_SPEC.md). #[derive(Clone)] pub struct EngineHandle { backend: Weak, } impl EngineHandle { /// Upgrade to the live backend, or `None` if the owning engine is gone. fn backend(&self) -> Option> { self.backend.upgrade() } /// Schedule `task` as cooperative engine work. /// /// A no-op once the owning engine has been dropped: the work is discarded /// rather than keeping the backend alive. pub fn spawn(&self, task: F) where F: Future + Send + 'static, { if let Some(backend) = self.backend() { backend.spawn(Box::pin(task)); } } /// Schedule `work` on a dedicated blocking thread. /// /// A no-op once the owning engine has been dropped. pub fn spawn_blocking(&self, work: F) where F: FnOnce() + Send + 'static, { if let Some(backend) = self.backend() { backend.spawn_blocking(Box::new(work)); } } /// Produce a future that completes after `delay`. /// /// Once the owning engine has been dropped this returns a timer that never /// fires. pub fn timer(&self, delay: Duration) -> Timer { match self.backend() { Some(backend) => Timer { inner: backend.timer(delay) }, None => Timer::closed(), } } /// Produce a future that recurs every `period`. pub fn interval(&self, period: Duration) -> Interval { Interval { period, backend: self.backend.clone(), current: None, } } /// Race `future` against an engine timer. /// /// Resolves to `Ok` with the future's output if it completes within /// `duration`, or [`Err(Elapsed)`](crate::Elapsed) when the timer fires pub fn timeout(&self, duration: Duration, future: F) -> Timeout { Timeout::new(self.timer(duration), future) } /// Read the engine's monotonic clock. /// /// Falls back to the real wall clock once the owning engine has been /// dropped, since the substrate clock is no longer available. pub fn now(&self) -> EngineInstant { match self.backend() { Some(backend) => backend.now(), None => EngineInstant::now(), } } /// Report the backend's advertised capabilities. /// /// Reports no capabilities once the owning engine has been dropped. pub fn capabilities(&self) -> Capabilities { match self.backend() { Some(backend) => backend.capabilities(), None => Capabilities::NONE, } } /// Validate that this engine satisfies `required` before starting work. /// /// Returns `Err` if the backend cannot provide a requested capability, or /// if the owning engine has been dropped. Call this before allocating /// resources, starting background work, or becoming externally visible so /// that an incompatible engine is rejected early (ENGINE_SPEC.md). pub fn require(&self, required: Capabilities) -> Result<(), EngineError> { match self.backend() { Some(backend) if backend.capabilities().satisfies(required) => Ok(()), _ => Err(EngineError::MissingRequiredCapability), } } }