mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-09-20 06:11:57 +00:00
add test cases, refactor deref
This commit is contained in:
parent
d34538f4da
commit
974680796f
7 changed files with 137 additions and 54 deletions
55
examples/backgrounded_futures.rs
Normal file
55
examples/backgrounded_futures.rs
Normal 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}
|
||||
}
|
||||
}
|
|
@ -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
36
examples/stale_memo.rs
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 }
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue