浏览代码

add test cases, refactor deref

Jonathan Kelley 1 年之前
父节点
当前提交
974680796f

+ 55 - 0
examples/backgrounded_futures.rs

@@ -0,0 +1,55 @@
+use dioxus::prelude::*;
+
+fn main() {
+    launch_desktop(app);
+}
+
+fn app() -> Element {
+    let mut show_child = use_signal(|| true);
+    let mut count = use_signal(|| 0);
+
+    let child = use_memo(move || {
+        rsx! {
+            Child {
+                count
+            }
+        }
+    });
+
+    rsx! {
+        button { onclick: move |_| show_child.toggle(), "Toggle child" }
+        button { onclick: move |_| count += 1, "Increment count" }
+        if show_child() {
+            {child.cloned()}
+        }
+    }
+}
+
+#[component]
+fn Child(count: Signal<i32>) -> Element {
+    let mut early_return = use_signal(|| false);
+
+    let early = rsx! {
+        button { onclick: move |_| early_return.toggle(), "Toggle {early_return} early return" }
+    };
+
+    if early_return() {
+        return early;
+    }
+
+    use_future(move || async move {
+        loop {
+            tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+            println!("Child")
+        }
+    });
+
+    use_effect(move || {
+        println!("Child count: {}", count());
+    });
+
+    rsx! {
+        "hellO!"
+        {early}
+    }
+}

+ 2 - 0
examples/memo_chain.rs

@@ -37,6 +37,8 @@ fn Child(
 
 
     println!("rendering child: {}", depth());
     println!("rendering child: {}", depth());
 
 
+    // These memos don't get re-computed when early returns happen
+    // In dioxus futures spawned with use_future won't progress if they don't get hit during rendering
     let state = use_memo(move || state() + 1);
     let state = use_memo(move || state() + 1);
     let item = use_memo(move || items()[dbg!(depth()) - 1]);
     let item = use_memo(move || items()[dbg!(depth()) - 1]);
     let depth = use_memo(move || depth() - 1);
     let depth = use_memo(move || depth() - 1);

+ 36 - 0
examples/stale_memo.rs

@@ -0,0 +1,36 @@
+use dioxus::prelude::*;
+
+fn main() {
+    launch_desktop(app);
+}
+
+fn app() -> Element {
+    let mut state = use_signal(|| 0);
+    let mut depth = use_signal(|| 1 as usize);
+
+    if depth() == 5 {
+        return rsx! {
+            div { "Max depth reached" }
+            button { onclick: move |_| depth -= 1, "Remove depth" }
+        };
+    }
+
+    let mut items = use_memo(move || (0..depth()).map(|f| f as _).collect::<Vec<isize>>());
+
+    rsx! {
+        button { onclick: move |_| state += 1, "Increment" }
+        button { onclick: move |_| depth += 1, "Add depth" }
+        button {
+            onclick: move |_| async move {
+                depth += 1;
+                tokio::time::sleep(std::time::Duration::from_millis(100)).await;
+                dbg!(items.read());
+                // if depth() is 5, this will be the old since the memo hasn't been re-computed
+                // use_memos are only re-computed when the signals they capture change
+                // *and* they are used in the current render
+                // If the use_memo isn't used, it can't be re-computed!
+            },
+            "Add depth with sleep"
+        }
+    }
+}

+ 7 - 1
packages/hooks/src/use_future.rs

@@ -1,6 +1,6 @@
 #![allow(missing_docs)]
 #![allow(missing_docs)]
 use dioxus_core::{
 use dioxus_core::{
-    prelude::{spawn, use_hook},
+    prelude::{spawn, use_drop, use_hook},
     ScopeState, Task,
     ScopeState, Task,
 };
 };
 use dioxus_signals::*;
 use dioxus_signals::*;
@@ -46,6 +46,12 @@ where
         Some(task)
         Some(task)
     });
     });
 
 
+    use_drop(move || {
+        if let Some(task) = task.take() {
+            task.stop();
+        }
+    });
+
     UseFuture { task, state }
     UseFuture { task, state }
 }
 }
 
 

+ 35 - 1
packages/signals/src/read.rs

@@ -1,4 +1,4 @@
-use std::ops::Deref;
+use std::{mem::MaybeUninit, ops::Deref};
 
 
 /// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadOnlySignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Readable`] type.
 /// A trait for states that can be read from like [`crate::Signal`], [`crate::GlobalSignal`], or [`crate::ReadOnlySignal`]. You may choose to accept this trait as a parameter instead of the concrete type to allow for more flexibility in your API. For example, instead of creating two functions, one that accepts a [`crate::Signal`] and one that accepts a [`crate::GlobalSignal`], you can create one function that accepts a [`Readable`] type.
 pub trait Readable<T: 'static = ()> {
 pub trait Readable<T: 'static = ()> {
@@ -49,6 +49,40 @@ pub trait Readable<T: 'static = ()> {
     {
     {
         Self::map_ref(self.read(), |v| v.index(index))
         Self::map_ref(self.read(), |v| v.index(index))
     }
     }
+
+    #[doc(hidden)]
+    fn deref_impl<'a>(&self) -> &'a dyn Fn() -> T
+    where
+        Self: Sized + 'a,
+        T: Clone,
+    {
+        // 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 &_
+    }
 }
 }
 
 
 /// An extension trait for Readable<Vec<T>> that provides some convenience methods.
 /// An extension trait for Readable<Vec<T>> that provides some convenience methods.

+ 1 - 26
packages/signals/src/read_only_signal.rs

@@ -110,31 +110,6 @@ impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for ReadOnlySignal<T,
     type Target = dyn Fn() -> T;
     type Target = dyn Fn() -> T;
 
 
     fn deref(&self) -> &Self::Target {
     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
+        Readable::deref_impl(self)
     }
     }
 }
 }

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

@@ -501,32 +501,7 @@ impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for Signal<T, S> {
     type Target = dyn Fn() -> T;
     type Target = dyn Fn() -> T;
 
 
     fn deref(&self) -> &Self::Target {
     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
+        Readable::deref_impl(self)
     }
     }
 }
 }