pros_core/task/mod.rs
1//! FreeRTOS task creation and management.
2//!
3//! Any method of creating a task will return a [`TaskHandle`].
4//! This handle can be used to control the task.
5//! A handle to the current task can be obtained with [`current`].
6//!
7//! Tasks can be created with the [`spawn`] function or, for more control, with a task [`Builder`].
8//! ## Example
9//! ```rust
10//! # use pros::prelude::println;
11//! use pros::task::{spawn, TaskPriority};
12//! spawn(|| {
13//! println!("Hello from a task!");
14//! });
15//! ```
16//!
17//! Task locals can be created with the [`os_task_local!`](crate::os_task_local!) macro.
18//! See the [`local`] module for more info on the custom task local implementation used.
19
20pub mod local;
21
22use alloc::{
23 boxed::Box,
24 string::{String, ToString},
25};
26use core::{ffi::CStr, hash::Hash, str::Utf8Error, time::Duration};
27
28use snafu::Snafu;
29
30use crate::{bail_on, map_errno};
31
32/// Creates a task to be run 'asynchronously' (More information at the [FreeRTOS docs](https://www.freertos.org/taskandcr.html)).
33/// Takes in a closure that can move variables if needed.
34/// If your task has a loop it is advised to use [`delay`] so that the task does not take up necessary system resources.
35/// Tasks should be long-living; starting many tasks can be slow and is usually not necessary.
36pub fn spawn<F>(f: F) -> TaskHandle
37where
38 F: FnOnce() + Send + 'static,
39{
40 Builder::new().spawn(f).expect("Failed to spawn task")
41}
42
43/// Low level task spawning functionality
44fn spawn_inner<F: FnOnce() + Send + 'static>(
45 function: F,
46 priority: TaskPriority,
47 stack_depth: TaskStackDepth,
48 name: Option<&str>,
49) -> Result<TaskHandle, SpawnError> {
50 let entrypoint = Box::new(TaskEntrypoint { function });
51 let name = alloc::ffi::CString::new(name.unwrap_or("<unnamed>"))
52 .unwrap()
53 .into_raw();
54 unsafe {
55 let task = bail_on!(
56 core::ptr::null(),
57 pros_sys::task_create(
58 Some(TaskEntrypoint::<F>::cast_and_call_external),
59 Box::into_raw(entrypoint).cast(),
60 priority as _,
61 stack_depth as _,
62 name,
63 )
64 );
65
66 _ = alloc::ffi::CString::from_raw(name);
67
68 Ok(TaskHandle { task })
69 }
70}
71
72/// An owned permission to perform actions on a task.
73#[derive(Debug, Clone)]
74pub struct TaskHandle {
75 pub(crate) task: pros_sys::task_t,
76}
77unsafe impl Send for TaskHandle {}
78impl Hash for TaskHandle {
79 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
80 self.task.hash(state)
81 }
82}
83
84impl PartialEq for TaskHandle {
85 fn eq(&self, other: &Self) -> bool {
86 self.task == other.task
87 }
88}
89impl Eq for TaskHandle {}
90
91impl TaskHandle {
92 /// Pause execution of the task.
93 /// This can have unintended consequences if you are not careful,
94 /// for example, if this task is holding a mutex when paused, there is no way to retrieve it until the task is unpaused.
95 pub fn pause(&self) {
96 unsafe {
97 pros_sys::task_suspend(self.task);
98 }
99 }
100
101 /// Resumes execution of the task.
102 pub fn unpause(&self) {
103 unsafe {
104 pros_sys::task_resume(self.task);
105 }
106 }
107
108 /// Sets the task's priority, allowing you to control how much cpu time is allocated to it.
109 pub fn set_priority(&self, priority: impl Into<u32>) {
110 unsafe {
111 pros_sys::task_set_priority(self.task, priority.into());
112 }
113 }
114
115 /// Get the state of the task.
116 pub fn state(&self) -> TaskState {
117 unsafe { pros_sys::task_get_state(self.task).into() }
118 }
119
120 /// Send a notification to the task.
121 pub fn notify(&self) {
122 unsafe {
123 pros_sys::task_notify(self.task);
124 }
125 }
126
127 /// Waits for the task to finish, and then deletes it.
128 pub fn join(self) {
129 unsafe {
130 pros_sys::task_join(self.task);
131 }
132 }
133
134 /// Aborts the task and consumes it. Memory allocated by the task will not be freed.
135 pub fn abort(self) {
136 unsafe {
137 pros_sys::task_delete(self.task);
138 }
139 }
140
141 /// Gets the name of the task if possible.
142 pub fn name(&self) -> Result<String, Utf8Error> {
143 unsafe {
144 let name = pros_sys::task_get_name(self.task);
145 let name_str = CStr::from_ptr(name);
146 Ok(name_str.to_str()?.to_string())
147 }
148 }
149}
150
151/// An ergonomic builder for tasks. Alternatively you can use [`spawn`].
152#[derive(Debug, Default)]
153pub struct Builder<'a> {
154 name: Option<&'a str>,
155 priority: Option<TaskPriority>,
156 stack_depth: Option<TaskStackDepth>,
157}
158
159impl<'a> Builder<'a> {
160 /// Creates a task builder.
161 pub fn new() -> Self {
162 Self::default()
163 }
164
165 /// Sets the name of the task, this is useful for debugging.
166 pub const fn name(mut self, name: &'a str) -> Self {
167 self.name = Some(name);
168 self
169 }
170
171 /// Sets the priority of the task (how much time the scheduler gives to it.).
172 pub const fn priority(mut self, priority: TaskPriority) -> Self {
173 self.priority = Some(priority);
174 self
175 }
176
177 /// Sets how large the stack for the task is.
178 /// This can usually be set to default
179 pub const fn stack_depth(mut self, stack_depth: TaskStackDepth) -> Self {
180 self.stack_depth = Some(stack_depth);
181 self
182 }
183
184 /// Builds and spawns the task
185 pub fn spawn<F>(self, function: F) -> Result<TaskHandle, SpawnError>
186 where
187 F: FnOnce() + Send + 'static,
188 {
189 spawn_inner(
190 function,
191 self.priority.unwrap_or_default(),
192 self.stack_depth.unwrap_or_default(),
193 self.name,
194 )
195 }
196}
197
198/// Represents the current state of a task.
199#[derive(Debug)]
200pub enum TaskState {
201 /// The task is currently utilizing the processor
202 Running,
203 /// The task is currently yielding but may run in the future
204 Ready,
205 /// The task is blocked. For example, it may be [`delay`]ing or waiting on a mutex.
206 /// Tasks that are in this state will usually return to the task queue after a set timeout.
207 Blocked,
208 /// The task is suspended. For example, it may be waiting on a mutex or semaphore.
209 Suspended,
210 /// The task has been deleted using [`TaskHandle::abort`].
211 Deleted,
212 /// The task's state is invalid somehow
213 Invalid,
214}
215
216impl From<u32> for TaskState {
217 fn from(value: u32) -> Self {
218 match value {
219 pros_sys::E_TASK_STATE_RUNNING => Self::Running,
220 pros_sys::E_TASK_STATE_READY => Self::Ready,
221 pros_sys::E_TASK_STATE_BLOCKED => Self::Blocked,
222 pros_sys::E_TASK_STATE_SUSPENDED => Self::Suspended,
223 pros_sys::E_TASK_STATE_DELETED => Self::Deleted,
224 pros_sys::E_TASK_STATE_INVALID => Self::Invalid,
225 _ => Self::Invalid,
226 }
227 }
228}
229
230#[repr(u32)]
231#[derive(Debug, Default)]
232/// Represents how much time the cpu should spend on this task.
233/// (Otherwise known as the priority)
234pub enum TaskPriority {
235 /// The highest priority, should be used sparingly.
236 /// Loops **MUST** have delays or sleeps to prevent starving other tasks.
237 High = 16,
238 /// The default priority.
239 #[default]
240 Default = 8,
241 /// The lowest priority, tasks with this priority will barely ever get cpu time.
242 Low = 1,
243}
244
245impl From<TaskPriority> for u32 {
246 fn from(val: TaskPriority) -> Self {
247 val as u32
248 }
249}
250
251/// Represents how large of a stack the task should get.
252/// Tasks that don't have any or many variables and/or don't need floats can use the low stack depth option.
253#[repr(u32)]
254#[derive(Debug, Default)]
255pub enum TaskStackDepth {
256 #[default]
257 /// The default stack depth.
258 Default = 8192,
259 /// Low task depth. Many tasks can get away with using this stack depth
260 /// however the brain has enough memory that this usually isn't necessary.
261 Low = 512,
262}
263
264struct TaskEntrypoint<F> {
265 function: F,
266}
267
268impl<F> TaskEntrypoint<F>
269where
270 F: FnOnce(),
271{
272 unsafe extern "C" fn cast_and_call_external(this: *mut core::ffi::c_void) {
273 // SAFETY: caller must ensure `this` is an owned `TaskEntrypoint<F>` on the heap
274 let this = unsafe { Box::from_raw(this.cast::<Self>()) };
275
276 (this.function)()
277 }
278}
279
280#[derive(Debug, Snafu)]
281/// Errors that can occur when spawning a task.
282pub enum SpawnError {
283 /// There is not enough memory to create the task.
284 TCBNotCreated,
285}
286
287map_errno! {
288 SpawnError {
289 ENOMEM => SpawnError::TCBNotCreated,
290 }
291}
292
293/// Blocks the current FreeRTOS task for the given amount of time.
294///
295/// ## Caveats
296///
297/// This function will block the entire task, preventing concurrent
298/// execution of async code. When in an async context, it is recommended
299/// to use the `sleep` function in [`pros_async`](https://crates.io/crates/pros-async) instead.
300pub fn delay(duration: Duration) {
301 unsafe { pros_sys::delay(duration.as_millis() as u32) }
302}
303
304/// An interval that can be used to repeatedly run code at a given rate.
305#[derive(Debug)]
306pub struct Interval {
307 last_unblock_time: u32,
308}
309
310impl Interval {
311 /// Creates a new interval. As time passes, the interval's actual delay
312 /// will become smaller so that the average rate is maintained.
313 pub fn start() -> Self {
314 Self {
315 last_unblock_time: unsafe { pros_sys::millis() },
316 }
317 }
318
319 /// Blocks the current FreeRTOS task until the interval has elapsed.
320 ///
321 /// ## Caveats
322 ///
323 /// This function will block the entire task, preventing concurrent
324 /// execution of async code. When in an async context, it is recommended
325 /// to an async-friendly equivalent instead.
326 pub fn delay(&mut self, delta: Duration) {
327 let delta = delta.as_millis() as u32;
328 unsafe {
329 // PROS handles loop overruns so there's no need to check for them here
330 pros_sys::task_delay_until((&mut self.last_unblock_time) as *mut _, delta);
331 }
332 }
333}
334
335/// Returns the task the function was called from.
336pub fn current() -> TaskHandle {
337 unsafe {
338 let task = pros_sys::task_get_current();
339 TaskHandle { task }
340 }
341}
342
343/// Gets the first notification in the queue.
344/// If there is none, blocks until a notification is received.
345/// I am unsure what happens if the thread is unblocked while waiting.
346/// returns the value of the notification
347pub fn get_notification() -> u32 {
348 unsafe { pros_sys::task_notify_take(false, pros_sys::TIMEOUT_MAX) }
349}
350
351#[derive(Debug)]
352/// A guard that can be used to suspend the FreeRTOS scheduler.
353/// When dropped, the scheduler will be resumed.
354pub struct SchedulerSuspendGuard {
355 _private: (),
356}
357
358impl Drop for SchedulerSuspendGuard {
359 fn drop(&mut self) {
360 unsafe {
361 pros_sys::rtos_resume_all();
362 }
363 }
364}
365
366/// Suspends the scheduler, preventing context switches.
367/// No other tasks will be run until the returned guard is dropped.
368///
369/// # Safety
370///
371/// API functions that have the potential to cause a context switch (e.g. [`delay`], [`get_notification`])
372/// must not be called while the scheduler is suspended.
373#[must_use = "The scheduler will only remain suspended for the lifetime of the returned guard"]
374pub unsafe fn suspend_all() -> SchedulerSuspendGuard {
375 // SAFETY: Caller must ensure that other FreeRTOS API functions are not called while the scheduler is suspended.
376 unsafe { pros_sys::rtos_suspend_all() };
377 SchedulerSuspendGuard { _private: () }
378}