فهرست منبع

create global selectors

Evan Almloff 1 سال پیش
والد
کامیت
c859ed3b12
4فایلهای تغییر یافته به همراه197 افزوده شده و 19 حذف شده
  1. 2 1
      examples/global.rs
  2. 140 9
      packages/signals/src/global.rs
  3. 32 8
      packages/signals/src/impls.rs
  4. 23 1
      packages/signals/src/signal.rs

+ 2 - 1
examples/global.rs

@@ -5,6 +5,7 @@
 use dioxus::prelude::*;
 
 static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
+static DOUBLED_COUNT: GlobalSelector<i32> = Signal::global_selector(|| COUNT() * 2);
 
 fn main() {
     launch_desktop(app);
@@ -12,7 +13,7 @@ fn main() {
 
 fn app() -> Element {
     rsx! {
-        h1 { "High-Five counter: {COUNT}" }
+        h1 { "{COUNT} x 2 = {DOUBLED_COUNT}" }
         button { onclick: move |_| *COUNT.write() += 1, "Up high!" }
         button { onclick: move |_| *COUNT.write() -= 1, "Down low!" }
     }

+ 140 - 9
packages/signals/src/global.rs

@@ -13,7 +13,7 @@ use dioxus_core::{
 };
 use generational_box::{GenerationalRef, GenerationalRefMut};
 
-use crate::{MappedSignal, Signal, Write};
+use crate::{selector, MappedSignal, ReadOnlySignal, Signal, Write};
 
 /// A signal that can be accessed from anywhere in the application and created in a static
 pub struct GlobalSignal<T> {
@@ -48,15 +48,20 @@ impl<T: 'static> GlobalSignal<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()
+        let read = context.signal.borrow();
+
+        match read.get(&key) {
+            Some(signal) => *signal.downcast_ref::<Signal<T>>().unwrap(),
+            None => {
+                drop(read);
+                // Constructors are always run in the root scope
+                let value = ScopeId::ROOT.in_runtime(self.initializer);
+                let signal = Signal::new(value);
+                context.signal.borrow_mut().insert(key, Box::new(signal));
+                signal
+            }
+        }
     }
 
     /// Get the scope the signal was created in.
@@ -193,3 +198,129 @@ impl<T: Clone + 'static> Deref for GlobalSignal<T> {
         reference_to_closure as &Self::Target
     }
 }
+
+/// A signal that can be accessed from anywhere in the application and created in a static
+pub struct GlobalSelector<T: 'static> {
+    selector: fn() -> T,
+}
+
+impl<T: PartialEq + 'static> GlobalSelector<T> {
+    /// Create a new global signal
+    pub const fn new(selector: fn() -> T) -> GlobalSelector<T>
+    where
+        T: PartialEq,
+    {
+        GlobalSelector { selector }
+    }
+
+    /// Get the signal that backs this global.
+    pub fn signal(&self) -> ReadOnlySignal<T> {
+        let key = self as *const _ as *const ();
+
+        let context = get_global_context();
+
+        let read = context.signal.borrow();
+        match read.get(&key) {
+            Some(signal) => *signal.downcast_ref::<ReadOnlySignal<T>>().unwrap(),
+            None => {
+                drop(read);
+                // Constructors are always run in the root scope
+                let signal = ScopeId::ROOT.in_runtime(|| selector(self.selector));
+                context.signal.borrow_mut().insert(key, Box::new(signal));
+                signal
+            }
+        }
+    }
+
+    /// 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.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.signal().peek()
+    }
+
+    /// 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.signal().with(f)
+    }
+
+    /// Get the generational id of the signal.
+    pub fn id(&self) -> generational_box::GenerationalBoxId {
+        self.signal().id()
+    }
+}
+
+impl<T: PartialEq + 'static> IntoAttributeValue for GlobalSelector<T>
+where
+    T: Clone + IntoAttributeValue,
+{
+    fn into_value(self) -> dioxus_core::AttributeValue {
+        self.signal().into_value()
+    }
+}
+
+impl<T: PartialEq + Clone + 'static> GlobalSelector<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<T: PartialEq + 'static> PartialEq for GlobalSelector<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: PartialEq + Clone + 'static> Deref for GlobalSelector<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
+    }
+}

