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}