Skip to main content

leodos_libcfs/runtime/
mod.rs

1//! A simple, single-threaded async runtime for `no_std` cFS applications.
2//!
3//! This module provides an ergonomic runtime that integrates with the cFS
4//! scheduler. The primary entry point is the [`Runtime`] struct.
5//!
6//! # Usage
7//!
8//! The entire application, from initialization to the main processing loops,
9//! can be defined within a single `async` block.
10//!
11//! ```rust,ignore
12//! use leodos_libcfs::join;
13//! use leodos_libcfs::runtime::Runtime;
14//! use leodos_libcfs::cfe::{evs, sb::pipe::Pipe};
15//!
16//! async fn task_one(pipe: &Pipe) { /* ... */ }
17//! async fn task_two() { /* ... */ }
18//!
19//! #[no_mangle]
20//! pub extern "C" fn CFE_ES_Main() {
21//!     Runtime::new().run(async {
22//!         evs::event::register(&[]).expect("EVS registration failed");
23//!         let pipe = Pipe::new("MY_PIPE", 16).expect("Pipe creation failed");
24//!
25//!         join!(task_one(&pipe), task_two()).await;
26//!     });
27//! }
28//! ```
29
30pub mod dyn_scope;
31pub mod join;
32pub mod reactor;
33pub mod scope;
34pub mod select_either;
35pub mod sync;
36mod task;
37pub mod time;
38
39pub use futures::select_biased;
40pub use futures::FutureExt;
41pub use pin_utils::pin_mut;
42
43use crate::cfe::es::app;
44use crate::cfe::es::perf::PerfMarker;
45use crate::log;
46use core::future::Future;
47
48/// Default timeout passed to `OS_SelectMultiple` when the task is
49/// idle. Bounds how long a `Sleep` or other non-fd future waits
50/// before being re-polled.
51const REACTOR_TIMEOUT_MS: i32 = 50;
52
53/// An async runtime designed to integrate with the cFS application lifecycle.
54///
55/// The runtime drives a single `Future` to completion by polling it
56/// every time the cFS scheduler wakes the application.
57pub struct Runtime {
58    perf_id: Option<u32>,
59}
60
61impl Runtime {
62    /// Creates a new cFS async runtime.
63    pub fn new() -> Self {
64        Self { perf_id: None }
65    }
66
67    /// Sets the performance monitor ID for this app.
68    ///
69    /// When set, the runtime automatically logs
70    /// `PerfLogEntry`/`PerfLogExit` around each poll cycle.
71    pub fn perf_id(mut self, id: u32) -> Self {
72        self.perf_id = Some(id);
73        self
74    }
75
76    /// Runs the main async task for the application.
77    ///
78    /// Polls the future until it completes or cFS commands
79    /// the app to exit. All resources owned by the future
80    /// are dropped before `CFE_ES_ExitApp` is called.
81    pub fn run(self, main_future: impl Future) -> ! {
82        let status = self.poll_until_done(main_future);
83        app::exit_app(status);
84    }
85
86    /// Polls the future in the cFS run loop until completion
87    /// or shutdown. Returns the exit status to pass to
88    /// `exit_app`. The future and all its owned resources
89    /// are dropped when this function returns.
90    fn poll_until_done(self, main_future: impl Future) -> app::RunStatus {
91        pin_mut!(main_future);
92
93        let reactor = reactor::Reactor::new();
94        // SAFETY: `reactor` lives for the whole of this function,
95        // and no clones of `waker` escape it.
96        let waker = unsafe { reactor::waker_from_reactor(&reactor) };
97        let mut context = core::task::Context::from_waker(&waker);
98
99        loop {
100            match app::run_loop() {
101                Ok(()) => {
102                    let _perf = self.perf_id.map(PerfMarker::new);
103                    if main_future.as_mut().poll(&mut context).is_ready() {
104                        log!("Async task finished.").ok();
105                        return app::RunStatus::Exit;
106                    }
107                    if reactor.was_woken() {
108                        // An in-process waker fired during the
109                        // poll; re-poll immediately without
110                        // blocking.
111                        continue;
112                    }
113                    reactor.block(REACTOR_TIMEOUT_MS);
114                }
115                Err(status) => {
116                    log!("Exit requested.").ok();
117                    return status;
118                }
119            }
120        }
121    }
122}
123
124impl Default for Runtime {
125    fn default() -> Self {
126        Self::new()
127    }
128}