use dioxus_core::{Runtime, ScopeId, Subscribers};
use generational_box::BorrowResult;
use std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc};
mod memo;
pub use memo::*;
mod signal;
pub use signal::*;
use crate::{Readable, ReadableExt, ReadableRef, Signal, Writable, WritableExt, WritableRef};
pub trait InitializeFromFunction<T> {
fn initialize_from_function(f: fn() -> T) -> Self;
}
impl<T> InitializeFromFunction<T> for T {
fn initialize_from_function(f: fn() -> T) -> Self {
f()
}
}
pub struct Global<T, R = T> {
constructor: fn() -> R,
key: GlobalKey<'static>,
phantom: std::marker::PhantomData<fn() -> T>,
}
impl<T: Clone, R: Clone> Deref for Global<T, R>
where
T: Readable<Target = R> + InitializeFromFunction<R> + 'static,
T::Target: 'static,
{
type Target = dyn Fn() -> R;
fn deref(&self) -> &Self::Target {
unsafe { ReadableExt::deref_impl(self) }
}
}
impl<T, R> Readable for Global<T, R>
where
T: Readable<Target = R> + InitializeFromFunction<R> + Clone + 'static,
{
type Target = R;
type Storage = T::Storage;
#[track_caller]
fn try_read_unchecked(
&self,
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
where
R: 'static,
{
self.resolve().try_read_unchecked()
}
#[track_caller]
fn try_peek_unchecked(&self) -> BorrowResult<ReadableRef<'static, Self>>
where
R: 'static,
{
self.resolve().try_peek_unchecked()
}
fn subscribers(&self) -> Subscribers
where
R: 'static,
{
self.resolve().subscribers()
}
}
impl<T: Clone, R> Writable for Global<T, R>
where
T: Writable<Target = R> + InitializeFromFunction<R> + 'static,
{
type WriteMetadata = T::WriteMetadata;
#[track_caller]
fn try_write_unchecked(
&self,
) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError> {
self.resolve().try_write_unchecked()
}
}
impl<T: Clone, R> Global<T, R>
where
T: Writable<Target = R> + InitializeFromFunction<R> + 'static,
{
pub fn write(&self) -> WritableRef<'static, T, R> {
self.resolve().try_write_unchecked().unwrap()
}
#[track_caller]
pub fn with_mut<O>(&self, f: impl FnOnce(&mut R) -> O) -> O
where
T::Target: 'static,
{
self.resolve().with_mut(f)
}
}
impl<T: Clone, R> Global<T, R>
where
T: InitializeFromFunction<R>,
{
#[track_caller]
pub const fn new(constructor: fn() -> R) -> Self {
let key = std::panic::Location::caller();
Self {
constructor,
key: GlobalKey::new(key),
phantom: std::marker::PhantomData,
}
}
#[track_caller]
pub const fn with_name(constructor: fn() -> R, key: &'static str) -> Self {
Self {
constructor,
key: GlobalKey::File {
file: key,
line: 0,
column: 0,
index: 0,
},
phantom: std::marker::PhantomData,
}
}
#[track_caller]
pub const fn with_location(
constructor: fn() -> R,
file: &'static str,
line: u32,
column: u32,
index: usize,
) -> Self {
Self {
constructor,
key: GlobalKey::File {
file,
line: line as _,
column: column as _,
index: index as _,
},
phantom: std::marker::PhantomData,
}
}
pub fn key(&self) -> GlobalKey<'static> {
self.key.clone()
}
pub fn resolve(&self) -> T
where
T: 'static,
{
let key = self.key();
let context = get_global_context();
let mut evicted_stale_entry = false;
{
let read = context.map.borrow();
if let Some(signal) = read.get(&key) {
if let Some(signal) = signal.downcast_ref::<T>() {
return signal.clone();
}
evicted_stale_entry = true;
}
}
if evicted_stale_entry {
context.map.borrow_mut().remove(&key);
}
let signal = dioxus_core::Runtime::current().in_scope(ScopeId::ROOT, || {
T::initialize_from_function(self.constructor)
});
context
.map
.borrow_mut()
.insert(key, Box::new(signal.clone()));
signal
}
pub fn origin_scope(&self) -> ScopeId {
ScopeId::ROOT
}
}
#[derive(Clone, Default)]
pub struct GlobalLazyContext {
map: Rc<RefCell<HashMap<GlobalKey<'static>, Box<dyn Any>>>>,
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum GlobalKey<'a> {
File {
file: &'a str,
line: u32,
column: u32,
index: u32,
},
Raw(&'a str),
}
impl<'a> GlobalKey<'a> {
pub const fn new(key: &'a Location<'a>) -> Self {
GlobalKey::File {
file: key.file(),
line: key.line(),
column: key.column(),
index: 0,
}
}
}
impl From<&'static Location<'static>> for GlobalKey<'static> {
fn from(key: &'static Location<'static>) -> Self {
Self::new(key)
}
}
impl GlobalLazyContext {
pub fn get_signal_with_key<T: 'static>(&self, key: GlobalKey) -> Option<Signal<T>> {
self.map.borrow().get(&key).map(|f| {
*f.downcast_ref::<Signal<T>>().unwrap_or_else(|| {
panic!(
"Global signal with key {:?} is not of the expected type. Keys are {:?}",
key,
self.map.borrow().keys()
)
})
})
}
#[doc(hidden)]
pub fn clear<T: 'static>(&self) {
self.map.borrow_mut().retain(|_k, v| !v.is::<T>());
}
}
pub fn get_global_context() -> GlobalLazyContext {
let rt = Runtime::current();
match rt.has_context(ScopeId::ROOT) {
Some(context) => context,
None => rt.provide_context(ScopeId::ROOT, Default::default()),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_global_keys() {
const MYSIGNAL: GlobalSignal<i32> = GlobalSignal::new(|| 42);
const MYSIGNAL2: GlobalSignal<i32> = GlobalSignal::new(|| 42);
const MYSIGNAL3: GlobalSignal<i32> = GlobalSignal::with_name(|| 42, "custom-keyed");
let a = MYSIGNAL.key();
let b = MYSIGNAL.key();
let c = MYSIGNAL.key();
assert_eq!(a, b);
assert_eq!(b, c);
let d = MYSIGNAL2.key();
assert_ne!(a, d);
let e = MYSIGNAL3.key();
assert_ne!(a, e);
}
}