add test cases, refactor deref

This commit is contained in:
Jonathan Kelley 2024-01-26 23:05:40 -08:00
parent d34538f4da
commit 974680796f
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
7 changed files with 137 additions and 54 deletions

View file

@ -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}
}
}

View file

@ -37,6 +37,8 @@ fn Child(
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 item = use_memo(move || items()[dbg!(depth()) - 1]);
let depth = use_memo(move || depth() - 1);

36
examples/stale_memo.rs Normal file
View file

@ -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"
}
}
}

View file

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

View file

@ -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.
pub trait Readable<T: 'static = ()> {
@ -49,6 +49,40 @@ pub trait Readable<T: 'static = ()> {
{
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.

View file

@ -110,31 +110,6 @@ impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for ReadOnlySignal<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
Readable::deref_impl(self)
}
}

View file

@ -501,32 +501,7 @@ impl<T: Clone, S: Storage<SignalData<T>> + 'static> Deref for Signal<T, S> {
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
Readable::deref_impl(self)
}
}