Skip to content
This repository has been archived by the owner on Jun 19, 2024. It is now read-only.

Commit

Permalink
docs: update task module documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Gavin-Niederman committed Dec 23, 2023
1 parent 1079336 commit 0b1c183
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 7 deletions.
49 changes: 42 additions & 7 deletions pros/src/task/local.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
//! A custom TLS implementation that allows for more than 5 entries in TLS.
//! [`LocalKey`]s can be created with the [`os_task_local!`](crate::os_task_local!) macro.
//! ## Example
//! ```rust
//! os_task_local! {
//! static FOO: u32 = 0;
//! static BAR: String = String::from("Hello, world!");
//! }
//! ```

use core::{cell::RefCell, ptr::NonNull, sync::atomic::AtomicU32};

use alloc::{boxed::Box, collections::BTreeMap};
Expand All @@ -6,60 +16,76 @@ use spin::Once;

use super::current;

/// A semaphore that makes sure that each [`LocalKey`] has a unique index into TLS.
static INDEX: AtomicU32 = AtomicU32::new(0);

// Unsafe because you can change the thread local storage while it is being read.
// This requires you to leak val so that you can be sure it lives the entire task.
unsafe fn task_local_storage_set<T>(task: pros_sys::task_t, val: &'static T, index: u32) {
/// Set a value in OS TLS.
/// This requires you to leak val so that you can be sure it lives as long as the task.
/// # Safety
/// Unsafe because you can change the thread local storage while it is being read.
unsafe fn thread_local_storage_set<T>(task: pros_sys::task_t, val: &'static T, index: u32) {
// Yes, we transmute val. This is the intended use of this function.
pros_sys::vTaskSetThreadLocalStoragePointer(task, index as _, (val as *const T).cast());
}

// Unsafe because we can't check if the type is the same as the one that was set.
unsafe fn task_local_storage_get<T>(task: pros_sys::task_t, index: u32) -> Option<&'static T> {
/// Get a value from OS TLS.
/// # Safety
/// Unsafe because we can't check if the type is the same as the one that was set.
unsafe fn thread_local_storage_get<T>(task: pros_sys::task_t, index: u32) -> Option<&'static T> {
let val = pros_sys::pvTaskGetThreadLocalStoragePointer(task, index as _);
val.cast::<T>().as_ref()
}

/// Get or create the [`ThreadLocalStorage`] for the current task.
fn fetch_storage() -> &'static RefCell<ThreadLocalStorage> {
let current = current();

// Get the thread local storage for this task.
// Creating it if it doesn't exist.
// This is safe as long as index 0 of the freeRTOS TLS is never set to any other type.
unsafe {
task_local_storage_get(current.task, 0).unwrap_or_else(|| {
thread_local_storage_get(current.task, 0).unwrap_or_else(|| {
let storage = Box::leak(Box::new(RefCell::new(ThreadLocalStorage {
data: BTreeMap::new(),
})));
task_local_storage_set(current.task, storage, 0);
thread_local_storage_set(current.task, storage, 0);
storage
})
}
}

/// A custom thread local storage implementation.
/// This itself is stored inside real OS TLS, it allows for more than 5 entries in TLS.
/// [`LocalKey`]s store their data inside this struct.
struct ThreadLocalStorage {
pub data: BTreeMap<usize, NonNull<()>>,
}

/// A TLS key that owns its data.
/// Can be created with the [`os_task_local`](crate::os_task_local!) macro.
pub struct LocalKey<T: 'static> {
index: Once<usize>,
init: fn() -> T,
}

impl<T: 'static> LocalKey<T> {
/// Creates a new local key that lazily initializes its data.
/// init is called to initialize the data when it is first accessed from a new thread.
pub const fn new(init: fn() -> T) -> Self {
Self {
index: Once::new(),
init,
}
}

/// Get the index of this key, or get the next one if it has never been created before.
fn index(&'static self) -> &usize {
self.index
.call_once(|| INDEX.fetch_add(1, core::sync::atomic::Ordering::Relaxed) as _)
}

/// Sets or initializes the value of this key.
/// Does not run the initializer.
pub fn set(&'static self, val: T) {
let storage = fetch_storage();
let index = *self.index();
Expand All @@ -72,6 +98,8 @@ impl<T: 'static> LocalKey<T> {
.insert(index, NonNull::new((val as *mut T).cast()).unwrap());
}

/// Passes a reference to the value of this key to the given closure.
/// If the value has not been initialized yet, it will be initialized.
pub fn with<F, R>(&'static self, f: F) -> R
where
F: FnOnce(&'static T) -> R,
Expand All @@ -96,6 +124,13 @@ impl<T: 'static> LocalKey<T> {
}
}

/// Create new [`LocalKey`]\(s)
/// # Example
/// ```rust
/// os_task_local! {
/// static FOO: u32 = 0;
/// static BAR: String = String::new();
/// }
#[macro_export]
macro_rules! os_task_local {
($($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr;)*) => {
Expand Down
19 changes: 19 additions & 0 deletions pros/src/task/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
//! Functions for creation and management of tasks.
//! Tasks are the main way to run code asynchronously.
//!
//! Tasks can be created with the [`spawn`] function or, for more control, with a task [`Builder`].
//! ## Example
//! ```rust
//! spawn(|| {
//! println!("Hello from a task!");
//! });
//! ```
//! After a task has been spawned you can manage it with the returned [`TaskHandle`].
//!
//! Task locals can be created with the [`os_task_local!`](crate::os_task_local!) macro.
//! See the [`local`] module for more info.

pub mod local;

use core::hash::Hash;
Expand All @@ -19,6 +34,7 @@ where
Builder::new().spawn(f).expect("Failed to spawn task")
}

/// Low level task spawning functionality
fn spawn_inner<F: FnOnce() + Send + 'static>(
function: F,
priority: TaskPriority,
Expand Down Expand Up @@ -265,6 +281,8 @@ pub fn delay(duration: core::time::Duration) {
unsafe { pros_sys::delay(duration.as_millis() as u32) }
}

/// A future that will complete after the given duration.
/// Sleep futures that are closer to completion are prioritized to improve accuracy.
pub struct SleepFuture {
target_millis: u32,
}
Expand All @@ -289,6 +307,7 @@ impl Future for SleepFuture {
}
}

/// Returns a future that will complete after the given duration.
pub fn sleep(duration: core::time::Duration) -> SleepFuture {
SleepFuture {
target_millis: unsafe { pros_sys::millis() + duration.as_millis() as u32 },
Expand Down

0 comments on commit 0b1c183

Please sign in to comment.