+ 32 - 8
packages/signals/src/impls.rs

@@ -1,6 +1,6 @@
 use crate::rt::CopyValue;
 use crate::signal::{ReadOnlySignal, Signal, Write};
-use crate::{GlobalSignal, SignalData};
+use crate::{GlobalSelector, GlobalSignal, SignalData};
 use generational_box::{GenerationalRef, Mappable};
 use generational_box::{MappableMut, Storage};
 
@@ -11,7 +11,7 @@ use std::{
 };
 
 macro_rules! read_impls {
-    ($ty:ident $(, $bound_ty:ident : $bound:path, $vec_bound_ty:ident : $vec_bound:path)?) => {
+    ($ty:ident $(: $extra_bounds:path)? $(, $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]
@@ -21,37 +21,37 @@ macro_rules! read_impls {
             }
         )?
 
-        impl<T $(,$bound_ty: $bound)?> std::clone::Clone for $ty<T $(, $bound_ty)?> {
+        impl<T $(: $extra_bounds)? $(,$bound_ty: $bound)?> std::clone::Clone for $ty<T $(, $bound_ty)?> {
             #[track_caller]
             fn clone(&self) -> Self {
                 *self
             }
         }
 
-        impl<T $(,$bound_ty: $bound)?> Copy for $ty<T $(, $bound_ty)?> {}
+        impl<T $(: $extra_bounds)? $(,$bound_ty: $bound)?> Copy for $ty<T $(, $bound_ty)?> {}
 
-        impl<T: Display + 'static $(,$bound_ty: $bound)?> Display for $ty<T $(, $bound_ty)?> {
+        impl<T: $($extra_bounds + )? 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 $(,$bound_ty: $bound)?> Debug for $ty<T $(, $bound_ty)?> {
+        impl<T: $($extra_bounds + )? 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 $(,$bound_ty: $bound)?> PartialEq<T> for $ty<T $(, $bound_ty)?> {
+        impl<T: $($extra_bounds + )? 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 $(,$vec_bound_ty: $vec_bound)?> $ty<Vec<T>, $($vec_bound_ty)?> {
+        impl<T: $($extra_bounds + )? '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 {
@@ -414,6 +414,30 @@ impl<T: 'static> GlobalSignal<Option<T>> {
     }
 }
 
+read_impls!(GlobalSelector: PartialEq);
+
+impl<T: PartialEq + 'static> GlobalSelector<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: PartialEq + 'static> GlobalSelector<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())
+    }
+}
+
 /// An iterator over the values of a `CopyValue<Vec<T>>`.
 pub struct CopyValueIterator<T: 'static, S: Storage<Vec<T>>> {
     index: usize,

+ 23 - 1
packages/signals/src/signal.rs

@@ -1,4 +1,4 @@
-use crate::{GlobalSignal, MappedSignal};
+use crate::{GlobalSelector, GlobalSignal, MappedSignal};
 use std::{
     cell::RefCell,
     marker::PhantomData,
@@ -225,6 +225,14 @@ impl<T: 'static> Signal<T> {
     pub const fn global(constructor: fn() -> T) -> GlobalSignal<T> {
         GlobalSignal::new(constructor)
     }
+
+    /// Creates a new global Signal that can be used in a global static.
+    pub const fn global_selector(constructor: fn() -> T) -> GlobalSelector<T>
+    where
+        T: PartialEq,
+    {
+        GlobalSelector::new(constructor)
+    }
 }
 
 impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
@@ -626,6 +634,20 @@ impl<T: 'static, S: Storage<SignalData<T>>> ReadOnlySignal<T, S> {
     pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
         self.inner.with(f)
     }
+
+    /// Get the id of the signal.
+    pub fn id(&self) -> generational_box::GenerationalBoxId {
+        self.inner.id()
+    }
+}
+
+impl<T> IntoAttributeValue for ReadOnlySignal<T>
+where
+    T: Clone + IntoAttributeValue,
+{
+    fn into_value(self) -> dioxus_core::AttributeValue {
+        self.with(|f| f.clone().into_value())
+    }
 }
 
 impl<T: Clone + 'static, S: Storage<SignalData<T>>> ReadOnlySignal<T, S> {