use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
use crate::{AppId, LocalContext};
#[doc(hidden)]
pub struct AppLocalConst<T: Send + Sync + 'static> {
value: RwLock<T>,
}
impl<T: Send + Sync + 'static> AppLocalConst<T> {
pub const fn new(init: T) -> Self {
Self { value: RwLock::new(init) }
}
}
#[doc(hidden)]
pub struct AppLocalOption<T: Send + Sync + 'static> {
value: RwLock<Option<T>>,
init: fn() -> T,
}
impl<T: Send + Sync + 'static> AppLocalOption<T> {
pub const fn new(init: fn() -> T) -> Self {
Self {
value: RwLock::new(None),
init,
}
}
fn read_impl(&'static self, read: RwLockReadGuard<'static, Option<T>>) -> MappedRwLockReadGuard<'static, T> {
if read.is_some() {
return RwLockReadGuard::map(read, |v| v.as_ref().unwrap());
}
drop(read);
let mut write = self.value.write();
if write.is_some() {
drop(write);
return self.read();
}
let value = (self.init)();
*write = Some(value);
let read = RwLockWriteGuard::downgrade(write);
RwLockReadGuard::map(read, |v| v.as_ref().unwrap())
}
fn write_impl(&'static self, mut write: RwLockWriteGuard<'static, Option<T>>) -> MappedRwLockWriteGuard<'static, T> {
if write.is_some() {
return RwLockWriteGuard::map(write, |v| v.as_mut().unwrap());
}
let value = (self.init)();
*write = Some(value);
RwLockWriteGuard::map(write, |v| v.as_mut().unwrap())
}
}
#[doc(hidden)]
pub struct AppLocalVec<T: Send + Sync + 'static> {
value: RwLock<Vec<(AppId, T)>>,
init: fn() -> T,
}
impl<T: Send + Sync + 'static> AppLocalVec<T> {
pub const fn new(init: fn() -> T) -> Self {
Self {
value: RwLock::new(vec![]),
init,
}
}
fn cleanup(&'static self, id: AppId) {
self.try_cleanup(id, 0);
}
fn try_cleanup(&'static self, id: AppId, tries: u8) {
if let Some(mut w) = self.value.try_write_for(if tries == 0 {
Duration::from_millis(50)
} else {
Duration::from_millis(500)
}) {
if let Some(i) = w.iter().position(|(s, _)| *s == id) {
w.swap_remove(i);
}
} else if tries > 5 {
tracing::error!("failed to cleanup `app_local` for {id:?}, was locked after app drop");
} else {
std::thread::spawn(move || {
self.try_cleanup(id, tries + 1);
});
}
}
fn read_impl(&'static self, read: RwLockReadGuard<'static, Vec<(AppId, T)>>) -> MappedRwLockReadGuard<'static, T> {
let id = LocalContext::current_app().expect("no app running, `app_local` can only be accessed inside apps");
if let Some(i) = read.iter().position(|(s, _)| *s == id) {
return RwLockReadGuard::map(read, |v| &v[i].1);
}
drop(read);
let mut write = self.value.write();
if write.iter().any(|(s, _)| *s == id) {
drop(write);
return self.read();
}
let value = (self.init)();
let i = write.len();
write.push((id, value));
LocalContext::register_cleanup(Box::new(move |id| self.cleanup(id)));
let read = RwLockWriteGuard::downgrade(write);
RwLockReadGuard::map(read, |v| &v[i].1)
}
fn write_impl(&'static self, mut write: RwLockWriteGuard<'static, Vec<(AppId, T)>>) -> MappedRwLockWriteGuard<'static, T> {
let id = LocalContext::current_app().expect("no app running, `app_local` can only be accessed inside apps");
if let Some(i) = write.iter().position(|(s, _)| *s == id) {
return RwLockWriteGuard::map(write, |v| &mut v[i].1);
}
let value = (self.init)();
let i = write.len();
write.push((id, value));
LocalContext::register_cleanup(move |id| self.cleanup(id));
RwLockWriteGuard::map(write, |v| &mut v[i].1)
}
}
#[doc(hidden)]
pub trait AppLocalImpl<T: Send + Sync + 'static>: Send + Sync + 'static {
fn read(&'static self) -> MappedRwLockReadGuard<'static, T>;
fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>>;
fn write(&'static self) -> MappedRwLockWriteGuard<'static, T>;
fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>>;
}
impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalVec<T> {
fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
self.read_impl(self.value.read_recursive())
}
fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
Some(self.read_impl(self.value.try_read_recursive()?))
}
fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
self.write_impl(self.value.write())
}
fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
Some(self.write_impl(self.value.try_write()?))
}
}
impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalOption<T> {
fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
self.read_impl(self.value.read_recursive())
}
fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
Some(self.read_impl(self.value.try_read_recursive()?))
}
fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
self.write_impl(self.value.write())
}
fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
Some(self.write_impl(self.value.try_write()?))
}
}
impl<T: Send + Sync + 'static> AppLocalImpl<T> for AppLocalConst<T> {
fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
RwLockReadGuard::map(self.value.read(), |l| l)
}
fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
Some(RwLockReadGuard::map(self.value.try_read()?, |l| l))
}
fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
RwLockWriteGuard::map(self.value.write(), |l| l)
}
fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
Some(RwLockWriteGuard::map(self.value.try_write()?, |l| l))
}
}
pub struct AppLocal<T: Send + Sync + 'static> {
inner: fn() -> &'static dyn AppLocalImpl<T>,
}
impl<T: Send + Sync + 'static> AppLocal<T> {
#[doc(hidden)]
pub const fn new(inner: fn() -> &'static dyn AppLocalImpl<T>) -> Self {
AppLocal { inner }
}
#[inline]
pub fn read(&'static self) -> MappedRwLockReadGuard<'static, T> {
(self.inner)().read()
}
#[inline]
pub fn try_read(&'static self) -> Option<MappedRwLockReadGuard<'static, T>> {
(self.inner)().try_read()
}
#[inline]
pub fn write(&'static self) -> MappedRwLockWriteGuard<'static, T> {
(self.inner)().write()
}
pub fn try_write(&'static self) -> Option<MappedRwLockWriteGuard<'static, T>> {
(self.inner)().try_write()
}
#[inline]
pub fn get(&'static self) -> T
where
T: Clone,
{
self.read().clone()
}
#[inline]
pub fn set(&'static self, value: T) {
*self.write() = value;
}
#[inline]
pub fn try_get(&'static self) -> Option<T>
where
T: Clone,
{
self.try_read().map(|l| l.clone())
}
#[inline]
pub fn try_set(&'static self, value: T) -> Result<(), T> {
match self.try_write() {
Some(mut l) => {
*l = value;
Ok(())
}
None => Err(value),
}
}
#[inline]
pub fn read_map<O>(&'static self, map: impl FnOnce(&T) -> &O) -> MappedRwLockReadGuard<'static, O> {
MappedRwLockReadGuard::map(self.read(), map)
}
#[inline]
pub fn try_read_map<O>(&'static self, map: impl FnOnce(&T) -> &O) -> Option<MappedRwLockReadGuard<'static, O>> {
let lock = self.try_read()?;
Some(MappedRwLockReadGuard::map(lock, map))
}
#[inline]
pub fn write_map<O>(&'static self, map: impl FnOnce(&mut T) -> &mut O) -> MappedRwLockWriteGuard<'static, O> {
MappedRwLockWriteGuard::map(self.write(), map)
}
#[inline]
pub fn try_write_map<O>(&'static self, map: impl FnOnce(&mut T) -> &mut O) -> Option<MappedRwLockWriteGuard<'static, O>> {
let lock = self.try_write()?;
Some(MappedRwLockWriteGuard::map(lock, map))
}
pub fn id(&'static self) -> AppLocalId {
AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _)
}
}
impl<T: Send + Sync + 'static> PartialEq for AppLocal<T> {
fn eq(&self, other: &Self) -> bool {
let a = AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
let b = AppLocalId((other.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
a == b
}
}
impl<T: Send + Sync + 'static> Eq for AppLocal<T> {}
impl<T: Send + Sync + 'static> std::hash::Hash for AppLocal<T> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
let a = AppLocalId((self.inner)() as *const dyn AppLocalImpl<T> as *const () as _);
std::hash::Hash::hash(&a, state)
}
}
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
pub struct AppLocalId(pub(crate) usize);
impl AppLocalId {
pub fn get(self) -> usize {
self.0 as _
}
}
impl fmt::Debug for AppLocalId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "AppLocalId({:#x})", self.0)
}
}
#[macro_export]
macro_rules! app_local {
($(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = $(const { $init_const:expr })? $($init:expr_2021)?;
)+) => {$(
$crate::app_local_impl! {
$(#[$meta])*
$vis static $IDENT: $T = $(const { $init_const })? $($init)?;
}
)+};
}
#[doc(hidden)]
#[macro_export]
macro_rules! app_local_impl_single {
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = const { $init:expr };
) => {
$(#[$meta])*
$vis static $IDENT: $crate::AppLocal<$T> = {
fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
$crate::hot_static! {
static IMPL: $crate::AppLocalConst<$T> = $crate::AppLocalConst::new($init);
}
$crate::hot_static_ref!(IMPL)
}
$crate::AppLocal::new(s)
};
};
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = $init:expr_2021;
) => {
$(#[$meta])*
$vis static $IDENT: $crate::AppLocal<$T> = {
fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
fn init() -> $T {
std::convert::Into::into($init)
}
$crate::hot_static! {
static IMPL: $crate::AppLocalOption<$T> = $crate::AppLocalOption::new(init);
}
$crate::hot_static_ref!(IMPL)
}
$crate::AppLocal::new(s)
};
};
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = ($tt:tt)*
) => {
std::compile_error!("expected `const { $expr };` or `$expr;`")
};
}
#[doc(hidden)]
#[macro_export]
macro_rules! app_local_impl_multi {
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = const { $init:expr };
) => {
$(#[$meta])*
$vis static $IDENT: $crate::AppLocal<$T> = {
fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
const fn init() -> $T {
$init
}
$crate::hot_static! {
static IMPL: $crate::AppLocalVec<$T> = $crate::AppLocalVec::new(init);
}
$crate::hot_static_ref!(IMPL)
}
$crate::AppLocal::new(s)
};
};
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = $init:expr_2021;
) => {
$(#[$meta])*
$vis static $IDENT: $crate::AppLocal<$T> = {
fn s() -> &'static dyn $crate::AppLocalImpl<$T> {
fn init() -> $T {
std::convert::Into::into($init)
}
$crate::hot_static! {
static IMPL: $crate::AppLocalVec<$T> = $crate::AppLocalVec::new(init);
}
$crate::hot_static_ref!(IMPL)
}
$crate::AppLocal::new(s)
};
};
(
$(#[$meta:meta])*
$vis:vis static $IDENT:ident : $T:ty = ($tt:tt)*
) => {
std::compile_error!("expected `const { $expr };` or `$expr;`")
};
}
use std::{fmt, time::Duration};
#[cfg(feature = "multi_app")]
#[doc(hidden)]
pub use app_local_impl_multi as app_local_impl;
#[cfg(not(feature = "multi_app"))]
#[doc(hidden)]
pub use app_local_impl_single as app_local_impl;