create Signal::global
This commit is contained in:
1 parent
3dce650367
commit
bc914deeaa
23 files changed
+324
-1105
No files matched your search
@@ -0,0 +1,194 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
collections::HashMap,
|
||||
mem::MaybeUninit,
|
||||
ops::Deref,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use dioxus_core::{
|
||||
prelude::{provide_context, try_consume_context, IntoAttributeValue},
|
||||
ScopeId,
|
||||
};
|
||||
use generational_box::{GenerationalRef, GenerationalRefMut};
|
||||
|
||||
use crate::{MappedSignal, Signal, Write};
|
||||
|
||||
/// A signal that can be accessed from anywhere in the application and created in a static
|
||||
pub struct GlobalSignal<T> {
|
||||
initializer: fn() -> T,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct GlobalSignalContext {
|
||||
signal: Rc<RefCell<HashMap<*const (), Box<dyn Any>>>>,
|
||||
}
|
||||
|
||||
fn get_global_context() -> GlobalSignalContext {
|
||||
match try_consume_context() {
|
||||
Some(context) => context,
|
||||
None => {
|
||||
let context = GlobalSignalContext {
|
||||
signal: Rc::new(RefCell::new(HashMap::new())),
|
||||
};
|
||||
provide_context(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> GlobalSignal<T> {
|
||||
/// Create a new global signal with the given initializer.
|
||||
pub const fn new(initializer: fn() -> T) -> GlobalSignal<T> {
|
||||
GlobalSignal { initializer }
|
||||
}
|
||||
|
||||
fn inner_signal(&self) -> Signal<T> {
|
||||
let key = self as *const _ as *const ();
|
||||
|
||||
let context = get_global_context();
|
||||
let mut write = context.signal.borrow_mut();
|
||||
let signal = write.entry(key).or_insert_with(|| {
|
||||
// Constructors are always run in the root scope
|
||||
let value = ScopeId::ROOT.in_runtime(self.initializer);
|
||||
let signal = Signal::new(value);
|
||||
Box::new(signal)
|
||||
});
|
||||
|
||||
*signal.downcast_ref::<Signal<T>>().unwrap()
|
||||
}
|
||||
|
||||
/// Get the scope the signal was created in.
|
||||
pub fn origin_scope(&self) -> ScopeId {
|
||||
ScopeId::ROOT
|
||||
}
|
||||
|
||||
/// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead.
|
||||
///
|
||||
/// If the signal has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
pub fn read(&self) -> GenerationalRef<T, Ref<'static, T>> {
|
||||
self.inner_signal().read()
|
||||
}
|
||||
|
||||
/// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.**
|
||||
///
|
||||
/// If the signal has been dropped, this will panic.
|
||||
pub fn peek(&self) -> GenerationalRef<T, Ref<'static, T>> {
|
||||
self.inner_signal().peek()
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the signal's value.
|
||||
///
|
||||
/// If the signal has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
pub fn write(&self) -> Write<T, GenerationalRefMut<T, RefMut<'static, T>>> {
|
||||
self.inner_signal().write()
|
||||
}
|
||||
|
||||
/// Set the value of the signal. This will trigger an update on all subscribers.
|
||||
#[track_caller]
|
||||
pub fn set(&self, value: T) {
|
||||
self.inner_signal().set(value);
|
||||
}
|
||||
|
||||
/// Set the value of the signal without triggering an update on subscribers.
|
||||
#[track_caller]
|
||||
pub fn set_untracked(&self, value: T) {
|
||||
self.inner_signal().set_untracked(value);
|
||||
}
|
||||
|
||||
/// Run a closure with a reference to the signal's value.
|
||||
/// If the signal has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||
self.inner_signal().with(f)
|
||||
}
|
||||
|
||||
/// Run a closure with a mutable reference to the signal's value.
|
||||
/// If the signal has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
|
||||
self.inner_signal().with_mut(f)
|
||||
}
|
||||
|
||||
/// Map the signal to a new type.
|
||||
pub fn map<O>(
|
||||
&self,
|
||||
f: impl Fn(&T) -> &O + 'static,
|
||||
) -> MappedSignal<GenerationalRef<O, Ref<'static, O>>> {
|
||||
MappedSignal::new(self.inner_signal(), f)
|
||||
}
|
||||
|
||||
/// Get the generational id of the signal.
|
||||
pub fn id(&self) -> generational_box::GenerationalBoxId {
|
||||
self.inner_signal().id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> IntoAttributeValue for GlobalSignal<T>
|
||||
where
|
||||
T: Clone + IntoAttributeValue,
|
||||
{
|
||||
fn into_value(self) -> dioxus_core::AttributeValue {
|
||||
self.inner_signal().into_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static> GlobalSignal<T> {
|
||||
/// Get the current value of the signal. This will subscribe the current scope to the signal.
|
||||
/// If the signal has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
pub fn cloned(&self) -> T {
|
||||
self.read().clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalSignal<bool> {
|
||||
/// Invert the boolean value of the signal. This will trigger an update on all subscribers.
|
||||
pub fn toggle(&self) {
|
||||
self.set(!self.cloned());
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> PartialEq for GlobalSignal<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self, other)
|
||||
}
|
||||
}
|
||||
|
||||
/// Allow calling a signal with signal() syntax
|
||||
///
|
||||
/// Currently only limited to copy types, though could probably specialize for string/arc/rc
|
||||
impl<T: Clone + 'static> Deref for GlobalSignal<T> {
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
||||
|
||||
// First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
|
||||
let uninit_callable = MaybeUninit::<Self>::uninit();
|
||||
// Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
|
||||
let uninit_closure = move || Self::read(unsafe { &*uninit_callable.as_ptr() }).clone();
|
||||
|
||||
// Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
|
||||
let size_of_closure = std::mem::size_of_val(&uninit_closure);
|
||||
assert_eq!(size_of_closure, std::mem::size_of::<Self>());
|
||||
|
||||
// Then cast the lifetime of the closure to the lifetime of &self.
|
||||
fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
|
||||
b
|
||||
}
|
||||
let reference_to_closure = cast_lifetime(
|
||||
{
|
||||
// The real closure that we will never use.
|
||||
&uninit_closure
|
||||
},
|
||||
// We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &self.
|
||||
unsafe { std::mem::transmute(self) },
|
||||
);
|
||||
|
||||
// Cast the closure to a trait object.
|
||||
reference_to_closure as &Self::Target
|
||||
}
|
||||
}
|
||||
@@ -1,54 +1,57 @@
|
||||
use crate::rt::CopyValue;
|
||||
use crate::signal::{ReadOnlySignal, Signal, Write};
|
||||
use crate::SignalData;
|
||||
use generational_box::Mappable;
|
||||
use crate::{GlobalSignal, SignalData};
|
||||
use generational_box::{GenerationalRef, Mappable};
|
||||
use generational_box::{MappableMut, Storage};
|
||||
|
||||
use std::cell::Ref;
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
};
|
||||
|
||||
macro_rules! read_impls {
|
||||
($ty:ident, $bound:path, $vec_bound:path) => {
|
||||
impl<T: Default + 'static, S: $bound> Default for $ty<T, S> {
|
||||
#[track_caller]
|
||||
fn default() -> Self {
|
||||
Self::new_maybe_sync(Default::default())
|
||||
($ty:ident $(, $bound_ty:ident : $bound:path, $vec_bound_ty:ident : $vec_bound:path)?) => {
|
||||
$(
|
||||
impl<T: Default + 'static, $bound_ty: $bound> Default for $ty<T, $bound_ty> {
|
||||
#[track_caller]
|
||||
fn default() -> Self {
|
||||
Self::new_maybe_sync(Default::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
)?
|
||||
|
||||
impl<T, S: $bound> std::clone::Clone for $ty<T, S> {
|
||||
impl<T $(,$bound_ty: $bound)?> std::clone::Clone for $ty<T $(, $bound_ty)?> {
|
||||
#[track_caller]
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: $bound> Copy for $ty<T, S> {}
|
||||
impl<T $(,$bound_ty: $bound)?> Copy for $ty<T $(, $bound_ty)?> {}
|
||||
|
||||
impl<T: Display + 'static, S: $bound> Display for $ty<T, S> {
|
||||
impl<T: Display + 'static $(,$bound_ty: $bound)?> Display for $ty<T $(, $bound_ty)?> {
|
||||
#[track_caller]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.with(|v| Display::fmt(v, f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Debug + 'static, S: $bound> Debug for $ty<T, S> {
|
||||
impl<T: Debug + 'static $(,$bound_ty: $bound)?> Debug for $ty<T $(, $bound_ty)?> {
|
||||
#[track_caller]
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.with(|v| Debug::fmt(v, f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq + 'static, S: $bound> PartialEq<T> for $ty<T, S> {
|
||||
impl<T: PartialEq + 'static $(,$bound_ty: $bound)?> PartialEq<T> for $ty<T $(, $bound_ty)?> {
|
||||
#[track_caller]
|
||||
fn eq(&self, other: &T) -> bool {
|
||||
self.with(|v| *v == *other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: $vec_bound> $ty<Vec<T>, S> {
|
||||
impl<T: 'static $(,$vec_bound_ty: $vec_bound)?> $ty<Vec<T>, $($vec_bound_ty)?> {
|
||||
/// Returns the length of the inner vector.
|
||||
#[track_caller]
|
||||
pub fn len(&self) -> usize {
|
||||
@@ -130,7 +133,13 @@ macro_rules! write_impls {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: $vec_bound> $ty<Vec<T>, S> {
|
||||
write_vec_impls!($ty, S: $vec_bound);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! write_vec_impls {
|
||||
($ty:ident $(, $vec_bound_ty:ident: $vec_bound:path)?) => {
|
||||
impl<T: 'static $(, $vec_bound_ty: $vec_bound)?> $ty<Vec<T> $(, $vec_bound_ty)?> {
|
||||
/// Pushes a new value to the end of the vector.
|
||||
#[track_caller]
|
||||
pub fn push(&mut self, value: T) {
|
||||
@@ -194,7 +203,7 @@ macro_rules! write_impls {
|
||||
};
|
||||
}
|
||||
|
||||
read_impls!(CopyValue, Storage<T>, Storage<Vec<T>>);
|
||||
read_impls!(CopyValue, S: Storage<T>, S: Storage<Vec<T>>);
|
||||
|
||||
impl<T: 'static, S: Storage<Vec<T>>> CopyValue<Vec<T>, S> {
|
||||
/// Read a value from the inner vector.
|
||||
@@ -259,7 +268,7 @@ impl<T: 'static, S: Storage<Option<T>>> CopyValue<Option<T>, S> {
|
||||
}
|
||||
}
|
||||
|
||||
read_impls!(Signal, Storage<SignalData<T>>, Storage<SignalData<Vec<T>>>);
|
||||
read_impls!(Signal, S: Storage<SignalData<T>>, S: Storage<SignalData<Vec<T>>>);
|
||||
|
||||
impl<T: 'static, S: Storage<SignalData<Vec<T>>>> Signal<Vec<T>, S> {
|
||||
/// Read a value from the inner vector.
|
||||
@@ -339,10 +348,72 @@ impl<T: 'static, S: Storage<SignalData<Option<T>>>> Signal<Option<T>, S> {
|
||||
|
||||
read_impls!(
|
||||
ReadOnlySignal,
|
||||
Storage<SignalData<T>>,
|
||||
Storage<SignalData<Vec<T>>>
|
||||
S: Storage<SignalData<T>>,
|
||||
S: Storage<SignalData<Vec<T>>>
|
||||
);
|
||||
|
||||
read_impls!(GlobalSignal);
|
||||
|
||||
impl<T: 'static> GlobalSignal<Vec<T>> {
|
||||
/// Read a value from the inner vector.
|
||||
pub fn get(&'static self, index: usize) -> Option<GenerationalRef<T, Ref<'static, T>>> {
|
||||
GenerationalRef::<Vec<T>, Ref<'static, Vec<T>>>::try_map(self.read(), move |v| v.get(index))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static> GlobalSignal<Option<T>> {
|
||||
/// Unwraps the inner value and clones it.
|
||||
pub fn unwrap(&'static self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
{
|
||||
self.with(|v| v.clone()).unwrap()
|
||||
}
|
||||
|
||||
/// Attempts to read the inner value of the Option.
|
||||
pub fn as_ref(&'static self) -> Option<GenerationalRef<T, Ref<'static, T>>> {
|
||||
GenerationalRef::<Option<T>, Ref<'static, Option<T>>>::try_map(self.read(), |v| v.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
write_vec_impls!(GlobalSignal);
|
||||
|
||||
impl<T: 'static> GlobalSignal<Option<T>> {
|
||||
/// Takes the value out of the Option.
|
||||
pub fn take(&self) -> Option<T> {
|
||||
self.with_mut(|v| v.take())
|
||||
}
|
||||
|
||||
/// Replace the value in the Option.
|
||||
pub fn replace(&self, value: T) -> Option<T> {
|
||||
self.with_mut(|v| v.replace(value))
|
||||
}
|
||||
|
||||
/// Gets the value out of the Option, or inserts the given value if the Option is empty.
|
||||
pub fn get_or_insert(&self, default: T) -> GenerationalRef<T, Ref<'static, T>> {
|
||||
self.get_or_insert_with(|| default)
|
||||
}
|
||||
|
||||
/// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty.
|
||||
pub fn get_or_insert_with(
|
||||
&self,
|
||||
default: impl FnOnce() -> T,
|
||||
) -> GenerationalRef<T, Ref<'static, T>> {
|
||||
let borrow = self.read();
|
||||
if borrow.is_none() {
|
||||
drop(borrow);
|
||||
self.with_mut(|v| *v = Some(default()));
|
||||
GenerationalRef::<Option<T>, Ref<'static, Option<T>>>::map(self.read(), |v| {
|
||||
v.as_ref().unwrap()
|
||||
})
|
||||
} else {
|
||||
GenerationalRef::<Option<T>, Ref<'static, Option<T>>>::map(borrow, |v| {
|
||||
v.as_ref().unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the values of a `CopyValue<Vec<T>>`.
|
||||
pub struct CopyValueIterator<T: 'static, S: Storage<Vec<T>>> {
|
||||
index: usize,
|
||||
|
||||
@@ -20,3 +20,5 @@ pub use generational_box::{Storage, SyncStorage, UnsyncStorage};
|
||||
pub use map::*;
|
||||
mod comparer;
|
||||
pub use comparer::*;
|
||||
mod global;
|
||||
pub use global::*;
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::MappedSignal;
|
||||
use crate::{GlobalSignal, MappedSignal};
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
marker::PhantomData,
|
||||
@@ -220,6 +220,11 @@ impl<T: 'static> Signal<T> {
|
||||
pub fn new_in_scope(value: T, owner: ScopeId) -> Self {
|
||||
Self::new_maybe_sync_in_scope(value, owner)
|
||||
}
|
||||
|
||||
/// Creates a new global Signal that can be used in a global static.
|
||||
pub const fn global(constructor: fn() -> T) -> GlobalSignal<T> {
|
||||
GlobalSignal::new(constructor)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
||||
@@ -389,7 +394,8 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
||||
|
||||
/// Set the value of the signal without triggering an update on subscribers.
|
||||
///
|
||||
/// todo: we should make it so setting while rendering doesn't trigger an update s
|
||||
// todo: we should make it so setting while rendering doesn't trigger an update s
|
||||
#[track_caller]
|
||||
pub fn set_untracked(&self, value: T) {
|
||||
let mut inner = self.inner.write();
|
||||
inner.value = value;
|
||||
@@ -512,7 +518,12 @@ impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S>
|
||||
/// B is the dynamically checked type of the write (RefMut)
|
||||
/// S is the storage type of the signal
|
||||
/// I is the type of the original signal
|
||||
pub struct Write<T: 'static, B: MappableMut<T>, S: Storage<SignalData<I>>, I: 'static = T> {
|
||||
pub struct Write<
|
||||
T: 'static,
|
||||
B: MappableMut<T>,
|
||||
S: Storage<SignalData<I>> = UnsyncStorage,
|
||||
I: 'static = T,
|
||||
> {
|
||||
write: B,
|
||||
signal: SignalSubscriberDrop<I, S>,
|
||||
phantom: std::marker::PhantomData<T>,
|
||||
|
||||
Reference in new issue
Block a user