pros_async/lib.rs
1//! Tiny async runtime and robot traits for `pros-rs`.
2//! The async executor supports spawning tasks and blocking on futures.
3//! It has a reactor to improve the performance of some futures.
4//! It is recommended to use the `AsyncRobot` trait to run robot code.
5//! FreeRTOS tasks can still be used, but it is recommended to use only async tasks for performance.
6
7#![no_std]
8#![feature(negative_impls)]
9
10extern crate alloc;
11
12use core::{future::Future, task::Poll};
13
14use async_task::Task;
15use executor::EXECUTOR;
16use pros_core::error::Result;
17
18mod executor;
19mod reactor;
20
21/// Runs a future in the background without having to await it
22/// To get the the return value you can await a task.
23pub fn spawn<T>(future: impl Future<Output = T> + 'static) -> Task<T> {
24 executor::EXECUTOR.with(|e| e.spawn(future))
25}
26
27/// Blocks the current task untill a return value can be extracted from the provided future.
28/// Does not poll all futures to completion.
29pub fn block_on<F: Future + 'static>(future: F) -> F::Output {
30 executor::EXECUTOR.with(|e| e.block_on(spawn(future)))
31}
32
33/// A future that will complete after the given duration.
34/// Sleep futures that are closer to completion are prioritized to improve accuracy.
35#[derive(Debug)]
36pub struct SleepFuture {
37 target_millis: u32,
38}
39impl Future for SleepFuture {
40 type Output = ();
41
42 fn poll(
43 self: core::pin::Pin<&mut Self>,
44 cx: &mut core::task::Context<'_>,
45 ) -> core::task::Poll<Self::Output> {
46 if self.target_millis < unsafe { pros_sys::millis() } {
47 Poll::Ready(())
48 } else {
49 EXECUTOR.with(|e| {
50 e.reactor
51 .borrow_mut()
52 .sleepers
53 .push(cx.waker().clone(), self.target_millis)
54 });
55 Poll::Pending
56 }
57 }
58}
59
60/// Returns a future that will complete after the given duration.
61pub fn sleep(duration: core::time::Duration) -> SleepFuture {
62 SleepFuture {
63 target_millis: unsafe { pros_sys::millis() + duration.as_millis() as u32 },
64 }
65}
66
67/// A trait for robot code that spins up the pros-rs async executor.
68/// This is the preferred trait to run robot code.
69pub trait AsyncRobot {
70 /// Runs during the operator control period.
71 /// This function may be called more than once.
72 /// For that reason, do not use `Peripherals::take`in this function.
73 fn opcontrol(&mut self) -> impl Future<Output = Result> {
74 async { Ok(()) }
75 }
76 /// Runs during the autonomous period.
77 fn auto(&mut self) -> impl Future<Output = Result> {
78 async { Ok(()) }
79 }
80 /// Runs continuously during the disabled period.
81 fn disabled(&mut self) -> impl Future<Output = Result> {
82 async { Ok(()) }
83 }
84 /// Runs once when the competition system is initialized.
85 fn comp_init(&mut self) -> impl Future<Output = Result> {
86 async { Ok(()) }
87 }
88}
89
90#[doc(hidden)]
91#[macro_export]
92macro_rules! __gen_async_exports {
93 ($rbt:ty) => {
94 pub static mut ROBOT: Option<$rbt> = None;
95
96 #[doc(hidden)]
97 #[no_mangle]
98 extern "C" fn opcontrol() {
99 $crate::block_on(<$rbt as $crate::AsyncRobot>::opcontrol(unsafe {
100 ROBOT
101 .as_mut()
102 .expect("Expected initialize to run before opcontrol")
103 }))
104 .unwrap();
105 }
106
107 #[doc(hidden)]
108 #[no_mangle]
109 extern "C" fn autonomous() {
110 $crate::block_on(<$rbt as $crate::AsyncRobot>::auto(unsafe {
111 ROBOT
112 .as_mut()
113 .expect("Expected initialize to run before auto")
114 }))
115 .unwrap();
116 }
117
118 #[doc(hidden)]
119 #[no_mangle]
120 extern "C" fn disabled() {
121 $crate::block_on(<$rbt as $crate::AsyncRobot>::disabled(unsafe {
122 ROBOT
123 .as_mut()
124 .expect("Expected initialize to run before disabled")
125 }))
126 .unwrap();
127 }
128
129 #[doc(hidden)]
130 #[no_mangle]
131 extern "C" fn competition_initialize() {
132 $crate::block_on(<$rbt as $crate::AsyncRobot>::comp_init(unsafe {
133 ROBOT
134 .as_mut()
135 .expect("Expected initialize to run before comp_init")
136 }))
137 .unwrap();
138 }
139 };
140}
141
142/// Allows your async robot code to be executed by the pros kernel.
143/// If your robot struct implements Default then you can just supply this macro with its type.
144/// If not, you can supply an expression that returns your robot type to initialize your robot struct.
145/// The code that runs to create your robot struct will run in the initialize function in PROS.
146///
147/// Example of using the macro with a struct that implements Default:
148/// ```rust
149/// use pros::prelude::*;
150/// #[derive(Default)]
151/// struct ExampleRobot;
152/// #[async_trait]
153/// impl AsyncRobot for ExampleRobot {
154/// asnyc fn opcontrol(&mut self) -> pros::Result {
155/// println!("Hello, world!");
156/// Ok(())
157/// }
158/// }
159/// async_robot!(ExampleRobot);
160/// ```
161///
162/// Example of using the macro with a struct that does not implement Default:
163/// ```rust
164/// use pros::prelude::*;
165/// struct ExampleRobot {
166/// x: i32,
167/// }
168/// #[async_trait]
169/// impl AsyncRobot for ExampleRobot {
170/// async fn opcontrol(&mut self) -> pros::Result {
171/// println!("Hello, world! {}", self.x);
172/// Ok(())
173/// }
174/// }
175/// impl ExampleRobot {
176/// pub fn new() -> Self {
177/// Self { x: 5 }
178/// }
179/// }
180/// async_robot!(ExampleRobot, ExampleRobot::new());
181#[macro_export]
182macro_rules! async_robot {
183 ($rbt:ty) => {
184 $crate::__gen_async_exports!($rbt);
185
186 #[no_mangle]
187 extern "C" fn initialize() {
188 let robot = Default::default();
189 unsafe {
190 ROBOT = Some(robot);
191 }
192 }
193 };
194 ($rbt:ty, $init:expr) => {
195 $crate::__gen_async_exports!($rbt);
196
197 #[no_mangle]
198 extern "C" fn initialize() {
199 let robot = $init;
200 unsafe {
201 ROBOT = Some(robot);
202 }
203 }
204 };
205}