use alloc::{boxed::Box, collections::BTreeMap};
use core::{
cell::{Cell, RefCell},
ptr::NonNull,
sync::atomic::AtomicU32,
};
use spin::Once;
use super::current;
static INDEX: AtomicU32 = AtomicU32::new(0);
unsafe fn thread_local_storage_set<T>(task: pros_sys::task_t, val: &'static T, index: u32) {
unsafe {
pros_sys::vTaskSetThreadLocalStoragePointer(task, index as _, (val as *const T).cast());
}
}
unsafe fn thread_local_storage_get<T>(task: pros_sys::task_t, index: u32) -> Option<&'static T> {
unsafe {
let val = pros_sys::pvTaskGetThreadLocalStoragePointer(task, index as _);
val.cast::<T>().as_ref()
}
}
fn fetch_storage() -> &'static RefCell<ThreadLocalStorage> {
let current = current();
unsafe {
thread_local_storage_get(current.task, 0).unwrap_or_else(|| {
let storage = Box::leak(Box::new(RefCell::new(ThreadLocalStorage {
data: BTreeMap::new(),
})));
thread_local_storage_set(current.task, storage, 0);
storage
})
}
}
struct ThreadLocalStorage {
pub data: BTreeMap<usize, NonNull<()>>,
}
#[derive(Debug)]
pub struct LocalKey<T: 'static> {
index: Once<usize>,
init: fn() -> T,
}
impl<T: 'static> LocalKey<T> {
pub const fn new(init: fn() -> T) -> Self {
Self {
index: Once::new(),
init,
}
}
fn index(&'static self) -> &usize {
self.index
.call_once(|| INDEX.fetch_add(1, core::sync::atomic::Ordering::Relaxed) as _)
}
pub fn with<F, R>(&'static self, f: F) -> R
where
F: FnOnce(&'static T) -> R,
{
self.initialize_with((self.init)(), |_, val| f(val))
}
fn initialize_with<F, R>(&'static self, init: T, f: F) -> R
where
F: FnOnce(Option<T>, &'static T) -> R,
{
let storage = fetch_storage();
let index = *self.index();
if let Some(val) = storage.borrow().data.get(&index) {
return f(Some(init), unsafe { val.cast().as_ref() });
}
let val = Box::leak(Box::new(init));
storage
.borrow_mut()
.data
.insert(index, NonNull::new((val as *mut T).cast::<()>()).unwrap());
f(None, val)
}
}
impl<T: 'static> LocalKey<Cell<T>> {
pub fn set(&'static self, value: T) {
self.initialize_with(Cell::new(value), |value, cell| {
if let Some(value) = value {
cell.set(value.into_inner());
}
});
}
pub fn get(&'static self) -> T
where
T: Copy,
{
self.with(|cell| cell.get())
}
pub fn take(&'static self) -> T
where
T: Default,
{
self.with(|cell| cell.replace(Default::default()))
}
pub fn replace(&'static self, value: T) -> T {
self.with(|cell| cell.replace(value))
}
}
impl<T: 'static> LocalKey<RefCell<T>> {
pub fn with_borrow<F, R>(&'static self, f: F) -> R
where
F: FnOnce(&T) -> R,
{
self.with(|cell| f(&cell.borrow()))
}
pub fn with_borrow_mut<F, R>(&'static self, f: F) -> R
where
F: FnOnce(&mut T) -> R,
{
self.with(|cell| f(&mut cell.borrow_mut()))
}
pub fn set(&'static self, value: T) {
self.initialize_with(RefCell::new(value), |value, cell| {
if let Some(value) = value {
*cell.borrow_mut() = value.into_inner();
}
});
}
pub fn take(&'static self) -> T
where
T: Default,
{
self.with(|cell| cell.take())
}
pub fn replace(&'static self, value: T) -> T {
self.with(|cell| cell.replace(value))
}
}
#[macro_export]
macro_rules! os_task_local {
($($(#[$attr:meta])* $vis:vis static $name:ident: $t:ty = $init:expr;)*) => {
$(
$(#[$attr])*
$vis static $name: $crate::task::local::LocalKey<$t> = $crate::task::local::LocalKey::new(|| $init);
)*
};
}