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}