Merge branch 'master' into debug-signals

This commit is contained in:
Evan Almloff committed 2023-10-31 13:55:20 -05:00
commit 8a62e4a987
149 files changed
+2440 -6196

No files matched your search

+89 -10
View File
@@ -1,5 +1,6 @@
use std::{
cell::RefCell,
mem::MaybeUninit,
ops::{Deref, DerefMut},
rc::Rc,
sync::Arc,
@@ -11,7 +12,7 @@ use dioxus_core::{
};
use generational_box::{GenerationalRef, GenerationalRefMut};
use crate::{CopyValue, Effect};
use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
///
@@ -45,6 +46,7 @@ use crate::{CopyValue, Effect};
/// }
/// ```
#[track_caller]
#[must_use]
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
#[cfg(debug_assertions)]
let caller = std::panic::Location::caller();
@@ -91,6 +93,7 @@ pub(crate) struct SignalData<T> {
pub(crate) subscribers: Rc<RefCell<Vec<ScopeId>>>,
pub(crate) effect_subscribers: Rc<RefCell<Vec<Effect>>>,
pub(crate) update_any: Arc<dyn Fn(ScopeId)>,
pub(crate) effect_stack: EffectStack,
pub(crate) value: T,
}
@@ -154,6 +157,7 @@ impl<T: 'static> Signal<T> {
effect_subscribers: Default::default(),
update_any: schedule_update_any().expect("in a virtual dom"),
value,
effect_stack: get_effect_stack(),
}),
}
}
@@ -170,6 +174,7 @@ impl<T: 'static> Signal<T> {
effect_subscribers: Default::default(),
update_any: schedule_update_any().expect("in a virtual dom"),
value,
effect_stack: get_effect_stack(),
},
#[cfg(debug_assertions)]
caller,
@@ -186,6 +191,7 @@ impl<T: 'static> Signal<T> {
effect_subscribers: Default::default(),
update_any: schedule_update_any().expect("in a virtual dom"),
value,
effect_stack: get_effect_stack(),
},
owner,
),
@@ -202,7 +208,7 @@ impl<T: 'static> Signal<T> {
#[track_caller]
pub fn read(&self) -> GenerationalRef<T> {
let inner = self.inner.read();
if let Some(effect) = Effect::current() {
if let Some(effect) = inner.effect_stack.current() {
let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
if !effect_subscribers.contains(&effect) {
effect_subscribers.push(effect);
@@ -230,7 +236,7 @@ impl<T: 'static> Signal<T> {
/// Get a mutable reference to the signal's value.
/// If the signal has been dropped, this will panic.
#[track_caller]
pub fn write(&self) -> Write<'_, T> {
pub fn write(&self) -> Write<T> {
let inner = self.inner.write();
let borrow = GenerationalRefMut::map(inner, |v| &mut v.value);
Write {
@@ -299,12 +305,52 @@ impl<T: Clone + 'static> Signal<T> {
}
}
impl Signal<bool> {
/// Invert the boolean value of the signal. This will trigger an update on all subscribers.
pub fn toggle(&self) {
self.set(!self.value());
}
}
impl<T: 'static> PartialEq for Signal<T> {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner
}
}
impl<T> Deref for Signal<T> {
type Target = dyn Fn() -> GenerationalRef<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() });
// 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
}
}
struct SignalSubscriberDrop<T: 'static> {
signal: Signal<T>,
}
@@ -316,14 +362,14 @@ impl<T: 'static> Drop for SignalSubscriberDrop<T> {
}
/// A mutable reference to a signal's value.
pub struct Write<'a, T: 'static, I: 'static = T> {
write: GenerationalRefMut<'a, T>,
pub struct Write<T: 'static, I: 'static = T> {
write: GenerationalRefMut<T>,
signal: SignalSubscriberDrop<I>,
}
impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
impl<T: 'static, I: 'static> Write<T, I> {
/// Map the mutable reference to the signal's value to a new type.
pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> {
pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<O, I> {
let Self { write, signal } = myself;
Write {
write: GenerationalRefMut::map(write, f),
@@ -335,14 +381,14 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
pub fn filter_map<O>(
myself: Self,
f: impl FnOnce(&mut T) -> Option<&mut O>,
) -> Option<Write<'a, O, I>> {
) -> Option<Write<O, I>> {
let Self { write, signal } = myself;
let write = GenerationalRefMut::filter_map(write, f);
write.map(|write| Write { write, signal })
}
}
impl<'a, T: 'static> Deref for Write<'a, T> {
impl<T: 'static, I: 'static> Deref for Write<T, I> {
type Target = T;
fn deref(&self) -> &Self::Target {
@@ -350,7 +396,7 @@ impl<'a, T: 'static> Deref for Write<'a, T> {
}
}
impl<T> DerefMut for Write<'_, T> {
impl<T, I> DerefMut for Write<T, I> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.write
}
@@ -397,3 +443,36 @@ impl<T: 'static> PartialEq for ReadOnlySignal<T> {
self.inner == other.inner
}
}
impl<T> Deref for ReadOnlySignal<T> {
type Target = dyn Fn() -> GenerationalRef<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() });
// 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
}
}