add test cases, refactor deref
This commit is contained in:
7 files changed
+137
-54
No files matched your search
@@ -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());
|
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);
|
||||||
|
|||||||
@@ -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)]
|
#![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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
Readable::deref_impl(self)
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
Readable::deref_impl(self)
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user