use std::any::TypeId;
use std::collections::hash_map::Entry;
use super::owner::with_root_owner;
use super::signal::{RwSignal, create_signal};
use super::state::with_reactive;
pub(crate) struct GlobalSignal<T: 'static> {
init: fn() -> T,
}
impl<T: Clone + Send + 'static> GlobalSignal<T> {
pub(crate) const fn new(init: fn() -> T) -> Self {
Self { init }
}
pub(crate) fn get(&'static self) -> RwSignal<T> {
let key = self as *const Self as usize;
let cached = with_reactive(|reactive| reactive.globals.borrow().get(&key).copied());
if let Some((id, declared)) = cached {
debug_assert_eq!(
declared,
TypeId::of::<T>(),
"two globals share an address: one of them is reading the other's signal"
);
if crate::reactive::storage::has_signal(id) {
return RwSignal::from_id(id);
}
}
let signal = with_root_owner(|| create_signal((self.init)()));
with_reactive(|reactive| {
match reactive.globals.borrow_mut().entry(key) {
Entry::Occupied(e) if crate::reactive::storage::has_signal(e.get().0) => {
RwSignal::from_id(e.get().0)
}
Entry::Occupied(mut e) => {
e.insert((signal.id(), TypeId::of::<T>()));
signal
}
Entry::Vacant(e) => {
e.insert((signal.id(), TypeId::of::<T>()));
signal
}
}
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reactive::owner::{create_root_owner, dispose_owner_now, with_owner};
static COUNT: GlobalSignal<u32> = GlobalSignal::new(|| 7);
static OTHER_COUNT: GlobalSignal<u32> = GlobalSignal::new(|| 100);
#[test]
fn a_global_outlives_the_scope_that_read_it_first() {
create_root_owner();
let ((), first_reader) = with_owner(|| {
assert_eq!(COUNT.get().get_untracked(), 7);
});
dispose_owner_now(first_reader);
assert_eq!(COUNT.get().get_untracked(), 7, "still readable");
COUNT.get().set(9);
assert_eq!(COUNT.get().get_untracked(), 9, "and still writable");
}
#[test]
fn every_read_of_one_global_is_the_same_signal() {
create_root_owner();
assert!(COUNT.get() == COUNT.get());
}
#[test]
fn a_global_recreates_after_its_root_is_disposed() {
let root = create_root_owner();
let stale = COUNT.get();
stale.set(3);
dispose_owner_now(root);
let fresh = COUNT.get();
assert!(fresh != stale);
assert_eq!(fresh.get_untracked(), 7);
assert!(fresh == COUNT.get());
fresh.set(9);
assert_eq!(COUNT.get().get_untracked(), 9);
assert!(stale.try_get_untracked().is_none());
}
#[test]
fn a_global_does_not_return_a_recycled_slot() {
let root = create_root_owner();
let stale = COUNT.get();
dispose_owner_now(root);
let replacement = create_signal(String::from("unrelated"));
assert_eq!(replacement.id().index(), stale.id().index());
let fresh = COUNT.get();
assert_eq!(fresh.get_untracked(), 7);
fresh.set(9);
assert_eq!(replacement.get_untracked(), "unrelated");
assert!(stale.try_get_untracked().is_none());
}
#[test]
fn a_reentrant_initializer_keeps_the_live_winner_after_disposal() {
use std::cell::Cell;
thread_local! {
static INITIALIZING: Cell<bool> = const { Cell::new(false) };
}
static REENTRANT: GlobalSignal<u32> = GlobalSignal::new(init);
fn init() -> u32 {
if !INITIALIZING.replace(true) {
REENTRANT.get().set(19);
INITIALIZING.set(false);
}
7
}
let root = create_root_owner();
let stale = REENTRANT.get();
assert_eq!(stale.get_untracked(), 19);
stale.set(23);
dispose_owner_now(root);
let fresh = REENTRANT.get();
assert!(fresh != stale);
assert_eq!(fresh.get_untracked(), 19);
assert!(fresh == REENTRANT.get());
fresh.set(29);
assert_eq!(REENTRANT.get().get_untracked(), 29);
}
#[test]
fn a_global_whose_signal_is_gone_makes_another() {
create_root_owner();
COUNT.get().set(3);
assert_eq!(COUNT.get().get_untracked(), 3);
crate::reactive::storage::discard_all_signals();
assert_eq!(
COUNT.get().get_untracked(),
7,
"back to what the declaration says, not a panic"
);
}
#[test]
fn two_globals_of_one_type_are_two_globals() {
create_root_owner();
COUNT.get().set(1);
OTHER_COUNT.get().set(2);
assert_eq!(COUNT.get().get_untracked(), 1);
assert_eq!(OTHER_COUNT.get().get_untracked(), 2);
}
}