use std::any::Any;
use std::cell::RefCell;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::reactive::ScopeId;
use crate::scope::current_scope_id;
static NEXT_KEY_ID: AtomicU64 = AtomicU64::new(1);
pub struct ContextKey<T: 'static> {
id: u64,
name: &'static str,
_t: PhantomData<fn() -> T>,
}
#[deprecated(note = "use create_context! / ContextKey instead (RFC 056 §6.3)")]
pub type InjectKey<T> = ContextKey<T>;
impl<T: 'static> ContextKey<T> {
pub fn new(name: &'static str) -> Self {
Self {
id: NEXT_KEY_ID.fetch_add(1, Ordering::Relaxed),
name,
_t: PhantomData,
}
}
pub fn name(&self) -> &'static str {
self.name
}
pub fn id(&self) -> u64 {
self.id
}
pub fn provide(&self, value: T)
where
T: Any + 'static,
{
provide(self, value);
}
pub fn inject(&self) -> Option<T>
where
T: Clone + Any + 'static,
{
inject(self)
}
}
impl<T: 'static> Copy for ContextKey<T> {}
impl<T: 'static> Clone for ContextKey<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T: 'static> std::fmt::Debug for ContextKey<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ContextKey")
.field("name", &self.name)
.field("id", &self.id)
.finish()
}
}
type ProvideMap = HashMap<u64, Box<dyn Any>>;
thread_local! {
static PARENTS: RefCell<HashMap<ScopeId, ScopeId>> =
RefCell::new(HashMap::new());
static PROVIDES: RefCell<HashMap<ScopeId, ProvideMap>> =
RefCell::new(HashMap::new());
}
pub fn set_parent(child: ScopeId, parent: ScopeId) {
PARENTS.with(|p| {
p.borrow_mut().insert(child, parent);
});
}
pub fn parent_of(scope: ScopeId) -> Option<ScopeId> {
PARENTS.with(|p| p.borrow().get(&scope).copied())
}
pub fn provide<T: Any + 'static>(key: &ContextKey<T>, value: T) {
let scope =
current_scope_id().expect("pocopine::provide called outside a handler / lifecycle context");
PROVIDES.with(|p| {
p.borrow_mut()
.entry(scope)
.or_default()
.insert(key.id(), Box::new(value));
});
}
pub fn inject<T: Clone + Any + 'static>(key: &ContextKey<T>) -> Option<T> {
let mut scope =
current_scope_id().expect("pocopine::inject called outside a handler / lifecycle context");
loop {
let hit = PROVIDES.with(|p| {
let map = p.borrow();
map.get(&scope)
.and_then(|entries| entries.get(&key.id()))
.and_then(|any| any.downcast_ref::<T>())
.cloned()
});
if let Some(v) = hit {
return Some(v);
}
match parent_of(scope) {
Some(parent) => scope = parent,
None => return None,
}
}
}
#[cfg(feature = "devtools")]
pub fn inject_chain(scope: ScopeId) -> Vec<(u64, ScopeId)> {
let mut out: Vec<(u64, ScopeId)> = Vec::new();
let mut cur = scope;
loop {
PROVIDES.with(|p| {
if let Some(entries) = p.borrow().get(&cur) {
for key_id in entries.keys() {
out.push((*key_id, cur));
}
}
});
match parent_of(cur) {
Some(parent) => cur = parent,
None => break,
}
}
out
}
pub fn clear_scope(scope: ScopeId) {
PARENTS.with(|p| {
p.borrow_mut().remove(&scope);
});
PROVIDES.with(|p| {
p.borrow_mut().remove(&scope);
});
}
pub trait ContextMarker: 'static {
type Value: Clone + Any + 'static;
fn key() -> &'static ContextKey<Self::Value>;
}
#[macro_export]
macro_rules! create_context {
($vis:vis $name:ident : $ty:ty) => {
$vis static $name: ::std::sync::LazyLock<$crate::context::ContextKey<$ty>> =
::std::sync::LazyLock::new(|| {
$crate::context::ContextKey::new(
concat!(module_path!(), "::", stringify!($name))
)
});
#[allow(non_camel_case_types)]
$vis struct $name {}
impl $crate::context::ContextMarker for $name {
type Value = $ty;
fn key() -> &'static $crate::context::ContextKey<$ty> {
&*$name
}
}
};
}
#[macro_export]
macro_rules! inject_key {
($vis:vis $name:ident : $ty:ty) => {
$crate::create_context!($vis $name : $ty);
};
}