From 3578f03f4f27e0784fbaf639cd7212eb49269693 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 11 Oct 2023 12:43:22 -0500 Subject: [PATCH 001/377] add the ability to map signals --- packages/signals/README.md | 4 +- packages/signals/src/lib.rs | 2 + packages/signals/src/map.rs | 91 +++++++++++++++++++++++++++++++++++ packages/signals/tests/map.rs | 32 ++++++++++++ 4 files changed, 127 insertions(+), 2 deletions(-) create mode 100644 packages/signals/src/map.rs create mode 100644 packages/signals/tests/map.rs diff --git a/packages/signals/README.md b/packages/signals/README.md index d1e772445..9a6f9e17c 100644 --- a/packages/signals/README.md +++ b/packages/signals/README.md @@ -106,7 +106,7 @@ use dioxus_signals::*; #[component] fn App(cx: Scope) -> Element { let signal = use_signal(cx, || 0); - let doubled = use_selector(cx, || signal * 2); + let doubled = use_selector(cx, move || signal * 2); render! { button { @@ -114,7 +114,7 @@ fn App(cx: Scope) -> Element { "Increase" } Child { - signal: signal + signal: doubled } } } diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 1f8bc3297..72cba6a2d 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -12,3 +12,5 @@ pub(crate) mod signal; pub use signal::*; mod dependency; pub use dependency::*; +mod map; +pub use map::*; diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs new file mode 100644 index 000000000..e83dabe7b --- /dev/null +++ b/packages/signals/src/map.rs @@ -0,0 +1,91 @@ +use crate::Signal; +use dioxus_core::ScopeId; +use std::cell::Ref; +use std::fmt::Debug; +use std::fmt::Display; + +/// A signal that can only be read from. +pub struct SignalMap { + inner: Signal, + mapping: fn(&T) -> &U, +} + +impl SignalMap { + /// Create a new read-only signal. + pub fn new(signal: Signal, mapping: fn(&T) -> &U) -> Self { + Self { + inner: signal, + mapping, + } + } + + /// Get the scope that the signal was created in. + pub fn origin_scope(&self) -> ScopeId { + self.inner.origin_scope() + } + + /// Get the current value of the signal. This will subscribe the current scope to the signal. + pub fn read(&self) -> Ref { + Ref::map(self.inner.read(), |v| (self.mapping)(v)) + } + + /// Run a closure with a reference to the signal's value. + pub fn with(&self, f: impl FnOnce(&U) -> O) -> O { + self.inner.with(|v| f((self.mapping)(v))) + } +} + +impl SignalMap { + /// Get the current value of the signal. This will subscribe the current scope to the signal. + pub fn value(&self) -> U { + self.read().clone() + } +} + +impl PartialEq for SignalMap { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl std::clone::Clone for SignalMap { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for SignalMap {} + +impl Display for SignalMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with(|v| Display::fmt(v, f)) + } +} + +impl Debug for SignalMap { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.with(|v| Debug::fmt(v, f)) + } +} + +impl SignalMap> { + /// Read a value from the inner vector. + pub fn get(&self, index: usize) -> Option> { + Ref::filter_map(self.read(), |v| v.get(index)).ok() + } +} + +impl SignalMap> { + /// Unwraps the inner value and clones it. + pub fn unwrap(&self) -> U + where + T: Clone, + { + self.with(|v| v.clone()).unwrap() + } + + /// Attemps to read the inner value of the Option. + pub fn as_ref(&self) -> Option> { + Ref::filter_map(self.read(), |v| v.as_ref()).ok() + } +} diff --git a/packages/signals/tests/map.rs b/packages/signals/tests/map.rs new file mode 100644 index 000000000..449e5e599 --- /dev/null +++ b/packages/signals/tests/map.rs @@ -0,0 +1,32 @@ +#![allow(unused, non_upper_case_globals, non_snake_case)] + +use dioxus::prelude::*; +use dioxus_core::ElementId; +use dioxus_signals::*; + +#[test] +fn create_signals_global() { + let mut dom = VirtualDom::new(|cx| { + render! { + for _ in 0..10 { + Child {} + } + } + }); + + fn Child(cx: Scope) -> Element { + let signal = create_without_cx(); + let mapped = SignalMap::new(signal, |v| v.as_bytes()); + + render! { + "{signal:?}" + "{mapped:?}" + } + } + + let _edits = dom.rebuild().santize(); + + fn create_without_cx() -> Signal { + Signal::new("hello world".to_string()) + } +} From 7ea868bd0835fc0572a0c34ae1a76bba900733db Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 11 Oct 2023 12:46:53 -0500 Subject: [PATCH 002/377] add a map method to signal --- packages/signals/src/signal.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index c1ae076d9..567534ec5 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,3 +1,4 @@ +use crate::SignalMap; use std::{ cell::{Ref, RefCell, RefMut}, ops::{Deref, DerefMut}, @@ -252,6 +253,11 @@ impl Signal { let mut write = self.write(); f(&mut *write) } + + /// Map the signal to a new type. + pub fn map(self, f: fn(&T) -> &O) -> SignalMap { + SignalMap::new(self, f) + } } impl Signal { From 365868f636ed48c59bd76b9a88fbf8d80fa7f46f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 17 Oct 2023 13:45:25 -0500 Subject: [PATCH 003/377] remove extra generic from SignalMap --- packages/signals/src/map.rs | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index e83dabe7b..9e1ec7ce5 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -4,18 +4,16 @@ use std::cell::Ref; use std::fmt::Debug; use std::fmt::Display; -/// A signal that can only be read from. -pub struct SignalMap { - inner: Signal, - mapping: fn(&T) -> &U, +/// A read only signal that has been mapped to a new type. +pub struct SignalMap { + mapping: Box Ref<'static, U>>, } impl SignalMap { - /// Create a new read-only signal. + /// Create a new mapped signal. pub fn new(signal: Signal, mapping: fn(&T) -> &U) -> Self { Self { - inner: signal, - mapping, + mapping: Box::new(move || Ref::map(signal.read(), |v| (mapping)(v))), } } @@ -26,56 +24,56 @@ impl SignalMap { /// Get the current value of the signal. This will subscribe the current scope to the signal. pub fn read(&self) -> Ref { - Ref::map(self.inner.read(), |v| (self.mapping)(v)) + (self.mapping)() } /// Run a closure with a reference to the signal's value. pub fn with(&self, f: impl FnOnce(&U) -> O) -> O { - self.inner.with(|v| f((self.mapping)(v))) + f(&*self.read()) } } -impl SignalMap { +impl SignalMap { /// Get the current value of the signal. This will subscribe the current scope to the signal. pub fn value(&self) -> U { self.read().clone() } } -impl PartialEq for SignalMap { +impl PartialEq for SignalMap { fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } -impl std::clone::Clone for SignalMap { +impl std::clone::Clone for SignalMap { fn clone(&self) -> Self { *self } } -impl Copy for SignalMap {} +impl Copy for SignalMap {} -impl Display for SignalMap { +impl Display for SignalMap { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Display::fmt(v, f)) } } -impl Debug for SignalMap { +impl Debug for SignalMap { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Debug::fmt(v, f)) } } -impl SignalMap> { +impl SignalMap> { /// Read a value from the inner vector. - pub fn get(&self, index: usize) -> Option> { + pub fn get(&self, index: usize) -> Option> { Ref::filter_map(self.read(), |v| v.get(index)).ok() } } -impl SignalMap> { +impl SignalMap> { /// Unwraps the inner value and clones it. pub fn unwrap(&self) -> U where @@ -85,7 +83,7 @@ impl SignalMap> { } /// Attemps to read the inner value of the Option. - pub fn as_ref(&self) -> Option> { + pub fn as_ref(&self) -> Option> { Ref::filter_map(self.read(), |v| v.as_ref()).ok() } } From bfacf94db483ade846c8aacc1d6791946ec296c3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 17 Oct 2023 13:50:21 -0500 Subject: [PATCH 004/377] finish removing generic from SignalMap --- packages/signals/src/map.rs | 23 +++++++++++++---------- packages/signals/src/signal.rs | 6 +++--- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index 9e1ec7ce5..1442309e1 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -1,3 +1,4 @@ +use crate::CopyValue; use crate::Signal; use dioxus_core::ScopeId; use std::cell::Ref; @@ -5,26 +6,28 @@ use std::fmt::Debug; use std::fmt::Display; /// A read only signal that has been mapped to a new type. -pub struct SignalMap { - mapping: Box Ref<'static, U>>, +pub struct SignalMap { + origin_scope: ScopeId, + mapping: CopyValue Ref<'static, U>>>, } -impl SignalMap { +impl SignalMap { /// Create a new mapped signal. - pub fn new(signal: Signal, mapping: fn(&T) -> &U) -> Self { + pub fn new(signal: Signal, mapping: fn(&T) -> &U) -> Self { Self { - mapping: Box::new(move || Ref::map(signal.read(), |v| (mapping)(v))), + origin_scope: signal.origin_scope(), + mapping: CopyValue::new(Box::new(move || Ref::map(signal.read(), |v| (mapping)(v)))), } } /// Get the scope that the signal was created in. pub fn origin_scope(&self) -> ScopeId { - self.inner.origin_scope() + self.origin_scope } /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn read(&self) -> Ref { - (self.mapping)() + pub fn read(&self) -> Ref<'static, U> { + (self.mapping.read())() } /// Run a closure with a reference to the signal's value. @@ -42,7 +45,7 @@ impl SignalMap { impl PartialEq for SignalMap { fn eq(&self, other: &Self) -> bool { - self.inner == other.inner + self.mapping == other.mapping } } @@ -77,7 +80,7 @@ impl SignalMap> { /// Unwraps the inner value and clones it. pub fn unwrap(&self) -> U where - T: Clone, + U: Clone, { self.with(|v| v.clone()).unwrap() } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 921aa356e..ad025c839 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -171,7 +171,7 @@ impl Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. - pub fn read(&self) -> Ref { + pub fn read(&self) -> Ref<'static, T> { let inner = self.inner.read(); if let Some(effect) = Effect::current() { let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); @@ -200,7 +200,7 @@ impl Signal { /// Get a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. - pub fn write(&self) -> Write<'_, T> { + pub fn write(&self) -> Write<'static, T> { let inner = self.inner.write(); let borrow = RefMut::map(inner, |v| &mut v.value); Write { @@ -257,7 +257,7 @@ impl Signal { } /// Map the signal to a new type. - pub fn map(self, f: fn(&T) -> &O) -> SignalMap { + pub fn map(self, f: fn(&T) -> &O) -> SignalMap { SignalMap::new(self, f) } } From 1126c1d3297e5c06f9d5b56f1492267a4ce0bec1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 26 Oct 2023 12:47:53 -0500 Subject: [PATCH 005/377] make signal sync --- packages/generational-box/Cargo.toml | 6 +++ packages/generational-box/benches/lock.rs | 24 +++++++++ packages/generational-box/src/lib.rs | 65 +++++++++++++---------- packages/signals/Cargo.toml | 2 + packages/signals/src/impls.rs | 34 ++++++------ packages/signals/src/rt.rs | 15 +++--- packages/signals/src/signal.rs | 35 ++++++------ 7 files changed, 113 insertions(+), 68 deletions(-) create mode 100644 packages/generational-box/benches/lock.rs diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 6c5a5a841..5567e9ccc 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -8,10 +8,16 @@ edition = "2018" [dependencies] bumpalo = { version = "3.6" } +parking_lot = "0.12.1" [dev-dependencies] rand = "0.8.5" +criterion = "0.3" [features] default = ["check_generation"] check_generation = [] + +[[bench]] +name = "lock" +harness = false diff --git a/packages/generational-box/benches/lock.rs b/packages/generational-box/benches/lock.rs new file mode 100644 index 000000000..fa3954fbb --- /dev/null +++ b/packages/generational-box/benches/lock.rs @@ -0,0 +1,24 @@ +#![allow(unused)] +use generational_box::{GenerationalBox, Owner, Store}; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn create(owner: &Owner) -> GenerationalBox { + owner.insert(0) +} + +fn set_read(signal: GenerationalBox) -> u32 { + signal.set(1); + *signal.read() +} + +fn bench_fib(c: &mut Criterion) { + let store = Store::default(); + let owner = store.owner(); + c.bench_function("create", |b| b.iter(|| create(black_box(&owner)))); + let signal = create(&owner); + c.bench_function("set_read", |b| b.iter(|| set_read(black_box(signal)))); +} + +criterion_group!(benches, bench_fib); +criterion_main!(benches); diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 4be383b4f..ac97937fd 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -1,11 +1,15 @@ #![doc = include_str!("../README.md")] #![warn(missing_docs)] +use parking_lot::{ + MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, +}; use std::{ - cell::{Cell, Ref, RefCell, RefMut}, + cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc, + sync::{atomic::AtomicU32, Arc}, }; use bumpalo::Bump; @@ -29,12 +33,12 @@ fn reused() { let first_ptr; { let owner = store.owner(); - first_ptr = owner.insert(1).raw.data.as_ptr(); + first_ptr = owner.insert(1).raw.data.data_ptr(); drop(owner); } { let owner = store.owner(); - let second_ptr = owner.insert(1234).raw.data.as_ptr(); + let second_ptr = owner.insert(1234).raw.data.data_ptr(); assert_eq!(first_ptr, second_ptr); drop(owner); } @@ -161,7 +165,7 @@ impl Debug for GenerationalBox { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!( "{:?}@{:?}", - self.raw.data.as_ptr(), + self.raw.data.data_ptr(), self.generation ))?; #[cfg(not(any(debug_assertions, feature = "check_generation")))] @@ -175,7 +179,10 @@ impl GenerationalBox { fn validate(&self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.generation.get() == self.generation + self.raw + .generation + .load(std::sync::atomic::Ordering::Relaxed) + == self.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { @@ -184,10 +191,10 @@ impl GenerationalBox { } /// Try to read the value. Returns None if the value is no longer valid. - pub fn try_read(&self) -> Option> { + pub fn try_read(&self) -> Option> { self.validate() .then(|| { - Ref::filter_map(self.raw.data.borrow(), |any| { + RwLockReadGuard::try_map(self.raw.data.read(), |any| { any.as_ref()?.downcast_ref::() }) .ok() @@ -196,15 +203,15 @@ impl GenerationalBox { } /// Read the value. Panics if the value is no longer valid. - pub fn read(&self) -> Ref<'static, T> { + pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { self.try_read().unwrap() } /// Try to write the value. Returns None if the value is no longer valid. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.validate() .then(|| { - RefMut::filter_map(self.raw.data.borrow_mut(), |any| { + RwLockWriteGuard::try_map(self.raw.data.write(), |any| { any.as_mut()?.downcast_mut::() }) .ok() @@ -213,14 +220,14 @@ impl GenerationalBox { } /// Write the value. Panics if the value is no longer valid. - pub fn write(&self) -> RefMut<'static, T> { + pub fn write(&self) -> MappedRwLockWriteGuard<'static, T> { self.try_write().unwrap() } /// Set the value. Panics if the value is no longer valid. pub fn set(&self, value: T) { self.validate().then(|| { - *self.raw.data.borrow_mut() = Some(Box::new(value)); + *self.raw.data.write() = Some(Box::new(value)); }); } @@ -228,7 +235,8 @@ impl GenerationalBox { pub fn ptr_eq(&self, other: &Self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation + self.raw.data.data_ptr() == other.raw.data.data_ptr() + && self.generation == other.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { @@ -247,25 +255,26 @@ impl Clone for GenerationalBox { #[derive(Clone, Copy)] struct MemoryLocation { - data: &'static RefCell>>, + data: &'static RwLock>>, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: &'static Cell, + generation: &'static AtomicU32, } impl MemoryLocation { #[allow(unused)] fn drop(&self) { - let old = self.data.borrow_mut().take(); + let old = self.data.write().take(); #[cfg(any(debug_assertions, feature = "check_generation"))] if old.is_some() { drop(old); - let new_generation = self.generation.get() + 1; - self.generation.set(new_generation); + let new_generation = self.generation.load(std::sync::atomic::Ordering::Relaxed) + 1; + self.generation + .store(new_generation, std::sync::atomic::Ordering::Relaxed); } } fn replace(&mut self, value: T) -> GenerationalBox { - let mut inner_mut = self.data.borrow_mut(); + let mut inner_mut = self.data.write(); let raw = Box::new(value); let old = inner_mut.replace(raw); @@ -273,7 +282,7 @@ impl MemoryLocation { GenerationalBox { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.generation.get(), + generation: self.generation.load(std::sync::atomic::Ordering::Relaxed), _marker: PhantomData, } } @@ -282,14 +291,12 @@ impl MemoryLocation { /// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread. #[derive(Clone)] pub struct Store { - bump: &'static Bump, - recycled: Rc>>, + recycled: Arc>>, } impl Default for Store { fn default() -> Self { Self { - bump: Box::leak(Box::new(Bump::new())), recycled: Default::default(), } } @@ -298,18 +305,18 @@ impl Default for Store { impl Store { fn recycle(&self, location: MemoryLocation) { location.drop(); - self.recycled.borrow_mut().push(location); + self.recycled.lock().push(location); } fn claim(&self) -> MemoryLocation { - if let Some(location) = self.recycled.borrow_mut().pop() { + if let Some(location) = self.recycled.lock().pop() { location } else { - let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None)); + let data: &'static RwLock<_> = Box::leak(Box::new(RwLock::new(None))); MemoryLocation { data, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.bump.alloc(Cell::new(0)), + generation: Box::leak(Box::new(Default::default())), } } } @@ -344,7 +351,9 @@ impl Owner { GenerationalBox { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: location.generation.get(), + generation: location + .generation + .load(std::sync::atomic::Ordering::Relaxed), _marker: PhantomData, } } diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 56e19bbd8..6d22ee0c8 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -12,6 +12,8 @@ generational-box = { workspace = true } tracing = { workspace = true } simple_logger = "4.2.0" serde = { version = "1", features = ["derive"], optional = true } +parking_lot = "0.12.1" +once_cell = "1.18.0" [dev-dependencies] dioxus = { workspace = true } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 6427c2e2b..9d6ea3019 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,7 +1,6 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; - -use std::cell::{Ref, RefMut}; +use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use std::{ fmt::{Debug, Display}, @@ -38,8 +37,8 @@ macro_rules! read_impls { impl $ty> { /// Read a value from the inner vector. - pub fn get(&self, index: usize) -> Option> { - Ref::filter_map(self.read(), |v| v.get(index)).ok() + pub fn get(&self, index: usize) -> Option> { + MappedRwLockReadGuard::try_map(self.read(), |v| v.get(index)).ok() } } @@ -53,8 +52,8 @@ macro_rules! read_impls { } /// Attemps to read the inner value of the Option. - pub fn as_ref(&self) -> Option> { - Ref::filter_map(self.read(), |v| v.as_ref()).ok() + pub fn as_ref(&self) -> Option> { + MappedRwLockReadGuard::try_map(self.read(), |v| v.as_ref()).ok() } } }; @@ -182,19 +181,22 @@ macro_rules! write_impls { } /// Gets the value out of the Option, or inserts the given value if the Option is empty. - pub fn get_or_insert(&self, default: T) -> Ref<'_, T> { + pub fn get_or_insert(&self, default: T) -> MappedRwLockReadGuard<'_, T> { self.get_or_insert_with(|| default) } /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. - pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> { + pub fn get_or_insert_with( + &self, + default: impl FnOnce() -> T, + ) -> MappedRwLockReadGuard<'_, T> { let borrow = self.read(); if borrow.is_none() { drop(borrow); self.with_mut(|v| *v = Some(default())); - Ref::map(self.read(), |v| v.as_ref().unwrap()) + MappedRwLockReadGuard::map(self.read(), |v| v.as_ref().unwrap()) } else { - Ref::map(borrow, |v| v.as_ref().unwrap()) + MappedRwLockReadGuard::map(borrow, |v| v.as_ref().unwrap()) } } } @@ -238,15 +240,15 @@ impl IntoIterator for CopyValue> { impl CopyValue> { /// Write to an element in the inner vector. - pub fn get_mut(&self, index: usize) -> Option> { - RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok() + pub fn get_mut(&self, index: usize) -> Option> { + MappedRwLockWriteGuard::try_map(self.write(), |v| v.get_mut(index)).ok() } } impl CopyValue> { /// Deref the inner value mutably. - pub fn as_mut(&self) -> Option> { - RefMut::filter_map(self.write(), |v| v.as_mut()).ok() + pub fn as_mut(&self) -> Option> { + MappedRwLockWriteGuard::try_map(self.write(), |v| v.as_mut()).ok() } } @@ -281,14 +283,14 @@ impl IntoIterator for Signal> { impl Signal> { /// Returns a reference to an element or `None` if out of bounds. - pub fn get_mut(&self, index: usize) -> Option>> { + pub fn get_mut(&self, index: usize) -> Option>> { Write::filter_map(self.write(), |v| v.get_mut(index)) } } impl Signal> { /// Returns a reference to an element or `None` if out of bounds. - pub fn as_mut(&self) -> Option>> { + pub fn as_mut(&self) -> Option>> { Write::filter_map(self.write(), |v| v.as_mut()) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 0f89a3fe6..c1fb2994d 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,5 +1,3 @@ -use std::cell::{Ref, RefMut}; - use std::mem::MaybeUninit; use std::ops::Deref; use std::rc::Rc; @@ -8,9 +6,12 @@ use dioxus_core::prelude::*; use dioxus_core::ScopeId; use generational_box::{GenerationalBox, Owner, Store}; +use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use crate::Effect; +static STORE: once_cell::sync::Lazy = once_cell::sync::Lazy::new(Store::default); + fn current_store() -> Store { match consume_context() { Some(rt) => rt, @@ -117,22 +118,22 @@ impl CopyValue { } /// Try to read the value. If the value has been dropped, this will return None. - pub fn try_read(&self) -> Option> { + pub fn try_read(&self) -> Option> { self.value.try_read() } /// Read the value. If the value has been dropped, this will panic. - pub fn read(&self) -> Ref<'static, T> { + pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { self.value.read() } /// Try to write the value. If the value has been dropped, this will return None. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.value.try_write() } /// Write the value. If the value has been dropped, this will panic. - pub fn write(&self) -> RefMut<'static, T> { + pub fn write(&self) -> MappedRwLockWriteGuard<'static, T> { self.value.write() } @@ -168,7 +169,7 @@ impl PartialEq for CopyValue { } impl Deref for CopyValue { - type Target = dyn Fn() -> Ref<'static, T>; + type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 9213307ff..43aa0c904 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,5 +1,5 @@ use std::{ - cell::{Ref, RefCell, RefMut}, + cell::RefCell, mem::MaybeUninit, ops::{Deref, DerefMut}, rc::Rc, @@ -10,6 +10,7 @@ use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; +use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use crate::{get_effect_stack, CopyValue, Effect, EffectStack}; @@ -173,7 +174,7 @@ impl Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. - pub fn read(&self) -> Ref { + pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { let inner = self.inner.read(); if let Some(effect) = inner.effect_stack.current() { let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); @@ -197,14 +198,14 @@ impl Signal { } } } - Ref::map(inner, |v| &v.value) + MappedRwLockReadGuard::map(inner, |v| &v.value) } /// Get a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. - pub fn write(&self) -> Write<'_, T> { + pub fn write(&self) -> Write { let inner = self.inner.write(); - let borrow = RefMut::map(inner, |v| &mut v.value); + let borrow = MappedRwLockWriteGuard::map(inner, |v| &mut v.value); Write { write: borrow, signal: SignalSubscriberDrop { signal: *self }, @@ -281,7 +282,7 @@ impl PartialEq for Signal { } impl Deref for Signal { - type Target = dyn Fn() -> Ref<'static, T>; + type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types @@ -324,17 +325,17 @@ impl Drop for SignalSubscriberDrop { } /// A mutable reference to a signal's value. -pub struct Write<'a, T: 'static, I: 'static = T> { - write: RefMut<'a, T>, +pub struct Write { + write: MappedRwLockWriteGuard<'static, T>, signal: SignalSubscriberDrop, } -impl<'a, T: 'static, I: 'static> Write<'a, T, I> { +impl Write { /// Map the mutable reference to the signal's value to a new type. - pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> { + pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write { let Self { write, signal } = myself; Write { - write: RefMut::map(write, f), + write: MappedRwLockWriteGuard::map(write, f), signal, } } @@ -343,14 +344,14 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> { pub fn filter_map( myself: Self, f: impl FnOnce(&mut T) -> Option<&mut O>, - ) -> Option> { + ) -> Option> { let Self { write, signal } = myself; - let write = RefMut::filter_map(write, f).ok(); + let write = MappedRwLockWriteGuard::try_map(write, f).ok(); write.map(|write| Write { write, signal }) } } -impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> { +impl Deref for Write { type Target = T; fn deref(&self) -> &Self::Target { @@ -358,7 +359,7 @@ impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> { } } -impl DerefMut for Write<'_, T, I> { +impl DerefMut for Write { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } @@ -381,7 +382,7 @@ impl ReadOnlySignal { } /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn read(&self) -> Ref { + pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { self.inner.read() } @@ -405,7 +406,7 @@ impl PartialEq for ReadOnlySignal { } impl Deref for ReadOnlySignal { - type Target = dyn Fn() -> Ref<'static, T>; + type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types From 4ac6aed4824fef652c800120dab01f0fc96f9e51 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 26 Oct 2023 16:36:41 -0500 Subject: [PATCH 006/377] try specialization --- packages/generational-box/src/lib.rs | 2 +- packages/generational-box/src/testing.rs | 44 ++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 packages/generational-box/src/testing.rs diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index ac97937fd..5d4866d05 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -12,7 +12,7 @@ use std::{ sync::{atomic::AtomicU32, Arc}, }; -use bumpalo::Bump; +mod testing; /// # Example /// diff --git a/packages/generational-box/src/testing.rs b/packages/generational-box/src/testing.rs new file mode 100644 index 000000000..0f8db53dd --- /dev/null +++ b/packages/generational-box/src/testing.rs @@ -0,0 +1,44 @@ +use core::marker::PhantomData; + +#[test] +fn testing() { + let testing1 = Testing::<_, false>::new(&() as *const _); + // std::thread::spawn(move || testing1); +} + +struct Testing { + ptr: *mut (), + phantom: PhantomData, +} + +trait CreateNew { + fn new(data: T) -> Testing { + Testing { + ptr: &mut (), + phantom: PhantomData, + } + } +} + +impl CreateNew for Testing { + fn new(data: T) -> Testing { + Testing { + ptr: &mut (), + phantom: PhantomData, + } + } +} + +impl Testing { + pub fn new(data: T) -> Self { + Testing { + ptr: &mut (), + phantom: PhantomData, + } + } +} + +impl Testing {} + +unsafe impl Send for Testing {} +unsafe impl Sync for Testing {} From 90e04edcdf4d99849aa891be721df8224a45f7e7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 30 Oct 2023 14:25:31 -0500 Subject: [PATCH 007/377] make the signal runtime global --- packages/generational-box/benches/lock.rs | 25 +- packages/generational-box/src/lib.rs | 385 +++++++++++++++------- packages/generational-box/src/testing.rs | 44 --- packages/signals/src/effect.rs | 5 +- packages/signals/src/impls.rs | 64 ++-- packages/signals/src/rt.rs | 47 ++- packages/signals/src/selector.rs | 13 +- packages/signals/src/signal.rs | 86 +++-- 8 files changed, 409 insertions(+), 260 deletions(-) delete mode 100644 packages/generational-box/src/testing.rs diff --git a/packages/generational-box/benches/lock.rs b/packages/generational-box/benches/lock.rs index fa3954fbb..14a94dd56 100644 --- a/packages/generational-box/benches/lock.rs +++ b/packages/generational-box/benches/lock.rs @@ -1,23 +1,32 @@ #![allow(unused)] -use generational_box::{GenerationalBox, Owner, Store}; +use generational_box::*; use criterion::{black_box, criterion_group, criterion_main, Criterion}; -fn create(owner: &Owner) -> GenerationalBox { +fn create>(owner: &Owner) -> GenerationalBox { owner.insert(0) } -fn set_read(signal: GenerationalBox) -> u32 { +fn set_read>(signal: GenerationalBox) -> u32 { signal.set(1); *signal.read() } fn bench_fib(c: &mut Criterion) { - let store = Store::default(); - let owner = store.owner(); - c.bench_function("create", |b| b.iter(|| create(black_box(&owner)))); - let signal = create(&owner); - c.bench_function("set_read", |b| b.iter(|| set_read(black_box(signal)))); + { + let owner = UnsyncStorage::owner(); + c.bench_function("create_unsync", |b| b.iter(|| create(black_box(&owner)))); + let signal = create(&owner); + c.bench_function("set_read_unsync", |b| { + b.iter(|| set_read(black_box(signal))) + }); + } + { + let owner = SyncStorage::owner(); + c.bench_function("create_sync", |b| b.iter(|| create(black_box(&owner)))); + let signal = create(&owner); + c.bench_function("set_read_sync", |b| b.iter(|| set_read(black_box(signal)))); + } } criterion_group!(benches, bench_fib); diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 5d4866d05..ec1986f39 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -5,21 +5,19 @@ use parking_lot::{ MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, }; use std::{ - cell::RefCell, + cell::{Ref, RefCell, RefMut}, fmt::Debug, marker::PhantomData, + ops::{Deref, DerefMut}, rc::Rc, - sync::{atomic::AtomicU32, Arc}, + sync::{atomic::AtomicU32, Arc, OnceLock}, }; -mod testing; - /// # Example /// /// ```compile_fail /// let data = String::from("hello world"); -/// let store = Store::default(); -/// let owner = store.owner(); +/// let owner = UnsyncStorage::owner(); /// let key = owner.insert(&data); /// drop(data); /// assert_eq!(*key.read(), "hello world"); @@ -29,15 +27,14 @@ fn compile_fail() {} #[test] fn reused() { - let store = Store::default(); let first_ptr; { - let owner = store.owner(); + let owner = UnsyncStorage::owner(); first_ptr = owner.insert(1).raw.data.data_ptr(); drop(owner); } { - let owner = store.owner(); + let owner = UnsyncStorage::owner(); let second_ptr = owner.insert(1234).raw.data.data_ptr(); assert_eq!(first_ptr, second_ptr); drop(owner); @@ -47,11 +44,10 @@ fn reused() { #[test] fn leaking_is_ok() { let data = String::from("hello world"); - let store = Store::default(); let key; { // create an owner - let owner = store.owner(); + let owner = UnsyncStorage::owner(); // insert data into the store key = owner.insert(data); // don't drop the owner @@ -63,11 +59,10 @@ fn leaking_is_ok() { #[test] fn drops() { let data = String::from("hello world"); - let store = Store::default(); let key; { // create an owner - let owner = store.owner(); + let owner = UnsyncStorage::owner(); // insert data into the store key = owner.insert(data); // drop the owner @@ -77,8 +72,7 @@ fn drops() { #[test] fn works() { - let store = Store::default(); - let owner = store.owner(); + let owner = UnsyncStorage::owner(); let key = owner.insert(1); assert_eq!(*key.read(), 1); @@ -86,8 +80,7 @@ fn works() { #[test] fn insert_while_reading() { - let store = Store::default(); - let owner = store.owner(); + let owner = UnsyncStorage::owner(); let key; { let data: String = "hello world".to_string(); @@ -101,8 +94,7 @@ fn insert_while_reading() { #[test] #[should_panic] fn panics() { - let store = Store::default(); - let owner = store.owner(); + let owner = UnsyncStorage::owner(); let key = owner.insert(1); drop(owner); @@ -112,7 +104,6 @@ fn panics() { #[test] fn fuzz() { fn maybe_owner_scope( - store: &Store, valid_keys: &mut Vec>, invalid_keys: &mut Vec>, path: &mut Vec, @@ -125,7 +116,7 @@ fn fuzz() { }; for i in 0..children { - let owner = store.owner(); + let owner = UnsyncStorage::owner(); let key = owner.insert(format!("hello world {path:?}")); valid_keys.push(key); path.push(i); @@ -140,27 +131,26 @@ fn fuzz() { for key in invalid_keys.iter() { assert!(!key.validate()); } - maybe_owner_scope(store, valid_keys, invalid_keys, path); + maybe_owner_scope(valid_keys, invalid_keys, path); invalid_keys.push(valid_keys.pop().unwrap()); path.pop(); } } for _ in 0..10 { - let store = Store::default(); - maybe_owner_scope(&store, &mut Vec::new(), &mut Vec::new(), &mut Vec::new()); + maybe_owner_scope(&mut Vec::new(), &mut Vec::new(), &mut Vec::new()); } } /// The core Copy state type. The generational box will be dropped when the [Owner] is dropped. -pub struct GenerationalBox { - raw: MemoryLocation, +pub struct GenerationalBox { + raw: MemoryLocation, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: u32, _marker: PhantomData, } -impl Debug for GenerationalBox { +impl Debug for GenerationalBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!( @@ -174,7 +164,7 @@ impl Debug for GenerationalBox { } } -impl GenerationalBox { +impl> GenerationalBox { #[inline(always)] fn validate(&self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] @@ -191,43 +181,29 @@ impl GenerationalBox { } /// Try to read the value. Returns None if the value is no longer valid. - pub fn try_read(&self) -> Option> { - self.validate() - .then(|| { - RwLockReadGuard::try_map(self.raw.data.read(), |any| { - any.as_ref()?.downcast_ref::() - }) - .ok() - }) - .flatten() + pub fn try_read(&self) -> Option { + self.validate().then(|| self.raw.data.try_read()).flatten() } /// Read the value. Panics if the value is no longer valid. - pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { + pub fn read(&self) -> S::Ref { self.try_read().unwrap() } /// Try to write the value. Returns None if the value is no longer valid. - pub fn try_write(&self) -> Option> { - self.validate() - .then(|| { - RwLockWriteGuard::try_map(self.raw.data.write(), |any| { - any.as_mut()?.downcast_mut::() - }) - .ok() - }) - .flatten() + pub fn try_write(&self) -> Option where { + self.validate().then(|| self.raw.data.try_write()).flatten() } /// Write the value. Panics if the value is no longer valid. - pub fn write(&self) -> MappedRwLockWriteGuard<'static, T> { + pub fn write(&self) -> S::Mut { self.try_write().unwrap() } /// Set the value. Panics if the value is no longer valid. pub fn set(&self, value: T) { self.validate().then(|| { - *self.raw.data.write() = Some(Box::new(value)); + self.raw.data.set(value); }); } @@ -245,40 +221,252 @@ impl GenerationalBox { } } -impl Copy for GenerationalBox {} +impl Copy for GenerationalBox {} -impl Clone for GenerationalBox { +impl Clone for GenerationalBox { fn clone(&self) -> Self { *self } } #[derive(Clone, Copy)] -struct MemoryLocation { - data: &'static RwLock>>, +pub struct UnsyncStorage(&'static RefCell>>); + +impl Default for UnsyncStorage { + fn default() -> Self { + Self(Box::leak(Box::new(RefCell::new(None)))) + } +} + +#[derive(Clone, Copy)] +pub struct SyncStorage(&'static RwLock>>); + +impl Default for SyncStorage { + fn default() -> Self { + Self(Box::leak(Box::new(RwLock::new(None)))) + } +} + +pub trait Mappable: Deref { + type Mapped: Deref; + + fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped; +} + +impl Mappable for Ref<'static, T> { + type Mapped = Ref<'static, U>; + + fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped { + Ref::map(_self, f) + } +} + +impl Mappable for MappedRwLockReadGuard<'static, T> { + type Mapped = MappedRwLockReadGuard<'static, U>; + + fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped { + MappedRwLockReadGuard::map(_self, f) + } +} + +pub trait MappableMut: DerefMut { + type Mapped: DerefMut; + + fn map(_self: Self, f: fn(&mut T) -> &mut U) -> Self::Mapped; +} + +impl MappableMut for RefMut<'static, T> { + type Mapped = RefMut<'static, U>; + + fn map(_self: Self, f: fn(&mut T) -> &mut U) -> Self::Mapped { + RefMut::map(_self, f) + } +} + +impl MappableMut for MappedRwLockWriteGuard<'static, T> { + type Mapped = MappedRwLockWriteGuard<'static, U>; + + fn map(_self: Self, f: fn(&mut T) -> &mut U) -> Self::Mapped { + MappedRwLockWriteGuard::map(_self, f) + } +} + +pub trait Storage: Copy + AnyStorage { + type Ref: Deref; + type Mut: DerefMut; + + fn try_read(&self) -> Option; + fn read(&self) -> Self::Ref { + self.try_read() + .expect("generational box has been invalidated or the type has changed") + } + fn try_write(&self) -> Option; + fn write(&self) -> Self::Mut { + self.try_write() + .expect("generational box has been invalidated or the type has changed") + } + + fn set(&self, value: Data); +} + +pub trait AnyStorage: Default { + fn data_ptr(&self) -> *const (); + + fn take(&self) -> bool; + + fn recycle(location: &MemoryLocation); + // { + // location.drop(); + // self.recycled.lock().push(location); + // } + + fn claim() -> MemoryLocation; + // where + // S: Default, + // { + // if let Some(location) = self.recycled.lock().pop() { + // location + // } else { + // MemoryLocation { + // data: Default::default(), + // #[cfg(any(debug_assertions, feature = "check_generation"))] + // generation: Box::leak(Box::new(Default::default())), + // } + // } + // } + + /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates. + fn owner() -> Owner { + Owner { + owned: Default::default(), + phantom: PhantomData, + } + } +} + +impl Storage for UnsyncStorage { + type Ref = Ref<'static, T>; + type Mut = RefMut<'static, T>; + + fn try_read(&self) -> Option { + Ref::filter_map(self.0.borrow(), |any| any.as_ref()?.downcast_ref()).ok() + } + + fn try_write(&self) -> Option { + RefMut::filter_map(self.0.borrow_mut(), |any| any.as_mut()?.downcast_mut()).ok() + } + + fn set(&self, value: T) { + *self.0.borrow_mut() = Some(Box::new(value)); + } +} + +thread_local! { + static UNSYNC_RUNTIME: RefCell>> = RefCell::new(Vec::new()); +} + +impl AnyStorage for UnsyncStorage { + fn data_ptr(&self) -> *const () { + self.0.as_ptr() as *const () + } + + fn take(&self) -> bool { + self.0.borrow_mut().take().is_some() + } + + fn claim() -> MemoryLocation { + UNSYNC_RUNTIME.with(|runtime| { + if let Some(location) = runtime.borrow_mut().pop() { + location + } else { + MemoryLocation { + data: UnsyncStorage(Box::leak(Box::new(RefCell::new(None)))), + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: Box::leak(Box::new(Default::default())), + } + } + }) + } + + fn recycle(location: &MemoryLocation) { + location.drop(); + UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(*location)); + } +} + +impl Storage for SyncStorage { + type Ref = MappedRwLockReadGuard<'static, T>; + type Mut = MappedRwLockWriteGuard<'static, T>; + + fn try_read(&self) -> Option { + RwLockReadGuard::try_map(self.0.read(), |any| any.as_ref()?.downcast_ref()).ok() + } + + fn try_write(&self) -> Option { + RwLockWriteGuard::try_map(self.0.write(), |any| any.as_mut()?.downcast_mut()).ok() + } + + fn set(&self, value: T) { + *self.0.write() = Some(Box::new(value)); + } +} + +static SYNC_RUNTIME: OnceLock>>>> = OnceLock::new(); + +fn sync_runtime() -> &'static Arc>>> { + SYNC_RUNTIME.get_or_init(|| Arc::new(Mutex::new(Vec::new()))) +} + +impl AnyStorage for SyncStorage { + fn data_ptr(&self) -> *const () { + self.0.data_ptr() as *const () + } + + fn take(&self) -> bool { + self.0.write().take().is_some() + } + + fn claim() -> MemoryLocation { + MemoryLocation { + data: SyncStorage(Box::leak(Box::new(RwLock::new(None)))), + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: Box::leak(Box::new(Default::default())), + } + } + + fn recycle(location: &MemoryLocation) { + location.drop(); + sync_runtime().lock().push(*location); + } +} + +#[derive(Clone, Copy)] +struct MemoryLocation { + data: S, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: &'static AtomicU32, } -impl MemoryLocation { +impl MemoryLocation { #[allow(unused)] - fn drop(&self) { - let old = self.data.write().take(); + fn drop(&self) + where + S: AnyStorage, + { + let old = self.data.take(); #[cfg(any(debug_assertions, feature = "check_generation"))] - if old.is_some() { - drop(old); + if old { let new_generation = self.generation.load(std::sync::atomic::Ordering::Relaxed) + 1; self.generation .store(new_generation, std::sync::atomic::Ordering::Relaxed); } } - fn replace(&mut self, value: T) -> GenerationalBox { - let mut inner_mut = self.data.write(); - - let raw = Box::new(value); - let old = inner_mut.replace(raw); - assert!(old.is_none()); + fn replace(&mut self, value: T) -> GenerationalBox + where + S: Storage + Copy, + { + self.data.set(value); GenerationalBox { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] @@ -288,66 +476,27 @@ impl MemoryLocation { } } -/// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread. -#[derive(Clone)] -pub struct Store { - recycled: Arc>>, -} - -impl Default for Store { - fn default() -> Self { - Self { - recycled: Default::default(), - } - } -} - -impl Store { - fn recycle(&self, location: MemoryLocation) { - location.drop(); - self.recycled.lock().push(location); - } - - fn claim(&self) -> MemoryLocation { - if let Some(location) = self.recycled.lock().pop() { - location - } else { - let data: &'static RwLock<_> = Box::leak(Box::new(RwLock::new(None))); - MemoryLocation { - data, - #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: Box::leak(Box::new(Default::default())), - } - } - } - - /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates. - pub fn owner(&self) -> Owner { - Owner { - store: self.clone(), - owned: Default::default(), - } - } -} - /// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped. -pub struct Owner { - store: Store, - owned: Rc>>, +pub struct Owner { + owned: Arc>>>, + phantom: PhantomData, } -impl Owner { +impl Owner { /// Insert a value into the store. The value will be dropped when the owner is dropped. - pub fn insert(&self, value: T) -> GenerationalBox { - let mut location = self.store.claim(); + pub fn insert(&self, value: T) -> GenerationalBox + where + S: Storage, + { + let mut location = S::claim(); let key = location.replace(value); - self.owned.borrow_mut().push(location); + self.owned.lock().push(location); key } /// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value. - pub fn invalid(&self) -> GenerationalBox { - let location = self.store.claim(); + pub fn invalid(&self) -> GenerationalBox { + let location = S::claim(); GenerationalBox { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] @@ -359,10 +508,10 @@ impl Owner { } } -impl Drop for Owner { +impl Drop for Owner { fn drop(&mut self) { - for location in self.owned.borrow().iter() { - self.store.recycle(*location) + for location in self.owned.lock().iter() { + S::recycle(location) } } } diff --git a/packages/generational-box/src/testing.rs b/packages/generational-box/src/testing.rs deleted file mode 100644 index 0f8db53dd..000000000 --- a/packages/generational-box/src/testing.rs +++ /dev/null @@ -1,44 +0,0 @@ -use core::marker::PhantomData; - -#[test] -fn testing() { - let testing1 = Testing::<_, false>::new(&() as *const _); - // std::thread::spawn(move || testing1); -} - -struct Testing { - ptr: *mut (), - phantom: PhantomData, -} - -trait CreateNew { - fn new(data: T) -> Testing { - Testing { - ptr: &mut (), - phantom: PhantomData, - } - } -} - -impl CreateNew for Testing { - fn new(data: T) -> Testing { - Testing { - ptr: &mut (), - phantom: PhantomData, - } - } -} - -impl Testing { - pub fn new(data: T) -> Self { - Testing { - ptr: &mut (), - phantom: PhantomData, - } - } -} - -impl Testing {} - -unsafe impl Send for Testing {} -unsafe impl Sync for Testing {} diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 307ee98ab..2ab29780d 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -2,13 +2,14 @@ use core::{self, fmt::Debug}; use std::fmt::{self, Formatter}; // use dioxus_core::prelude::*; +use generational_box::SyncStorage; use crate::use_signal; use crate::{dependency::Dependency, CopyValue}; #[derive(Copy, Clone, PartialEq)] pub(crate) struct EffectStack { - pub(crate) effects: CopyValue>, + pub(crate) effects: CopyValue, SyncStorage>, } impl Default for EffectStack { @@ -68,7 +69,7 @@ pub fn use_effect_with_dependencies( #[derive(Copy, Clone, PartialEq)] pub struct Effect { pub(crate) source: ScopeId, - pub(crate) callback: CopyValue>, + pub(crate) callback: CopyValue, SyncStorage>, pub(crate) effect_stack: EffectStack, } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 9d6ea3019..e97ebc452 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,5 +1,7 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; +use crate::SignalData; +use generational_box::{MappableMut, Storage}; use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use std::{ @@ -203,19 +205,19 @@ macro_rules! write_impls { }; } -read_impls!(CopyValue); -write_impls!(CopyValue); -read_impls!(Signal); -write_impls!(Signal); -read_impls!(ReadOnlySignal); +// read_impls!(CopyValue); +// write_impls!(CopyValue); +// read_impls!(Signal); +// write_impls!(Signal); +// read_impls!(ReadOnlySignal); /// An iterator over the values of a `CopyValue>`. -pub struct CopyValueIterator { +pub struct CopyValueIterator> { index: usize, - value: CopyValue>, + value: CopyValue, S>, } -impl Iterator for CopyValueIterator { +impl> Iterator for CopyValueIterator { type Item = T; fn next(&mut self) -> Option { @@ -225,8 +227,8 @@ impl Iterator for CopyValueIterator { } } -impl IntoIterator for CopyValue> { - type IntoIter = CopyValueIterator; +impl> IntoIterator for CopyValue, S> { + type IntoIter = CopyValueIterator; type Item = T; @@ -238,27 +240,33 @@ impl IntoIterator for CopyValue> { } } -impl CopyValue> { +impl>> CopyValue, S> +where + >>::Mut: MappableMut, T>, +{ /// Write to an element in the inner vector. - pub fn get_mut(&self, index: usize) -> Option> { + pub fn get_mut(&self, index: usize) -> Option<, T>>::Mapped> { MappedRwLockWriteGuard::try_map(self.write(), |v| v.get_mut(index)).ok() } } -impl CopyValue> { +impl>> CopyValue, S> +where + >>::Mut: MappableMut, T>, +{ /// Deref the inner value mutably. - pub fn as_mut(&self) -> Option> { - MappedRwLockWriteGuard::try_map(self.write(), |v| v.as_mut()).ok() + pub fn as_mut(&self) -> Option<, T>>::Mapped> { + S::Mut::map(self.try_write(), |v| v.as_mut()).ok() } } /// An iterator over items in a `Signal>`. -pub struct SignalIterator { +pub struct SignalIterator> { index: usize, - value: Signal>, + value: Signal, S>, } -impl Iterator for SignalIterator { +impl> Iterator for SignalIterator { type Item = T; fn next(&mut self) -> Option { @@ -268,8 +276,8 @@ impl Iterator for SignalIterator { } } -impl IntoIterator for Signal> { - type IntoIter = SignalIterator; +impl> IntoIterator for Signal, S> { + type IntoIter = SignalIterator; type Item = T; @@ -281,16 +289,22 @@ impl IntoIterator for Signal> { } } -impl Signal> { +impl>>> Signal, S> +where + S::Mut: MappableMut, T>, +{ /// Returns a reference to an element or `None` if out of bounds. - pub fn get_mut(&self, index: usize) -> Option>> { - Write::filter_map(self.write(), |v| v.get_mut(index)) + pub fn get_mut( + &self, + index: usize, + ) -> Option, T>>::Mapped, S, Vec>> { + S::Mut::map(self.write(), |v| v.get_mut(index)) } } -impl Signal> { +impl>>> Signal, S> { /// Returns a reference to an element or `None` if out of bounds. - pub fn as_mut(&self) -> Option>> { + pub fn as_mut(&self) -> Option>, S::Mut, S, Option>> { Write::filter_map(self.write(), |v| v.as_mut()) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index c1fb2994d..3499de21a 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -5,23 +5,12 @@ use std::rc::Rc; use dioxus_core::prelude::*; use dioxus_core::ScopeId; -use generational_box::{GenerationalBox, Owner, Store}; -use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; +use generational_box::AnyStorage; +use generational_box::Storage; +use generational_box::{GenerationalBox, Owner}; use crate::Effect; -static STORE: once_cell::sync::Lazy = once_cell::sync::Lazy::new(Store::default); - -fn current_store() -> Store { - match consume_context() { - Some(rt) => rt, - None => { - let store = Store::default(); - provide_root_context(store).expect("in a virtual dom") - } - } -} - fn current_owner() -> Rc { match Effect::current() { // If we are inside of an effect, we should use the owner of the effect as the owner of the value. @@ -50,14 +39,22 @@ fn owner_in_scope(scope: ScopeId) -> Rc { } } +#[derive(Debug)] /// CopyValue is a wrapper around a value to make the value mutable and Copy. /// /// It is internally backed by [`generational_box::GenerationalBox`]. -pub struct CopyValue { - pub(crate) value: GenerationalBox, +pub struct CopyValue { + pub(crate) value: GenerationalBox, origin_scope: ScopeId, } +impl Copy for CopyValue {} +impl Clone for CopyValue { + fn clone(&self) -> Self { + *self + } +} + #[cfg(feature = "serde")] impl serde::Serialize for CopyValue where @@ -80,7 +77,7 @@ where } } -impl CopyValue { +impl> CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. @@ -118,22 +115,22 @@ impl CopyValue { } /// Try to read the value. If the value has been dropped, this will return None. - pub fn try_read(&self) -> Option> { + pub fn try_read(&self) -> Option { self.value.try_read() } /// Read the value. If the value has been dropped, this will panic. - pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { + pub fn read(&self) -> S::Ref { self.value.read() } /// Try to write the value. If the value has been dropped, this will return None. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option { self.value.try_write() } /// Write the value. If the value has been dropped, this will panic. - pub fn write(&self) -> MappedRwLockWriteGuard<'static, T> { + pub fn write(&self) -> S::Mut { self.value.write() } @@ -155,21 +152,21 @@ impl CopyValue { } } -impl CopyValue { +impl> CopyValue { /// Get the value. If the value has been dropped, this will panic. pub fn value(&self) -> T { self.read().clone() } } -impl PartialEq for CopyValue { +impl> PartialEq for CopyValue { fn eq(&self, other: &Self) -> bool { self.value.ptr_eq(&other.value) } } -impl Deref for CopyValue { - type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; +impl> Deref for CopyValue { + type Target = dyn Fn() -> S::Ref; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index 556c8437c..de3f206eb 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -1,4 +1,5 @@ use dioxus_core::prelude::*; +use generational_box::Storage; use crate::dependency::Dependency; use crate::use_signal; @@ -22,10 +23,10 @@ use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySig /// } /// ``` #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] -pub fn use_selector( +pub fn use_selector>>( cx: &ScopeState, f: impl FnMut() -> R + 'static, -) -> ReadOnlySignal { +) -> ReadOnlySignal { *cx.use_hook(|| selector(f)) } @@ -46,11 +47,11 @@ pub fn use_selector( /// } /// ``` #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] -pub fn use_selector_with_dependencies( +pub fn use_selector_with_dependencies>>( cx: &ScopeState, dependencies: D, mut f: impl FnMut(D::Out) -> R + 'static, -) -> ReadOnlySignal +) -> ReadOnlySignal where D::Out: 'static, { @@ -71,7 +72,9 @@ where /// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. -pub fn selector(mut f: impl FnMut() -> R + 'static) -> ReadOnlySignal { +pub fn selector>>( + mut f: impl FnMut() -> R + 'static, +) -> ReadOnlySignal { let state = Signal:: { inner: CopyValue::invalid(), }; diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 43aa0c904..a12b43648 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -10,6 +10,7 @@ use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; +use generational_box::{AnyStorage, Mappable, MappableMut, Storage}; use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use crate::{get_effect_stack, CopyValue, Effect, EffectStack}; @@ -46,7 +47,10 @@ use crate::{get_effect_stack, CopyValue, Effect, EffectStack}; /// } /// ``` #[must_use] -pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { +pub fn use_signal>>( + cx: &ScopeState, + f: impl FnOnce() -> T, +) -> Signal { *cx.use_hook(|| Signal::new(f())) } @@ -119,8 +123,8 @@ pub(crate) struct SignalData { /// } /// } /// ``` -pub struct Signal { - pub(crate) inner: CopyValue>, +pub struct Signal { + pub(crate) inner: CopyValue, S>, } #[cfg(feature = "serde")] @@ -137,7 +141,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign } } -impl Signal { +impl>> Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. pub fn new(value: T) -> Self { Self { @@ -174,7 +178,10 @@ impl Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. - pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { + pub fn read(&self) -> <>>::Ref as Mappable, T>>::Mapped + where + >>::Ref: Mappable, T>, + { let inner = self.inner.read(); if let Some(effect) = inner.effect_stack.current() { let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); @@ -198,14 +205,19 @@ impl Signal { } } } - MappedRwLockReadGuard::map(inner, |v| &v.value) + S::Ref::map(inner, |v| &v.value) } /// Get a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. - pub fn write(&self) -> Write { + pub fn write( + &self, + ) -> Write>>::Mut as MappableMut, T>>::Mapped, S> + where + >>::Mut: MappableMut, T>, + { let inner = self.inner.write(); - let borrow = MappedRwLockWriteGuard::map(inner, |v| &mut v.value); + let borrow = S::Mut::map(inner, |v| &mut v.value); Write { write: borrow, signal: SignalSubscriberDrop { signal: *self }, @@ -260,7 +272,7 @@ impl Signal { } } -impl Signal { +impl>> Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. pub fn value(&self) -> T { @@ -268,21 +280,21 @@ impl Signal { } } -impl Signal { +impl> Signal { /// Invert the boolean value of the signal. This will trigger an update on all subscribers. pub fn toggle(&self) { self.set(!self.value()); } } -impl PartialEq for Signal { +impl>> PartialEq for Signal { fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } -impl Deref for Signal { - type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; +impl>> Deref for Signal { + type Target = dyn Fn() -> S::Ref; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types @@ -314,28 +326,31 @@ impl Deref for Signal { } } -struct SignalSubscriberDrop { - signal: Signal, +struct SignalSubscriberDrop>> { + signal: Signal, } -impl Drop for SignalSubscriberDrop { +impl>> Drop for SignalSubscriberDrop { fn drop(&mut self) { self.signal.update_subscribers(); } } /// A mutable reference to a signal's value. -pub struct Write { - write: MappedRwLockWriteGuard<'static, T>, - signal: SignalSubscriberDrop, +pub struct Write, S: Storage>, I: 'static = T> { + write: B, + signal: SignalSubscriberDrop, } -impl Write { +impl, S: Storage>, I: 'static> Write { /// Map the mutable reference to the signal's value to a new type. - pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write { + pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write + where + S: MappableMut, + { let Self { write, signal } = myself; Write { - write: MappedRwLockWriteGuard::map(write, f), + write: S::map(write, f), signal, } } @@ -344,14 +359,19 @@ impl Write { pub fn filter_map( myself: Self, f: impl FnOnce(&mut T) -> Option<&mut O>, - ) -> Option> { + ) -> Option> + where + S: MappableMut, + { let Self { write, signal } = myself; let write = MappedRwLockWriteGuard::try_map(write, f).ok(); write.map(|write| Write { write, signal }) } } -impl Deref for Write { +impl, S: Storage>, I: 'static> Deref + for Write +{ type Target = T; fn deref(&self) -> &Self::Target { @@ -359,20 +379,20 @@ impl Deref for Write { } } -impl DerefMut for Write { +impl, S: Storage>, I> DerefMut for Write { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } } /// A signal that can only be read from. -pub struct ReadOnlySignal { - inner: Signal, +pub struct ReadOnlySignal>> { + inner: Signal, } -impl ReadOnlySignal { +impl>> ReadOnlySignal { /// Create a new read-only signal. - pub fn new(signal: Signal) -> Self { + pub fn new(signal: Signal) -> Self { Self { inner: signal } } @@ -392,21 +412,21 @@ impl ReadOnlySignal { } } -impl ReadOnlySignal { +impl>> ReadOnlySignal { /// Get the current value of the signal. This will subscribe the current scope to the signal. pub fn value(&self) -> T { self.read().clone() } } -impl PartialEq for ReadOnlySignal { +impl>> PartialEq for ReadOnlySignal { fn eq(&self, other: &Self) -> bool { self.inner == other.inner } } -impl Deref for ReadOnlySignal { - type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; +impl>> Deref for ReadOnlySignal { + type Target = dyn Fn() -> S::Ref; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types From d815c7cc8980c1b8592c131314af3b63867347c0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 31 Oct 2023 18:30:06 -0500 Subject: [PATCH 008/377] create a reactive selector called comparer --- packages/signals/Cargo.toml | 3 +- packages/signals/src/comparer.rs | 109 +++++++++++++++++++++++++++++++ packages/signals/src/lib.rs | 2 + packages/signals/src/signal.rs | 6 ++ 4 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 packages/signals/src/comparer.rs diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 56e19bbd8..f78487c8c 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -12,6 +12,7 @@ generational-box = { workspace = true } tracing = { workspace = true } simple_logger = "4.2.0" serde = { version = "1", features = ["derive"], optional = true } +rustc-hash.workspace = true [dev-dependencies] dioxus = { workspace = true } @@ -20,4 +21,4 @@ tokio = { version = "1", features = ["full"] } [features] default = [] -serialize = ["serde"] \ No newline at end of file +serialize = ["serde"] diff --git a/packages/signals/src/comparer.rs b/packages/signals/src/comparer.rs new file mode 100644 index 000000000..3fea11cf5 --- /dev/null +++ b/packages/signals/src/comparer.rs @@ -0,0 +1,109 @@ +use std::hash::Hash; + +use dioxus_core::prelude::*; + +use crate::{CopyValue, Effect, ReadOnlySignal, Signal}; +use rustc_hash::FxHashMap; + +/// An object that can efficiently compare a value to a set of values. +#[derive(Debug)] +pub struct Comparer { + subscribers: CopyValue>>, +} + +impl Comparer { + /// Returns a signal which is true when the value is equal to the value passed to this function. + pub fn equal(&self, value: R) -> ReadOnlySignal { + let subscribers = self.subscribers.read(); + + match subscribers.get(&value) { + Some(&signal) => signal.into(), + None => { + drop(subscribers); + let mut subscribers = self.subscribers.write(); + let signal = Signal::new(false); + subscribers.insert(value, signal.clone()); + signal.into() + } + } + } +} + +impl Clone for Comparer { + fn clone(&self) -> Self { + Self { + subscribers: self.subscribers.clone(), + } + } +} + +impl Copy for Comparer {} + +/// Creates a new Comparer which efficiently tracks when a value changes to check if it is equal to a set of values. +/// +/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_selector`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to. +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_signals::*; +/// +/// fn App(cx: Scope) -> Element { +/// let mut count = use_signal(cx, || 0); +/// let comparer = use_comparer(cx, move || count.value()); +/// +/// render! { +/// for i in 0..10 { +/// // Child will only re-render when i == count +/// Child { active: comparer.equal(i) } +/// } +/// button { +/// // This will only rerender the child with the old and new value of i == count +/// // Because we are using a comparer, this will be O(1) instead of the O(n) performance of a selector +/// onclick: move |_| count += 1, +/// "Increment" +/// } +/// } +/// } +/// +/// #[component] +/// fn Child(cx: Scope, active: ReadOnlySignal) -> Element { +/// if *active() { +/// render! { "Active" } +/// } else { +/// render! { "Inactive" } +/// } +/// } +/// ``` +#[must_use] +pub fn use_comparer(cx: &ScopeState, f: impl FnMut() -> R + 'static) -> Comparer { + *cx.use_hook(move || comparer(f)) +} + +/// Creates a new Comparer which efficiently tracks when a value changes to check if it is equal to a set of values. +/// +/// Generally, you shouldn't need to use this hook. Instead you can use [`crate::use_selector`]. If you have many values that you need to compare to a single value, this hook will change updates from O(n) to O(1) where n is the number of values you are comparing to. +pub fn comparer(mut f: impl FnMut() -> R + 'static) -> Comparer { + let subscribers: CopyValue>> = CopyValue::new(FxHashMap::default()); + let previous = CopyValue::new(None); + + Effect::new(move || { + let subscribers = subscribers.read(); + let mut previous = previous.write(); + + if let Some(previous) = previous.take() { + if let Some(value) = subscribers.get(&previous) { + value.set(false); + } + } + + let current = f(); + + if let Some(value) = subscribers.get(¤t) { + value.set(true); + } + + *previous = Some(current); + }); + + Comparer { subscribers } +} diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index dce9af0a8..5ee243afd 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -14,3 +14,5 @@ pub(crate) mod signal; pub use signal::*; mod dependency; pub use dependency::*; +mod comparer; +pub use comparer::*; diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 9213307ff..fb7c7fa6a 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -369,6 +369,12 @@ pub struct ReadOnlySignal { inner: Signal, } +impl From> for ReadOnlySignal { + fn from(inner: Signal) -> Self { + Self { inner } + } +} + impl ReadOnlySignal { /// Create a new read-only signal. pub fn new(signal: Signal) -> Self { From ff263808389c4b43ded90ebf166add08f9813852 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 1 Nov 2023 08:21:04 -0500 Subject: [PATCH 009/377] fix clippy --- packages/core/src/diff.rs | 2 +- packages/fermi/src/lib.rs | 2 -- packages/rsx/src/lib.rs | 2 +- packages/signals/src/comparer.rs | 6 ++---- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 74017db0f..2de862218 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -560,7 +560,7 @@ impl<'b> VirtualDom { // If none of the old keys are reused by the new children, then we remove all the remaining old children and // create the new children afresh. if shared_keys.is_empty() { - if old.get(0).is_some() { + if old.first().is_some() { self.remove_nodes(&old[1..]); self.replace(&old[0], new); } else { diff --git a/packages/fermi/src/lib.rs b/packages/fermi/src/lib.rs index 52bf4cd09..dc5ccf010 100644 --- a/packages/fermi/src/lib.rs +++ b/packages/fermi/src/lib.rs @@ -22,8 +22,6 @@ mod atoms { pub use atom::*; pub use atomfamily::*; pub use atomref::*; - pub use selector::*; - pub use selectorfamily::*; } pub mod hooks { diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index bdf32b272..5db6baee9 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -193,7 +193,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { fn to_tokens(&self, out_tokens: &mut TokenStream2) { let mut context = DynamicContext::default(); - let key = match self.roots.get(0) { + let key = match self.roots.first() { Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(), Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(), _ => None, diff --git a/packages/signals/src/comparer.rs b/packages/signals/src/comparer.rs index 3fea11cf5..e55654ced 100644 --- a/packages/signals/src/comparer.rs +++ b/packages/signals/src/comparer.rs @@ -22,7 +22,7 @@ impl Comparer { drop(subscribers); let mut subscribers = self.subscribers.write(); let signal = Signal::new(false); - subscribers.insert(value, signal.clone()); + subscribers.insert(value, signal); signal.into() } } @@ -31,9 +31,7 @@ impl Comparer { impl Clone for Comparer { fn clone(&self) -> Self { - Self { - subscribers: self.subscribers.clone(), - } + *self } } From 0a8de1d40f3154133e3bc3cf779dfe9eee7a4cfa Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 2 Nov 2023 19:35:53 -0500 Subject: [PATCH 010/377] clean up ref mapping with GATs --- packages/generational-box/src/lib.rs | 81 ++++++++++----- packages/signals/src/effect.rs | 9 +- packages/signals/src/impls.rs | 100 ++++++++++-------- packages/signals/src/rt.rs | 57 +++++------ packages/signals/src/selector.rs | 3 +- packages/signals/src/signal.rs | 145 +++++++++++++++++++-------- 6 files changed, 245 insertions(+), 150 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index ec1986f39..87941c99e 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -247,53 +247,82 @@ impl Default for SyncStorage { } } -pub trait Mappable: Deref { - type Mapped: Deref; +pub trait Mappable: Deref { + type Mapped: Mappable + Deref; - fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped; + fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped; + + fn try_map(_self: Self, f: fn(&T) -> Option<&U>) -> Option>; } -impl Mappable for Ref<'static, T> { - type Mapped = Ref<'static, U>; +impl Mappable for Ref<'static, T> { + type Mapped = Ref<'static, U>; - fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped { + fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped { Ref::map(_self, f) } + + fn try_map(_self: Self, f: fn(&T) -> Option<&U>) -> Option> { + Ref::try_map(_self, f) + } } -impl Mappable for MappedRwLockReadGuard<'static, T> { - type Mapped = MappedRwLockReadGuard<'static, U>; +impl Mappable for MappedRwLockReadGuard<'static, T> { + type Mapped = MappedRwLockReadGuard<'static, U>; - fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped { + fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped { MappedRwLockReadGuard::map(_self, f) } -} -pub trait MappableMut: DerefMut { - type Mapped: DerefMut; - - fn map(_self: Self, f: fn(&mut T) -> &mut U) -> Self::Mapped; -} - -impl MappableMut for RefMut<'static, T> { - type Mapped = RefMut<'static, U>; - - fn map(_self: Self, f: fn(&mut T) -> &mut U) -> Self::Mapped { - RefMut::map(_self, f) + fn try_map(_self: Self, f: fn(&T) -> Option<&U>) -> Option> { + MappedRwLockReadGuard::try_map(_self, f).ok() } } -impl MappableMut for MappedRwLockWriteGuard<'static, T> { - type Mapped = MappedRwLockWriteGuard<'static, U>; +pub trait MappableMut: DerefMut { + type Mapped: MappableMut + DerefMut; - fn map(_self: Self, f: fn(&mut T) -> &mut U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped; + + fn try_map( + _self: Self, + f: impl FnOnce(&mut T) -> Option<&mut U>, + ) -> Option>; +} + +impl MappableMut for RefMut<'static, T> { + type Mapped = RefMut<'static, U>; + + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { + RefMut::map(_self, f) + } + + fn try_map( + _self: Self, + f: impl FnOnce(&mut T) -> Option<&mut U>, + ) -> Option> { + RefMut::try_map(_self, f) + } +} + +impl MappableMut for MappedRwLockWriteGuard<'static, T> { + type Mapped = MappedRwLockWriteGuard<'static, U>; + + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { MappedRwLockWriteGuard::map(_self, f) } + + fn try_map( + _self: Self, + f: impl FnOnce(&mut T) -> Option<&mut U>, + ) -> Option> { + MappedRwLockWriteGuard::try_map(_self, f).ok() + } } pub trait Storage: Copy + AnyStorage { - type Ref: Deref; - type Mut: DerefMut; + type Ref: Mappable + Deref; + type Mut: MappableMut + DerefMut; fn try_read(&self) -> Option; fn read(&self) -> Self::Ref { diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 2ab29780d..9d6231eb3 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -1,15 +1,14 @@ use core::{self, fmt::Debug}; -use std::fmt::{self, Formatter}; -// use dioxus_core::prelude::*; -use generational_box::SyncStorage; +use generational_box::UnsyncStorage; +use std::fmt::{self, Formatter}; use crate::use_signal; use crate::{dependency::Dependency, CopyValue}; #[derive(Copy, Clone, PartialEq)] pub(crate) struct EffectStack { - pub(crate) effects: CopyValue, SyncStorage>, + pub(crate) effects: CopyValue, UnsyncStorage>, } impl Default for EffectStack { @@ -69,7 +68,7 @@ pub fn use_effect_with_dependencies( #[derive(Copy, Clone, PartialEq)] pub struct Effect { pub(crate) source: ScopeId, - pub(crate) callback: CopyValue, SyncStorage>, + pub(crate) callback: CopyValue, UnsyncStorage>, pub(crate) effect_stack: EffectStack, } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index e97ebc452..a217dfaa4 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,6 +1,7 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; use crate::SignalData; +use generational_box::Mappable; use generational_box::{MappableMut, Storage}; use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; @@ -11,40 +12,40 @@ use std::{ macro_rules! read_impls { ($ty:ident) => { - impl Default for $ty { + impl> Default for $ty { fn default() -> Self { Self::new(Default::default()) } } - impl std::clone::Clone for $ty { + impl> std::clone::Clone for $ty { fn clone(&self) -> Self { *self } } - impl Copy for $ty {} + impl + Copy> Copy for $ty {} - impl Display for $ty { + impl> Display for $ty { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Display::fmt(v, f)) } } - impl Debug for $ty { + impl> Debug for $ty { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Debug::fmt(v, f)) } } - impl $ty> { + impl> $ty, S> { /// Read a value from the inner vector. pub fn get(&self, index: usize) -> Option> { MappedRwLockReadGuard::try_map(self.read(), |v| v.get(index)).ok() } } - impl $ty> { + impl> $ty, S> { /// Unwraps the inner value and clones it. pub fn unwrap(&self) -> T where @@ -63,7 +64,7 @@ macro_rules! read_impls { macro_rules! write_impls { ($ty:ident) => { - impl + Copy + 'static> std::ops::Add for $ty { + impl + Copy + 'static, S: Storage> std::ops::Add for $ty { type Output = T; fn add(self, rhs: T) -> Self::Output { @@ -71,19 +72,23 @@ macro_rules! write_impls { } } - impl + Copy + 'static> std::ops::AddAssign for $ty { + impl + Copy + 'static, S: Storage> std::ops::AddAssign + for $ty + { fn add_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v + rhs) } } - impl + Copy + 'static> std::ops::SubAssign for $ty { + impl + Copy + 'static, S: Storage> std::ops::SubAssign + for $ty + { fn sub_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v - rhs) } } - impl + Copy + 'static> std::ops::Sub for $ty { + impl + Copy + 'static, S: Storage> std::ops::Sub for $ty { type Output = T; fn sub(self, rhs: T) -> Self::Output { @@ -91,13 +96,15 @@ macro_rules! write_impls { } } - impl + Copy + 'static> std::ops::MulAssign for $ty { + impl + Copy + 'static, S: Storage> std::ops::MulAssign + for $ty + { fn mul_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v * rhs) } } - impl + Copy + 'static> std::ops::Mul for $ty { + impl + Copy + 'static, S: Storage> std::ops::Mul for $ty { type Output = T; fn mul(self, rhs: T) -> Self::Output { @@ -105,13 +112,15 @@ macro_rules! write_impls { } } - impl + Copy + 'static> std::ops::DivAssign for $ty { + impl + Copy + 'static, S: Storage> std::ops::DivAssign + for $ty + { fn div_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v / rhs) } } - impl + Copy + 'static> std::ops::Div for $ty { + impl + Copy + 'static, S: Storage> std::ops::Div for $ty { type Output = T; fn div(self, rhs: T) -> Self::Output { @@ -119,7 +128,7 @@ macro_rules! write_impls { } } - impl $ty> { + impl> $ty, S> { /// Pushes a new value to the end of the vector. pub fn push(&self, value: T) { self.with_mut(|v| v.push(value)) @@ -171,7 +180,7 @@ macro_rules! write_impls { } } - impl $ty> { + impl> $ty, S> { /// Takes the value out of the Option. pub fn take(&self) -> Option { self.with_mut(|v| v.take()) @@ -183,7 +192,7 @@ macro_rules! write_impls { } /// Gets the value out of the Option, or inserts the given value if the Option is empty. - pub fn get_or_insert(&self, default: T) -> MappedRwLockReadGuard<'_, T> { + pub fn get_or_insert(&self, default: T) -> S::Ref { self.get_or_insert_with(|| default) } @@ -191,25 +200,25 @@ macro_rules! write_impls { pub fn get_or_insert_with( &self, default: impl FnOnce() -> T, - ) -> MappedRwLockReadGuard<'_, T> { + ) -> >::Mapped { let borrow = self.read(); if borrow.is_none() { drop(borrow); self.with_mut(|v| *v = Some(default())); - MappedRwLockReadGuard::map(self.read(), |v| v.as_ref().unwrap()) + S::Ref::map(self.read(), |v| v.as_ref().unwrap()) } else { - MappedRwLockReadGuard::map(borrow, |v| v.as_ref().unwrap()) + S::Ref::map(borrow, |v| v.as_ref().unwrap()) } } } }; } -// read_impls!(CopyValue); -// write_impls!(CopyValue); -// read_impls!(Signal); -// write_impls!(Signal); -// read_impls!(ReadOnlySignal); +read_impls!(CopyValue); +write_impls!(CopyValue); +read_impls!(Signal); +write_impls!(Signal); +read_impls!(ReadOnlySignal); /// An iterator over the values of a `CopyValue>`. pub struct CopyValueIterator> { @@ -240,23 +249,19 @@ impl> IntoIterator for CopyValue, S> { } } -impl>> CopyValue, S> -where - >>::Mut: MappableMut, T>, -{ +impl>> CopyValue, S> { /// Write to an element in the inner vector. - pub fn get_mut(&self, index: usize) -> Option<, T>>::Mapped> { - MappedRwLockWriteGuard::try_map(self.write(), |v| v.get_mut(index)).ok() + pub fn get_mut(&self, index: usize) -> Option<>>::Mapped> { + S::Mut::try_map(self.write(), |v: &mut Vec| v.get_mut(index)) } } -impl>> CopyValue, S> -where - >>::Mut: MappableMut, T>, -{ +impl>> CopyValue, S> { /// Deref the inner value mutably. - pub fn as_mut(&self) -> Option<, T>>::Mapped> { - S::Mut::map(self.try_write(), |v| v.as_mut()).ok() + pub fn as_mut( + &self, + ) -> Option<<>>::Mut as MappableMut>>::Mapped> { + S::Mut::try_map(self.write(), |v: &mut Option| v.as_mut()) } } @@ -291,20 +296,31 @@ impl> IntoIterator for Signal, S> { impl>>> Signal, S> where - S::Mut: MappableMut, T>, + <>>>::Mut as MappableMut< + SignalData>, + >>::Mapped>: MappableMut>, { /// Returns a reference to an element or `None` if out of bounds. pub fn get_mut( &self, index: usize, - ) -> Option, T>>::Mapped, S, Vec>> { - S::Mut::map(self.write(), |v| v.get_mut(index)) + ) -> Option< + Write< + T, + <<>>>::Mut as MappableMut>>>::Mapped< + Vec, + > as MappableMut>>::Mapped, + S, + Vec, + >, + > { + Write::filter_map(self.write(), |v| v.get_mut(index)) } } impl>>> Signal, S> { /// Returns a reference to an element or `None` if out of bounds. - pub fn as_mut(&self) -> Option>, S::Mut, S, Option>> { + pub fn as_mut(&self) -> Option>>>::Mut as MappableMut>>>::Mapped> as MappableMut>>::Mapped, S, Option>>{ Write::filter_map(self.write(), |v| v.as_mut()) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 3499de21a..7ae6365f7 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -11,32 +11,34 @@ use generational_box::{GenerationalBox, Owner}; use crate::Effect; -fn current_owner() -> Rc { - match Effect::current() { - // If we are inside of an effect, we should use the owner of the effect as the owner of the value. - Some(effect) => { - let scope_id = effect.source; - owner_in_scope(scope_id) - } - // Otherwise either get an owner from the current scope or create a new one. - None => match has_context() { - Some(rt) => rt, - None => { - let owner = Rc::new(current_store().owner()); - provide_context(owner).expect("in a virtual dom") - } - }, - } +fn current_owner, T>() -> Rc> { + todo!() + // match Effect::current() { + // // If we are inside of an effect, we should use the owner of the effect as the owner of the value. + // Some(effect) => { + // let scope_id = effect.source; + // owner_in_scope(scope_id) + // } + // // Otherwise either get an owner from the current scope or create a new one. + // None => match has_context() { + // Some(rt) => rt, + // None => { + // let owner = Rc::new(current_store().owner()); + // provide_context(owner).expect("in a virtual dom") + // } + // }, + // } } -fn owner_in_scope(scope: ScopeId) -> Rc { - match consume_context_from_scope(scope) { - Some(rt) => rt, - None => { - let owner = Rc::new(current_store().owner()); - provide_context_to_scope(scope, owner).expect("in a virtual dom") - } - } +fn owner_in_scope, T>(scope: ScopeId) -> Rc> { + todo!() + // match consume_context_from_scope(scope) { + // Some(rt) => rt, + // None => { + // let owner = Rc::new(current_store().owner()); + // provide_context_to_scope(scope, owner).expect("in a virtual dom") + // } + // } } #[derive(Debug)] @@ -48,13 +50,6 @@ pub struct CopyValue { origin_scope: ScopeId, } -impl Copy for CopyValue {} -impl Clone for CopyValue { - fn clone(&self) -> Self { - *self - } -} - #[cfg(feature = "serde")] impl serde::Serialize for CopyValue where diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index de3f206eb..28d8ca38e 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -75,7 +75,7 @@ where pub fn selector>>( mut f: impl FnMut() -> R + 'static, ) -> ReadOnlySignal { - let state = Signal:: { + let state = Signal:: { inner: CopyValue::invalid(), }; let effect = Effect { @@ -89,7 +89,6 @@ pub fn selector>>( } state.inner.value.set(SignalData { subscribers: Default::default(), - effect_subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value: f(), effect_stack: get_effect_stack(), diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index a12b43648..501f736dd 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,5 +1,6 @@ use std::{ cell::RefCell, + marker::PhantomData, mem::MaybeUninit, ops::{Deref, DerefMut}, rc::Rc, @@ -10,8 +11,8 @@ use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; -use generational_box::{AnyStorage, Mappable, MappableMut, Storage}; -use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; +use generational_box::{AnyStorage, Mappable, MappableMut, Storage, SyncStorage, UnsyncStorage}; +use parking_lot::RwLock; use crate::{get_effect_stack, CopyValue, Effect, EffectStack}; @@ -47,11 +48,51 @@ use crate::{get_effect_stack, CopyValue, Effect, EffectStack}; /// } /// ``` #[must_use] -pub fn use_signal>>( +pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { + *cx.use_hook(|| Signal::new(f())) +} + +/// Creates a new `Send + Sync`` Signal. Signals are a Copy state management solution with automatic dependency tracking. +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_signals::*; +/// +/// fn App(cx: Scope) -> Element { +/// let mut count = use_signal(cx, || 0); +/// +/// // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated. +/// // The app component will never be rerendered in this example. +/// render! { Child { state: count } } +/// } +/// +/// #[component] +/// fn Child(cx: Scope, state: Signal) -> Element { +/// let state = *state; +/// +/// use_future!(cx, |()| async move { +/// // This signal is Send + Sync, so we can use it in an another thread +/// tokio::spawn(async move { +/// // Because the signal is a Copy type, we can use it in an async block without cloning it. +/// *state.write() += 1; +/// }).await; +/// }); +/// +/// render! { +/// button { +/// onclick: move |_| *state.write() += 1, +/// "{state}" +/// } +/// } +/// } +/// ``` +#[must_use] +pub fn use_signal_sync( cx: &ScopeState, f: impl FnOnce() -> T, -) -> Signal { - *cx.use_hook(|| Signal::new(f())) +) -> Signal { + // *cx.use_hook(|| Signal::new(f())) + todo!() } #[derive(Clone)] @@ -83,10 +124,15 @@ fn current_unsubscriber() -> Unsubscriber { } } +#[derive(Default)] +pub(crate) struct SignalSubscribers { + pub(crate) subscribers: Vec, + pub(crate) effect_subscribers: Vec, +} + pub(crate) struct SignalData { - pub(crate) subscribers: Rc>>, - pub(crate) effect_subscribers: Rc>>, - pub(crate) update_any: Arc, + pub(crate) subscribers: Arc>, + pub(crate) update_any: Arc, pub(crate) effect_stack: EffectStack, pub(crate) value: T, } @@ -123,7 +169,7 @@ pub(crate) struct SignalData { /// } /// } /// ``` -pub struct Signal { +pub struct Signal>> { pub(crate) inner: CopyValue, S>, } @@ -147,7 +193,6 @@ impl>> Signal { Self { inner: CopyValue::new(SignalData { subscribers: Default::default(), - effect_subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value, effect_stack: get_effect_stack(), @@ -161,7 +206,6 @@ impl>> Signal { inner: CopyValue::new_in_scope( SignalData { subscribers: Default::default(), - effect_subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value, effect_stack: get_effect_stack(), @@ -178,13 +222,13 @@ impl>> Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. - pub fn read(&self) -> <>>::Ref as Mappable, T>>::Mapped - where - >>::Ref: Mappable, T>, - { + pub fn read( + &self, + ) -> <>>::Ref as Mappable>>::Mapped { let inner = self.inner.read(); if let Some(effect) = inner.effect_stack.current() { - let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); + let mut subscribers = inner.subscribers.write(); + let mut effect_subscribers = &mut subscribers.effect_subscribers; if !effect_subscribers.contains(&effect) { effect_subscribers.push(effect); } @@ -196,12 +240,17 @@ impl>> Signal { self.inner.value, current_scope_id ); - let mut subscribers = inner.subscribers.borrow_mut(); + let mut subscribers = inner.subscribers.write(); + let subscribers = &mut subscribers.subscribers; if !subscribers.contains(¤t_scope_id) { subscribers.push(current_scope_id); drop(subscribers); let unsubscriber = current_unsubscriber(); - inner.subscribers.borrow_mut().push(unsubscriber.scope); + inner + .subscribers + .write() + .subscribers + .push(unsubscriber.scope); } } } @@ -212,22 +261,21 @@ impl>> Signal { /// If the signal has been dropped, this will panic. pub fn write( &self, - ) -> Write>>::Mut as MappableMut, T>>::Mapped, S> - where - >>::Mut: MappableMut, T>, + ) -> Write>>::Mut as MappableMut>>::Mapped, S> { let inner = self.inner.write(); let borrow = S::Mut::map(inner, |v| &mut v.value); Write { write: borrow, signal: SignalSubscriberDrop { signal: *self }, + phantom: std::marker::PhantomData, } } fn update_subscribers(&self) { { let inner = self.inner.read(); - for &scope_id in &*inner.subscribers.borrow() { + for &scope_id in &*inner.subscribers.read().subscribers { tracing::trace!( "Write on {:?} triggered update on {:?}", self.inner.value, @@ -239,7 +287,7 @@ impl>> Signal { let subscribers = { let self_read = self.inner.read(); - let mut effects = self_read.effect_subscribers.borrow_mut(); + let mut effects = &mut self_read.subscribers.write().effect_subscribers; std::mem::take(&mut *effects) }; for effect in subscribers { @@ -280,7 +328,7 @@ impl>> Signal { } } -impl> Signal { +impl>> Signal { /// Invert the boolean value of the signal. This will trigger an update on all subscribers. pub fn toggle(&self) { self.set(!self.value()); @@ -294,7 +342,8 @@ impl>> PartialEq for Signal { } impl>> Deref for Signal { - type Target = dyn Fn() -> S::Ref; + type Target = + dyn Fn() -> <>>::Ref as Mappable>>::Mapped; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types @@ -337,21 +386,25 @@ impl>> Drop for SignalSubscriberDrop } /// A mutable reference to a signal's value. -pub struct Write, S: Storage>, I: 'static = T> { +/// +/// T is the current type of the write +/// B is the dynamicly checked type of the write (RefMut) +/// S is the storage type of the signal +/// I is the type of the original signal +pub struct Write, S: Storage>, I: 'static = T> { write: B, signal: SignalSubscriberDrop, + phantom: std::marker::PhantomData, } -impl, S: Storage>, I: 'static> Write { +impl, S: Storage>, I: 'static> Write { /// Map the mutable reference to the signal's value to a new type. - pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write - where - S: MappableMut, - { - let Self { write, signal } = myself; + pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write, S, I> { + let Self { write, signal, .. } = myself; Write { - write: S::map(write, f), + write: B::map(write, f), signal, + phantom: std::marker::PhantomData, } } @@ -359,17 +412,18 @@ impl, S: Storage>, I: 'static> pub fn filter_map( myself: Self, f: impl FnOnce(&mut T) -> Option<&mut O>, - ) -> Option> - where - S: MappableMut, - { - let Self { write, signal } = myself; - let write = MappedRwLockWriteGuard::try_map(write, f).ok(); - write.map(|write| Write { write, signal }) + ) -> Option, S, I>> { + let Self { write, signal, .. } = myself; + let write = B::try_map(write, f); + write.map(|write| Write { + write, + signal, + phantom: PhantomData, + }) } } -impl, S: Storage>, I: 'static> Deref +impl, S: Storage>, I: 'static> Deref for Write { type Target = T; @@ -379,7 +433,7 @@ impl, S: Storage>, I: 'static> } } -impl, S: Storage>, I> DerefMut for Write { +impl, S: Storage>, I> DerefMut for Write { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } @@ -402,7 +456,9 @@ impl>> ReadOnlySignal { } /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { + pub fn read( + &self, + ) -> <>>::Ref as Mappable>>::Mapped { self.inner.read() } @@ -426,7 +482,8 @@ impl>> PartialEq for ReadOnlySignal { } impl>> Deref for ReadOnlySignal { - type Target = dyn Fn() -> S::Ref; + type Target = + dyn Fn() -> <>>::Ref as Mappable>>::Mapped; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types From ebbaacf073ac7eef14bf32eeac72f1532351b66b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 3 Nov 2023 10:27:21 -0500 Subject: [PATCH 011/377] fix read and write impls --- packages/generational-box/src/lib.rs | 23 ++- packages/signals/src/impls.rs | 250 +++++++++++++++++---------- packages/signals/src/rt.rs | 3 +- packages/signals/src/signal.rs | 9 +- 4 files changed, 182 insertions(+), 103 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 87941c99e..040fb842b 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -250,19 +250,25 @@ impl Default for SyncStorage { pub trait Mappable: Deref { type Mapped: Mappable + Deref; - fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped; + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped; - fn try_map(_self: Self, f: fn(&T) -> Option<&U>) -> Option>; + fn try_map( + _self: Self, + f: impl FnOnce(&T) -> Option<&U>, + ) -> Option>; } impl Mappable for Ref<'static, T> { type Mapped = Ref<'static, U>; - fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { Ref::map(_self, f) } - fn try_map(_self: Self, f: fn(&T) -> Option<&U>) -> Option> { + fn try_map( + _self: Self, + f: impl FnOnce(&T) -> Option<&U>, + ) -> Option> { Ref::try_map(_self, f) } } @@ -270,11 +276,14 @@ impl Mappable for Ref<'static, T> { impl Mappable for MappedRwLockReadGuard<'static, T> { type Mapped = MappedRwLockReadGuard<'static, U>; - fn map(_self: Self, f: fn(&T) -> &U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { MappedRwLockReadGuard::map(_self, f) } - fn try_map(_self: Self, f: fn(&T) -> Option<&U>) -> Option> { + fn try_map( + _self: Self, + f: impl FnOnce(&T) -> Option<&U>, + ) -> Option> { MappedRwLockReadGuard::try_map(_self, f).ok() } } @@ -320,7 +329,7 @@ impl MappableMut for MappedRwLockWriteGuard<'static, T> { } } -pub trait Storage: Copy + AnyStorage { +pub trait Storage: Copy + AnyStorage + 'static { type Ref: Mappable + Deref; type Mut: MappableMut + DerefMut; diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index a217dfaa4..bf994cf4b 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -3,7 +3,7 @@ use crate::signal::{ReadOnlySignal, Signal, Write}; use crate::SignalData; use generational_box::Mappable; use generational_box::{MappableMut, Storage}; -use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; +use std::ops::Deref; use std::{ fmt::{Debug, Display}, @@ -11,60 +11,38 @@ use std::{ }; macro_rules! read_impls { - ($ty:ident) => { - impl> Default for $ty { + ($ty:ident, $bound:path) => { + impl Default for $ty { fn default() -> Self { Self::new(Default::default()) } } - impl> std::clone::Clone for $ty { + impl std::clone::Clone for $ty { fn clone(&self) -> Self { *self } } - impl + Copy> Copy for $ty {} + impl Copy for $ty {} - impl> Display for $ty { + impl Display for $ty { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Display::fmt(v, f)) } } - impl> Debug for $ty { + impl Debug for $ty { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Debug::fmt(v, f)) } } - - impl> $ty, S> { - /// Read a value from the inner vector. - pub fn get(&self, index: usize) -> Option> { - MappedRwLockReadGuard::try_map(self.read(), |v| v.get(index)).ok() - } - } - - impl> $ty, S> { - /// Unwraps the inner value and clones it. - pub fn unwrap(&self) -> T - where - T: Clone, - { - self.with(|v| v.clone()).unwrap() - } - - /// Attemps to read the inner value of the Option. - pub fn as_ref(&self) -> Option> { - MappedRwLockReadGuard::try_map(self.read(), |v| v.as_ref()).ok() - } - } }; } macro_rules! write_impls { - ($ty:ident) => { - impl + Copy + 'static, S: Storage> std::ops::Add for $ty { + ($ty:ident, $bound:path, $vec_bound:path) => { + impl + Copy + 'static, S: $bound> std::ops::Add for $ty { type Output = T; fn add(self, rhs: T) -> Self::Output { @@ -72,23 +50,19 @@ macro_rules! write_impls { } } - impl + Copy + 'static, S: Storage> std::ops::AddAssign - for $ty - { + impl + Copy + 'static, S: $bound> std::ops::AddAssign for $ty { fn add_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v + rhs) } } - impl + Copy + 'static, S: Storage> std::ops::SubAssign - for $ty - { + impl + Copy + 'static, S: $bound> std::ops::SubAssign for $ty { fn sub_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v - rhs) } } - impl + Copy + 'static, S: Storage> std::ops::Sub for $ty { + impl + Copy + 'static, S: $bound> std::ops::Sub for $ty { type Output = T; fn sub(self, rhs: T) -> Self::Output { @@ -96,15 +70,13 @@ macro_rules! write_impls { } } - impl + Copy + 'static, S: Storage> std::ops::MulAssign - for $ty - { + impl + Copy + 'static, S: $bound> std::ops::MulAssign for $ty { fn mul_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v * rhs) } } - impl + Copy + 'static, S: Storage> std::ops::Mul for $ty { + impl + Copy + 'static, S: $bound> std::ops::Mul for $ty { type Output = T; fn mul(self, rhs: T) -> Self::Output { @@ -112,15 +84,13 @@ macro_rules! write_impls { } } - impl + Copy + 'static, S: Storage> std::ops::DivAssign - for $ty - { + impl + Copy + 'static, S: $bound> std::ops::DivAssign for $ty { fn div_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v / rhs) } } - impl + Copy + 'static, S: Storage> std::ops::Div for $ty { + impl + Copy + 'static, S: $bound> std::ops::Div for $ty { type Output = T; fn div(self, rhs: T) -> Self::Output { @@ -128,7 +98,7 @@ macro_rules! write_impls { } } - impl> $ty, S> { + impl $ty, S> { /// Pushes a new value to the end of the vector. pub fn push(&self, value: T) { self.with_mut(|v| v.push(value)) @@ -179,54 +149,154 @@ macro_rules! write_impls { self.with_mut(|v| v.split_off(at)) } } - - impl> $ty, S> { - /// Takes the value out of the Option. - pub fn take(&self) -> Option { - self.with_mut(|v| v.take()) - } - - /// Replace the value in the Option. - pub fn replace(&self, value: T) -> Option { - self.with_mut(|v| v.replace(value)) - } - - /// Gets the value out of the Option, or inserts the given value if the Option is empty. - pub fn get_or_insert(&self, default: T) -> S::Ref { - self.get_or_insert_with(|| default) - } - - /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. - pub fn get_or_insert_with( - &self, - default: impl FnOnce() -> T, - ) -> >::Mapped { - let borrow = self.read(); - if borrow.is_none() { - drop(borrow); - self.with_mut(|v| *v = Some(default())); - S::Ref::map(self.read(), |v| v.as_ref().unwrap()) - } else { - S::Ref::map(borrow, |v| v.as_ref().unwrap()) - } - } - } }; } -read_impls!(CopyValue); -write_impls!(CopyValue); -read_impls!(Signal); -write_impls!(Signal); -read_impls!(ReadOnlySignal); +read_impls!(CopyValue, Storage); + +impl>> CopyValue, S> { + /// Read a value from the inner vector. + pub fn get(&self, index: usize) -> Option<>>::Mapped> { + S::Ref::try_map(self.read(), move |v| v.get(index)) + } +} + +impl>> CopyValue, S> { + /// Unwraps the inner value and clones it. + pub fn unwrap(&self) -> T + where + T: Clone, + { + self.with(|v| v.clone()).unwrap() + } + + /// Attempts to read the inner value of the Option. + pub fn as_ref(&self) -> Option<>>::Mapped> { + S::Ref::try_map(self.read(), |v| v.as_ref()) + } +} + +write_impls!(CopyValue, Storage, Storage>); + +impl>> CopyValue, S> { + /// Takes the value out of the Option. + pub fn take(&self) -> Option { + self.with_mut(|v| v.take()) + } + + /// Replace the value in the Option. + pub fn replace(&self, value: T) -> Option { + self.with_mut(|v| v.replace(value)) + } + + /// Gets the value out of the Option, or inserts the given value if the Option is empty. + pub fn get_or_insert(&self, default: T) -> >>::Mapped { + self.get_or_insert_with(|| default) + } + + /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. + pub fn get_or_insert_with( + &self, + default: impl FnOnce() -> T, + ) -> >>::Mapped { + let borrow = self.read(); + if borrow.is_none() { + drop(borrow); + self.with_mut(|v| *v = Some(default())); + S::Ref::map(self.read(), |v| v.as_ref().unwrap()) + } else { + S::Ref::map(borrow, |v| v.as_ref().unwrap()) + } + } +} + +read_impls!(Signal, Storage>); + +impl>>> Signal, S> { + /// Read a value from the inner vector. + pub fn get( + &self, + index: usize, + ) -> Option< + <<>>>::Ref as Mappable>>>::Mapped> as Mappable< + Vec, + >>::Mapped, + >{ + <>>>::Ref as Mappable>>>::Mapped::>::try_map(self.read(), move |v| v.get(index)) + } +} + +impl>>> Signal, S> { + /// Unwraps the inner value and clones it. + pub fn unwrap(&self) -> T + where + T: Clone, + { + self.with(|v| v.clone()).unwrap() + } + + /// Attempts to read the inner value of the Option. + pub fn as_ref( + &self, + ) -> Option< + <<>>>::Ref as Mappable>>>::Mapped< + Option, + > as Mappable>>::Mapped, + > { + <>>>::Ref as Mappable>>>::Mapped::< + Option, + >::try_map(self.read(), |v| v.as_ref()) + } +} + +write_impls!(Signal, Storage>, Storage>>); + +impl>>> Signal, S> { + /// Takes the value out of the Option. + pub fn take(&self) -> Option { + self.with_mut(|v| v.take()) + } + + /// Replace the value in the Option. + pub fn replace(&self, value: T) -> Option { + self.with_mut(|v| v.replace(value)) + } + + /// Gets the value out of the Option, or inserts the given value if the Option is empty. + pub fn get_or_insert(&self, default: T) -> <>>>::Mapped> as Mappable>>::Mapped{ + self.get_or_insert_with(|| default) + } + + /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. + pub fn get_or_insert_with( + &self, + default: impl FnOnce() -> T, + ) -><>>>::Mapped> as Mappable>>::Mapped{ + let borrow = self.read(); + if borrow.is_none() { + drop(borrow); + self.with_mut(|v| *v = Some(default())); + >>>::Mapped::>::map( + self.read(), + |v| v.as_ref().unwrap(), + ) + } else { + >>>::Mapped::>::map(borrow, |v| { + v.as_ref().unwrap() + }) + } + } +} + +read_impls!(ReadOnlySignal, Storage>); /// An iterator over the values of a `CopyValue>`. -pub struct CopyValueIterator> { +pub struct CopyValueIterator>> { index: usize, value: CopyValue, S>, } -impl> Iterator for CopyValueIterator { +impl>> Iterator for CopyValueIterator { type Item = T; fn next(&mut self) -> Option { @@ -236,7 +306,7 @@ impl> Iterator for CopyValueIterator { } } -impl> IntoIterator for CopyValue, S> { +impl>> IntoIterator for CopyValue, S> { type IntoIter = CopyValueIterator; type Item = T; @@ -266,12 +336,12 @@ impl>> CopyValue, S> { } /// An iterator over items in a `Signal>`. -pub struct SignalIterator> { +pub struct SignalIterator>>> { index: usize, value: Signal, S>, } -impl> Iterator for SignalIterator { +impl>>> Iterator for SignalIterator { type Item = T; fn next(&mut self) -> Option { @@ -281,7 +351,7 @@ impl> Iterator for SignalIterator { } } -impl> IntoIterator for Signal, S> { +impl>>> IntoIterator for Signal, S> { type IntoIter = SignalIterator; type Item = T; diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 7ae6365f7..a6005748a 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -41,11 +41,10 @@ fn owner_in_scope, T>(scope: ScopeId) -> Rc> { // } } -#[derive(Debug)] /// CopyValue is a wrapper around a value to make the value mutable and Copy. /// /// It is internally backed by [`generational_box::GenerationalBox`]. -pub struct CopyValue { +pub struct CopyValue> { pub(crate) value: GenerationalBox, origin_scope: ScopeId, } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 501f736dd..61186b92c 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -90,7 +90,7 @@ pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal< pub fn use_signal_sync( cx: &ScopeState, f: impl FnOnce() -> T, -) -> Signal { +) -> Signal { // *cx.use_hook(|| Signal::new(f())) todo!() } @@ -130,7 +130,8 @@ pub(crate) struct SignalSubscribers { pub(crate) effect_subscribers: Vec, } -pub(crate) struct SignalData { +/// The data stored for tracking in a signal. +pub struct SignalData { pub(crate) subscribers: Arc>, pub(crate) update_any: Arc, pub(crate) effect_stack: EffectStack, @@ -341,7 +342,7 @@ impl>> PartialEq for Signal { } } -impl>> Deref for Signal { +impl> + 'static> Deref for Signal { type Target = dyn Fn() -> <>>::Ref as Mappable>>::Mapped; @@ -481,7 +482,7 @@ impl>> PartialEq for ReadOnlySignal { } } -impl>> Deref for ReadOnlySignal { +impl> + 'static> Deref for ReadOnlySignal { type Target = dyn Fn() -> <>>::Ref as Mappable>>::Mapped; From 6eb4e7358bd746d984d4f5a4557d276a6b9baf68 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 6 Nov 2023 14:46:26 -0600 Subject: [PATCH 012/377] fix selector and signal unsync default --- packages/signals/Cargo.toml | 1 + packages/signals/src/impls.rs | 2 +- packages/signals/src/rt.rs | 21 ++++++-- packages/signals/src/selector.rs | 82 ++++++++++++++++++++++++++++---- packages/signals/src/signal.rs | 58 ++++++++++++++-------- 5 files changed, 130 insertions(+), 34 deletions(-) diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 6d22ee0c8..c081155a8 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -14,6 +14,7 @@ simple_logger = "4.2.0" serde = { version = "1", features = ["derive"], optional = true } parking_lot = "0.12.1" once_cell = "1.18.0" +ghost = "0.1.16" [dev-dependencies] dioxus = { workspace = true } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index bf994cf4b..f59b8391f 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -14,7 +14,7 @@ macro_rules! read_impls { ($ty:ident, $bound:path) => { impl Default for $ty { fn default() -> Self { - Self::new(Default::default()) + Self::new_maybe_sync(Default::default()) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index a6005748a..be4810680 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,3 +1,4 @@ +use generational_box::UnsyncStorage; use std::mem::MaybeUninit; use std::ops::Deref; use std::rc::Rc; @@ -44,7 +45,7 @@ fn owner_in_scope, T>(scope: ScopeId) -> Rc> { /// CopyValue is a wrapper around a value to make the value mutable and Copy. /// /// It is internally backed by [`generational_box::GenerationalBox`]. -pub struct CopyValue> { +pub struct CopyValue = UnsyncStorage> { pub(crate) value: GenerationalBox, origin_scope: ScopeId, } @@ -71,11 +72,25 @@ where } } -impl> CopyValue { +impl CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. pub fn new(value: T) -> Self { + Self::new_maybe_sync(value) + } + + /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. + pub fn new_in_scope(value: T, scope: ScopeId) -> Self { + Self::new_maybe_sync_in_scope(value, scope) + } +} + +impl> CopyValue { + /// Create a new CopyValue. The value will be stored in the current component. + /// + /// Once the component this value is created in is dropped, the value will be dropped. + pub fn new_maybe_sync(value: T) -> Self { let owner = current_owner(); Self { @@ -85,7 +100,7 @@ impl> CopyValue { } /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. - pub fn new_in_scope(value: T, scope: ScopeId) -> Self { + pub fn new_maybe_sync_in_scope(value: T, scope: ScopeId) -> Self { let owner = owner_in_scope(scope); Self { diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index 28d8ca38e..5de6395d5 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -5,7 +5,7 @@ use crate::dependency::Dependency; use crate::use_signal; use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySignal, Signal}; -/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes. +/// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. /// @@ -23,14 +23,41 @@ use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySig /// } /// ``` #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] -pub fn use_selector>>( +pub fn use_selector( + cx: &ScopeState, + f: impl FnMut() -> R + 'static, +) -> ReadOnlySignal { + use_maybe_sync_selector(cx, f) +} + + +/// Creates a new Selector that may be sync. The selector will be run immediately and whenever any signal it reads changes. +/// +/// Selectors can be used to efficiently compute derived data from signals. +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_signals::*; +/// +/// fn App(cx: Scope) -> Element { +/// let mut count = use_signal(cx, || 0); +/// let double = use_selector(cx, move || count * 2); +/// count += 1; +/// assert_eq!(double.value(), count * 2); +/// +/// render! { "{double}" } +/// } +/// ``` +#[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] +pub fn use_maybe_sync_selector>>( cx: &ScopeState, f: impl FnMut() -> R + 'static, ) -> ReadOnlySignal { - *cx.use_hook(|| selector(f)) + *cx.use_hook(|| maybe_sync_selector(f)) } -/// Creates a new Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes + +/// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes /// /// Selectors can be used to efficiently compute derived data from signals. /// @@ -47,7 +74,35 @@ pub fn use_selector>>( /// } /// ``` #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] -pub fn use_selector_with_dependencies>>( +pub fn use_selector_with_dependencies( + cx: &ScopeState, + dependencies: D, + mut f: impl FnMut(D::Out) -> R + 'static, +) -> ReadOnlySignal +where + D::Out: 'static, +{ + use_maybe_sync_selector_with_dependencies(cx, dependencies, f) +} + +/// Creates a new Selector that may be sync with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes +/// +/// Selectors can be used to efficiently compute derived data from signals. +/// +/// ```rust +/// use dioxus::prelude::*; +/// use dioxus_signals::*; +/// +/// fn App(cx: Scope) -> Element { +/// let mut local_state = use_state(cx, || 0); +/// let double = use_selector_with_dependencies(cx, (local_state.get(),), move |(local_state,)| local_state * 2); +/// local_state.set(1); +/// +/// render! { "{double}" } +/// } +/// ``` +#[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] +pub fn use_maybe_sync_selector_with_dependencies>>( cx: &ScopeState, dependencies: D, mut f: impl FnMut(D::Out) -> R + 'static, @@ -57,7 +112,7 @@ where { let dependencies_signal = use_signal(cx, || dependencies.out()); let selector = *cx.use_hook(|| { - selector(move || { + maybe_sync_selector(move || { let deref = &*dependencies_signal.read(); f(deref.clone()) }) @@ -69,10 +124,19 @@ where selector } -/// Creates a new Selector. The selector will be run immediately and whenever any signal it reads changes. +/// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. -pub fn selector>>( +pub fn selector( + mut f: impl FnMut() -> R + 'static, +) -> ReadOnlySignal { + maybe_sync_selector(f) +} + +/// Creates a new Selector that may be Sync + Send. The selector will be run immediately and whenever any signal it reads changes. +/// +/// Selectors can be used to efficiently compute derived data from signals. +pub fn maybe_sync_selector>>( mut f: impl FnMut() -> R + 'static, ) -> ReadOnlySignal { let state = Signal:: { @@ -108,5 +172,5 @@ pub fn selector>>( } })); - ReadOnlySignal::new(state) + ReadOnlySignal::new_maybe_sync(state) } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 61186b92c..7d83c5d52 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -170,7 +170,7 @@ pub struct SignalData { /// } /// } /// ``` -pub struct Signal>> { +pub struct Signal> = UnsyncStorage> { pub(crate) inner: CopyValue, S>, } @@ -188,11 +188,23 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign } } -impl>> Signal { +impl Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. pub fn new(value: T) -> Self { + Self::new_maybe_sync(value) + } + + /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. + pub fn new_in_scope(value: T, owner: ScopeId) -> Self { + Self::new_maybe_sync_in_scope(value, owner) + } +} + +impl>> Signal { + /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + pub fn new_maybe_sync(value: T) -> Self { Self { - inner: CopyValue::new(SignalData { + inner: CopyValue::, S>::new_maybe_sync(SignalData { subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value, @@ -202,9 +214,9 @@ impl>> Signal { } /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. - pub fn new_in_scope(value: T, owner: ScopeId) -> Self { + pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self { Self { - inner: CopyValue::new_in_scope( + inner: CopyValue::, S>::new_maybe_sync_in_scope( SignalData { subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), @@ -228,10 +240,11 @@ impl>> Signal { ) -> <>>::Ref as Mappable>>::Mapped { let inner = self.inner.read(); if let Some(effect) = inner.effect_stack.current() { - let mut subscribers = inner.subscribers.write(); - let mut effect_subscribers = &mut subscribers.effect_subscribers; - if !effect_subscribers.contains(&effect) { - effect_subscribers.push(effect); + let subscribers = inner.subscribers.read(); + if !subscribers.effect_subscribers.contains(&effect) { + drop(subscribers); + let mut subscribers = inner.subscribers.write(); + subscribers.effect_subscribers.push(effect); } } else if let Some(current_scope_id) = current_scope_id() { // only subscribe if the vdom is rendering @@ -241,17 +254,13 @@ impl>> Signal { self.inner.value, current_scope_id ); - let mut subscribers = inner.subscribers.write(); - let subscribers = &mut subscribers.subscribers; - if !subscribers.contains(¤t_scope_id) { - subscribers.push(current_scope_id); + let subscribers = inner.subscribers.read(); + if !subscribers.subscribers.contains(¤t_scope_id) { drop(subscribers); + let mut subscribers = inner.subscribers.write(); + subscribers.subscribers.push(current_scope_id); let unsubscriber = current_unsubscriber(); - inner - .subscribers - .write() - .subscribers - .push(unsubscriber.scope); + subscribers.subscribers.push(unsubscriber.scope); } } } @@ -441,13 +450,20 @@ impl, S: Storage>, I> DerefMut for Write>> { +pub struct ReadOnlySignal> = UnsyncStorage> { inner: Signal, } -impl>> ReadOnlySignal { +impl ReadOnlySignal { /// Create a new read-only signal. - pub fn new(signal: Signal) -> Self { + pub fn new(signal: Signal) -> Self { + Self::new_maybe_sync(signal) + } +} + +impl>> ReadOnlySignal { + /// Create a new read-only signal that is maybe sync. + pub fn new_maybe_sync(signal: Signal) -> Self { Self { inner: signal } } From c33276e0a000c9f905068c943591d8aed4c29d5f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 6 Nov 2023 14:46:38 -0600 Subject: [PATCH 013/377] remove ghost --- packages/signals/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index c081155a8..6d22ee0c8 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -14,7 +14,6 @@ simple_logger = "4.2.0" serde = { version = "1", features = ["derive"], optional = true } parking_lot = "0.12.1" once_cell = "1.18.0" -ghost = "0.1.16" [dev-dependencies] dioxus = { workspace = true } From 466005890d08a4b8c6bcc4a32dbeeacda2368e9b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 6 Nov 2023 18:47:51 -0600 Subject: [PATCH 014/377] make effect system async to make signals send + sync --- packages/generational-box/src/lib.rs | 27 +++++++ packages/signals/Cargo.toml | 5 +- packages/signals/src/effect.rs | 104 ++++++++++++++++++++++----- packages/signals/src/impls.rs | 1 - packages/signals/src/lib.rs | 1 + packages/signals/src/rt.rs | 55 +++++++------- packages/signals/src/selector.rs | 55 +++++++------- packages/signals/src/signal.rs | 33 +++++---- 8 files changed, 195 insertions(+), 86 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 040fb842b..d358e78e0 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -142,6 +142,24 @@ fn fuzz() { } } +/// The type erased id of a generational box. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +pub struct GenerationalBoxId { + data_ptr: *const (), + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: u32, +} + +impl Debug for GenerationalBoxId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + #[cfg(any(debug_assertions, feature = "check_generation"))] + f.write_fmt(format_args!("{:?}@{:?}", self.data_ptr, self.generation))?; + #[cfg(not(any(debug_assertions, feature = "check_generation")))] + f.write_fmt(format_args!("{:?}", self.data_ptr))?; + Ok(()) + } +} + /// The core Copy state type. The generational box will be dropped when the [Owner] is dropped. pub struct GenerationalBox { raw: MemoryLocation, @@ -180,6 +198,15 @@ impl> GenerationalBox { } } + /// Get the id of the generational box. + pub fn id(&self) -> GenerationalBoxId { + GenerationalBoxId { + data_ptr: self.raw.data.data_ptr(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: self.generation, + } + } + /// Try to read the value. Returns None if the value is no longer valid. pub fn try_read(&self) -> Option { self.validate().then(|| self.raw.data.try_read()).flatten() diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 6d22ee0c8..18108b689 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -14,6 +14,9 @@ simple_logger = "4.2.0" serde = { version = "1", features = ["derive"], optional = true } parking_lot = "0.12.1" once_cell = "1.18.0" +rustc-hash.workspace = true +futures-channel.workspace = true +futures-util.workspace = true [dev-dependencies] dioxus = { workspace = true } @@ -22,4 +25,4 @@ tokio = { version = "1", features = ["full"] } [features] default = [] -serialize = ["serde"] \ No newline at end of file +serialize = ["serde"] diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 9d6231eb3..526caa221 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -1,20 +1,29 @@ use core::{self, fmt::Debug}; use dioxus_core::prelude::*; -use generational_box::UnsyncStorage; +use futures_channel::mpsc::UnboundedSender; +use futures_util::StreamExt; +use generational_box::GenerationalBoxId; +use parking_lot::RwLock; +use rustc_hash::FxHashMap; use std::fmt::{self, Formatter}; use crate::use_signal; use crate::{dependency::Dependency, CopyValue}; -#[derive(Copy, Clone, PartialEq)] +thread_local! { + pub(crate)static EFFECT_STACK: EffectStack = EffectStack::default(); +} + pub(crate) struct EffectStack { - pub(crate) effects: CopyValue, UnsyncStorage>, + pub(crate) effects: RwLock>, + pub(crate) effect_mapping: RwLock>, } impl Default for EffectStack { fn default() -> Self { Self { - effects: CopyValue::new_in_scope(Vec::new(), ScopeId::ROOT), + effects: RwLock::new(Vec::new()), + effect_mapping: RwLock::new(FxHashMap::default()), } } } @@ -25,13 +34,38 @@ impl EffectStack { } } -pub(crate) fn get_effect_stack() -> EffectStack { +/// This is a thread safe reference to an effect stack running on another thread. +#[derive(Clone)] +pub(crate) struct EffectStackRef { + rerun_effect: UnboundedSender, +} + +impl EffectStackRef { + pub(crate) fn rerun_effect(&self, id: GenerationalBoxId) { + self.rerun_effect.unbounded_send(id).unwrap(); + } +} + +pub(crate) fn get_effect_ref() -> EffectStackRef { match consume_context() { Some(rt) => rt, None => { - let store = EffectStack::default(); - provide_root_context(store); - store + let (sender, mut receiver) = futures_channel::mpsc::unbounded(); + spawn(async move { + while let Some(id) = receiver.next().await { + EFFECT_STACK.with(|stack| { + let effect_mapping = stack.effect_mapping.read(); + if let Some(effect) = effect_mapping.get(&id) { + effect.try_run(); + } + }); + } + }); + let stack_ref = EffectStackRef { + rerun_effect: sender, + }; + provide_root_context(stack_ref.clone()); + stack_ref } } } @@ -68,19 +102,43 @@ pub fn use_effect_with_dependencies( #[derive(Copy, Clone, PartialEq)] pub struct Effect { pub(crate) source: ScopeId, - pub(crate) callback: CopyValue, UnsyncStorage>, - pub(crate) effect_stack: EffectStack, + pub(crate) inner: CopyValue, +} + +pub(crate) struct EffectInner { + pub(crate) callback: Box, + pub(crate) id: GenerationalBoxId, +} + +impl EffectInner { + pub(crate) fn new(callback: Box) -> CopyValue { + let copy = CopyValue::invalid(); + let inner = EffectInner { + callback: Box::new(callback), + id: copy.id(), + }; + copy.set(inner); + copy + } +} + +impl Drop for EffectInner { + fn drop(&mut self) { + EFFECT_STACK.with(|stack| { + stack.effect_mapping.write().remove(&self.id); + }); + } } impl Debug for Effect { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - f.write_fmt(format_args!("{:?}", self.callback.value)) + f.write_fmt(format_args!("{:?}", self.inner.value)) } } impl Effect { pub(crate) fn current() -> Option { - get_effect_stack().effects.read().last().copied() + EFFECT_STACK.with(|stack| stack.effects.read().last().copied()) } /// Create a new effect. The effect will be run immediately and whenever any signal it reads changes. @@ -89,10 +147,16 @@ impl Effect { pub fn new(callback: impl FnMut() + 'static) -> Self { let myself = Self { source: current_scope_id().expect("in a virtual dom"), - callback: CopyValue::new(Box::new(callback)), - effect_stack: get_effect_stack(), + inner: EffectInner::new(Box::new(callback)), }; + EFFECT_STACK.with(|stack| { + stack + .effect_mapping + .write() + .insert(myself.inner.id(), myself); + }); + myself.try_run(); myself @@ -100,13 +164,17 @@ impl Effect { /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. pub fn try_run(&self) { - if let Some(mut callback) = self.callback.try_write() { + if let Some(mut inner) = self.inner.try_write() { { - self.effect_stack.effects.write().push(*self); + EFFECT_STACK.with(|stack| { + stack.effects.write().push(*self); + }); } - callback(); + (inner.callback)(); { - self.effect_stack.effects.write().pop(); + EFFECT_STACK.with(|stack| { + stack.effects.write().pop(); + }); } } } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index f59b8391f..4983f1f33 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -3,7 +3,6 @@ use crate::signal::{ReadOnlySignal, Signal, Write}; use crate::SignalData; use generational_box::Mappable; use generational_box::{MappableMut, Storage}; -use std::ops::Deref; use std::{ fmt::{Debug, Display}, diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index dce9af0a8..886f7a1f1 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -2,6 +2,7 @@ #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")] #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")] #![warn(missing_docs)] +#![allow(clippy::type_complexity)] mod rt; pub use rt::*; diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index be4810680..2e7244a5e 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,3 +1,4 @@ +use generational_box::GenerationalBoxId; use generational_box::UnsyncStorage; use std::mem::MaybeUninit; use std::ops::Deref; @@ -6,40 +7,37 @@ use std::rc::Rc; use dioxus_core::prelude::*; use dioxus_core::ScopeId; -use generational_box::AnyStorage; use generational_box::Storage; use generational_box::{GenerationalBox, Owner}; use crate::Effect; fn current_owner, T>() -> Rc> { - todo!() - // match Effect::current() { - // // If we are inside of an effect, we should use the owner of the effect as the owner of the value. - // Some(effect) => { - // let scope_id = effect.source; - // owner_in_scope(scope_id) - // } - // // Otherwise either get an owner from the current scope or create a new one. - // None => match has_context() { - // Some(rt) => rt, - // None => { - // let owner = Rc::new(current_store().owner()); - // provide_context(owner).expect("in a virtual dom") - // } - // }, - // } + match Effect::current() { + // If we are inside of an effect, we should use the owner of the effect as the owner of the value. + Some(effect) => { + let scope_id = effect.source; + owner_in_scope(scope_id) + } + // Otherwise either get an owner from the current scope or create a new one. + None => match has_context() { + Some(rt) => rt, + None => { + let owner = Rc::new(S::owner()); + provide_context(owner).expect("in a virtual dom") + } + }, + } } fn owner_in_scope, T>(scope: ScopeId) -> Rc> { - todo!() - // match consume_context_from_scope(scope) { - // Some(rt) => rt, - // None => { - // let owner = Rc::new(current_store().owner()); - // provide_context_to_scope(scope, owner).expect("in a virtual dom") - // } - // } + match consume_context_from_scope(scope) { + Some(rt) => rt, + None => { + let owner = Rc::new(S::owner()); + provide_context_to_scope(scope, owner).expect("in a virtual dom") + } + } } /// CopyValue is a wrapper around a value to make the value mutable and Copy. @@ -144,7 +142,7 @@ impl> CopyValue { } /// Set the value. If the value has been dropped, this will panic. - pub fn set(&mut self, value: T) { + pub fn set(&self, value: T) { *self.write() = value; } @@ -159,6 +157,11 @@ impl> CopyValue { let mut write = self.write(); f(&mut *write) } + + /// Get the generational id of the value. + pub fn id(&self) -> GenerationalBoxId { + self.value.id() + } } impl> CopyValue { diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index 5de6395d5..9ae42fea8 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -2,8 +2,8 @@ use dioxus_core::prelude::*; use generational_box::Storage; use crate::dependency::Dependency; -use crate::use_signal; -use crate::{get_effect_stack, signal::SignalData, CopyValue, Effect, ReadOnlySignal, Signal}; +use crate::{get_effect_ref, signal::SignalData, CopyValue, Effect, ReadOnlySignal, Signal}; +use crate::{use_signal, EffectInner, EFFECT_STACK}; /// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes. /// @@ -30,7 +30,6 @@ pub fn use_selector( use_maybe_sync_selector(cx, f) } - /// Creates a new Selector that may be sync. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. @@ -56,7 +55,6 @@ pub fn use_maybe_sync_selector>>( *cx.use_hook(|| maybe_sync_selector(f)) } - /// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes /// /// Selectors can be used to efficiently compute derived data from signals. @@ -74,14 +72,14 @@ pub fn use_maybe_sync_selector>>( /// } /// ``` #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] -pub fn use_selector_with_dependencies( +pub fn use_selector_with_dependencies( cx: &ScopeState, dependencies: D, - mut f: impl FnMut(D::Out) -> R + 'static, + f: impl FnMut(D::Out) -> R + 'static, ) -> ReadOnlySignal where D::Out: 'static, -{ +{ use_maybe_sync_selector_with_dependencies(cx, dependencies, f) } @@ -102,7 +100,11 @@ where /// } /// ``` #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] -pub fn use_maybe_sync_selector_with_dependencies>>( +pub fn use_maybe_sync_selector_with_dependencies< + R: PartialEq, + D: Dependency, + S: Storage>, +>( cx: &ScopeState, dependencies: D, mut f: impl FnMut(D::Out) -> R + 'static, @@ -127,9 +129,7 @@ where /// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. -pub fn selector( - mut f: impl FnMut() -> R + 'static, -) -> ReadOnlySignal { +pub fn selector(f: impl FnMut() -> R + 'static) -> ReadOnlySignal { maybe_sync_selector(f) } @@ -144,33 +144,36 @@ pub fn maybe_sync_selector>>( }; let effect = Effect { source: current_scope_id().expect("in a virtual dom"), - callback: CopyValue::invalid(), - effect_stack: get_effect_stack(), + inner: CopyValue::invalid(), }; { - get_effect_stack().effects.write().push(effect); + EFFECT_STACK.with(|stack| stack.effects.write().push(effect)); } state.inner.value.set(SignalData { subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value: f(), - effect_stack: get_effect_stack(), + effect_ref: get_effect_ref(), }); { - get_effect_stack().effects.write().pop(); + EFFECT_STACK.with(|stack| stack.effects.write().pop()); } - effect.callback.value.set(Box::new(move || { - let value = f(); - let changed = { - let old = state.inner.read(); - value != old.value - }; - if changed { - state.set(value) - } - })); + let invalid_id = state.inner.value.id(); + effect.inner.value.set(EffectInner { + callback: Box::new(move || { + let value = f(); + let changed = { + let old = state.inner.read(); + value != old.value + }; + if changed { + state.set(value) + } + }), + id: invalid_id, + }); ReadOnlySignal::new_maybe_sync(state) } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 7d83c5d52..9af0ffc86 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -11,10 +11,10 @@ use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; -use generational_box::{AnyStorage, Mappable, MappableMut, Storage, SyncStorage, UnsyncStorage}; +use generational_box::{GenerationalBoxId, Mappable, MappableMut, Storage, UnsyncStorage}; use parking_lot::RwLock; -use crate::{get_effect_stack, CopyValue, Effect, EffectStack}; +use crate::{get_effect_ref, CopyValue, EffectStackRef, EFFECT_STACK}; /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. /// @@ -91,8 +91,7 @@ pub fn use_signal_sync( cx: &ScopeState, f: impl FnOnce() -> T, ) -> Signal { - // *cx.use_hook(|| Signal::new(f())) - todo!() + *cx.use_hook(|| Signal::new(f())) } #[derive(Clone)] @@ -127,14 +126,14 @@ fn current_unsubscriber() -> Unsubscriber { #[derive(Default)] pub(crate) struct SignalSubscribers { pub(crate) subscribers: Vec, - pub(crate) effect_subscribers: Vec, + pub(crate) effect_subscribers: Vec, } /// The data stored for tracking in a signal. pub struct SignalData { pub(crate) subscribers: Arc>, pub(crate) update_any: Arc, - pub(crate) effect_stack: EffectStack, + pub(crate) effect_ref: EffectStackRef, pub(crate) value: T, } @@ -208,7 +207,7 @@ impl>> Signal { subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value, - effect_stack: get_effect_stack(), + effect_ref: get_effect_ref(), }), } } @@ -221,7 +220,7 @@ impl>> Signal { subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value, - effect_stack: get_effect_stack(), + effect_ref: get_effect_ref(), }, owner, ), @@ -239,12 +238,12 @@ impl>> Signal { &self, ) -> <>>::Ref as Mappable>>::Mapped { let inner = self.inner.read(); - if let Some(effect) = inner.effect_stack.current() { + if let Some(effect) = EFFECT_STACK.with(|stack| stack.current()) { let subscribers = inner.subscribers.read(); - if !subscribers.effect_subscribers.contains(&effect) { + if !subscribers.effect_subscribers.contains(&effect.inner.id()) { drop(subscribers); let mut subscribers = inner.subscribers.write(); - subscribers.effect_subscribers.push(effect); + subscribers.effect_subscribers.push(effect.inner.id()); } } else if let Some(current_scope_id) = current_scope_id() { // only subscribe if the vdom is rendering @@ -295,18 +294,19 @@ impl>> Signal { } } + let self_read = &self.inner.read(); let subscribers = { - let self_read = self.inner.read(); - let mut effects = &mut self_read.subscribers.write().effect_subscribers; + let effects = &mut self_read.subscribers.write().effect_subscribers; std::mem::take(&mut *effects) }; + let effect_ref = &self_read.effect_ref; for effect in subscribers { tracing::trace!( "Write on {:?} triggered effect {:?}", self.inner.value, effect ); - effect.try_run(); + effect_ref.rerun_effect(effect); } } @@ -328,6 +328,11 @@ impl>> Signal { let mut write = self.write(); f(&mut *write) } + + /// Get the generational id of the signal. + pub fn id(&self) -> generational_box::GenerationalBoxId { + self.inner.id() + } } impl>> Signal { From 7daa945ae3a49f1170910d10cd41a9ce0f764909 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 6 Nov 2023 18:54:35 -0600 Subject: [PATCH 015/377] fix effects --- packages/signals/src/rt.rs | 2 +- packages/signals/tests/effect.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 2e7244a5e..ddcb94b78 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -143,7 +143,7 @@ impl> CopyValue { /// Set the value. If the value has been dropped, this will panic. pub fn set(&self, value: T) { - *self.write() = value; + self.value.set(value); } /// Run a function with a reference to the value. If the value has been dropped, this will panic. diff --git a/packages/signals/tests/effect.rs b/packages/signals/tests/effect.rs index 513b7e929..f06f740f6 100644 --- a/packages/signals/tests/effect.rs +++ b/packages/signals/tests/effect.rs @@ -6,8 +6,8 @@ use dioxus::prelude::*; use dioxus_core::ElementId; use dioxus_signals::*; -#[test] -fn effects_rerun() { +#[tokio::test] +async fn effects_rerun() { simple_logger::SimpleLogger::new().init().unwrap(); #[derive(Default)] @@ -40,6 +40,8 @@ fn effects_rerun() { ); let _ = dom.rebuild().santize(); + dom.render_with_deadline(tokio::time::sleep(std::time::Duration::from_millis(100))) + .await; let current_counter = counter.borrow(); assert_eq!(current_counter.component, 1); From 4e19f8f2be44cf488aa545172c440ac9113fe03e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 6 Nov 2023 18:57:55 -0600 Subject: [PATCH 016/377] make GenerationalBoxId send and sync --- packages/generational-box/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index d358e78e0..77e209b22 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -150,6 +150,10 @@ pub struct GenerationalBoxId { generation: u32, } +// Safety: GenerationalBoxId is Send and Sync because there is no way to access the pointer. +unsafe impl Send for GenerationalBoxId {} +unsafe impl Sync for GenerationalBoxId {} + impl Debug for GenerationalBoxId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #[cfg(any(debug_assertions, feature = "check_generation"))] From e77c1586368e262402d328b7bca02c4da4ffaaa9 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Nov 2023 09:19:33 -0600 Subject: [PATCH 017/377] fix selector effect id --- packages/signals/src/effect.rs | 13 ++++++++++++- packages/signals/src/selector.rs | 6 +++++- packages/signals/src/signal.rs | 2 ++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 526caa221..7e61e8014 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -51,12 +51,15 @@ pub(crate) fn get_effect_ref() -> EffectStackRef { Some(rt) => rt, None => { let (sender, mut receiver) = futures_channel::mpsc::unbounded(); - spawn(async move { + spawn_forever(async move { while let Some(id) = receiver.next().await { EFFECT_STACK.with(|stack| { let effect_mapping = stack.effect_mapping.read(); if let Some(effect) = effect_mapping.get(&id) { + tracing::trace!("Rerunning effect: {:?}", id); effect.try_run(); + } else { + tracing::trace!("Effect not found: {:?}", id); } }); } @@ -125,6 +128,7 @@ impl EffectInner { impl Drop for EffectInner { fn drop(&mut self) { EFFECT_STACK.with(|stack| { + tracing::trace!("Dropping effect: {:?}", self.id); stack.effect_mapping.write().remove(&self.id); }); } @@ -156,6 +160,7 @@ impl Effect { .write() .insert(myself.inner.id(), myself); }); + tracing::trace!("Created effect: {:?}", myself); myself.try_run(); @@ -164,6 +169,7 @@ impl Effect { /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. pub fn try_run(&self) { + tracing::trace!("Running effect: {:?}", self); if let Some(mut inner) = self.inner.try_write() { { EFFECT_STACK.with(|stack| { @@ -178,4 +184,9 @@ impl Effect { } } } + + /// Get the id of this effect. + pub fn id(&self) -> GenerationalBoxId { + self.inner.id() + } } diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index 9ae42fea8..560c2c94b 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -160,7 +160,8 @@ pub fn maybe_sync_selector>>( EFFECT_STACK.with(|stack| stack.effects.write().pop()); } - let invalid_id = state.inner.value.id(); + let invalid_id = effect.id(); + tracing::trace!("Creating effect: {:?}", invalid_id); effect.inner.value.set(EffectInner { callback: Box::new(move || { let value = f(); @@ -174,6 +175,9 @@ pub fn maybe_sync_selector>>( }), id: invalid_id, }); + { + EFFECT_STACK.with(|stack| stack.effect_mapping.write().insert(invalid_id, effect)); + } ReadOnlySignal::new_maybe_sync(state) } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 9af0ffc86..c04fcbc46 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -201,6 +201,7 @@ impl Signal { impl>> Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + #[tracing::instrument(skip(value))] pub fn new_maybe_sync(value: T) -> Self { Self { inner: CopyValue::, S>::new_maybe_sync(SignalData { @@ -213,6 +214,7 @@ impl>> Signal { } /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. + #[tracing::instrument(skip(value))] pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self { Self { inner: CopyValue::, S>::new_maybe_sync_in_scope( From 2731ca0b3012f3cd5bdf055b9bf129f6a4fa4809 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Nov 2023 09:26:12 -0600 Subject: [PATCH 018/377] fix ref try map --- packages/generational-box/src/lib.rs | 3 +-- packages/signals/Cargo.toml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 77e209b22..89844f713 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -9,7 +9,6 @@ use std::{ fmt::Debug, marker::PhantomData, ops::{Deref, DerefMut}, - rc::Rc, sync::{atomic::AtomicU32, Arc, OnceLock}, }; @@ -300,7 +299,7 @@ impl Mappable for Ref<'static, T> { _self: Self, f: impl FnOnce(&T) -> Option<&U>, ) -> Option> { - Ref::try_map(_self, f) + Ref::filter_map(_self, f).ok() } } diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 18108b689..6c66f3faf 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -22,6 +22,7 @@ futures-util.workspace = true dioxus = { workspace = true } dioxus-desktop = { workspace = true } tokio = { version = "1", features = ["full"] } +tracing-subscriber = "0.3.17" [features] default = [] From b3c51585240aacb4f1e671c29ef11cca184cbe1b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Nov 2023 13:15:19 -0600 Subject: [PATCH 019/377] fix tests --- packages/signals/README.md | 4 ++-- packages/signals/examples/send.rs | 26 ++++++++++++++++++++++++++ packages/signals/src/impls.rs | 6 ++++++ packages/signals/src/lib.rs | 1 + packages/signals/src/signal.rs | 12 +++++++----- packages/signals/tests/selector.rs | 20 +++++++++++++++----- 6 files changed, 57 insertions(+), 12 deletions(-) create mode 100644 packages/signals/examples/send.rs diff --git a/packages/signals/README.md b/packages/signals/README.md index d1e772445..9a6f9e17c 100644 --- a/packages/signals/README.md +++ b/packages/signals/README.md @@ -106,7 +106,7 @@ use dioxus_signals::*; #[component] fn App(cx: Scope) -> Element { let signal = use_signal(cx, || 0); - let doubled = use_selector(cx, || signal * 2); + let doubled = use_selector(cx, move || signal * 2); render! { button { @@ -114,7 +114,7 @@ fn App(cx: Scope) -> Element { "Increase" } Child { - signal: signal + signal: doubled } } } diff --git a/packages/signals/examples/send.rs b/packages/signals/examples/send.rs new file mode 100644 index 000000000..9dda4480e --- /dev/null +++ b/packages/signals/examples/send.rs @@ -0,0 +1,26 @@ +use dioxus::prelude::*; +use dioxus_signals::*; + +fn main() { + tracing_subscriber::fmt::init(); + dioxus_desktop::launch(App); +} + +#[component] +fn App(cx: Scope) -> Element { + let mut signal = use_signal_sync(cx, || 0); + cx.use_hook(|| { + std::thread::spawn(move || loop { + std::thread::sleep(std::time::Duration::from_secs(1)); + signal += 1; + }) + }); + + render! { + button { + onclick: move |_| signal += 1, + "Increase" + } + "{signal}" + } +} diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 4983f1f33..676101b20 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -36,6 +36,12 @@ macro_rules! read_impls { self.with(|v| Debug::fmt(v, f)) } } + + impl PartialEq for $ty { + fn eq(&self, other: &T) -> bool { + self.with(|v| *v == *other) + } + } }; } diff --git a/packages/signals/src/lib.rs b/packages/signals/src/lib.rs index 886f7a1f1..f2876f4a5 100644 --- a/packages/signals/src/lib.rs +++ b/packages/signals/src/lib.rs @@ -15,3 +15,4 @@ pub(crate) mod signal; pub use signal::*; mod dependency; pub use dependency::*; +pub use generational_box::{Storage, SyncStorage, UnsyncStorage}; diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index c04fcbc46..0c89e06a5 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -11,7 +11,9 @@ use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; -use generational_box::{GenerationalBoxId, Mappable, MappableMut, Storage, UnsyncStorage}; +use generational_box::{ + GenerationalBoxId, Mappable, MappableMut, Storage, SyncStorage, UnsyncStorage, +}; use parking_lot::RwLock; use crate::{get_effect_ref, CopyValue, EffectStackRef, EFFECT_STACK}; @@ -59,7 +61,7 @@ pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal< /// use dioxus_signals::*; /// /// fn App(cx: Scope) -> Element { -/// let mut count = use_signal(cx, || 0); +/// let mut count = use_signal_sync(cx, || 0); /// /// // Because signals have automatic dependency tracking, if you never read them in a component, that component will not be re-rended when the signal is updated. /// // The app component will never be rerendered in this example. @@ -67,7 +69,7 @@ pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal< /// } /// /// #[component] -/// fn Child(cx: Scope, state: Signal) -> Element { +/// fn Child(cx: Scope, state: Signal) -> Element { /// let state = *state; /// /// use_future!(cx, |()| async move { @@ -90,8 +92,8 @@ pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal< pub fn use_signal_sync( cx: &ScopeState, f: impl FnOnce() -> T, -) -> Signal { - *cx.use_hook(|| Signal::new(f())) +) -> Signal { + *cx.use_hook(|| Signal::new_maybe_sync(f())) } #[derive(Clone)] diff --git a/packages/signals/tests/selector.rs b/packages/signals/tests/selector.rs index 811871e88..44321bd0f 100644 --- a/packages/signals/tests/selector.rs +++ b/packages/signals/tests/selector.rs @@ -24,7 +24,7 @@ fn memos_rerun() { counter.borrow_mut().component += 1; let mut signal = use_signal(cx, || 0); - let memo = cx.use_hook(move || { + let memo = *cx.use_hook(move || { to_owned![counter]; selector(move || { counter.borrow_mut().effect += 1; @@ -32,9 +32,17 @@ fn memos_rerun() { signal.value() }) }); - assert_eq!(memo.value(), 0); + let generation = use_signal(cx, || cx.generation()); + generation.set(cx.generation()); + dioxus_signals::use_effect(cx, move || { + if generation == 1 { + assert_eq!(memo.value(), 0); + } + if generation == 3 { + assert_eq!(memo.value(), 1); + } + }); signal += 1; - assert_eq!(memo.value(), 1); render! { div {} @@ -44,14 +52,15 @@ fn memos_rerun() { ); let _ = dom.rebuild().santize(); + let _ = dom.render_immediate(); let current_counter = counter.borrow(); assert_eq!(current_counter.component, 1); assert_eq!(current_counter.effect, 2); } -#[test] -fn memos_prevents_component_rerun() { +#[tokio::test] +async fn memos_prevents_component_rerun() { let _ = simple_logger::SimpleLogger::new().init(); #[derive(Default)] @@ -126,6 +135,7 @@ fn memos_prevents_component_rerun() { let _ = dom.rebuild().santize(); dom.mark_dirty(ScopeId::ROOT); dom.render_immediate(); + dom.render_immediate(); { let current_counter = counter.borrow(); From c1bfe9514f42cfd7dd6335100a50bb5859fc1108 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 7 Nov 2023 13:19:26 -0600 Subject: [PATCH 020/377] document remaining items in generational box --- packages/generational-box/src/lib.rs | 49 ++++++++++++++++------------ 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 89844f713..692af76b0 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -259,6 +259,7 @@ impl Clone for GenerationalBox { } } +/// A unsync storage. This is the default storage type. #[derive(Clone, Copy)] pub struct UnsyncStorage(&'static RefCell>>); @@ -268,6 +269,7 @@ impl Default for UnsyncStorage { } } +/// A thread safe storage. This is slower than the unsync storage, but allows you to share the value between threads. #[derive(Clone, Copy)] pub struct SyncStorage(&'static RwLock>>); @@ -277,11 +279,15 @@ impl Default for SyncStorage { } } +/// A trait for types that can be mapped. pub trait Mappable: Deref { + /// The type after the mapping. type Mapped: Mappable + Deref; + /// Map the value. fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped; + /// Try to map the value. fn try_map( _self: Self, f: impl FnOnce(&T) -> Option<&U>, @@ -318,11 +324,15 @@ impl Mappable for MappedRwLockReadGuard<'static, T> { } } +/// A trait for types that can be mapped mutably. pub trait MappableMut: DerefMut { + /// The type after the mapping. type Mapped: MappableMut + DerefMut; + /// Map the value. fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped; + /// Try to map the value. fn try_map( _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, @@ -340,7 +350,7 @@ impl MappableMut for RefMut<'static, T> { _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, ) -> Option> { - RefMut::try_map(_self, f) + RefMut::filter_map(_self, f).ok() } } @@ -359,49 +369,45 @@ impl MappableMut for MappedRwLockWriteGuard<'static, T> { } } +/// A trait for a storage backing type. (RefCell, RwLock, etc.) pub trait Storage: Copy + AnyStorage + 'static { + /// The reference this storage type returns. type Ref: Mappable + Deref; + /// The mutable reference this storage type returns. type Mut: MappableMut + DerefMut; + /// Try to read the value. Returns None if the value is no longer valid. fn try_read(&self) -> Option; + /// Read the value. Panics if the value is no longer valid. fn read(&self) -> Self::Ref { self.try_read() .expect("generational box has been invalidated or the type has changed") } + /// Try to write the value. Returns None if the value is no longer valid. fn try_write(&self) -> Option; + /// Write the value. Panics if the value is no longer valid. fn write(&self) -> Self::Mut { self.try_write() .expect("generational box has been invalidated or the type has changed") } + /// Set the value. Panics if the value is no longer valid. fn set(&self, value: Data); } +/// A trait for any storage backing type. pub trait AnyStorage: Default { + /// Get the data pointer. No guarantees are made about the data pointer. It should only be used for debugging. fn data_ptr(&self) -> *const (); + /// Take the value out of the storage. This will return true if the value was taken. fn take(&self) -> bool; + /// Recycle a memory location. This will drop the memory location and return it to the runtime. fn recycle(location: &MemoryLocation); - // { - // location.drop(); - // self.recycled.lock().push(location); - // } + /// Claim a new memory location. This will either create a new memory location or recycle an old one. fn claim() -> MemoryLocation; - // where - // S: Default, - // { - // if let Some(location) = self.recycled.lock().pop() { - // location - // } else { - // MemoryLocation { - // data: Default::default(), - // #[cfg(any(debug_assertions, feature = "check_generation"))] - // generation: Box::leak(Box::new(Default::default())), - // } - // } - // } /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates. fn owner() -> Owner { @@ -450,7 +456,7 @@ impl AnyStorage for UnsyncStorage { MemoryLocation { data: UnsyncStorage(Box::leak(Box::new(RefCell::new(None)))), #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: Box::leak(Box::new(Default::default())), + generation: Box::leak(Box::default()), } } }) @@ -498,7 +504,7 @@ impl AnyStorage for SyncStorage { MemoryLocation { data: SyncStorage(Box::leak(Box::new(RwLock::new(None)))), #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: Box::leak(Box::new(Default::default())), + generation: Box::leak(Box::default()), } } @@ -508,8 +514,9 @@ impl AnyStorage for SyncStorage { } } +/// A memory location. This is the core type that is used to store values. #[derive(Clone, Copy)] -struct MemoryLocation { +pub struct MemoryLocation { data: S, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: &'static AtomicU32, From 70c3abb8df5c7b723977675b4a488ef0622a5546 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 14 Dec 2023 12:29:32 -0600 Subject: [PATCH 021/377] Merge branch 'master' into maybe-sync-signal --- .github/workflows/docs stable.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/main.yml | 7 +- .github/workflows/miri.yml | 3 +- .github/workflows/playwright.yml | 2 +- Cargo.toml | 8 +- Makefile.toml | 74 ++- examples/calculator.rs | 1 + examples/openid_connect_demo/Cargo.toml | 1 + examples/optional_props.rs | 12 + examples/query_segments_demo/Cargo.toml | 1 + examples/tailwind/Cargo.toml | 2 +- packages/autofmt/src/buffer.rs | 11 +- packages/autofmt/src/element.rs | 12 +- packages/autofmt/src/expr.rs | 6 +- packages/autofmt/src/indent.rs | 108 ++++ packages/autofmt/src/lib.rs | 27 +- packages/autofmt/src/writer.rs | 4 +- packages/autofmt/tests/samples.rs | 2 +- packages/autofmt/tests/wrong.rs | 15 +- .../wrong/{comments.rsx => comments-4sp.rsx} | 0 ...ments.wrong.rsx => comments-4sp.wrong.rsx} | 0 packages/autofmt/tests/wrong/comments-tab.rsx | 7 + .../tests/wrong/comments-tab.wrong.rsx | 5 + .../tests/wrong/{multi.rsx => multi-4sp.rsx} | 0 .../{multi.wrong.rsx => multi-4sp.wrong.rsx} | 0 packages/autofmt/tests/wrong/multi-tab.rsx | 3 + .../autofmt/tests/wrong/multi-tab.wrong.rsx | 5 + .../{multiexpr.rsx => multiexpr-4sp.rsx} | 0 ...expr.wrong.rsx => multiexpr-4sp.wrong.rsx} | 0 .../autofmt/tests/wrong/multiexpr-tab.rsx | 8 + .../tests/wrong/multiexpr-tab.wrong.rsx | 5 + packages/cli/Cargo.toml | 2 +- packages/cli/README.md | 10 +- packages/cli/src/assets/dioxus.toml | 2 +- packages/cli/src/builder.rs | 21 +- packages/cli/src/cli/autoformat.rs | 68 ++- packages/cli/src/cli/build.rs | 8 +- packages/cli/src/config.rs | 2 +- packages/cli/src/error.rs | 3 + packages/cli/src/logging.rs | 14 +- packages/cli/src/main.rs | 86 ++-- packages/cli/src/server/desktop/mod.rs | 27 +- packages/cli/src/server/mod.rs | 24 +- packages/cli/src/server/output.rs | 25 +- .../component_body_deserializers/component.rs | 1 + .../inline_props.rs | 14 +- packages/core-macro/src/props/mod.rs | 51 +- packages/core/src/arena.rs | 12 +- packages/core/src/bump_frame.rs | 44 +- packages/core/src/diff.rs | 2 +- packages/core/src/events.rs | 2 - packages/core/src/lazynodes.rs | 33 +- packages/core/src/mutations.rs | 2 +- packages/core/src/nodes.rs | 8 +- packages/core/src/scope_arena.rs | 4 +- packages/core/src/scopes.rs | 19 +- packages/desktop/Cargo.toml | 1 + packages/desktop/build.rs | 9 + packages/desktop/src/lib.rs | 17 +- packages/desktop/src/webview.rs | 2 +- packages/dioxus-tui/examples/colorpicker.rs | 12 +- packages/extension/src/lib.rs | 46 +- packages/extension/src/main.ts | 8 +- packages/fermi/src/hooks/atom_root.rs | 2 +- packages/fermi/src/hooks/state.rs | 4 +- packages/fermi/src/lib.rs | 2 - packages/fullstack/Cargo.toml | 2 +- .../examples/axum-hello-world/src/main.rs | 1 + .../fullstack/src/adapters/axum_adapter.rs | 66 ++- packages/fullstack/src/adapters/mod.rs | 38 +- packages/fullstack/src/layer.rs | 5 + packages/fullstack/src/lib.rs | 2 + packages/fullstack/src/render.rs | 2 + packages/fullstack/src/router.rs | 2 +- packages/fullstack/src/server_fn.rs | 8 - packages/generational-box/Cargo.toml | 9 +- packages/generational-box/README.md | 2 + packages/generational-box/src/lib.rs | 475 ++++++++++++++++-- packages/hooks/src/lib.rs | 3 + packages/hooks/src/use_const.rs | 76 +++ packages/hooks/src/use_effect.rs | 79 ++- packages/hooks/src/use_future.rs | 11 +- packages/hooks/src/use_shared_state.rs | 1 + packages/hot-reload/src/file_watcher.rs | 15 +- packages/hot-reload/src/lib.rs | 8 +- packages/html/src/elements.rs | 54 +- packages/html/src/global_attributes.rs | 3 + packages/native-core/src/dioxus.rs | 5 + packages/rink/Cargo.toml | 2 +- packages/rink/src/lib.rs | 6 +- packages/rink/src/render.rs | 7 +- packages/rink/src/style.rs | 3 +- packages/rink/src/style_attributes.rs | 8 +- packages/rink/src/widget.rs | 2 +- packages/router-macro/src/redirect.rs | 1 + packages/router/Cargo.toml | 5 + packages/router/examples/simple_routes.rs | 72 ++- .../router/src/components/history_buttons.rs | 2 +- packages/router/src/components/link.rs | 14 + packages/router/src/contexts/router.rs | 2 +- packages/router/src/history/liveview.rs | 441 ++++++++++++++++ packages/router/src/history/mod.rs | 5 + packages/rsx/Cargo.toml | 1 + .../rsx/src/hot_reload/hot_reload_diff.rs | 44 +- .../src/hot_reload/hot_reloading_file_map.rs | 27 +- packages/rsx/src/lib.rs | 2 +- packages/server-macro/Cargo.toml | 2 +- packages/signals/Cargo.toml | 8 +- .../signals/examples/split_subscriptions.rs | 18 +- packages/signals/src/rt.rs | 28 +- packages/signals/src/signal.rs | 66 ++- packages/ssr/Cargo.toml | 3 + packages/ssr/src/eval.rs | 42 ++ packages/ssr/src/incremental.rs | 1 + packages/ssr/src/lib.rs | 2 + packages/web/src/dom.rs | 2 +- packages/web/src/hot_reload.rs | 15 +- 118 files changed, 2206 insertions(+), 412 deletions(-) create mode 100644 packages/autofmt/src/indent.rs rename packages/autofmt/tests/wrong/{comments.rsx => comments-4sp.rsx} (100%) rename packages/autofmt/tests/wrong/{comments.wrong.rsx => comments-4sp.wrong.rsx} (100%) create mode 100644 packages/autofmt/tests/wrong/comments-tab.rsx create mode 100644 packages/autofmt/tests/wrong/comments-tab.wrong.rsx rename packages/autofmt/tests/wrong/{multi.rsx => multi-4sp.rsx} (100%) rename packages/autofmt/tests/wrong/{multi.wrong.rsx => multi-4sp.wrong.rsx} (100%) create mode 100644 packages/autofmt/tests/wrong/multi-tab.rsx create mode 100644 packages/autofmt/tests/wrong/multi-tab.wrong.rsx rename packages/autofmt/tests/wrong/{multiexpr.rsx => multiexpr-4sp.rsx} (100%) rename packages/autofmt/tests/wrong/{multiexpr.wrong.rsx => multiexpr-4sp.wrong.rsx} (100%) create mode 100644 packages/autofmt/tests/wrong/multiexpr-tab.rsx create mode 100644 packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx create mode 100644 packages/desktop/build.rs create mode 100644 packages/hooks/src/use_const.rs create mode 100644 packages/router/src/history/liveview.rs create mode 100644 packages/ssr/src/eval.rs diff --git a/.github/workflows/docs stable.yml b/.github/workflows/docs stable.yml index 642216f0f..523a24067 100644 --- a/.github/workflows/docs stable.yml +++ b/.github/workflows/docs stable.yml @@ -33,7 +33,7 @@ jobs: # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4.4.3 + uses: JamesIves/github-pages-deploy-action@v4.5.0 with: branch: gh-pages # The branch the action should deploy to. folder: docs/nightly # The folder the action should deploy. diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 88542aea8..7701209c1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -39,7 +39,7 @@ jobs: # cd fermi && mdbook build -d ../nightly/fermi && cd .. - name: Deploy 🚀 - uses: JamesIves/github-pages-deploy-action@v4.4.3 + uses: JamesIves/github-pages-deploy-action@v4.5.0 with: branch: gh-pages # The branch the action should deploy to. folder: docs/nightly # The folder the action should deploy. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 037525557..1b1f1ed7b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -124,8 +124,6 @@ jobs: } steps: - - uses: actions/checkout@v4 - - name: install stable uses: dtolnay/rust-toolchain@master with: @@ -141,6 +139,11 @@ jobs: workspaces: core -> ../target save-if: ${{ matrix.features.key == 'all' }} + - name: Install rustfmt + run: rustup component add rustfmt + + - uses: actions/checkout@v4 + - name: test run: | ${{ env.RUST_CARGO_COMMAND }} ${{ matrix.platform.command }} ${{ matrix.platform.args }} --target ${{ matrix.platform.target }} diff --git a/.github/workflows/miri.yml b/.github/workflows/miri.yml index 4a467b9b2..f9ad3855f 100644 --- a/.github/workflows/miri.yml +++ b/.github/workflows/miri.yml @@ -86,8 +86,7 @@ jobs: # working-directory: tokio env: - # todo: disable memory leaks ignore - MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields -Zmiri-ignore-leaks + MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance -Zmiri-retag-fields PROPTEST_CASES: 10 # Cache the global cargo directory, but NOT the local `target` directory which diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 500d41564..cb5a1f1bb 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -20,7 +20,7 @@ jobs: steps: # Do our best to cache the toolchain and node install steps - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: node-version: 16 - name: Install Rust diff --git a/Cargo.toml b/Cargo.toml index a78f60d3e..1772c1f47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ members = [ exclude = ["examples/mobile_demo"] [workspace.package] -version = "0.4.2" +version = "0.4.3" # dependencies that are shared across packages [workspace.dependencies] @@ -77,7 +77,7 @@ dioxus-native-core = { path = "packages/native-core", version = "0.4.0" } dioxus-native-core-macro = { path = "packages/native-core-macro", version = "0.4.0" } rsx-rosetta = { path = "packages/rsx-rosetta", version = "0.4.0" } dioxus-signals = { path = "packages/signals" } -generational-box = { path = "packages/generational-box" } +generational-box = { path = "packages/generational-box", version = "0.4.3" } dioxus-hot-reload = { path = "packages/hot-reload", version = "0.4.0" } dioxus-fullstack = { path = "packages/fullstack", version = "0.4.1" } dioxus_server_macro = { path = "packages/server-macro", version = "0.4.1" } @@ -88,7 +88,7 @@ slab = "0.4.2" futures-channel = "0.3.21" futures-util = { version = "0.3", default-features = false } rustc-hash = "1.1.0" -wasm-bindgen = "0.2.87" +wasm-bindgen = "0.2.88" html_parser = "0.7.0" thiserror = "1.0.40" prettyplease = { package = "prettier-please", version = "0.2", features = [ @@ -99,7 +99,7 @@ prettyplease = { package = "prettier-please", version = "0.2", features = [ # It is not meant to be published, but is used so "cargo run --example XYZ" works properly [package] name = "dioxus-examples" -version = "0.0.0" +version = "0.4.3" authors = ["Jonathan Kelley"] edition = "2021" description = "Top level crate for the Dioxus repository" diff --git a/Makefile.toml b/Makefile.toml index 6f331b98f..1bfb02217 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -24,12 +24,64 @@ script = [ ] script_runner = "@duckscript" +[tasks.format] +command = "cargo" +args = ["fmt", "--all"] + +[tasks.check] +command = "cargo" +args = ["check", "--workspace", "--examples", "--tests"] + +[tasks.clippy] +command = "cargo" +args = [ + "clippy", + "--workspace", + "--examples", + "--tests", + "--", + "-D", + "warnings", +] + +[tasks.tidy] +category = "Formatting" +dependencies = ["format", "check", "clippy"] +description = "Format and Check workspace" + +[tasks.install-miri] +toolchain = "nightly" +install_crate = { rustup_component_name = "miri", binary = "cargo +nightly miri", test_arg = "--help" } +private = true + +[tasks.miri-native] +command = "cargo" +toolchain = "nightly" +dependencies = ["install-miri"] +args = [ + "miri", + "test", + "--package", + "dioxus-native-core", + "--test", + "miri_native", +] + +[tasks.miri-stress] +command = "cargo" +toolchain = "nightly" +dependencies = ["install-miri"] +args = ["miri", "test", "--package", "dioxus-core", "--test", "miri_stress"] + +[tasks.miri] +dependencies = ["miri-native", "miri-stress"] + [tasks.tests] category = "Testing" dependencies = ["tests-setup"] description = "Run all tests" -env = {CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"]} -run_task = {name = ["test-flow", "test-with-browser"], fork = true} +env = { CARGO_MAKE_WORKSPACE_SKIP_MEMBERS = ["**/examples/*"] } +run_task = { name = ["test-flow", "test-with-browser"], fork = true } [tasks.build] command = "cargo" @@ -42,10 +94,24 @@ private = true [tasks.test] dependencies = ["build"] command = "cargo" -args = ["test", "--lib", "--bins", "--tests", "--examples", "--workspace", "--exclude", "dioxus-router", "--exclude", "dioxus-desktop"] +args = [ + "test", + "--lib", + "--bins", + "--tests", + "--examples", + "--workspace", + "--exclude", + "dioxus-router", + "--exclude", + "dioxus-desktop", +] private = true [tasks.test-with-browser] -env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = ["**/packages/router", "**/packages/desktop"] } +env = { CARGO_MAKE_WORKSPACE_INCLUDE_MEMBERS = [ + "**/packages/router", + "**/packages/desktop", +] } private = true workspace = true diff --git a/examples/calculator.rs b/examples/calculator.rs index bc7f13459..648ccfbc3 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -62,6 +62,7 @@ fn app(cx: Scope) -> Element { div { id: "wrapper", div { class: "app", div { class: "calculator", + tabindex: "0", onkeydown: handle_key_down_event, div { class: "calculator-display", val.to_string() } div { class: "calculator-keypad", diff --git a/examples/openid_connect_demo/Cargo.toml b/examples/openid_connect_demo/Cargo.toml index 4c5f47061..2a1abe173 100644 --- a/examples/openid_connect_demo/Cargo.toml +++ b/examples/openid_connect_demo/Cargo.toml @@ -2,6 +2,7 @@ name = "openid_auth_demo" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/optional_props.rs b/examples/optional_props.rs index c5652a2e7..8108d437d 100644 --- a/examples/optional_props.rs +++ b/examples/optional_props.rs @@ -16,8 +16,20 @@ fn app(cx: Scope) -> Element { a: "asd".to_string(), c: "asd".to_string(), d: Some("asd".to_string()), + e: Some("asd".to_string()), + } + Button { + a: "asd".to_string(), + b: "asd".to_string(), + c: "asd".to_string(), + d: Some("asd".to_string()), e: "asd".to_string(), } + Button { + a: "asd".to_string(), + c: "asd".to_string(), + d: Some("asd".to_string()), + } }) } diff --git a/examples/query_segments_demo/Cargo.toml b/examples/query_segments_demo/Cargo.toml index 4a7d784c7..ee9f49c70 100644 --- a/examples/query_segments_demo/Cargo.toml +++ b/examples/query_segments_demo/Cargo.toml @@ -2,6 +2,7 @@ name = "query_segments_demo" version = "0.1.0" edition = "2021" +publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/examples/tailwind/Cargo.toml b/examples/tailwind/Cargo.toml index 5c2fbe666..26ac202f1 100644 --- a/examples/tailwind/Cargo.toml +++ b/examples/tailwind/Cargo.toml @@ -18,4 +18,4 @@ dioxus = { path = "../../packages/dioxus" } dioxus-desktop = { path = "../../packages/desktop" } [target.'cfg(target_arch = "wasm32")'.dependencies] -dioxus-web = { path = "../../packages/web" } \ No newline at end of file +dioxus-web = { path = "../../packages/web" } diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index fea5a4a3f..f19a7d8dc 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -8,13 +8,14 @@ use std::fmt::{Result, Write}; use dioxus_rsx::IfmtInput; -use crate::write_ifmt; +use crate::{indent::IndentOptions, write_ifmt}; /// The output buffer that tracks indent and string #[derive(Debug, Default)] pub struct Buffer { pub buf: String, - pub indent: usize, + pub indent_level: usize, + pub indent: IndentOptions, } impl Buffer { @@ -31,16 +32,16 @@ impl Buffer { } pub fn tab(&mut self) -> Result { - self.write_tabs(self.indent) + self.write_tabs(self.indent_level) } pub fn indented_tab(&mut self) -> Result { - self.write_tabs(self.indent + 1) + self.write_tabs(self.indent_level + 1) } pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result { for _ in 0..num { - write!(self.buf, " ")? + write!(self.buf, "{}", self.indent.indent_str())? } Ok(()) } diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index dbc336401..27e7b31f1 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -66,7 +66,7 @@ impl Writer<'_> { // check if we have a lot of attributes let attr_len = self.is_short_attrs(attributes); - let is_short_attr_list = (attr_len + self.out.indent * 4) < 80; + let is_short_attr_list = (attr_len + self.out.indent_level * 4) < 80; let children_len = self.is_short_children(children); let is_small_children = children_len.is_some(); @@ -86,7 +86,7 @@ impl Writer<'_> { // if we have few children and few attributes, make it a one-liner if is_short_attr_list && is_small_children { - if children_len.unwrap() + attr_len + self.out.indent * 4 < 100 { + if children_len.unwrap() + attr_len + self.out.indent_level * 4 < 100 { opt_level = ShortOptimization::Oneliner; } else { opt_level = ShortOptimization::PropsOnTop; @@ -185,11 +185,11 @@ impl Writer<'_> { } while let Some(attr) = attr_iter.next() { - self.out.indent += 1; + self.out.indent_level += 1; if !sameline { self.write_comments(attr.attr.start())?; } - self.out.indent -= 1; + self.out.indent_level -= 1; if !sameline { self.out.indented_tabbed_line()?; @@ -398,14 +398,14 @@ impl Writer<'_> { for idx in start.line..end.line { let line = &self.src[idx]; if line.trim().starts_with("//") { - for _ in 0..self.out.indent + 1 { + for _ in 0..self.out.indent_level + 1 { write!(self.out, " ")? } writeln!(self.out, "{}", line.trim()).unwrap(); } } - for _ in 0..self.out.indent { + for _ in 0..self.out.indent_level { write!(self.out, " ")? } diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index b0257f8c3..b7a93a4fc 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -29,7 +29,7 @@ impl Writer<'_> { let first_line = &self.src[start.line - 1]; write!(self.out, "{}", &first_line[start.column - 1..].trim_start())?; - let prev_block_indent_level = crate::leading_whitespaces(first_line) / 4; + let prev_block_indent_level = self.out.indent.count_indents(first_line); for (id, line) in self.src[start.line..end.line].iter().enumerate() { writeln!(self.out)?; @@ -43,9 +43,9 @@ impl Writer<'_> { }; // trim the leading whitespace - let previous_indent = crate::leading_whitespaces(line) / 4; + let previous_indent = self.out.indent.count_indents(line); let offset = previous_indent.saturating_sub(prev_block_indent_level); - let required_indent = self.out.indent + offset; + let required_indent = self.out.indent_level + offset; self.out.write_tabs(required_indent)?; let line = line.trim_start(); diff --git a/packages/autofmt/src/indent.rs b/packages/autofmt/src/indent.rs new file mode 100644 index 000000000..2cce7cf1e --- /dev/null +++ b/packages/autofmt/src/indent.rs @@ -0,0 +1,108 @@ +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IndentType { + Spaces, + Tabs, +} + +#[derive(Debug, Clone)] +pub struct IndentOptions { + width: usize, + indent_string: String, +} + +impl IndentOptions { + pub fn new(typ: IndentType, width: usize) -> Self { + assert_ne!(width, 0, "Cannot have an indent width of 0"); + Self { + width, + indent_string: match typ { + IndentType::Tabs => "\t".into(), + IndentType::Spaces => " ".repeat(width), + }, + } + } + + /// Gets a string containing one indent worth of whitespace + pub fn indent_str(&self) -> &str { + &self.indent_string + } + + /// Computes the line length in characters, counting tabs as the indent width. + pub fn line_length(&self, line: &str) -> usize { + line.chars() + .map(|ch| if ch == '\t' { self.width } else { 1 }) + .sum() + } + + /// Estimates how many times the line has been indented. + pub fn count_indents(&self, mut line: &str) -> usize { + let mut indent = 0; + while !line.is_empty() { + // Try to count tabs + let num_tabs = line.chars().take_while(|ch| *ch == '\t').count(); + if num_tabs > 0 { + indent += num_tabs; + line = &line[num_tabs..]; + continue; + } + + // Try to count spaces + let num_spaces = line.chars().take_while(|ch| *ch == ' ').count(); + if num_spaces >= self.width { + // Intentionally floor here to take only the amount of space that matches an indent + let num_space_indents = num_spaces / self.width; + indent += num_space_indents; + line = &line[num_space_indents * self.width..]; + continue; + } + + // Line starts with either non-indent characters or an unevent amount of spaces, + // so no more indent remains. + break; + } + indent + } +} + +impl Default for IndentOptions { + fn default() -> Self { + Self::new(IndentType::Spaces, 4) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn count_indents() { + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents("no indentation here!"), + 0 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"), + 1 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"), + 2 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents(" v += 2"), + 2 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\tv += 2"), + 2 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 4).count_indents("\t\t v += 2"), + 2 + ); + assert_eq!( + IndentOptions::new(IndentType::Spaces, 2).count_indents(" v += 2"), + 2 + ); + } +} diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 64a342885..0c3fe6bb8 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -16,8 +16,11 @@ mod collect_macros; mod component; mod element; mod expr; +mod indent; mod writer; +pub use indent::{IndentOptions, IndentType}; + /// A modification to the original file to be applied by an IDE /// /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes. @@ -47,7 +50,7 @@ pub struct FormattedBlock { /// back to the file precisely. /// /// Nested blocks of RSX will be handled automatically -pub fn fmt_file(contents: &str) -> Vec { +pub fn fmt_file(contents: &str, indent: IndentOptions) -> Vec { let mut formatted_blocks = Vec::new(); let parsed = syn::parse_file(contents).unwrap(); @@ -61,6 +64,7 @@ pub fn fmt_file(contents: &str) -> Vec { } let mut writer = Writer::new(contents); + writer.out.indent = indent; // Don't parse nested macros let mut end_span = LineColumn { column: 0, line: 0 }; @@ -76,7 +80,10 @@ pub fn fmt_file(contents: &str) -> Vec { let rsx_start = macro_path.span().start(); - writer.out.indent = leading_whitespaces(writer.src[rsx_start.line - 1]) / 4; + writer.out.indent_level = writer + .out + .indent + .count_indents(writer.src[rsx_start.line - 1]); write_body(&mut writer, &body); @@ -159,12 +166,13 @@ pub fn fmt_block_from_expr(raw: &str, expr: ExprMacro) -> Option { buf.consume() } -pub fn fmt_block(block: &str, indent_level: usize) -> Option { +pub fn fmt_block(block: &str, indent_level: usize, indent: IndentOptions) -> Option { let body = syn::parse_str::(block).unwrap(); let mut buf = Writer::new(block); - buf.out.indent = indent_level; + buf.out.indent = indent; + buf.out.indent_level = indent_level; write_body(&mut buf, &body); @@ -230,14 +238,3 @@ pub(crate) fn write_ifmt(input: &IfmtInput, writable: &mut impl Write) -> std::f let display = DisplayIfmt(input); write!(writable, "{}", display) } - -pub fn leading_whitespaces(input: &str) -> usize { - input - .chars() - .map_while(|c| match c { - ' ' => Some(1), - '\t' => Some(4), - _ => None, - }) - .sum() -} diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index 59f135592..bb333e8df 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -96,11 +96,11 @@ impl<'a> Writer<'a> { // Push out the indent level and write each component, line by line pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result { - self.out.indent += 1; + self.out.indent_level += 1; self.write_body_no_indent(children)?; - self.out.indent -= 1; + self.out.indent_level -= 1; Ok(()) } diff --git a/packages/autofmt/tests/samples.rs b/packages/autofmt/tests/samples.rs index 8145b0e8e..9431d700e 100644 --- a/packages/autofmt/tests/samples.rs +++ b/packages/autofmt/tests/samples.rs @@ -12,7 +12,7 @@ macro_rules! twoway { #[test] fn $name() { let src = include_str!(concat!("./samples/", stringify!($name), ".rsx")); - let formatted = dioxus_autofmt::fmt_file(src); + let formatted = dioxus_autofmt::fmt_file(src, Default::default()); let out = dioxus_autofmt::apply_formats(src, formatted); // normalize line endings let out = out.replace("\r", ""); diff --git a/packages/autofmt/tests/wrong.rs b/packages/autofmt/tests/wrong.rs index 5a0fb8a87..06a0f00d1 100644 --- a/packages/autofmt/tests/wrong.rs +++ b/packages/autofmt/tests/wrong.rs @@ -1,10 +1,12 @@ +use dioxus_autofmt::{IndentOptions, IndentType}; + macro_rules! twoway { - ($val:literal => $name:ident) => { + ($val:literal => $name:ident ($indent:expr)) => { #[test] fn $name() { let src_right = include_str!(concat!("./wrong/", $val, ".rsx")); let src_wrong = include_str!(concat!("./wrong/", $val, ".wrong.rsx")); - let formatted = dioxus_autofmt::fmt_file(src_wrong); + let formatted = dioxus_autofmt::fmt_file(src_wrong, $indent); let out = dioxus_autofmt::apply_formats(src_wrong, formatted); // normalize line endings @@ -16,8 +18,11 @@ macro_rules! twoway { }; } -twoway!("comments" => comments); +twoway!("comments-4sp" => comments_4sp (IndentOptions::new(IndentType::Spaces, 4))); +twoway!("comments-tab" => comments_tab (IndentOptions::new(IndentType::Tabs, 4))); -twoway!("multi" => multi); +twoway!("multi-4sp" => multi_4sp (IndentOptions::new(IndentType::Spaces, 4))); +twoway!("multi-tab" => multi_tab (IndentOptions::new(IndentType::Tabs, 4))); -twoway!("multiexpr" => multiexpr); +twoway!("multiexpr-4sp" => multiexpr_4sp (IndentOptions::new(IndentType::Spaces, 4))); +twoway!("multiexpr-tab" => multiexpr_tab (IndentOptions::new(IndentType::Tabs, 4))); diff --git a/packages/autofmt/tests/wrong/comments.rsx b/packages/autofmt/tests/wrong/comments-4sp.rsx similarity index 100% rename from packages/autofmt/tests/wrong/comments.rsx rename to packages/autofmt/tests/wrong/comments-4sp.rsx diff --git a/packages/autofmt/tests/wrong/comments.wrong.rsx b/packages/autofmt/tests/wrong/comments-4sp.wrong.rsx similarity index 100% rename from packages/autofmt/tests/wrong/comments.wrong.rsx rename to packages/autofmt/tests/wrong/comments-4sp.wrong.rsx diff --git a/packages/autofmt/tests/wrong/comments-tab.rsx b/packages/autofmt/tests/wrong/comments-tab.rsx new file mode 100644 index 000000000..4c05e347e --- /dev/null +++ b/packages/autofmt/tests/wrong/comments-tab.rsx @@ -0,0 +1,7 @@ +rsx! { + div { + // Comments + class: "asdasd", + "hello world" + } +} diff --git a/packages/autofmt/tests/wrong/comments-tab.wrong.rsx b/packages/autofmt/tests/wrong/comments-tab.wrong.rsx new file mode 100644 index 000000000..eac96642d --- /dev/null +++ b/packages/autofmt/tests/wrong/comments-tab.wrong.rsx @@ -0,0 +1,5 @@ +rsx! { + div { + // Comments + class: "asdasd", "hello world" } +} diff --git a/packages/autofmt/tests/wrong/multi.rsx b/packages/autofmt/tests/wrong/multi-4sp.rsx similarity index 100% rename from packages/autofmt/tests/wrong/multi.rsx rename to packages/autofmt/tests/wrong/multi-4sp.rsx diff --git a/packages/autofmt/tests/wrong/multi.wrong.rsx b/packages/autofmt/tests/wrong/multi-4sp.wrong.rsx similarity index 100% rename from packages/autofmt/tests/wrong/multi.wrong.rsx rename to packages/autofmt/tests/wrong/multi-4sp.wrong.rsx diff --git a/packages/autofmt/tests/wrong/multi-tab.rsx b/packages/autofmt/tests/wrong/multi-tab.rsx new file mode 100644 index 000000000..1fb85c3d0 --- /dev/null +++ b/packages/autofmt/tests/wrong/multi-tab.rsx @@ -0,0 +1,3 @@ +fn app(cx: Scope) -> Element { + cx.render(rsx! { div { "hello world" } }) +} diff --git a/packages/autofmt/tests/wrong/multi-tab.wrong.rsx b/packages/autofmt/tests/wrong/multi-tab.wrong.rsx new file mode 100644 index 000000000..d818f9535 --- /dev/null +++ b/packages/autofmt/tests/wrong/multi-tab.wrong.rsx @@ -0,0 +1,5 @@ +fn app(cx: Scope) -> Element { + cx.render(rsx! { + div {"hello world" } + }) +} diff --git a/packages/autofmt/tests/wrong/multiexpr.rsx b/packages/autofmt/tests/wrong/multiexpr-4sp.rsx similarity index 100% rename from packages/autofmt/tests/wrong/multiexpr.rsx rename to packages/autofmt/tests/wrong/multiexpr-4sp.rsx diff --git a/packages/autofmt/tests/wrong/multiexpr.wrong.rsx b/packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx similarity index 100% rename from packages/autofmt/tests/wrong/multiexpr.wrong.rsx rename to packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx diff --git a/packages/autofmt/tests/wrong/multiexpr-tab.rsx b/packages/autofmt/tests/wrong/multiexpr-tab.rsx new file mode 100644 index 000000000..1fc3401c4 --- /dev/null +++ b/packages/autofmt/tests/wrong/multiexpr-tab.rsx @@ -0,0 +1,8 @@ +fn ItWroks() { + cx.render(rsx! { + div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", + left, + right + } + }) +} diff --git a/packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx b/packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx new file mode 100644 index 000000000..073107541 --- /dev/null +++ b/packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx @@ -0,0 +1,5 @@ +fn ItWroks() { + cx.render(rsx! { + div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right } + }) +} diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index adb0239f2..128133602 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "dioxus-cli" -version = "0.4.1" +version = "0.4.3" authors = ["Jonathan Kelley"] edition = "2021" description = "CLI tool for developing, testing, and publishing Dioxus apps" diff --git a/packages/cli/README.md b/packages/cli/README.md index 72a813468..2325d6784 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -10,8 +10,8 @@ It handles building, bundling, development and publishing to simplify developmen ### Install the stable version (recommended) -``` -cargo install dioxus-cli --locked +```shell +cargo install dioxus-cli ``` ### Install the latest development build through git @@ -20,7 +20,7 @@ To get the latest bug fixes and features, you can install the development versio However, this is not fully tested. That means you're probably going to have more bugs despite having the latest bug fixes. -``` +```shell cargo install --git https://github.com/DioxusLabs/dioxus dioxus-cli ``` @@ -29,7 +29,7 @@ and install it in Cargo's global binary directory (`~/.cargo/bin/` by default). ### Install from local folder -``` +```shell cargo install --path . --debug ``` @@ -40,7 +40,7 @@ It will be cloned from the [dioxus-template](https://github.com/DioxusLabs/dioxu Alternatively, you can specify the template path: -``` +```shell dx create hello --template gh:dioxuslabs/dioxus-template ``` diff --git a/packages/cli/src/assets/dioxus.toml b/packages/cli/src/assets/dioxus.toml index 6386fb76e..892a6cdf8 100644 --- a/packages/cli/src/assets/dioxus.toml +++ b/packages/cli/src/assets/dioxus.toml @@ -23,7 +23,7 @@ title = "Dioxus | An elegant GUI library for Rust" index_on_404 = true -watch_path = ["src"] +watch_path = ["src", "examples"] # include `assets` in web platform [web.resource] diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 68c494c42..759be7dec 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -48,14 +48,25 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { // [1] Build the .wasm module log::info!("🚅 Running build command..."); + + let wasm_check_command = std::process::Command::new("rustup") + .args(["show"]) + .output()?; + let wasm_check_output = String::from_utf8(wasm_check_command.stdout).unwrap(); + if !wasm_check_output.contains("wasm32-unknown-unknown") { + log::info!("wasm32-unknown-unknown target not detected, installing.."); + let _ = std::process::Command::new("rustup") + .args(["target", "add", "wasm32-unknown-unknown"]) + .output()?; + } + let cmd = subprocess::Exec::cmd("cargo"); let cmd = cmd .cwd(crate_dir) .arg("build") .arg("--target") .arg("wasm32-unknown-unknown") - .arg("--message-format=json") - .arg("--quiet"); + .arg("--message-format=json"); let cmd = if config.release { cmd.arg("--release") @@ -65,7 +76,7 @@ pub fn build(config: &CrateConfig, quiet: bool) -> Result { let cmd = if config.verbose { cmd.arg("--verbose") } else { - cmd + cmd.arg("--quiet") }; let cmd = if config.custom_profile.is_some() { @@ -261,6 +272,8 @@ pub fn build_desktop(config: &CrateConfig, _is_serve: bool) -> Result String { .unwrap_or_default() .contains_key("tailwindcss") { - style_str.push_str("\n"); + style_str.push_str("\n"); } replace_or_insert_before("{style_include}", &style_str, " { - let edits = dioxus_autofmt::fmt_file(&s); + let edits = dioxus_autofmt::fmt_file(&s, indent); let out = dioxus_autofmt::apply_formats(&s, edits); if file == "-" { print!("{}", out); @@ -93,6 +99,12 @@ async fn autoformat_project(check: bool) -> Result<()> { let mut files_to_format = vec![]; collect_rs_files(&crate_config.crate_dir, &mut files_to_format); + if files_to_format.is_empty() { + return Ok(()); + } + + let indent = indentation_for(&files_to_format[0])?; + let counts = files_to_format .into_iter() .filter(|file| { @@ -104,10 +116,11 @@ async fn autoformat_project(check: bool) -> Result<()> { }) .map(|path| async { let _path = path.clone(); + let _indent = indent.clone(); let res = tokio::spawn(async move { let contents = tokio::fs::read_to_string(&path).await?; - let edits = dioxus_autofmt::fmt_file(&contents); + let edits = dioxus_autofmt::fmt_file(&contents, _indent.clone()); let len = edits.len(); if !edits.is_empty() { @@ -151,6 +164,49 @@ async fn autoformat_project(check: bool) -> Result<()> { Ok(()) } +fn indentation_for(file_or_dir: impl AsRef) -> Result { + let out = std::process::Command::new("cargo") + .args(["fmt", "--", "--print-config", "current"]) + .arg(file_or_dir.as_ref()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::inherit()) + .output()?; + if !out.status.success() { + return Err(Error::CargoError("cargo fmt failed".into())); + } + + let config = String::from_utf8_lossy(&out.stdout); + + let hard_tabs = config + .lines() + .find(|line| line.starts_with("hard_tabs ")) + .and_then(|line| line.split_once('=')) + .map(|(_, value)| value.trim() == "true") + .ok_or_else(|| { + Error::RuntimeError("Could not find hard_tabs option in rustfmt config".into()) + })?; + let tab_spaces = config + .lines() + .find(|line| line.starts_with("tab_spaces ")) + .and_then(|line| line.split_once('=')) + .map(|(_, value)| value.trim().parse::()) + .ok_or_else(|| { + Error::RuntimeError("Could not find tab_spaces option in rustfmt config".into()) + })? + .map_err(|_| { + Error::RuntimeError("Could not parse tab_spaces option in rustfmt config".into()) + })?; + + Ok(IndentOptions::new( + if hard_tabs { + IndentType::Tabs + } else { + IndentType::Spaces + }, + tab_spaces, + )) +} + fn collect_rs_files(folder: &Path, files: &mut Vec) { let Ok(folder) = folder.read_dir() else { return; diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 7fe29f8ce..63a2969e0 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -37,8 +37,8 @@ impl Build { .platform .unwrap_or(crate_config.dioxus_config.application.default_platform); - #[cfg(feature = "plugin")] - let _ = PluginManager::on_build_start(&crate_config, &platform); + // #[cfg(feature = "plugin")] + // let _ = PluginManager::on_build_start(&crate_config, &platform); match platform { Platform::Web => { @@ -66,8 +66,8 @@ impl Build { )?; file.write_all(temp.as_bytes())?; - #[cfg(feature = "plugin")] - let _ = PluginManager::on_build_finish(&crate_config, &platform); + // #[cfg(feature = "plugin")] + // let _ = PluginManager::on_build_finish(&crate_config, &platform); Ok(()) } diff --git a/packages/cli/src/config.rs b/packages/cli/src/config.rs index 0654dc375..3abf47f0c 100644 --- a/packages/cli/src/config.rs +++ b/packages/cli/src/config.rs @@ -105,7 +105,7 @@ impl Default for DioxusConfig { }, proxy: Some(vec![]), watcher: WebWatcherConfig { - watch_path: Some(vec![PathBuf::from("src")]), + watch_path: Some(vec![PathBuf::from("src"), PathBuf::from("examples")]), reload_html: Some(false), index_on_404: Some(true), }, diff --git a/packages/cli/src/error.rs b/packages/cli/src/error.rs index 84b9d4b71..d577b2b40 100644 --- a/packages/cli/src/error.rs +++ b/packages/cli/src/error.rs @@ -29,6 +29,9 @@ pub enum Error { #[error("Cargo Error: {0}")] CargoError(String), + #[error("Couldn't retrieve cargo metadata")] + CargoMetadata(#[source] cargo_metadata::Error), + #[error("{0}")] CustomError(String), diff --git a/packages/cli/src/logging.rs b/packages/cli/src/logging.rs index 48c2a2788..31d088737 100644 --- a/packages/cli/src/logging.rs +++ b/packages/cli/src/logging.rs @@ -28,7 +28,19 @@ pub fn set_up_logging() { message = message, )); }) - .level(log::LevelFilter::Info) + .level(match std::env::var("DIOXUS_LOG") { + Ok(level) => match level.to_lowercase().as_str() { + "error" => log::LevelFilter::Error, + "warn" => log::LevelFilter::Warn, + "info" => log::LevelFilter::Info, + "debug" => log::LevelFilter::Debug, + "trace" => log::LevelFilter::Trace, + _ => { + panic!("Invalid log level: {}", level) + } + }, + Err(_) => log::LevelFilter::Info, + }) .chain(std::io::stdout()) .apply() .unwrap(); diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index fe860a0e6..2358551f2 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -9,42 +9,31 @@ use dioxus_cli::plugin::PluginManager; use Commands::*; -fn get_bin(bin: Option) -> Result> { - const ERR_MESSAGE: &str = "The `--bin` flag has to be ran in a Cargo workspace."; +fn get_bin(bin: Option) -> Result { + let metadata = cargo_metadata::MetadataCommand::new() + .exec() + .map_err(Error::CargoMetadata)?; + let package = if let Some(bin) = bin { + metadata + .workspace_packages() + .into_iter() + .find(|p| p.name == bin) + .ok_or(format!("no such package: {}", bin)) + .map_err(Error::CargoError)? + } else { + metadata + .root_package() + .ok_or("no root package?".into()) + .map_err(Error::CargoError)? + }; - if let Some(ref bin) = bin { - let manifest = cargo_toml::Manifest::from_path("./Cargo.toml") - .map_err(|_| Error::CargoError(ERR_MESSAGE.to_string()))?; + let crate_dir = package + .manifest_path + .parent() + .ok_or("couldn't take parent dir".into()) + .map_err(Error::CargoError)?; - if let Some(workspace) = manifest.workspace { - for item in workspace.members.iter() { - let path = PathBuf::from(item); - - if !path.exists() { - continue; - } - - if !path.is_dir() { - continue; - } - - if path.ends_with(bin.clone()) { - return Ok(Some(path)); - } - } - } else { - return Err(Error::CargoError(ERR_MESSAGE.to_string())); - } - } - - // If the bin exists but we couldn't find it - if bin.is_some() { - return Err(Error::CargoError( - "The specified bin does not exist.".to_string(), - )); - } - - Ok(None) + Ok(crate_dir.into()) } #[tokio::main] @@ -53,34 +42,36 @@ async fn main() -> anyhow::Result<()> { set_up_logging(); - let bin = get_bin(args.bin)?; + let bin = get_bin(args.bin); - let _dioxus_config = DioxusConfig::load(bin.clone()) + if let Ok(bin) = &bin { + let _dioxus_config = DioxusConfig::load(Some(bin.clone())) .map_err(|e| anyhow!("Failed to load Dioxus config because: {e}"))? .unwrap_or_else(|| { log::warn!("You appear to be creating a Dioxus project from scratch; we will use the default config"); DioxusConfig::default() }); - #[cfg(feature = "plugin")] - PluginManager::init(_dioxus_config.plugin) - .map_err(|e| anyhow!("🚫 Plugin system initialization failed: {e}"))?; + #[cfg(feature = "plugin")] + PluginManager::init(_dioxus_config.plugin) + .map_err(|e| anyhow!("🚫 Plugin system initialization failed: {e}"))?; + } match args.action { Translate(opts) => opts .translate() .map_err(|e| anyhow!("🚫 Translation of HTML into RSX failed: {}", e)), - Build(opts) => opts - .build(bin.clone()) + Build(opts) if bin.is_ok() => opts + .build(Some(bin.unwrap().clone())) .map_err(|e| anyhow!("🚫 Building project failed: {}", e)), - Clean(opts) => opts - .clean(bin.clone()) + Clean(opts) if bin.is_ok() => opts + .clean(Some(bin.unwrap().clone())) .map_err(|e| anyhow!("🚫 Cleaning project failed: {}", e)), - Serve(opts) => opts - .serve(bin.clone()) + Serve(opts) if bin.is_ok() => opts + .serve(Some(bin.unwrap().clone())) .await .map_err(|e| anyhow!("🚫 Serving project failed: {}", e)), @@ -92,8 +83,8 @@ async fn main() -> anyhow::Result<()> { .config() .map_err(|e| anyhow!("🚫 Configuring new project failed: {}", e)), - Bundle(opts) => opts - .bundle(bin.clone()) + Bundle(opts) if bin.is_ok() => opts + .bundle(Some(bin.unwrap().clone())) .map_err(|e| anyhow!("🚫 Bundling project failed: {}", e)), #[cfg(feature = "plugin")] @@ -118,5 +109,6 @@ async fn main() -> anyhow::Result<()> { Ok(()) } + _ => Err(anyhow::anyhow!(bin.unwrap_err())), } } diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 978b97d43..fec1e5a99 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -43,8 +43,6 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let hot_reload_tx = broadcast::channel(100).0; - clear_paths(); - Some(HotReloadState { messages: hot_reload_tx.clone(), file_map: file_map.clone(), @@ -73,6 +71,7 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option move || { let mut current_child = currently_running_child.write().unwrap(); + log::trace!("Killing old process"); current_child.kill()?; let (child, result) = start_desktop(&config)?; *current_child = child; @@ -109,7 +108,14 @@ pub async fn serve(config: CrateConfig, hot_reload_state: Option } async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<()> { - match LocalSocketListener::bind("@dioxusin") { + let metadata = cargo_metadata::MetadataCommand::new() + .no_deps() + .exec() + .unwrap(); + let target_dir = metadata.target_directory.as_std_path(); + let path = target_dir.join("dioxusin"); + clear_paths(&path); + match LocalSocketListener::bind(path) { Ok(local_socket_stream) => { let aborted = Arc::new(Mutex::new(false)); // States @@ -121,9 +127,9 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<() let file_map = hot_reload_state.file_map.clone(); let channels = channels.clone(); let aborted = aborted.clone(); - let _ = local_socket_stream.set_nonblocking(true); move || { loop { + //accept() will block the thread when local_socket_stream is in blocking mode (default) match local_socket_stream.accept() { Ok(mut connection) => { // send any templates than have changed before the socket connected @@ -181,17 +187,14 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<() Ok(()) } -fn clear_paths() { +fn clear_paths(file_socket_path: &std::path::Path) { if cfg!(target_os = "macos") { // On unix, if you force quit the application, it can leave the file socket open // This will cause the local socket listener to fail to open // We check if the file socket is already open from an old session and then delete it - let paths = ["./dioxusin", "./@dioxusin"]; - for path in paths { - let path = std::path::PathBuf::from(path); - if path.exists() { - let _ = std::fs::remove_file(path); - } + + if file_socket_path.exists() { + let _ = std::fs::remove_file(file_socket_path); } } } @@ -212,6 +215,7 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool { pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { // Run the desktop application + log::trace!("Building application"); let result = crate::builder::build_desktop(config, true)?; match &config.executable { @@ -222,6 +226,7 @@ pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { if cfg!(windows) { file.set_extension("exe"); } + log::trace!("Running application from {:?}", file); let child = Command::new(file.to_str().unwrap()).spawn()?; Ok((child, result)) diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index c6073d787..eb4021184 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -32,7 +32,7 @@ async fn setup_file_watcher Result + Send + 'static>( .watcher .watch_path .clone() - .unwrap_or_else(|| vec![PathBuf::from("src")]); + .unwrap_or_else(|| vec![PathBuf::from("src"), PathBuf::from("examples")]); let watcher_config = config.clone(); let mut watcher = notify::recommended_watcher(move |info: notify::Result| { @@ -55,6 +55,16 @@ async fn setup_file_watcher Result + Send + 'static>( break; } + // Workaround for notify and vscode-like editor: + // when edit & save a file in vscode, there will be two notifications, + // the first one is a file with empty content. + // filter the empty file notification to avoid false rebuild during hot-reload + if let Ok(metadata) = fs::metadata(path) { + if metadata.len() == 0 { + continue; + } + } + match rsx_file_map.update_rsx(path, &config.crate_dir) { Ok(UpdateResult::UpdatedRsx(msgs)) => { messages.extend(msgs); @@ -121,12 +131,12 @@ async fn setup_file_watcher Result + Send + 'static>( .unwrap(); for sub_path in allow_watch_path { - watcher - .watch( - &config.crate_dir.join(sub_path), - notify::RecursiveMode::Recursive, - ) - .unwrap(); + if let Err(err) = watcher.watch( + &config.crate_dir.join(sub_path), + notify::RecursiveMode::Recursive, + ) { + log::error!("Failed to watch path: {}", err); + } } Ok(watcher) } diff --git a/packages/cli/src/server/output.rs b/packages/cli/src/server/output.rs index 71323dd29..4e148b0f1 100644 --- a/packages/cli/src/server/output.rs +++ b/packages/cli/src/server/output.rs @@ -22,17 +22,20 @@ pub fn print_console_info( options: PrettierOptions, web_info: Option, ) { - if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") { - "cls" - } else { - "clear" - }) - .output() - { - print!("{}", String::from_utf8_lossy(&native_clearseq.stdout)); - } else { - // Try ANSI-Escape characters - print!("\x1b[2J\x1b[H"); + // Don't clear the screen if the user has set the DIOXUS_LOG environment variable to "trace" so that we can see the logs + if Some("trace") != std::env::var("DIOXUS_LOG").ok().as_deref() { + if let Ok(native_clearseq) = Command::new(if cfg!(target_os = "windows") { + "cls" + } else { + "clear" + }) + .output() + { + print!("{}", String::from_utf8_lossy(&native_clearseq.stdout)); + } else { + // Try ANSI-Escape characters + print!("\x1b[2J\x1b[H"); + } } let mut profile = if config.release { "Release" } else { "Debug" }.to_string(); diff --git a/packages/core-macro/src/component_body_deserializers/component.rs b/packages/core-macro/src/component_body_deserializers/component.rs index e43b1819f..36cda850c 100644 --- a/packages/core-macro/src/component_body_deserializers/component.rs +++ b/packages/core-macro/src/component_body_deserializers/component.rs @@ -31,6 +31,7 @@ fn get_out_comp_fn(orig_comp_fn: &ItemFn, cx_pat: &Pat) -> ItemFn { block: parse_quote! { { #[warn(non_snake_case)] + #[allow(clippy::inline_always)] #[inline(always)] #inner_comp_fn #inner_comp_ident (#cx_pat) diff --git a/packages/core-macro/src/component_body_deserializers/inline_props.rs b/packages/core-macro/src/component_body_deserializers/inline_props.rs index 670a14d88..d51132623 100644 --- a/packages/core-macro/src/component_body_deserializers/inline_props.rs +++ b/packages/core-macro/src/component_body_deserializers/inline_props.rs @@ -186,13 +186,13 @@ fn get_props_docs(fn_ident: &Ident, inputs: Vec<&FnArg>) -> Vec { let arg_name = arg_name.into_token_stream().to_string(); let arg_type = crate::utils::format_type_string(arg_type); - let input_arg_doc = - keep_up_to_n_consecutive_chars(input_arg_doc.trim(), 2, '\n').replace('\n', "
"); + let input_arg_doc = keep_up_to_n_consecutive_chars(input_arg_doc.trim(), 2, '\n') + .replace("\n\n", "

"); let prop_def_link = format!("{props_def_link}::{arg_name}"); let mut arg_doc = format!("- [`{arg_name}`]({prop_def_link}) : `{arg_type}`"); if let Some(deprecation) = deprecation { - arg_doc.push_str("
👎 Deprecated"); + arg_doc.push_str("

👎 Deprecated"); if let Some(since) = deprecation.since { arg_doc.push_str(&format!(" since {since}")); @@ -205,14 +205,16 @@ fn get_props_docs(fn_ident: &Ident, inputs: Vec<&FnArg>) -> Vec { arg_doc.push_str(&format!(": {note}")); } + arg_doc.push_str("

"); + if !input_arg_doc.is_empty() { arg_doc.push_str("
"); } - } else { - arg_doc.push_str("
"); } - arg_doc.push_str(&input_arg_doc); + if !input_arg_doc.is_empty() { + arg_doc.push_str(&format!("

{input_arg_doc}

")); + } props_docs.push(parse_quote! { #[doc = #arg_doc] diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index 605983afc..d7a580705 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -243,10 +243,6 @@ mod field_info { } .into() } - - pub fn type_from_inside_option(&self, check_option_name: bool) -> Option<&syn::Type> { - type_from_inside_option(self.ty, check_option_name) - } } #[derive(Debug, Default, Clone)] @@ -551,18 +547,16 @@ mod struct_info { let generics_with_empty = modify_types_generics_hack(&ty_generics, |args| { args.insert(0, syn::GenericArgument::Type(empties_tuple.clone().into())); }); - let phantom_generics = self.generics.params.iter().map(|param| match param { + let phantom_generics = self.generics.params.iter().filter_map(|param| match param { syn::GenericParam::Lifetime(lifetime) => { let lifetime = &lifetime.lifetime; - quote!(::core::marker::PhantomData<&#lifetime ()>) + Some(quote!(::core::marker::PhantomData<&#lifetime ()>)) } syn::GenericParam::Type(ty) => { let ty = &ty.ident; - quote!(::core::marker::PhantomData<#ty>) - } - syn::GenericParam::Const(_cnst) => { - quote!() + Some(quote!(::core::marker::PhantomData<#ty>)) } + syn::GenericParam::Const(_cnst) => None, }); let builder_method_doc = match self.builder_attr.builder_method_doc { Some(ref doc) => quote!(#doc), @@ -633,7 +627,7 @@ Finally, call `.build()` to create the instance of `{name}`. Ok(quote! { impl #impl_generics #name #ty_generics #where_clause { #[doc = #builder_method_doc] - #[allow(dead_code)] + #[allow(dead_code, clippy::type_complexity)] #vis fn builder() -> #builder_name #generics_with_empty { #builder_name { fields: #empties_tuple, @@ -785,31 +779,16 @@ Finally, call `.build()` to create the instance of `{name}`. None => quote!(), }; - // NOTE: both auto_into and strip_option affect `arg_type` and `arg_expr`, but the order of - // nesting is different so we have to do this little dance. - let arg_type = if field.builder_attr.strip_option { - field.type_from_inside_option(false).ok_or_else(|| { - Error::new_spanned( - field_type, - "can't `strip_option` - field is not `Option<...>`", + let arg_type = field_type; + let (arg_type, arg_expr) = + if field.builder_attr.auto_into || field.builder_attr.strip_option { + ( + quote!(impl ::core::convert::Into<#arg_type>), + quote!(#field_name.into()), ) - })? - } else { - field_type - }; - let (arg_type, arg_expr) = if field.builder_attr.auto_into { - ( - quote!(impl ::core::convert::Into<#arg_type>), - quote!(#field_name.into()), - ) - } else { - (quote!(#arg_type), quote!(#field_name)) - }; - let arg_expr = if field.builder_attr.strip_option { - quote!(Some(#arg_expr)) - } else { - arg_expr - }; + } else { + (quote!(#arg_type), quote!(#field_name)) + }; let repeated_fields_error_type_name = syn::Ident::new( &format!( @@ -825,6 +804,7 @@ Finally, call `.build()` to create the instance of `{name}`. #[allow(dead_code, non_camel_case_types, missing_docs)] impl #impl_generics #builder_name < #( #ty_generics ),* > #where_clause { #doc + #[allow(clippy::type_complexity)] pub fn #field_name (self, #field_name: #arg_type) -> #builder_name < #( #target_generics ),* > { let #field_name = (#arg_expr,); let ( #(#descructuring,)* ) = self.fields; @@ -843,6 +823,7 @@ Finally, call `.build()` to create the instance of `{name}`. #[deprecated( note = #repeated_fields_error_message )] + #[allow(clippy::type_complexity)] pub fn #field_name (self, _: #repeated_fields_error_type_name) -> #builder_name < #( #target_generics ),* > { self } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index b7f768755..785d4b174 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -164,17 +164,11 @@ impl VirtualDom { }); // Now that all the references are gone, we can safely drop our own references in our listeners. - let mut listeners = scope.attributes_to_drop.borrow_mut(); + let mut listeners = scope.attributes_to_drop_before_render.borrow_mut(); listeners.drain(..).for_each(|listener| { let listener = unsafe { &*listener }; - match &listener.value { - AttributeValue::Listener(l) => { - _ = l.take(); - } - AttributeValue::Any(a) => { - _ = a.take(); - } - _ => (), + if let AttributeValue::Listener(l) = &listener.value { + _ = l.take(); } }); } diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs index 0fe7b3867..6927bd8bc 100644 --- a/packages/core/src/bump_frame.rs +++ b/packages/core/src/bump_frame.rs @@ -1,10 +1,16 @@ use crate::nodes::RenderReturn; +use crate::{Attribute, AttributeValue, VComponent}; use bumpalo::Bump; +use std::cell::RefCell; use std::cell::{Cell, UnsafeCell}; pub(crate) struct BumpFrame { pub bump: UnsafeCell, pub node: Cell<*const RenderReturn<'static>>, + + // The bump allocator will not call the destructor of the objects it allocated. Attributes and props need to have there destructor called, so we keep a list of them to drop before the bump allocator is reset. + pub(crate) attributes_to_drop_before_reset: RefCell>>, + pub(crate) props_to_drop_before_reset: RefCell>>, } impl BumpFrame { @@ -13,6 +19,8 @@ impl BumpFrame { Self { bump: UnsafeCell::new(bump), node: Cell::new(std::ptr::null()), + attributes_to_drop_before_reset: Default::default(), + props_to_drop_before_reset: Default::default(), } } @@ -31,8 +39,38 @@ impl BumpFrame { unsafe { &*self.bump.get() } } - #[allow(clippy::mut_from_ref)] - pub(crate) unsafe fn bump_mut(&self) -> &mut Bump { - unsafe { &mut *self.bump.get() } + pub(crate) fn add_attribute_to_drop(&self, attribute: *const Attribute<'static>) { + self.attributes_to_drop_before_reset + .borrow_mut() + .push(attribute); + } + + /// Reset the bump allocator and drop all the attributes and props that were allocated in it. + /// + /// # Safety + /// The caller must insure that no reference to anything allocated in the bump allocator is available after this function is called. + pub(crate) unsafe fn reset(&self) { + let mut attributes = self.attributes_to_drop_before_reset.borrow_mut(); + attributes.drain(..).for_each(|attribute| { + let attribute = unsafe { &*attribute }; + if let AttributeValue::Any(l) = &attribute.value { + _ = l.take(); + } + }); + let mut props = self.props_to_drop_before_reset.borrow_mut(); + props.drain(..).for_each(|prop| { + let prop = unsafe { &*prop }; + _ = prop.props.borrow_mut().take(); + }); + unsafe { + let bump = &mut *self.bump.get(); + bump.reset(); + } + } +} + +impl Drop for BumpFrame { + fn drop(&mut self) { + unsafe { self.reset() } } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 74017db0f..2de862218 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -560,7 +560,7 @@ impl<'b> VirtualDom { // If none of the old keys are reused by the new children, then we remove all the remaining old children and // create the new children afresh. if shared_keys.is_empty() { - if old.get(0).is_some() { + if old.first().is_some() { self.remove_nodes(&old[1..]); self.replace(&old[0], new); } else { diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 3b8edb05c..784a8c865 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -107,8 +107,6 @@ impl std::fmt::Debug for Event { } } -#[doc(hidden)] - /// The callback type generated by the `rsx!` macro when an `on` field is specified for components. /// /// This makes it possible to pass `move |evt| {}` style closures into components as property fields. diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 811dbb734..3189086b4 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -23,8 +23,37 @@ use crate::{innerlude::VNode, ScopeState}; /// /// /// ```rust, ignore -/// LazyNodes::new(|f| f.element("div", [], [], [] None)) +/// LazyNodes::new(|f| { +/// static TEMPLATE: dioxus::core::Template = dioxus::core::Template { +/// name: "main.rs:5:5:20", // Source location of the template for hot reloading +/// roots: &[ +/// dioxus::core::TemplateNode::Element { +/// tag: dioxus_elements::div::TAG_NAME, +/// namespace: dioxus_elements::div::NAME_SPACE, +/// attrs: &[], +/// children: &[], +/// }, +/// ], +/// node_paths: &[], +/// attr_paths: &[], +/// }; +/// dioxus::core::VNode { +/// parent: None, +/// key: None, +/// template: std::cell::Cell::new(TEMPLATE), +/// root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in( +/// 1usize, +/// f.bump(), +/// ) +/// .into(), +/// dynamic_nodes: f.bump().alloc([]), +/// dynamic_attrs: f.bump().alloc([]), +/// }) +/// } /// ``` +/// +/// Find more information about how to construct [`VNode`] at + pub struct LazyNodes<'a, 'b> { #[cfg(not(miri))] inner: SmallBox VNode<'a> + 'b, S16>, @@ -61,7 +90,7 @@ impl<'a, 'b> LazyNodes<'a, 'b> { /// Call the closure with the given factory to produce real [`VNode`]. /// /// ```rust, ignore - /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None)); + /// let f = LazyNodes::new(/* Closure for creating VNodes */); /// /// let node = f.call(cac); /// ``` diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 27976c157..f33590e2e 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -91,7 +91,7 @@ pub enum Mutation<'a> { id: ElementId, }, - /// Create an placeholder int he DOM that we will use later. + /// Create a placeholder in the DOM that we will use later. /// /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing CreatePlaceholder { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index c83b1059a..78868d29f 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -707,7 +707,7 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str { impl IntoDynNode<'_> for String { fn into_vnode(self, cx: &ScopeState) -> DynamicNode { DynamicNode::Text(VText { - value: cx.bump().alloc(self), + value: cx.bump().alloc_str(&self), id: Default::default(), }) } @@ -791,6 +791,12 @@ impl<'a> IntoAttributeValue<'a> for &'a str { } } +impl<'a> IntoAttributeValue<'a> for String { + fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> { + AttributeValue::Text(cx.alloc_str(&self)) + } +} + impl<'a> IntoAttributeValue<'a> for f64 { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Float(self) diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 1ed5b816c..397f5328e 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -35,7 +35,7 @@ impl VirtualDom { hook_idx: Default::default(), borrowed_props: Default::default(), - attributes_to_drop: Default::default(), + attributes_to_drop_before_render: Default::default(), })); let context = @@ -54,7 +54,7 @@ impl VirtualDom { let new_nodes = unsafe { let scope = &self.scopes[scope_id.0]; - scope.previous_frame().bump_mut().reset(); + scope.previous_frame().reset(); scope.context().suspended.set(false); diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 996529a0d..f0bbd776c 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -94,7 +94,7 @@ pub struct ScopeState { pub(crate) hook_idx: Cell, pub(crate) borrowed_props: RefCell>>, - pub(crate) attributes_to_drop: RefCell>>, + pub(crate) attributes_to_drop_before_render: RefCell>>, pub(crate) props: Option>>, } @@ -348,25 +348,36 @@ impl<'src> ScopeState { pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> { let element = rsx.call(self); - let mut listeners = self.attributes_to_drop.borrow_mut(); + let mut listeners = self.attributes_to_drop_before_render.borrow_mut(); for attr in element.dynamic_attrs { match attr.value { - AttributeValue::Any(_) | AttributeValue::Listener(_) => { + // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped + AttributeValue::Listener(_) => { let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; listeners.push(unbounded); } + // We need to drop any values manually to make sure that their drop implementation is called before the next render + AttributeValue::Any(_) => { + let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; + self.previous_frame().add_attribute_to_drop(unbounded); + } _ => (), } } let mut props = self.borrowed_props.borrow_mut(); + let mut drop_props = self + .previous_frame() + .props_to_drop_before_reset + .borrow_mut(); for node in element.dynamic_nodes { if let DynamicNode::Component(comp) = node { + let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) }; if !comp.static_props { - let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) }; props.push(unbounded); } + drop_props.push(unbounded); } } diff --git a/packages/desktop/Cargo.toml b/packages/desktop/Cargo.toml index dcb994120..f0ea9e189 100644 --- a/packages/desktop/Cargo.toml +++ b/packages/desktop/Cargo.toml @@ -59,6 +59,7 @@ devtools = ["wry/devtools"] tray = ["wry/tray"] dox = ["wry/dox"] hot-reload = ["dioxus-hot-reload"] +gnu = [] [package.metadata.docs.rs] default-features = false diff --git a/packages/desktop/build.rs b/packages/desktop/build.rs new file mode 100644 index 000000000..2061732e8 --- /dev/null +++ b/packages/desktop/build.rs @@ -0,0 +1,9 @@ +fn main() { + // WARN about wry support on windows gnu targets. GNU windows targets don't work well in wry currently + if std::env::var("CARGO_CFG_WINDOWS").is_ok() + && std::env::var("CARGO_CFG_TARGET_ENV").unwrap() == "gnu" + && !cfg!(feature = "gnu") + { + println!("cargo:warning=GNU windows targets have some limitations within Wry. Using the MSVC windows toolchain is recommended. If you would like to use continue using GNU, you can read https://github.com/wravery/webview2-rs#cross-compilation and disable this warning by adding the gnu feature to dioxus-desktop in your Cargo.toml") + } +} diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index badbe3276..63a8914f3 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -54,7 +54,7 @@ use wry::{application::window::WindowId, webview::WebContext}; /// /// This function will start a multithreaded Tokio runtime as well the WebView event loop. /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; /// /// fn main() { @@ -77,11 +77,12 @@ pub fn launch(root: Component) { /// /// You can configure the WebView window with a configuration closure /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; +/// use dioxus_desktop::*; /// /// fn main() { -/// dioxus_desktop::launch_cfg(app, |c| c.with_window(|w| w.with_title("My App"))); +/// dioxus_desktop::launch_cfg(app, Config::default().with_window(WindowBuilder::new().with_title("My App"))); /// } /// /// fn app(cx: Scope) -> Element { @@ -100,8 +101,9 @@ pub fn launch_cfg(root: Component, config_builder: Config) { /// /// You can configure the WebView window with a configuration closure /// -/// ```rust, ignore +/// ```rust, no_run /// use dioxus::prelude::*; +/// use dioxus_desktop::Config; /// /// fn main() { /// dioxus_desktop::launch_with_props(app, AppProps { name: "asd" }, Config::default()); @@ -161,6 +163,7 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) // iOS panics if we create a window before the event loop is started let props = Rc::new(Cell::new(Some(props))); let cfg = Rc::new(Cell::new(Some(cfg))); + let mut is_visible_before_start = true; event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; @@ -210,6 +213,8 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) // Create a dom let dom = VirtualDom::new_with_props(root, props); + is_visible_before_start = cfg.window.window.visible; + let handler = create_new_window( cfg, event_loop, @@ -323,6 +328,10 @@ pub fn launch_with_props(root: Component

, props: P, cfg: Config) EventData::Ipc(msg) if msg.method() == "initialize" => { let view = webviews.get_mut(&event.1).unwrap(); send_edits(view.dom.rebuild(), &view.desktop_context.webview); + view.desktop_context + .webview + .window() + .set_visible(is_visible_before_start); } EventData::Ipc(msg) if msg.method() == "browser_open" => { diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index 469aa8068..6e6d26b17 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -13,7 +13,7 @@ pub fn build( proxy: EventLoopProxy, ) -> (WebView, WebContext) { let builder = cfg.window.clone(); - let window = builder.build(event_loop).unwrap(); + let window = builder.with_visible(false).build(event_loop).unwrap(); let file_handler = cfg.file_drop_handler.take(); let custom_head = cfg.custom_head.clone(); let index_file = cfg.custom_index.clone(); diff --git a/packages/dioxus-tui/examples/colorpicker.rs b/packages/dioxus-tui/examples/colorpicker.rs index 00f8ef7e0..01cc38cda 100644 --- a/packages/dioxus-tui/examples/colorpicker.rs +++ b/packages/dioxus-tui/examples/colorpicker.rs @@ -15,21 +15,21 @@ fn app(cx: Scope) -> Element { let mapping: DioxusElementToNodeId = cx.consume_context().unwrap(); // disable templates so that every node has an id and can be queried cx.render(rsx! { - div{ + div { width: "100%", background_color: "hsl({hue}, 70%, {brightness}%)", onmousemove: move |evt| { if let RenderReturn::Ready(node) = cx.root_node() { - if let Some(id) = node.root_ids.borrow().get(0).cloned() { + if let Some(id) = node.root_ids.borrow().first().cloned() { let node = tui_query.get(mapping.get_node_id(id).unwrap()); - let Size{width, height} = node.size().unwrap(); + let Size { width, height } = node.size().unwrap(); let pos = evt.inner().element_coordinates(); - hue.set((pos.x as f32/width as f32)*255.0); - brightness.set((pos.y as f32/height as f32)*100.0); + hue.set((pos.x as f32 / width as f32) * 255.0); + brightness.set((pos.y as f32 / height as f32) * 100.0); } } }, - "hsl({hue}, 70%, {brightness}%)", + "hsl({hue}, 70%, {brightness}%)" } }) } diff --git a/packages/extension/src/lib.rs b/packages/extension/src/lib.rs index abcdaf598..fb9cca8a9 100644 --- a/packages/extension/src/lib.rs +++ b/packages/extension/src/lib.rs @@ -1,17 +1,39 @@ //! This file exports functions into the vscode extension -use dioxus_autofmt::FormattedBlock; +use dioxus_autofmt::{FormattedBlock, IndentOptions, IndentType}; use wasm_bindgen::prelude::*; #[wasm_bindgen] -pub fn format_rsx(raw: String) -> String { - let block = dioxus_autofmt::fmt_block(&raw, 0); +pub fn format_rsx(raw: String, use_tabs: bool, indent_size: usize) -> String { + let block = dioxus_autofmt::fmt_block( + &raw, + 0, + IndentOptions::new( + if use_tabs { + IndentType::Tabs + } else { + IndentType::Spaces + }, + indent_size, + ), + ); block.unwrap() } #[wasm_bindgen] -pub fn format_selection(raw: String) -> String { - let block = dioxus_autofmt::fmt_block(&raw, 0); +pub fn format_selection(raw: String, use_tabs: bool, indent_size: usize) -> String { + let block = dioxus_autofmt::fmt_block( + &raw, + 0, + IndentOptions::new( + if use_tabs { + IndentType::Tabs + } else { + IndentType::Spaces + }, + indent_size, + ), + ); block.unwrap() } @@ -35,8 +57,18 @@ impl FormatBlockInstance { } #[wasm_bindgen] -pub fn format_file(contents: String) -> FormatBlockInstance { - let _edits = dioxus_autofmt::fmt_file(&contents); +pub fn format_file(contents: String, use_tabs: bool, indent_size: usize) -> FormatBlockInstance { + let _edits = dioxus_autofmt::fmt_file( + &contents, + IndentOptions::new( + if use_tabs { + IndentType::Tabs + } else { + IndentType::Spaces + }, + indent_size, + ), + ); let out = dioxus_autofmt::apply_formats(&contents, _edits.clone()); FormatBlockInstance { new: out, _edits } } diff --git a/packages/extension/src/main.ts b/packages/extension/src/main.ts index fc12457ad..76b7261f1 100644 --- a/packages/extension/src/main.ts +++ b/packages/extension/src/main.ts @@ -90,7 +90,13 @@ function fmtDocument(document: vscode.TextDocument) { if (!editor) return; // Need an editor to apply text edits. const contents = editor.document.getText(); - const formatted = dioxus.format_file(contents); + let tabSize: number; + if (typeof editor.options.tabSize === 'number') { + tabSize = editor.options.tabSize; + } else { + tabSize = 4; + } + const formatted = dioxus.format_file(contents, !editor.options.insertSpaces, tabSize); // Replace the entire text document // Yes, this is a bit heavy handed, but the dioxus side doesn't know the line/col scheme that vscode is using diff --git a/packages/fermi/src/hooks/atom_root.rs b/packages/fermi/src/hooks/atom_root.rs index 24fd70093..468f5f980 100644 --- a/packages/fermi/src/hooks/atom_root.rs +++ b/packages/fermi/src/hooks/atom_root.rs @@ -7,6 +7,6 @@ use dioxus_core::ScopeState; pub fn use_atom_root(cx: &ScopeState) -> &Rc { cx.use_hook(|| match cx.consume_context::>() { Some(root) => root, - None => panic!("No atom root found in context. Did you forget place an AtomRoot component at the top of your app?"), + None => panic!("No atom root found in context. Did you forget to call use_init_atom_root at the top of your app?"), }) } diff --git a/packages/fermi/src/hooks/state.rs b/packages/fermi/src/hooks/state.rs index d4ebc529c..7d473588a 100644 --- a/packages/fermi/src/hooks/state.rs +++ b/packages/fermi/src/hooks/state.rs @@ -86,7 +86,9 @@ impl AtomState { /// ``` #[must_use] pub fn current(&self) -> Rc { - self.value.as_ref().unwrap().clone() + let atoms = self.root.atoms.borrow(); + let slot = atoms.get(&self.id).unwrap(); + slot.value.clone().downcast().unwrap() } /// Get the `setter` function directly without the `AtomState` wrapper. diff --git a/packages/fermi/src/lib.rs b/packages/fermi/src/lib.rs index 52bf4cd09..dc5ccf010 100644 --- a/packages/fermi/src/lib.rs +++ b/packages/fermi/src/lib.rs @@ -22,8 +22,6 @@ mod atoms { pub use atom::*; pub use atomfamily::*; pub use atomref::*; - pub use selector::*; - pub use selectorfamily::*; } pub mod hooks { diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml index 8ab213e78..8de27a9b1 100644 --- a/packages/fullstack/Cargo.toml +++ b/packages/fullstack/Cargo.toml @@ -11,7 +11,7 @@ keywords = ["ui", "gui", "react", "ssr", "fullstack"] [dependencies] # server functions -server_fn = { version = "0.4.6", default-features = false } +server_fn = { version = "0.5.2", default-features = false } dioxus_server_macro = { workspace = true } # warp diff --git a/packages/fullstack/examples/axum-hello-world/src/main.rs b/packages/fullstack/examples/axum-hello-world/src/main.rs index 8f1d3c8c6..64e5b44a0 100644 --- a/packages/fullstack/examples/axum-hello-world/src/main.rs +++ b/packages/fullstack/examples/axum-hello-world/src/main.rs @@ -24,6 +24,7 @@ fn app(cx: Scope) -> Element { let mut count = use_state(cx, || 0); let text = use_state(cx, || "...".to_string()); + let eval = use_eval(cx); cx.render(rsx! { div { diff --git a/packages/fullstack/src/adapters/axum_adapter.rs b/packages/fullstack/src/adapters/axum_adapter.rs index 95d9ee6cd..41a7c92be 100644 --- a/packages/fullstack/src/adapters/axum_adapter.rs +++ b/packages/fullstack/src/adapters/axum_adapter.rs @@ -369,15 +369,65 @@ fn apply_request_parts_to_response( } } -/// SSR renderer handler for Axum -pub async fn render_handler( - State((cfg, ssr_state)): State<(ServeConfig

, SSRState)>, +/// SSR renderer handler for Axum with added context injection. +/// +/// # Example +/// ```rust,no_run +/// #![allow(non_snake_case)] +/// use std::sync::{Arc, Mutex}; +/// +/// use axum::routing::get; +/// use dioxus::prelude::*; +/// use dioxus_fullstack::{axum_adapter::render_handler_with_context, prelude::*}; +/// +/// fn app(cx: Scope) -> Element { +/// render! { +/// "hello!" +/// } +/// } +/// +/// #[tokio::main] +/// async fn main() { +/// let cfg = ServeConfigBuilder::new(app, ()) +/// .assets_path("dist") +/// .build(); +/// let ssr_state = SSRState::new(&cfg); +/// +/// // This could be any state you want to be accessible from your server +/// // functions using `[DioxusServerContext::get]`. +/// let state = Arc::new(Mutex::new("state".to_string())); +/// +/// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); +/// axum::Server::bind(&addr) +/// .serve( +/// axum::Router::new() +/// // Register server functions, etc. +/// // Note you probably want to use `register_server_fns_with_handler` +/// // to inject the context into server functions running outside +/// // of an SSR render context. +/// .fallback(get(render_handler_with_context).with_state(( +/// move |ctx| ctx.insert(state.clone()).unwrap(), +/// cfg, +/// ssr_state, +/// ))) +/// .into_make_service(), +/// ) +/// .await +/// .unwrap(); +/// } +/// ``` +pub async fn render_handler_with_context< + P: Clone + serde::Serialize + Send + Sync + 'static, + F: FnMut(&mut DioxusServerContext), +>( + State((mut inject_context, cfg, ssr_state)): State<(F, ServeConfig

, SSRState)>, request: Request, ) -> impl IntoResponse { let (parts, _) = request.into_parts(); let url = parts.uri.path_and_query().unwrap().to_string(); let parts: Arc> = Arc::new(RwLock::new(parts.into())); - let server_context = DioxusServerContext::new(parts.clone()); + let mut server_context = DioxusServerContext::new(parts.clone()); + inject_context(&mut server_context); match ssr_state.render(url, &cfg, &server_context).await { Ok(rendered) => { @@ -395,6 +445,14 @@ pub async fn render_handler } } +/// SSR renderer handler for Axum +pub async fn render_handler( + State((cfg, ssr_state)): State<(ServeConfig

, SSRState)>, + request: Request, +) -> impl IntoResponse { + render_handler_with_context(State((|_: &mut _| (), cfg, ssr_state)), request).await +} + fn report_err(e: E) -> Response { Response::builder() .status(StatusCode::INTERNAL_SERVER_ERROR) diff --git a/packages/fullstack/src/adapters/mod.rs b/packages/fullstack/src/adapters/mod.rs index 98d190919..d368b9c73 100644 --- a/packages/fullstack/src/adapters/mod.rs +++ b/packages/fullstack/src/adapters/mod.rs @@ -89,26 +89,26 @@ impl Service for ServerFnHandler { let parts = Arc::new(RwLock::new(parts)); // Because the future returned by `server_fn_handler` is `Send`, and the future returned by this function must be send, we need to spawn a new runtime - let (resp_tx, resp_rx) = tokio::sync::oneshot::channel(); let pool = get_local_pool(); - pool.spawn_pinned({ - let function = function.clone(); - let mut server_context = server_context.clone(); - server_context.parts = parts; - move || async move { - let data = match function.encoding() { - Encoding::Url | Encoding::Cbor => &body, - Encoding::GetJSON | Encoding::GetCBOR => &query, - }; - let server_function_future = function.call((), data); - let server_function_future = - ProvideServerContext::new(server_function_future, server_context.clone()); - let resp = server_function_future.await; - - resp_tx.send(resp).unwrap(); - } - }); - let result = resp_rx.await.unwrap(); + let result = pool + .spawn_pinned({ + let function = function.clone(); + let mut server_context = server_context.clone(); + server_context.parts = parts; + move || async move { + let data = match function.encoding() { + Encoding::Url | Encoding::Cbor => &body, + Encoding::GetJSON | Encoding::GetCBOR => &query, + }; + let server_function_future = function.call((), data); + let server_function_future = ProvideServerContext::new( + server_function_future, + server_context.clone(), + ); + server_function_future.await + } + }) + .await?; let mut res = http::Response::builder(); // Set the headers from the server context diff --git a/packages/fullstack/src/layer.rs b/packages/fullstack/src/layer.rs index 10e67b3f5..60f76107b 100644 --- a/packages/fullstack/src/layer.rs +++ b/packages/fullstack/src/layer.rs @@ -3,7 +3,9 @@ use tracing_futures::Instrument; use http::{Request, Response}; +/// A layer that wraps a service. This can be used to add additional information to the request, or response on top of some other service pub trait Layer: Send + Sync + 'static { + /// Wrap a boxed service with this layer fn layer(&self, inner: BoxedService) -> BoxedService; } @@ -17,7 +19,9 @@ where } } +/// A service is a function that takes a request and returns an async response pub trait Service { + /// Run the service and produce a future that resolves to a response fn run( &mut self, req: http::Request, @@ -55,6 +59,7 @@ where } } +/// A boxed service is a type-erased service that can be used without knowing the underlying type pub struct BoxedService(pub Box); impl tower::Service> for BoxedService { diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index 1fe7504f1..7ef582da1 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -40,6 +40,8 @@ pub mod prelude { #[cfg(not(feature = "ssr"))] pub use crate::html_storage::deserialize::get_root_props_from_document; pub use crate::launch::LaunchBuilder; + #[cfg(feature = "ssr")] + pub use crate::layer::{Layer, Service}; #[cfg(all(feature = "ssr", feature = "router"))] pub use crate::render::pre_cache_static_routes_with_props; #[cfg(feature = "ssr")] diff --git a/packages/fullstack/src/render.rs b/packages/fullstack/src/render.rs index cda41f82c..886a5bd99 100644 --- a/packages/fullstack/src/render.rs +++ b/packages/fullstack/src/render.rs @@ -45,6 +45,8 @@ impl SsrRendererPool { .expect("couldn't spawn runtime") .block_on(async move { let mut vdom = VirtualDom::new_with_props(component, props); + // Make sure the evaluator is initialized + dioxus_ssr::eval::init_eval(vdom.base_scope()); let mut to = WriteBuffer { buffer: Vec::new() }; // before polling the future, we need to set the context let prev_context = diff --git a/packages/fullstack/src/router.rs b/packages/fullstack/src/router.rs index b556aa000..b8d93f0e0 100644 --- a/packages/fullstack/src/router.rs +++ b/packages/fullstack/src/router.rs @@ -53,7 +53,7 @@ fn default_external_navigation_handler() -> fn(Scope) -> Element { dioxus_router::prelude::FailureExternalNavigation } -/// The configeration for the router +/// The configuration for the router #[derive(Props, serde::Serialize, serde::Deserialize)] pub struct FullstackRouterConfig where diff --git a/packages/fullstack/src/server_fn.rs b/packages/fullstack/src/server_fn.rs index 539a0d70a..fd0639372 100644 --- a/packages/fullstack/src/server_fn.rs +++ b/packages/fullstack/src/server_fn.rs @@ -125,14 +125,6 @@ impl server_fn::ServerFunctionRegistry<()> for DioxusServerFnRegistry { } } - fn register( - url: &'static str, - server_function: ServerFunction, - encoding: server_fn::Encoding, - ) -> Result<(), Self::Error> { - Self::register_explicit("", url, server_function, encoding) - } - /// Returns the server function registered at the given URL, or `None` if no function is registered at that URL. fn get(url: &str) -> Option> { REGISTERED_SERVER_FUNCTIONS diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 5567e9ccc..1bc298e25 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -1,9 +1,12 @@ [package] name = "generational-box" authors = ["Evan Almloff"] -version = "0.0.0" +version = "0.4.3" edition = "2018" - +description = "A box backed by a generational runtime" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +keywords = ["generational", "box", "memory", "allocator"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] @@ -17,6 +20,8 @@ criterion = "0.3" [features] default = ["check_generation"] check_generation = [] +debug_borrows = [] +debug_ownership = [] [[bench]] name = "lock" diff --git a/packages/generational-box/README.md b/packages/generational-box/README.md index 7d3088eda..95aab8402 100644 --- a/packages/generational-box/README.md +++ b/packages/generational-box/README.md @@ -11,6 +11,8 @@ Three main types manage state in Generational Box: Example: ```rust +use generational_box::Store; + // Create a store for this thread let store = Store::default(); diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 692af76b0..293bf6c5b 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -4,12 +4,16 @@ use parking_lot::{ MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, }; +use std::any::Any; +use std::error::Error; +use std::fmt::Display; +use std::sync::atomic::AtomicU32; use std::{ cell::{Ref, RefCell, RefMut}, fmt::Debug, marker::PhantomData, ops::{Deref, DerefMut}, - sync::{atomic::AtomicU32, Arc, OnceLock}, + sync::{Arc, OnceLock}, }; /// # Example @@ -52,7 +56,10 @@ fn leaking_is_ok() { // don't drop the owner std::mem::forget(owner); } - assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string())); + assert_eq!( + key.try_read().as_deref().unwrap(), + &"hello world".to_string() + ); } #[test] @@ -66,7 +73,7 @@ fn drops() { key = owner.insert(data); // drop the owner } - assert!(key.try_read().is_none()); + assert!(key.try_read().is_err()); } #[test] @@ -123,7 +130,7 @@ fn fuzz() { println!("{:?}", path); for key in valid_keys.iter() { let value = key.read(); - println!("{:?}", value); + println!("{:?}", &*value); assert!(value.starts_with("hello world")); } #[cfg(any(debug_assertions, feature = "check_generation"))] @@ -164,10 +171,12 @@ impl Debug for GenerationalBoxId { } /// The core Copy state type. The generational box will be dropped when the [Owner] is dropped. -pub struct GenerationalBox { +pub struct GenerationalBox { raw: MemoryLocation, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: u32, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, _marker: PhantomData, } @@ -176,11 +185,11 @@ impl Debug for GenerationalBox { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!( "{:?}@{:?}", - self.raw.data.data_ptr(), + self.raw.0.data.data_ptr(), self.generation ))?; #[cfg(not(any(debug_assertions, feature = "check_generation")))] - f.write_fmt(format_args!("{:?}", self.raw.data.as_ptr()))?; + f.write_fmt(format_args!("{:?}", self.raw.0.data.as_ptr()))?; Ok(()) } } @@ -191,6 +200,7 @@ impl> GenerationalBox { #[cfg(any(debug_assertions, feature = "check_generation"))] { self.raw + .0 .generation .load(std::sync::atomic::Ordering::Relaxed) == self.generation @@ -204,28 +214,54 @@ impl> GenerationalBox { /// Get the id of the generational box. pub fn id(&self) -> GenerationalBoxId { GenerationalBoxId { - data_ptr: self.raw.data.data_ptr(), + data_ptr: self.raw.0.data.data_ptr(), #[cfg(any(debug_assertions, feature = "check_generation"))] generation: self.generation, } } /// Try to read the value. Returns None if the value is no longer valid. - pub fn try_read(&self) -> Option { - self.validate().then(|| self.raw.data.try_read()).flatten() + #[track_caller] + pub fn try_read(&self) -> Result { + if !self.validate() { + return Err(BorrowError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: self.created_at, + })); + } + self.raw.0.data.try_read().ok_or_else(|| { + BorrowError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: self.created_at, + }) + }) } /// Read the value. Panics if the value is no longer valid. + #[track_caller] pub fn read(&self) -> S::Ref { self.try_read().unwrap() } /// Try to write the value. Returns None if the value is no longer valid. - pub fn try_write(&self) -> Option where { - self.validate().then(|| self.raw.data.try_write()).flatten() + #[track_caller] + pub fn try_write(&self) -> Result { + if !self.validate() { + return Err(BorrowMutError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: self.created_at, + })); + } + self.raw.0.data.try_write().ok_or_else(|| { + BorrowMutError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: self.created_at, + }) + }) } /// Write the value. Panics if the value is no longer valid. + #[track_caller] pub fn write(&self) -> S::Mut { self.try_write().unwrap() } @@ -233,7 +269,7 @@ impl> GenerationalBox { /// Set the value. Panics if the value is no longer valid. pub fn set(&self, value: T) { self.validate().then(|| { - self.raw.data.set(value); + self.raw.0.data.set(value); }); } @@ -241,7 +277,7 @@ impl> GenerationalBox { pub fn ptr_eq(&self, other: &Self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.data.data_ptr() == other.raw.data.data_ptr() + self.raw.0.data.data_ptr() == other.raw.0.data.data_ptr() && self.generation == other.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] @@ -251,7 +287,7 @@ impl> GenerationalBox { } } -impl Copy for GenerationalBox {} +impl Copy for GenerationalBox {} impl Clone for GenerationalBox { fn clone(&self) -> Self { @@ -260,12 +296,11 @@ impl Clone for GenerationalBox { } /// A unsync storage. This is the default storage type. -#[derive(Clone, Copy)] -pub struct UnsyncStorage(&'static RefCell>>); +pub struct UnsyncStorage(RefCell>>); impl Default for UnsyncStorage { fn default() -> Self { - Self(Box::leak(Box::new(RefCell::new(None)))) + Self(RefCell::new(None)) } } @@ -370,7 +405,7 @@ impl MappableMut for MappedRwLockWriteGuard<'static, T> { } /// A trait for a storage backing type. (RefCell, RwLock, etc.) -pub trait Storage: Copy + AnyStorage + 'static { +pub trait Storage: AnyStorage + 'static { /// The reference this storage type returns. type Ref: Mappable + Deref; /// The mutable reference this storage type returns. @@ -453,11 +488,17 @@ impl AnyStorage for UnsyncStorage { if let Some(location) = runtime.borrow_mut().pop() { location } else { - MemoryLocation { - data: UnsyncStorage(Box::leak(Box::new(RefCell::new(None)))), - #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: Box::leak(Box::default()), - } + let data: &'static MemoryLocationInner = + &*Box::leak(Box::new(MemoryLocationInner { + data: Self::default(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: 0.into(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: Default::default(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: Default::default(), + })); + MemoryLocation(data) } }) } @@ -501,11 +542,19 @@ impl AnyStorage for SyncStorage { } fn claim() -> MemoryLocation { - MemoryLocation { - data: SyncStorage(Box::leak(Box::new(RwLock::new(None)))), - #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: Box::leak(Box::default()), - } + sync_runtime().lock().pop().unwrap_or_else(|| { + let data: &'static MemoryLocationInner = + &*Box::leak(Box::new(MemoryLocationInner { + data: Self::default(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: 0.into(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: Default::default(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: Default::default(), + })); + MemoryLocation(data) + }) } fn recycle(location: &MemoryLocation) { @@ -514,12 +563,17 @@ impl AnyStorage for SyncStorage { } } -/// A memory location. This is the core type that is used to store values. #[derive(Clone, Copy)] -pub struct MemoryLocation { +struct MemoryLocation(&'static MemoryLocationInner); + +struct MemoryLocationInner { data: S, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: &'static AtomicU32, + generation: AtomicU32, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: RwLock>>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: RwLock>>, } impl MemoryLocation { @@ -528,47 +582,383 @@ impl MemoryLocation { where S: AnyStorage, { - let old = self.data.take(); + let old = self.0.data.take(); #[cfg(any(debug_assertions, feature = "check_generation"))] if old { - let new_generation = self.generation.load(std::sync::atomic::Ordering::Relaxed) + 1; - self.generation + let new_generation = self.0.generation.load(std::sync::atomic::Ordering::Relaxed) + 1; + self.0 + .generation .store(new_generation, std::sync::atomic::Ordering::Relaxed); } } - fn replace(&mut self, value: T) -> GenerationalBox + fn replace_with_caller( + &mut self, + value: T, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + caller: &'static std::panic::Location<'static>, + ) -> GenerationalBox where - S: Storage + Copy, + S: Storage, { - self.data.set(value); + self.0.data.set(value); GenerationalBox { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.generation.load(std::sync::atomic::Ordering::Relaxed), + generation: self.0.generation.load(std::sync::atomic::Ordering::Relaxed), + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: caller, _marker: PhantomData, } } + + #[track_caller] + fn try_borrow( + &self, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, + ) -> Result, BorrowError> + where + S: Storage, + { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + self.0 + .borrowed_at + .write() + .push(std::panic::Location::caller()); + match self.0.data.try_read() { + Some(borrow) => { + match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref::()) { + Ok(reference) => Ok(GenerationalRef { + inner: reference, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: std::panic::Location::caller(), + borrowed_from: self.0, + }, + }), + Err(_) => Err(BorrowError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at, + })), + } + } + None => Err(BorrowError::AlreadyBorrowedMut(AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: self.0.borrowed_mut_at.read().unwrap(), + })), + } + } + + #[track_caller] + fn try_borrow_mut( + &self, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, + ) -> Result, BorrowMutError> { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + { + self.0.borrowed_mut_at.write().unwrap() = Some(std::panic::Location::caller()); + } + match self.0.data.try_borrow_mut() { + Ok(borrow_mut) => { + match RefMut::filter_map(borrow_mut, |any| any.as_mut()?.downcast_mut::()) { + Ok(reference) => Ok(GenerationalRefMut { + inner: reference, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefMutBorrowInfo { + borrowed_from: self.0, + }, + }), + Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at, + })), + } + } + Err(_) => Err(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: self.0.borrowed_at.read().clone(), + })), + } + } +} + +#[derive(Debug, Clone)] +/// An error that can occur when trying to borrow a value. +pub enum BorrowError { + /// The value was dropped. + Dropped(ValueDroppedError), + /// The value was already borrowed mutably. + AlreadyBorrowedMut(AlreadyBorrowedMutError), +} + +impl Display for BorrowError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BorrowError::Dropped(error) => Display::fmt(error, f), + BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f), + } + } +} + +impl Error for BorrowError {} + +#[derive(Debug, Clone)] +/// An error that can occur when trying to borrow a value mutably. +pub enum BorrowMutError { + /// The value was dropped. + Dropped(ValueDroppedError), + /// The value was already borrowed. + AlreadyBorrowed(AlreadyBorrowedError), + /// The value was already borrowed mutably. + AlreadyBorrowedMut(AlreadyBorrowedMutError), +} + +impl Display for BorrowMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BorrowMutError::Dropped(error) => Display::fmt(error, f), + BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f), + BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f), + } + } +} + +impl Error for BorrowMutError {} + +/// An error that can occur when trying to use a value that has been dropped. +#[derive(Debug, Copy, Clone)] +pub struct ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, +} + +impl Display for ValueDroppedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow because the value was dropped.")?; + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + f.write_fmt(format_args!("created_at: {}", self.created_at))?; + Ok(()) + } +} + +impl std::error::Error for ValueDroppedError {} + +/// An error that can occur when trying to borrow a value that has already been borrowed mutably. +#[derive(Debug, Copy, Clone)] +pub struct AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at: &'static std::panic::Location<'static>, +} + +impl Display for AlreadyBorrowedMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow because the value was already borrowed mutably.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?; + Ok(()) + } +} + +impl std::error::Error for AlreadyBorrowedMutError {} + +/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably. +#[derive(Debug, Clone)] +pub struct AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: Vec<&'static std::panic::Location<'static>>, +} + +impl Display for AlreadyBorrowedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_str("borrowed_at:")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + for location in self.borrowed_at.iter() { + f.write_fmt(format_args!("\t{}", location))?; + } + Ok(()) + } +} + +impl std::error::Error for AlreadyBorrowedError {} + +/// A reference to a value in a generational box. +pub struct GenerationalRef { + inner: Ref<'static, T>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo, +} + +impl GenerationalRef { + /// Map one ref type to another. + pub fn map(orig: GenerationalRef, f: F) -> GenerationalRef + where + F: FnOnce(&T) -> &U, + { + GenerationalRef { + inner: Ref::map(orig.inner, f), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: orig.borrow.borrowed_at, + borrowed_from: orig.borrow.borrowed_from, + }, + } + } + + /// Filter one ref type to another. + pub fn filter_map(orig: GenerationalRef, f: F) -> Option> + where + F: FnOnce(&T) -> Option<&U>, + { + let Self { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + } = orig; + Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: borrow.borrowed_at, + borrowed_from: borrow.borrowed_from, + }, + }) + } +} + +impl Deref for GenerationalRef { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +struct GenerationalRefBorrowInfo { + borrowed_at: &'static std::panic::Location<'static>, + borrowed_from: &'static MemoryLocationInner, +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +impl Drop for GenerationalRefBorrowInfo { + fn drop(&mut self) { + self.borrowed_from + .borrowed_at + .borrow_mut() + .retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _)); + } +} + +/// A mutable reference to a value in a generational box. +pub struct GenerationalRefMut { + inner: RefMut<'static, T>, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefMutBorrowInfo, +} + +impl GenerationalRefMut { + /// Map one ref type to another. + pub fn map(orig: GenerationalRefMut, f: F) -> GenerationalRefMut + where + F: FnOnce(&mut T) -> &mut U, + { + GenerationalRefMut { + inner: RefMut::map(orig.inner, f), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: orig.borrow, + } + } + + /// Filter one ref type to another. + pub fn filter_map(orig: GenerationalRefMut, f: F) -> Option> + where + F: FnOnce(&mut T) -> Option<&mut U>, + { + let Self { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + } = orig; + RefMut::filter_map(inner, f) + .ok() + .map(|inner| GenerationalRefMut { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + }) + } +} + +impl Deref for GenerationalRefMut { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl DerefMut for GenerationalRefMut { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +struct GenerationalRefMutBorrowInfo { + borrowed_from: &'static MemoryLocationInner, +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +impl Drop for GenerationalRefMutBorrowInfo { + fn drop(&mut self) { + self.borrowed_from.borrowed_mut_at.take(); + } } /// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped. -pub struct Owner { +pub struct Owner { owned: Arc>>>, phantom: PhantomData, } impl Owner { /// Insert a value into the store. The value will be dropped when the owner is dropped. + #[track_caller] pub fn insert(&self, value: T) -> GenerationalBox where S: Storage, { let mut location = S::claim(); - let key = location.replace(value); + let key = location.replace_with_caller( + value, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + std::panic::Location::caller(), + ); self.owned.lock().push(location); key } + /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped. + pub fn insert_with_caller( + &self, + value: T, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + caller: &'static std::panic::Location<'static>, + ) -> GenerationalBox { + let mut location = self.store.claim(); + let key = location.replace_with_caller( + value, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + caller, + ); + self.owned.borrow_mut().push(location); + key + } + /// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value. pub fn invalid(&self) -> GenerationalBox { let location = S::claim(); @@ -576,8 +966,11 @@ impl Owner { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: location + .0 .generation .load(std::sync::atomic::Ordering::Relaxed), + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: std::panic::Location::caller(), _marker: PhantomData, } } diff --git a/packages/hooks/src/lib.rs b/packages/hooks/src/lib.rs index a5b92a036..8178d4d3d 100644 --- a/packages/hooks/src/lib.rs +++ b/packages/hooks/src/lib.rs @@ -60,6 +60,9 @@ pub mod computed; mod use_on_destroy; pub use use_on_destroy::*; +mod use_const; +pub use use_const::*; + mod use_context; pub use use_context::*; diff --git a/packages/hooks/src/use_const.rs b/packages/hooks/src/use_const.rs new file mode 100644 index 000000000..47ddb1ed2 --- /dev/null +++ b/packages/hooks/src/use_const.rs @@ -0,0 +1,76 @@ +use std::rc::Rc; + +use dioxus_core::prelude::*; + +/// Store constant state between component renders. +/// +/// UseConst allows you to store state that is initialized once and then remains constant across renders. +/// You can only get an immutable reference after initalization. +/// This can be useful for values that don't need to update reactively, thus can be memoized easily +/// +/// ```rust, ignore +/// struct ComplexData(i32); +/// +/// fn Component(cx: Scope) -> Element { +/// let id = use_const(cx, || ComplexData(100)); +/// +/// cx.render(rsx! { +/// div { "{id.0}" } +/// }) +/// } +/// ``` +#[must_use] +pub fn use_const( + cx: &ScopeState, + initial_state_fn: impl FnOnce() -> T, +) -> &UseConst { + cx.use_hook(|| UseConst { + value: Rc::new(initial_state_fn()), + }) +} + +#[derive(Clone)] +pub struct UseConst { + value: Rc, +} + +impl PartialEq for UseConst { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.value, &other.value) + } +} + +impl core::fmt::Display for UseConst { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.value.fmt(f) + } +} + +impl UseConst { + pub fn get_rc(&self) -> &Rc { + &self.value + } +} + +impl std::ops::Deref for UseConst { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.value.as_ref() + } +} + +#[test] +fn use_const_makes_sense() { + #[allow(unused)] + + fn app(cx: Scope) -> Element { + let const_val = use_const(cx, || vec![0, 1, 2, 3]); + + assert!(const_val[0] == 0); + + // const_val.remove(0); // Cannot Compile, cannot get mutable reference now + + None + } +} diff --git a/packages/hooks/src/use_effect.rs b/packages/hooks/src/use_effect.rs index e72afe27f..6fb2fcf9e 100644 --- a/packages/hooks/src/use_effect.rs +++ b/packages/hooks/src/use_effect.rs @@ -1,5 +1,10 @@ use dioxus_core::{ScopeState, TaskId}; -use std::{any::Any, cell::Cell, future::Future}; +use std::{ + any::Any, + cell::{Cell, RefCell}, + future::Future, + rc::Rc, +}; use crate::UseFutureDep; @@ -14,7 +19,7 @@ use crate::UseFutureDep; /// ## Arguments /// /// - `dependencies`: a tuple of references to values that are `PartialEq` + `Clone`. -/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future. +/// - `future`: a closure that takes the `dependencies` as arguments and returns a `'static` future. That future may return nothing or a closure that will be executed when the dependencies change to clean up the effect. /// /// ## Examples /// @@ -33,6 +38,16 @@ use crate::UseFutureDep; /// } /// }); /// +/// // Only fetch the user data when the id changes. +/// use_effect(cx, (id,), |(id,)| { +/// to_owned![name]; +/// async move { +/// let user = fetch_user(id).await; +/// name.set(user.name); +/// move || println!("Cleaning up from {}", id) +/// } +/// }); +/// /// let name = name.get().clone().unwrap_or("Loading...".to_string()); /// /// render!( @@ -45,34 +60,80 @@ use crate::UseFutureDep; /// render!(Profile { id: 0 }) /// } /// ``` -pub fn use_effect(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> F) +pub fn use_effect(cx: &ScopeState, dependencies: D, future: impl FnOnce(D::Out) -> R) where - T: 'static, - F: Future + 'static, D: UseFutureDep, + R: UseEffectReturn, { struct UseEffect { needs_regen: bool, task: Cell>, dependencies: Vec>, + cleanup: UseEffectCleanup, + } + + impl Drop for UseEffect { + fn drop(&mut self) { + if let Some(cleanup) = self.cleanup.borrow_mut().take() { + cleanup(); + } + } } let state = cx.use_hook(move || UseEffect { needs_regen: true, task: Cell::new(None), dependencies: Vec::new(), + cleanup: Rc::new(RefCell::new(None)), }); if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen { + // Call the cleanup function if it exists + if let Some(cleanup) = state.cleanup.borrow_mut().take() { + cleanup(); + } + // We don't need regen anymore state.needs_regen = false; // Create the new future - let fut = future(dependencies.out()); + let return_value = future(dependencies.out()); - state.task.set(Some(cx.push_future(async move { - fut.await; - }))); + if let Some(task) = return_value.apply(state.cleanup.clone(), cx) { + state.task.set(Some(task)); + } + } +} + +type UseEffectCleanup = Rc>>>; + +/// Something that can be returned from a `use_effect` hook. +pub trait UseEffectReturn { + fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option; +} + +impl UseEffectReturn<()> for T +where + T: Future + 'static, +{ + fn apply(self, _: UseEffectCleanup, cx: &ScopeState) -> Option { + Some(cx.push_future(self)) + } +} + +#[doc(hidden)] +pub struct CleanupFutureMarker; +impl UseEffectReturn for T +where + T: Future + 'static, + F: FnOnce() + 'static, +{ + fn apply(self, oncleanup: UseEffectCleanup, cx: &ScopeState) -> Option { + let task = cx.push_future(async move { + let cleanup = self.await; + *oncleanup.borrow_mut() = Some(Box::new(cleanup) as Box); + }); + Some(task) } } diff --git a/packages/hooks/src/use_future.rs b/packages/hooks/src/use_future.rs index 67bcdc74e..b9aa20c31 100644 --- a/packages/hooks/src/use_future.rs +++ b/packages/hooks/src/use_future.rs @@ -31,13 +31,14 @@ where let state = cx.use_hook(move || UseFuture { update: cx.schedule_update(), - needs_regen: Cell::new(true), + needs_regen: Rc::new(Cell::new(true)), state: val.clone(), task: Default::default(), - dependencies: Vec::new(), }); - if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() { + let state_dependencies = cx.use_hook(Vec::new); + + if dependencies.clone().apply(state_dependencies) || state.needs_regen.get() { // kill the old one, if it exists if let Some(task) = state.task.take() { cx.remove_future(task); @@ -69,11 +70,11 @@ pub enum FutureState<'a, T> { Regenerating(&'a T), // the old value } +#[derive(Clone)] pub struct UseFuture { update: Arc, - needs_regen: Cell, + needs_regen: Rc>, task: Rc>>, - dependencies: Vec>, state: UseState>, } diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 37226c759..0c0f43508 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -26,6 +26,7 @@ macro_rules! debug_location { } pub mod error { + #[cfg(debug_assertions)] fn locations_display(locations: &[&'static std::panic::Location<'static>]) -> String { locations .iter() diff --git a/packages/hot-reload/src/file_watcher.rs b/packages/hot-reload/src/file_watcher.rs index 2509dd789..0323561fe 100644 --- a/packages/hot-reload/src/file_watcher.rs +++ b/packages/hot-reload/src/file_watcher.rs @@ -122,7 +122,7 @@ pub fn init(cfg: Config) { } = cfg; if let Ok(crate_dir) = PathBuf::from_str(root_path) { - // try to find the gitingore file + // try to find the gitignore file let gitignore_file_path = crate_dir.join(".gitignore"); let (gitignore, _) = ignore::gitignore::Gitignore::new(gitignore_file_path); @@ -152,21 +152,20 @@ pub fn init(cfg: Config) { } let file_map = Arc::new(Mutex::new(file_map)); + let target_dir = crate_dir.join("target"); + let hot_reload_socket_path = target_dir.join("dioxusin"); + #[cfg(target_os = "macos")] { // On unix, if you force quit the application, it can leave the file socket open // This will cause the local socket listener to fail to open // We check if the file socket is already open from an old session and then delete it - let paths = ["./dioxusin", "./@dioxusin"]; - for path in paths { - let path = PathBuf::from(path); - if path.exists() { - let _ = std::fs::remove_file(path); - } + if hot_reload_socket_path.exists() { + let _ = std::fs::remove_file(hot_reload_socket_path); } } - match LocalSocketListener::bind("@dioxusin") { + match LocalSocketListener::bind(hot_reload_socket_path) { Ok(local_socket_stream) => { let aborted = Arc::new(Mutex::new(false)); diff --git a/packages/hot-reload/src/lib.rs b/packages/hot-reload/src/lib.rs index 68692b72b..46efb8341 100644 --- a/packages/hot-reload/src/lib.rs +++ b/packages/hot-reload/src/lib.rs @@ -1,4 +1,7 @@ -use std::io::{BufRead, BufReader}; +use std::{ + io::{BufRead, BufReader}, + path::PathBuf, +}; use dioxus_core::Template; #[cfg(feature = "file_watcher")] @@ -24,7 +27,8 @@ pub enum HotReloadMsg { /// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected pub fn connect(mut f: impl FnMut(HotReloadMsg) + Send + 'static) { std::thread::spawn(move || { - if let Ok(socket) = LocalSocketStream::connect("@dioxusin") { + let path = PathBuf::from("./").join("target").join("dioxusin"); + if let Ok(socket) = LocalSocketStream::connect(path) { let mut buf_reader = BufReader::new(socket); loop { let mut buf = String::new(); diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 6a0e546fa..a42c01950 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -74,7 +74,7 @@ macro_rules! impl_attribute_match { $attr:ident $fil:ident: $vil:ident (in $ns:literal), ) => { if $attr == stringify!($fil) { - return Some((stringify!(fil), Some(ns))); + return Some((stringify!(fil), Some($ns))); } }; } @@ -180,14 +180,26 @@ macro_rules! impl_element_match { }; ( - $el:ident $name:ident $namespace:tt { + $el:ident $name:ident $namespace:literal { $( $fil:ident: $vil:ident $extra:tt, )* } ) => { if $el == stringify!($name) { - return Some((stringify!($name), Some(stringify!($namespace)))); + return Some((stringify!($name), Some($namespace))); + } + }; + + ( + $el:ident $name:ident [$_:literal, $namespace:tt] { + $( + $fil:ident: $vil:ident $extra:tt, + )* + } + ) => { + if $el == stringify!($name) { + return Some((stringify!($name), Some($namespace))); } }; } @@ -207,6 +219,8 @@ macro_rules! impl_element_match_attributes { $attr $fil: $vil ($extra), ); )* + + return impl_map_global_attributes!($el $attr $name None); } }; @@ -223,10 +237,41 @@ macro_rules! impl_element_match_attributes { $attr $fil: $vil ($extra), ); )* + + return impl_map_global_attributes!($el $attr $name $namespace); } } } +#[cfg(feature = "hot-reload-context")] +macro_rules! impl_map_global_attributes { + ( + $el:ident $attr:ident $element:ident None + ) => { + map_global_attributes($attr) + }; + + ( + $el:ident $attr:ident $element:ident $namespace:literal + ) => { + if $namespace == "http://www.w3.org/2000/svg" { + map_svg_attributes($attr) + } else { + map_global_attributes($attr) + } + }; + + ( + $el:ident $attr:ident $element:ident [$name:literal, $namespace:tt] + ) => { + if $namespace == "http://www.w3.org/2000/svg" { + map_svg_attributes($attr) + } else { + map_global_attributes($attr) + } + }; +} + macro_rules! builder_constructors { ( $( @@ -254,7 +299,7 @@ macro_rules! builder_constructors { } ); )* - map_global_attributes(attribute).or_else(|| map_svg_attributes(attribute)) + None } fn map_element(element: &str) -> Option<(&'static str, Option<&'static str>)> { @@ -782,6 +827,7 @@ builder_constructors! { decoding: ImageDecoding DEFAULT, height: usize DEFAULT, ismap: Bool DEFAULT, + loading: String DEFAULT, src: Uri DEFAULT, srcset: String DEFAULT, // FIXME this is much more complicated usemap: String DEFAULT, // FIXME should be a fragment starting with '#' diff --git a/packages/html/src/global_attributes.rs b/packages/html/src/global_attributes.rs index 3792397ad..02b0b32ed 100644 --- a/packages/html/src/global_attributes.rs +++ b/packages/html/src/global_attributes.rs @@ -269,6 +269,9 @@ trait_methods! { /// azimuth: "azimuth", "style"; + /// + backdrop_filter: "backdrop-filter", "style"; + /// backface_visibility: "backface-visibility", "style"; diff --git a/packages/native-core/src/dioxus.rs b/packages/native-core/src/dioxus.rs index 3e9ff9ee0..c245de4fc 100644 --- a/packages/native-core/src/dioxus.rs +++ b/packages/native-core/src/dioxus.rs @@ -57,7 +57,12 @@ impl DioxusState { node.insert(ElementIdComponent(element_id)); if self.node_id_mapping.len() <= element_id.0 { self.node_id_mapping.resize(element_id.0 + 1, None); + } else if let Some(mut node) = + self.node_id_mapping[element_id.0].and_then(|id| node.real_dom_mut().get_mut(id)) + { + node.remove(); } + self.node_id_mapping[element_id.0] = Some(node_id); } diff --git a/packages/rink/Cargo.toml b/packages/rink/Cargo.toml index ba3d549f8..92d1eae8c 100644 --- a/packages/rink/Cargo.toml +++ b/packages/rink/Cargo.toml @@ -14,7 +14,7 @@ dioxus-html = { workspace = true } dioxus-native-core = { workspace = true, features = ["layout-attributes"] } dioxus-native-core-macro = { workspace = true } -tui = "0.17.0" +ratatui = "0.24.0" crossterm = "0.26.1" anyhow = "1.0.42" tokio = { workspace = true, features = ["full"] } diff --git a/packages/rink/src/lib.rs b/packages/rink/src/lib.rs index 2eba1b98a..4a7261b55 100644 --- a/packages/rink/src/lib.rs +++ b/packages/rink/src/lib.rs @@ -17,6 +17,7 @@ use futures::{channel::mpsc::UnboundedSender, pin_mut, Future, StreamExt}; use futures_channel::mpsc::unbounded; use layout::TaffyLayout; use prevent_default::PreventDefault; +use ratatui::{backend::CrosstermBackend, Terminal}; use std::{io, time::Duration}; use std::{ pin::Pin, @@ -26,7 +27,6 @@ use std::{rc::Rc, sync::RwLock}; use style_attributes::StyleModifier; pub use taffy::{geometry::Point, prelude::*}; use tokio::select; -use tui::{backend::CrosstermBackend, Terminal}; use widgets::{register_widgets, RinkWidgetResponder, RinkWidgetTraitObject}; mod config; @@ -180,7 +180,7 @@ pub fn render( if !to_rerender.is_empty() || updated { updated = false; - fn resize(dims: tui::layout::Rect, taffy: &mut Taffy, rdom: &RealDom) { + fn resize(dims: ratatui::layout::Rect, taffy: &mut Taffy, rdom: &RealDom) { let width = screen_to_layout_space(dims.width); let height = screen_to_layout_space(dims.height); let root_node = rdom @@ -222,7 +222,7 @@ pub fn render( } else { let rdom = rdom.read().unwrap(); resize( - tui::layout::Rect { + ratatui::layout::Rect { x: 0, y: 0, width: 1000, diff --git a/packages/rink/src/render.rs b/packages/rink/src/render.rs index 19d642720..4185f3a62 100644 --- a/packages/rink/src/render.rs +++ b/packages/rink/src/render.rs @@ -1,11 +1,10 @@ use dioxus_native_core::{prelude::*, tree::TreeRef}; -use std::io::Stdout; +use ratatui::{layout::Rect, style::Color}; use taffy::{ geometry::Point, prelude::{Dimension, Layout, Size}, Taffy, }; -use tui::{backend::CrosstermBackend, layout::Rect, style::Color}; use crate::{ focus::Focused, @@ -20,7 +19,7 @@ use crate::{ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5]; pub(crate) fn render_vnode( - frame: &mut tui::Frame>, + frame: &mut ratatui::Frame, layout: &Taffy, node: NodeRef, cfg: Config, @@ -96,7 +95,7 @@ pub(crate) fn render_vnode( impl RinkWidget for NodeRef<'_> { fn render(self, area: Rect, mut buf: RinkBuffer<'_>) { - use tui::symbols::line::*; + use ratatui::symbols::line::*; enum Direction { Left, diff --git a/packages/rink/src/style.rs b/packages/rink/src/style.rs index 857322079..905cc4b34 100644 --- a/packages/rink/src/style.rs +++ b/packages/rink/src/style.rs @@ -1,6 +1,6 @@ use std::{num::ParseFloatError, str::FromStr}; -use tui::style::{Color, Modifier, Style}; +use ratatui::style::{Color, Modifier, Style}; use crate::RenderingMode; @@ -442,6 +442,7 @@ impl RinkStyle { impl From for Style { fn from(val: RinkStyle) -> Self { Style { + underline_color: None, fg: val.fg.map(|c| c.color), bg: val.bg.map(|c| c.color), add_modifier: val.add_modifier, diff --git a/packages/rink/src/style_attributes.rs b/packages/rink/src/style_attributes.rs index 0c87e3f83..d1e8c6bee 100644 --- a/packages/rink/src/style_attributes.rs +++ b/packages/rink/src/style_attributes.rs @@ -187,8 +187,8 @@ pub enum BorderStyle { } impl BorderStyle { - pub fn symbol_set(&self) -> Option { - use tui::symbols::line::*; + pub fn symbol_set(&self) -> Option { + use ratatui::symbols::line::*; const DASHED: Set = Set { horizontal: "╌", vertical: "╎", @@ -570,7 +570,7 @@ fn apply_animation(name: &str, _value: &str, _style: &mut StyleModifier) { } fn apply_font(name: &str, value: &str, style: &mut StyleModifier) { - use tui::style::Modifier; + use ratatui::style::Modifier; match name { "font" => (), "font-family" => (), @@ -593,7 +593,7 @@ fn apply_font(name: &str, value: &str, style: &mut StyleModifier) { } fn apply_text(name: &str, value: &str, style: &mut StyleModifier) { - use tui::style::Modifier; + use ratatui::style::Modifier; match name { "text-align" => todo!(), diff --git a/packages/rink/src/widget.rs b/packages/rink/src/widget.rs index ab9044998..91af5b4f6 100644 --- a/packages/rink/src/widget.rs +++ b/packages/rink/src/widget.rs @@ -1,4 +1,4 @@ -use tui::{ +use ratatui::{ buffer::Buffer, layout::Rect, style::{Color, Modifier}, diff --git a/packages/router-macro/src/redirect.rs b/packages/router-macro/src/redirect.rs index c1efb8b4d..2933bbc8e 100644 --- a/packages/router-macro/src/redirect.rs +++ b/packages/router-macro/src/redirect.rs @@ -75,6 +75,7 @@ impl Redirect { let (segments, query) = parse_route_segments( path.span(), + #[allow(clippy::map_identity)] closure_arguments.iter().map(|(name, ty)| (name, ty)), &path.value(), )?; diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 80923d539..da9d3fe85 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -19,6 +19,7 @@ thiserror = { workspace = true } futures-util = { workspace = true } urlencoding = "2.1.3" serde = { version = "1", features = ["derive"], optional = true } +serde_json = { version = "1.0.91", optional = true } url = "2.3.1" wasm-bindgen = { workspace = true, optional = true } web-sys = { version = "0.3.60", optional = true, features = [ @@ -26,18 +27,22 @@ web-sys = { version = "0.3.60", optional = true, features = [ ] } js-sys = { version = "0.3.63", optional = true } gloo-utils = { version = "0.1.6", optional = true } +dioxus-liveview = { workspace = true, optional = true } dioxus-ssr = { workspace = true, optional = true } tokio = { workspace = true, features = ["full"], optional = true } [features] default = ["web"] ssr = ["dioxus-ssr", "tokio"] +liveview = ["dioxus-liveview", "tokio", "dep:serde", "serde_json"] wasm_test = [] serde = ["dep:serde", "gloo-utils/serde"] web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils", "js-sys"] [dev-dependencies] +axum = { version = "0.6.1", features = ["ws"] } dioxus = { path = "../dioxus" } +dioxus-liveview = { workspace = true, features = ["axum"] } dioxus-ssr = { path = "../ssr" } criterion = { version = "0.5", features = ["async_tokio", "html_reports"] } diff --git a/packages/router/examples/simple_routes.rs b/packages/router/examples/simple_routes.rs index 3634f7df3..aac1e8658 100644 --- a/packages/router/examples/simple_routes.rs +++ b/packages/router/examples/simple_routes.rs @@ -2,6 +2,47 @@ use dioxus::prelude::*; use dioxus_router::prelude::*; use std::str::FromStr; +#[cfg(feature = "liveview")] +#[tokio::main] +async fn main() { + use axum::{extract::ws::WebSocketUpgrade, response::Html, routing::get, Router}; + + let listen_address: std::net::SocketAddr = ([127, 0, 0, 1], 3030).into(); + let view = dioxus_liveview::LiveViewPool::new(); + let app = Router::new() + .fallback(get(move || async move { + Html(format!( + r#" + + + +

+ {glue} + + "#, + glue = dioxus_liveview::interpreter_glue(&format!("ws://{listen_address}/ws")) + )) + })) + .route( + "/ws", + get(move |ws: WebSocketUpgrade| async move { + ws.on_upgrade(move |socket| async move { + _ = view + .launch(dioxus_liveview::axum_socket(socket), Root) + .await; + }) + }), + ); + + println!("Listening on http://{listen_address}"); + + axum::Server::bind(&listen_address.to_string().parse().unwrap()) + .serve(app.into_make_service()) + .await + .unwrap(); +} + +#[cfg(not(feature = "liveview"))] fn main() { #[cfg(not(target_arch = "wasm32"))] dioxus_desktop::launch(Root); @@ -10,21 +51,26 @@ fn main() { dioxus_web::launch(root); } +#[cfg(feature = "liveview")] #[component] fn Root(cx: Scope) -> Element { - render! { - Router:: {} - } + let history = LiveviewHistory::new(cx); + render! { Router:: { + config: || RouterConfig::default().history(history), + } } +} + +#[cfg(not(feature = "liveview"))] +#[component] +fn Root(cx: Scope) -> Element { + render! { Router:: {} } } #[component] fn UserFrame(cx: Scope, user_id: usize) -> Element { render! { - pre { - "UserFrame{{\n\tuser_id:{user_id}\n}}" - } - div { - background_color: "rgba(0,0,0,50%)", + pre { "UserFrame{{\n\tuser_id:{user_id}\n}}" } + div { background_color: "rgba(0,0,0,50%)", "children:" Outlet:: {} } @@ -88,6 +134,16 @@ fn Route3(cx: Scope, dynamic: String) -> Element { to: Route::Route2 { user_id: 8888 }, "hello world link" } + button { + disabled: !navigator.can_go_back(), + onclick: move |_| { navigator.go_back(); }, + "go back" + } + button { + disabled: !navigator.can_go_forward(), + onclick: move |_| { navigator.go_forward(); }, + "go forward" + } button { onclick: move |_| { navigator.push("https://www.google.com"); }, "google link" diff --git a/packages/router/src/components/history_buttons.rs b/packages/router/src/components/history_buttons.rs index eb62e9285..ae11ad276 100644 --- a/packages/router/src/components/history_buttons.rs +++ b/packages/router/src/components/history_buttons.rs @@ -142,7 +142,7 @@ pub fn GoForwardButton<'a>(cx: Scope<'a, HistoryButtonProps<'a>>) -> Element { } }; - let disabled = !router.can_go_back(); + let disabled = !router.can_go_forward(); render! { button { diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 787f9cdc9..639d99ae7 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -11,6 +11,8 @@ use crate::navigation::NavigationTarget; use crate::prelude::Routable; use crate::utils::use_router_internal::use_router_internal; +use url::Url; + /// Something that can be converted into a [`NavigationTarget`]. #[derive(Clone)] pub enum IntoRoutable { @@ -53,6 +55,18 @@ impl From<&str> for IntoRoutable { } } +impl From for IntoRoutable { + fn from(url: Url) -> Self { + IntoRoutable::FromStr(url.to_string()) + } +} + +impl From<&Url> for IntoRoutable { + fn from(url: &Url) -> Self { + IntoRoutable::FromStr(url.to_string()) + } +} + /// The properties for a [`Link`]. #[derive(Props)] pub struct LinkProps<'a> { diff --git a/packages/router/src/contexts/router.rs b/packages/router/src/contexts/router.rs index b15956991..079640e61 100644 --- a/packages/router/src/contexts/router.rs +++ b/packages/router/src/contexts/router.rs @@ -232,7 +232,7 @@ impl RouterContext { IntoRoutable::FromStr(url) => { let parsed_route: NavigationTarget> = match self.route_from_str(&url) { Ok(route) => NavigationTarget::Internal(route), - Err(err) => NavigationTarget::External(err), + Err(_) => NavigationTarget::External(url), }; parsed_route } diff --git a/packages/router/src/history/liveview.rs b/packages/router/src/history/liveview.rs new file mode 100644 index 000000000..9ffb09c34 --- /dev/null +++ b/packages/router/src/history/liveview.rs @@ -0,0 +1,441 @@ +use super::HistoryProvider; +use crate::routable::Routable; +use dioxus::prelude::*; +use serde::{Deserialize, Serialize}; +use std::sync::{Mutex, RwLock}; +use std::{collections::BTreeMap, rc::Rc, str::FromStr, sync::Arc}; + +/// A [`HistoryProvider`] that evaluates history through JS. +pub struct LiveviewHistory +where + ::Err: std::fmt::Display, +{ + action_tx: tokio::sync::mpsc::UnboundedSender>, + timeline: Arc>>, + updater_callback: Arc>>, +} + +struct Timeline +where + ::Err: std::fmt::Display, +{ + current_index: usize, + routes: BTreeMap, +} + +#[derive(Serialize, Deserialize, Debug)] +struct State { + index: usize, +} + +#[derive(Serialize, Deserialize, Debug)] +struct Session +where + ::Err: std::fmt::Display, +{ + #[serde(with = "routes")] + routes: BTreeMap, + last_visited: usize, +} + +#[derive(Serialize, Deserialize)] +struct SessionStorage { + liveview: Option, +} + +enum Action { + GoBack, + GoForward, + Push(R), + Replace(R), + External(String), +} + +impl Timeline +where + ::Err: std::fmt::Display, +{ + fn new(initial_path: R) -> Self { + Self { + current_index: 0, + routes: BTreeMap::from([(0, initial_path)]), + } + } + + fn init( + &mut self, + route: R, + state: Option, + session: Option>, + depth: usize, + ) -> State { + if let Some(session) = session { + self.routes = session.routes; + if state.is_none() { + // top of stack + let last_visited = session.last_visited; + self.routes.retain(|&lhs, _| lhs <= last_visited); + } + }; + let state = match state { + Some(state) => { + self.current_index = state.index; + state + } + None => { + let index = depth - 1; + self.current_index = index; + State { index } + } + }; + self.routes.insert(state.index, route); + state + } + + fn update(&mut self, route: R, state: Option) -> State { + if let Some(state) = state { + self.current_index = state.index; + self.routes.insert(self.current_index, route); + state + } else { + self.push(route) + } + } + + fn push(&mut self, route: R) -> State { + // top of stack + let index = self.current_index + 1; + self.current_index = index; + self.routes.insert(index, route); + self.routes.retain(|&rhs, _| index >= rhs); + State { + index: self.current_index, + } + } + + fn replace(&mut self, route: R) -> State { + self.routes.insert(self.current_index, route); + State { + index: self.current_index, + } + } + + fn current_route(&self) -> &R { + &self.routes[&self.current_index] + } + + fn session(&self) -> Session { + Session { + routes: self.routes.clone(), + last_visited: self.current_index, + } + } +} + +impl LiveviewHistory +where + ::Err: std::fmt::Display, +{ + /// Create a [`LiveviewHistory`] in the given scope. + /// When using a [`LiveviewHistory`] in combination with use_eval, history must be untampered with. + /// + /// # Panics + /// + /// Panics if not in a Liveview context. + pub fn new(cx: &ScopeState) -> Self { + Self::new_with_initial_path( + cx, + "/".parse().unwrap_or_else(|err| { + panic!("index route does not exist:\n{}\n use LiveviewHistory::new_with_initial_path to set a custom path", err) + }), + ) + } + + /// Create a [`LiveviewHistory`] in the given scope, starting at `initial_path`. + /// When using a [`LiveviewHistory`] in combination with use_eval, history must be untampered with. + /// + /// # Panics + /// + /// Panics if not in a Liveview context. + pub fn new_with_initial_path(cx: &ScopeState, initial_path: R) -> Self { + let (action_tx, action_rx) = tokio::sync::mpsc::unbounded_channel::>(); + let action_rx = Arc::new(Mutex::new(action_rx)); + let timeline = Arc::new(Mutex::new(Timeline::new(initial_path))); + let updater_callback: Arc>> = + Arc::new(RwLock::new(Arc::new(|| {}))); + + let eval_provider = cx + .consume_context::>() + .expect("evaluator not provided"); + + let create_eval = Rc::new(move |script: &str| { + eval_provider + .new_evaluator(script.to_string()) + .map(UseEval::new) + }) as Rc Result>; + + // Listen to server actions + cx.push_future({ + let timeline = timeline.clone(); + let action_rx = action_rx.clone(); + let create_eval = create_eval.clone(); + async move { + let mut action_rx = action_rx.lock().expect("unpoisoned mutex"); + loop { + let eval = action_rx.recv().await.expect("sender to exist"); + let _ = match eval { + Action::GoBack => create_eval( + r#" + // this triggers a PopState event + history.back(); + "#, + ), + Action::GoForward => create_eval( + r#" + // this triggers a PopState event + history.forward(); + "#, + ), + Action::Push(route) => { + let mut timeline = timeline.lock().expect("unpoisoned mutex"); + let state = timeline.push(route.clone()); + let state = serde_json::to_string(&state).expect("serializable state"); + let session = serde_json::to_string(&timeline.session()) + .expect("serializable session"); + create_eval(&format!( + r#" + // this does not trigger a PopState event + history.pushState({state}, "", "{route}"); + sessionStorage.setItem("liveview", '{session}'); + "# + )) + } + Action::Replace(route) => { + let mut timeline = timeline.lock().expect("unpoisoned mutex"); + let state = timeline.replace(route.clone()); + let state = serde_json::to_string(&state).expect("serializable state"); + let session = serde_json::to_string(&timeline.session()) + .expect("serializable session"); + create_eval(&format!( + r#" + // this does not trigger a PopState event + history.replaceState({state}, "", "{route}"); + sessionStorage.setItem("liveview", '{session}'); + "# + )) + } + Action::External(url) => create_eval(&format!( + r#" + location.href = "{url}"; + "# + )), + }; + } + } + }); + + // Listen to browser actions + cx.push_future({ + let updater = updater_callback.clone(); + let timeline = timeline.clone(); + let create_eval = create_eval.clone(); + async move { + let popstate_eval = { + let init_eval = create_eval( + r#" + return [ + document.location.pathname + "?" + document.location.search + "\#" + document.location.hash, + history.state, + JSON.parse(sessionStorage.getItem("liveview")), + history.length, + ]; + "#, + ).expect("failed to load state").await.expect("serializable state"); + let (route, state, session, depth) = serde_json::from_value::<( + String, + Option, + Option>, + usize, + )>(init_eval).expect("serializable state"); + let Ok(route) = R::from_str(&route.to_string()) else { + return; + }; + let mut timeline = timeline.lock().expect("unpoisoned mutex"); + let state = timeline.init(route.clone(), state, session, depth); + let state = serde_json::to_string(&state).expect("serializable state"); + let session = serde_json::to_string(&timeline.session()) + .expect("serializable session"); + + // Call the updater callback + (updater.read().unwrap())(); + + create_eval(&format!(r#" + // this does not trigger a PopState event + history.replaceState({state}, "", "{route}"); + sessionStorage.setItem("liveview", '{session}'); + + window.addEventListener("popstate", (event) => {{ + dioxus.send([ + document.location.pathname + "?" + document.location.search + "\#" + document.location.hash, + event.state, + ]); + }}); + "#)).expect("failed to initialize popstate") + }; + + loop { + let event = match popstate_eval.recv().await { + Ok(event) => event, + Err(_) => continue, + }; + let (route, state) = serde_json::from_value::<(String, Option)>(event).expect("serializable state"); + let Ok(route) = R::from_str(&route.to_string()) else { + return; + }; + let mut timeline = timeline.lock().expect("unpoisoned mutex"); + let state = timeline.update(route.clone(), state); + let state = serde_json::to_string(&state).expect("serializable state"); + let session = serde_json::to_string(&timeline.session()) + .expect("serializable session"); + + let _ = create_eval(&format!( + r#" + // this does not trigger a PopState event + history.replaceState({state}, "", "{route}"); + sessionStorage.setItem("liveview", '{session}'); + "#)); + + // Call the updater callback + (updater.read().unwrap())(); + } + } + }); + + Self { + action_tx, + timeline, + updater_callback, + } + } +} + +impl HistoryProvider for LiveviewHistory +where + ::Err: std::fmt::Display, +{ + fn go_back(&mut self) { + let _ = self.action_tx.send(Action::GoBack); + } + + fn go_forward(&mut self) { + let _ = self.action_tx.send(Action::GoForward); + } + + fn push(&mut self, route: R) { + let _ = self.action_tx.send(Action::Push(route)); + } + + fn replace(&mut self, route: R) { + let _ = self.action_tx.send(Action::Replace(route)); + } + + fn external(&mut self, url: String) -> bool { + let _ = self.action_tx.send(Action::External(url)); + true + } + + fn current_route(&self) -> R { + let timeline = self.timeline.lock().expect("unpoisoned mutex"); + timeline.current_route().clone() + } + + fn can_go_back(&self) -> bool { + let timeline = self.timeline.lock().expect("unpoisoned mutex"); + // Check if the one before is contiguous (i.e., not an external page) + let visited_indices: Vec = timeline.routes.keys().cloned().collect(); + visited_indices + .iter() + .position(|&rhs| timeline.current_index == rhs) + .map_or(false, |index| { + index > 0 && visited_indices[index - 1] == timeline.current_index - 1 + }) + } + + fn can_go_forward(&self) -> bool { + let timeline = self.timeline.lock().expect("unpoisoned mutex"); + // Check if the one after is contiguous (i.e., not an external page) + let visited_indices: Vec = timeline.routes.keys().cloned().collect(); + visited_indices + .iter() + .rposition(|&rhs| timeline.current_index == rhs) + .map_or(false, |index| { + index < visited_indices.len() - 1 + && visited_indices[index + 1] == timeline.current_index + 1 + }) + } + + fn updater(&mut self, callback: Arc) { + let mut updater_callback = self.updater_callback.write().unwrap(); + *updater_callback = callback; + } +} + +mod routes { + use crate::prelude::Routable; + use core::str::FromStr; + use serde::de::{MapAccess, Visitor}; + use serde::{ser::SerializeMap, Deserializer, Serializer}; + use std::collections::BTreeMap; + + pub fn serialize(routes: &BTreeMap, serializer: S) -> Result + where + S: Serializer, + R: Routable, + { + let mut map = serializer.serialize_map(Some(routes.len()))?; + for (index, route) in routes.iter() { + map.serialize_entry(&index.to_string(), &route.to_string())?; + } + map.end() + } + + pub fn deserialize<'de, D, R>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + R: Routable, + ::Err: std::fmt::Display, + { + struct BTreeMapVisitor { + marker: std::marker::PhantomData, + } + + impl<'de, R> Visitor<'de> for BTreeMapVisitor + where + R: Routable, + ::Err: std::fmt::Display, + { + type Value = BTreeMap; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a map with indices and routable values") + } + + fn visit_map(self, mut map: M) -> Result + where + M: MapAccess<'de>, + { + let mut routes = BTreeMap::new(); + while let Some((index, route)) = map.next_entry::()? { + let index = index.parse::().map_err(serde::de::Error::custom)?; + let route = R::from_str(&route).map_err(serde::de::Error::custom)?; + routes.insert(index, route); + } + Ok(routes) + } + } + + deserializer.deserialize_map(BTreeMapVisitor { + marker: std::marker::PhantomData, + }) + } +} diff --git a/packages/router/src/history/mod.rs b/packages/router/src/history/mod.rs index e9668b979..cf7183366 100644 --- a/packages/router/src/history/mod.rs +++ b/packages/router/src/history/mod.rs @@ -22,6 +22,11 @@ pub use web::*; #[cfg(feature = "web")] pub(crate) mod web_history; +#[cfg(feature = "liveview")] +mod liveview; +#[cfg(feature = "liveview")] +pub use liveview::*; + // #[cfg(feature = "web")] // mod web_hash; // #[cfg(feature = "web")] diff --git a/packages/rsx/Cargo.toml b/packages/rsx/Cargo.toml index bc866acb7..72b51a051 100644 --- a/packages/rsx/Cargo.toml +++ b/packages/rsx/Cargo.toml @@ -20,6 +20,7 @@ quote = { version = "1.0" } serde = { version = "1.0", features = ["derive"], optional = true } internment = { version = "0.7.0", optional = true } krates = { version = "0.12.6", optional = true } +tracing.workspace = true [features] hot_reload = ["krates", "internment"] diff --git a/packages/rsx/src/hot_reload/hot_reload_diff.rs b/packages/rsx/src/hot_reload/hot_reload_diff.rs index 22a01875d..7e19d3aad 100644 --- a/packages/rsx/src/hot_reload/hot_reload_diff.rs +++ b/packages/rsx/src/hot_reload/hot_reload_diff.rs @@ -1,4 +1,5 @@ use proc_macro2::TokenStream; +use quote::ToTokens; use syn::{File, Macro}; pub enum DiffResult { @@ -10,13 +11,30 @@ pub enum DiffResult { pub fn find_rsx(new: &File, old: &File) -> DiffResult { let mut rsx_calls = Vec::new(); if new.items.len() != old.items.len() { + tracing::trace!( + "found not hot reload-able change {:#?} != {:#?}", + new.items + .iter() + .map(|i| i.to_token_stream().to_string()) + .collect::>(), + old.items + .iter() + .map(|i| i.to_token_stream().to_string()) + .collect::>() + ); return DiffResult::CodeChanged; } for (new, old) in new.items.iter().zip(old.items.iter()) { if find_rsx_item(new, old, &mut rsx_calls) { + tracing::trace!( + "found not hot reload-able change {:#?} != {:#?}", + new.to_token_stream().to_string(), + old.to_token_stream().to_string() + ); return DiffResult::CodeChanged; } } + tracing::trace!("found hot reload-able changes {:#?}", rsx_calls); DiffResult::RsxChanged(rsx_calls) } @@ -94,6 +112,9 @@ fn find_rsx_item( (syn::ImplItem::Macro(new_item), syn::ImplItem::Macro(old_item)) => { old_item != new_item } + (syn::ImplItem::Verbatim(stream), syn::ImplItem::Verbatim(stream2)) => { + stream.to_string() != stream2.to_string() + } _ => true, } { return true; @@ -186,10 +207,12 @@ fn find_rsx_trait( } } (syn::TraitItem::Fn(new_item), syn::TraitItem::Fn(old_item)) => { - if let (Some(new_block), Some(old_block)) = (&new_item.default, &old_item.default) { - find_rsx_block(new_block, old_block, rsx_calls) - } else { - true + match (&new_item.default, &old_item.default) { + (Some(new_block), Some(old_block)) => { + find_rsx_block(new_block, old_block, rsx_calls) + } + (None, None) => false, + _ => true, } } (syn::TraitItem::Type(new_item), syn::TraitItem::Type(old_item)) => { @@ -198,6 +221,9 @@ fn find_rsx_trait( (syn::TraitItem::Macro(new_item), syn::TraitItem::Macro(old_item)) => { old_item != new_item } + (syn::TraitItem::Verbatim(stream), syn::TraitItem::Verbatim(stream2)) => { + stream.to_string() != stream2.to_string() + } _ => true, } { return true; @@ -355,6 +381,11 @@ fn find_rsx_expr( || new_expr.or2_token != old_expr.or2_token || new_expr.output != old_expr.output } + (syn::Expr::Const(new_expr), syn::Expr::Const(old_expr)) => { + find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.const_token != old_expr.const_token + } (syn::Expr::Continue(new_expr), syn::Expr::Continue(old_expr)) => old_expr != new_expr, (syn::Expr::Field(new_expr), syn::Expr::Field(old_expr)) => { find_rsx_expr(&new_expr.base, &old_expr.base, rsx_calls) @@ -402,6 +433,7 @@ fn find_rsx_expr( || new_expr.attrs != old_expr.attrs || new_expr.bracket_token != old_expr.bracket_token } + (syn::Expr::Infer(new_expr), syn::Expr::Infer(old_expr)) => new_expr != old_expr, (syn::Expr::Let(new_expr), syn::Expr::Let(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs @@ -589,7 +621,9 @@ fn find_rsx_expr( _ => true, } } - (syn::Expr::Verbatim(_), syn::Expr::Verbatim(_)) => false, + (syn::Expr::Verbatim(stream), syn::Expr::Verbatim(stream2)) => { + stream.to_string() != stream2.to_string() + } _ => true, } } diff --git a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs index 0393142b4..7e36dddf9 100644 --- a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs +++ b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs @@ -51,17 +51,24 @@ impl FileMap { fn find_rs_files( root: PathBuf, filter: &mut impl FnMut(&Path) -> bool, - ) -> io::Result { + ) -> FileMapSearchResult { let mut files = HashMap::new(); let mut errors = Vec::new(); if root.is_dir() { - for entry in (fs::read_dir(root)?).flatten() { + let read_dir = match fs::read_dir(root) { + Ok(read_dir) => read_dir, + Err(err) => { + errors.push(err); + return FileMapSearchResult { map: files, errors }; + } + }; + for entry in read_dir.flatten() { let path = entry.path(); if !filter(&path) { let FileMapSearchResult { map, errors: child_errors, - } = find_rs_files(path, filter)?; + } = find_rs_files(path, filter); errors.extend(child_errors); files.extend(map); } @@ -69,14 +76,20 @@ impl FileMap { } else if root.extension().and_then(|s| s.to_str()) == Some("rs") { if let Ok(mut file) = File::open(root.clone()) { let mut src = String::new(); - file.read_to_string(&mut src)?; - files.insert(root, (src, None)); + match file.read_to_string(&mut src) { + Ok(_) => { + files.insert(root, (src, None)); + } + Err(err) => { + errors.push(err); + } + } } } - Ok(FileMapSearchResult { map: files, errors }) + FileMapSearchResult { map: files, errors } } - let FileMapSearchResult { map, errors } = find_rs_files(path, &mut filter)?; + let FileMapSearchResult { map, errors } = find_rs_files(path, &mut filter); let result = Self { map, in_workspace: HashMap::new(), diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index bdf32b272..5db6baee9 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -193,7 +193,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> { fn to_tokens(&self, out_tokens: &mut TokenStream2) { let mut context = DynamicContext::default(); - let key = match self.roots.get(0) { + let key = match self.roots.first() { Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(), Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(), _ => None, diff --git a/packages/server-macro/Cargo.toml b/packages/server-macro/Cargo.toml index 5a38acb40..a91cc626a 100644 --- a/packages/server-macro/Cargo.toml +++ b/packages/server-macro/Cargo.toml @@ -17,7 +17,7 @@ proc-macro2 = "^1.0.63" quote = "^1.0.26" syn = { version = "2", features = ["full"] } convert_case = "^0.6.0" -server_fn_macro = "^0.4.6" +server_fn_macro = "^0.5.2" [lib] proc-macro = true diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 6c66f3faf..f48120ca2 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -1,8 +1,14 @@ [package] name = "dioxus-signals" authors = ["Jonathan Kelley"] -version = "0.0.0" +version = "0.4.3" edition = "2018" +description = "Signals for Dioxus" +license = "MIT OR Apache-2.0" +repository = "https://github.com/DioxusLabs/dioxus/" +homepage = "https://dioxuslabs.com" +keywords = ["dom", "ui", "gui", "react", "wasm"] +rust-version = "1.60.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/packages/signals/examples/split_subscriptions.rs b/packages/signals/examples/split_subscriptions.rs index 3eb60ed29..bb286b4b7 100644 --- a/packages/signals/examples/split_subscriptions.rs +++ b/packages/signals/examples/split_subscriptions.rs @@ -22,15 +22,9 @@ fn app(cx: Scope) -> Element { use_context_provider(cx, ApplicationData::default); render! { - div { - ReadsFirst {} - } - div { - ReadsSecond {} - } - div { - ReadsManySignals {} - } + div { ReadsFirst {} } + div { ReadsSecond {} } + div { ReadsManySignals {} } } } @@ -107,16 +101,14 @@ fn ReadsManySignals(cx: Scope) -> Element { } button { onclick: move |_| { - if let Some(first) = data.many_signals.read().get(0) { + if let Some(first) = data.many_signals.read().first() { *first.write() += 1; } }, "Increase First Item" } for signal in data.many_signals { - Child { - count: signal, - } + Child { count: signal } } } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index ddcb94b78..d9b178e54 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -7,8 +7,10 @@ use std::rc::Rc; use dioxus_core::prelude::*; use dioxus_core::ScopeId; -use generational_box::Storage; -use generational_box::{GenerationalBox, Owner}; +use generational_box::{ + BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, + Storage, Store, +}; use crate::Effect; @@ -74,6 +76,7 @@ impl CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. + #[track_caller] pub fn new(value: T) -> Self { Self::new_maybe_sync(value) } @@ -97,6 +100,22 @@ impl> CopyValue { } } + pub(crate) fn new_with_caller( + value: T, + #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, + ) -> Self { + let owner = current_owner(); + + Self { + value: owner.insert_with_caller( + value, + #[cfg(debug_assertions)] + caller, + ), + origin_scope: current_scope_id().expect("in a virtual dom"), + } + } + /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. pub fn new_maybe_sync_in_scope(value: T, scope: ScopeId) -> Self { let owner = owner_in_scope(scope); @@ -122,21 +141,26 @@ impl> CopyValue { } /// Try to read the value. If the value has been dropped, this will return None. + #[track_caller] + pub fn try_read(&self) -> Option { self.value.try_read() } /// Read the value. If the value has been dropped, this will panic. + #[track_caller] pub fn read(&self) -> S::Ref { self.value.read() } /// Try to write the value. If the value has been dropped, this will return None. + #[track_caller] pub fn try_write(&self) -> Option { self.value.try_write() } /// Write the value. If the value has been dropped, this will panic. + #[track_caller] pub fn write(&self) -> S::Mut { self.value.write() } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 0c89e06a5..8feb7fe95 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -49,9 +49,19 @@ use crate::{get_effect_ref, CopyValue, EffectStackRef, EFFECT_STACK}; /// } /// } /// ``` +#[track_caller] #[must_use] pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal { - *cx.use_hook(|| Signal::new(f())) + #[cfg(debug_assertions)] + let caller = std::panic::Location::caller(); + + *cx.use_hook(|| { + Signal::new_with_caller( + f(), + #[cfg(debug_assertions)] + caller, + ) + }) } /// Creates a new `Send + Sync`` Signal. Signals are a Copy state management solution with automatic dependency tracking. @@ -191,6 +201,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign impl Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + #[track_caller] pub fn new(value: T) -> Self { Self::new_maybe_sync(value) } @@ -215,6 +226,26 @@ impl>> Signal { } } + /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + fn new_with_caller( + value: T, + #[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>, + ) -> Self { + Self { + inner: CopyValue::new_with_caller( + SignalData { + subscribers: Default::default(), + effect_subscribers: Default::default(), + update_any: schedule_update_any().expect("in a virtual dom"), + value, + effect_stack: get_effect_stack(), + }, + #[cfg(debug_assertions)] + caller, + ), + } + } + /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. #[tracing::instrument(skip(value))] pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self { @@ -236,8 +267,10 @@ impl>> Signal { self.inner.origin_scope() } - /// Get the current value of the signal. This will subscribe the current scope to the signal. + /// Get the current value of the signal. This will subscribe the current scope to the signal. If you would like to read the signal without subscribing to it, you can use [`Self::peek`] instead. + /// /// If the signal has been dropped, this will panic. + #[track_caller] pub fn read( &self, ) -> <>>::Ref as Mappable>>::Mapped { @@ -270,8 +303,20 @@ impl>> Signal { S::Ref::map(inner, |v| &v.value) } - /// Get a mutable reference to the signal's value. + /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** + /// /// If the signal has been dropped, this will panic. + pub fn peek( + &self, + ) -> <>>::Ref as Mappable>>::Mapped { + let inner = self.inner.read(); + S::Ref::map(inner, |v| &v.value) + } + + /// 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>>::Mut as MappableMut>>::Mapped, S> @@ -315,12 +360,14 @@ impl>> Signal { } /// Set the value of the signal. This will trigger an update on all subscribers. + #[track_caller] pub fn set(&self, value: T) { *self.write() = value; } /// Run a closure with a reference to the signal's value. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { let write = self.read(); f(&*write) @@ -328,6 +375,7 @@ impl>> Signal { /// Run a closure with a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn with_mut(&self, f: impl FnOnce(&mut T) -> O) -> O { let mut write = self.write(); f(&mut *write) @@ -342,6 +390,7 @@ impl>> Signal { impl>> Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. + #[track_caller] pub fn value(&self) -> T { self.read().clone() } @@ -482,13 +531,24 @@ impl>> ReadOnlySignal { } /// Get the current value of the signal. This will subscribe the current scope to the signal. + /// + /// If the signal has been dropped, this will panic. + #[track_caller] pub fn read( &self, ) -> <>>::Ref as Mappable>>::Mapped { self.inner.read() } + /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** + /// + /// If the signal has been dropped, this will panic. + pub fn peek(&self) -> GenerationalRef { + self.inner.peek() + } + /// Run a closure with a reference to the signal's value. + #[track_caller] pub fn with(&self, f: impl FnOnce(&T) -> O) -> O { self.inner.with(f) } diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index f6376452d..71f5b26a0 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -10,6 +10,7 @@ keywords = ["dom", "ui", "gui", "react", "ssr"] [dependencies] dioxus-core = { workspace = true, features = ["serialize"] } +dioxus-html = { workspace = true } askama_escape = "0.10.3" thiserror = "1.0.23" rustc-hash = "1.1.0" @@ -17,6 +18,8 @@ lru = "0.10.0" tracing = { workspace = true } http = "0.2.9" tokio = { version = "1.28", features = ["full"], optional = true } +async-trait = "0.1.58" +serde_json = { version = "1.0" } [dev-dependencies] dioxus = { workspace = true } diff --git a/packages/ssr/src/eval.rs b/packages/ssr/src/eval.rs new file mode 100644 index 000000000..2ab74f4e1 --- /dev/null +++ b/packages/ssr/src/eval.rs @@ -0,0 +1,42 @@ +use async_trait::async_trait; +use dioxus_core::ScopeState; +use dioxus_html::prelude::{EvalError, EvalProvider, Evaluator}; +use std::rc::Rc; + +/// Provides the SSREvalProvider through [`cx.provide_context`]. +pub fn init_eval(cx: &ScopeState) { + let provider: Rc = Rc::new(SSREvalProvider {}); + cx.provide_context(provider); +} + +/// Reprents the ssr-target's provider of evaluators. +pub struct SSREvalProvider; +impl EvalProvider for SSREvalProvider { + fn new_evaluator(&self, _: String) -> Result, EvalError> { + Ok(Rc::new(SSREvaluator) as Rc) + } +} + +/// Represents a ssr-target's JavaScript evaluator. +pub struct SSREvaluator; + +// In ssr rendering we never run or resolve evals. +#[async_trait(?Send)] +impl Evaluator for SSREvaluator { + /// Runs the evaluated JavaScript. + async fn join(&self) -> Result { + std::future::pending::<()>().await; + unreachable!() + } + + /// Sends a message to the evaluated JavaScript. + fn send(&self, _el: serde_json::Value) -> Result<(), EvalError> { + Ok(()) + } + + /// Gets an UnboundedReceiver to receive messages from the evaluated JavaScript. + async fn recv(&self) -> Result { + std::future::pending::<()>().await; + unreachable!() + } +} diff --git a/packages/ssr/src/incremental.rs b/packages/ssr/src/incremental.rs index c3bb25314..34ffa3443 100644 --- a/packages/ssr/src/incremental.rs +++ b/packages/ssr/src/incremental.rs @@ -81,6 +81,7 @@ impl IncrementalRenderer { let mut html_buffer = WriteBuffer { buffer: Vec::new() }; { let mut vdom = VirtualDom::new_with_props(comp, props); + crate::eval::init_eval(vdom.base_scope()); rebuild_with(&mut vdom).await; renderer.render_before_body(&mut *html_buffer)?; diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 6794d1f14..fdc0e9333 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -10,6 +10,7 @@ pub mod incremental; #[cfg(feature = "incremental")] mod incremental_cfg; +pub mod eval; pub mod renderer; pub mod template; @@ -42,6 +43,7 @@ pub fn render_lazy(f: LazyNodes<'_, '_>) -> String { }; let mut dom = VirtualDom::new_with_props(lazy_app, props); + crate::eval::init_eval(dom.base_scope()); _ = dom.rebuild(); Renderer::new().render(&dom) diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 4fa5c9444..f45d6558a 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -294,7 +294,7 @@ pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) - "select" => Rc::new(SelectionData {}), "touchcancel" | "touchend" | "touchmove" | "touchstart" => Rc::new(TouchData::from(event)), - "scroll" => Rc::new(()), + "scroll" => Rc::new(ScrollData {}), "wheel" => Rc::new(WheelData::from(event)), "animationstart" | "animationend" | "animationiteration" => { Rc::new(AnimationData::from(event)) diff --git a/packages/web/src/hot_reload.rs b/packages/web/src/hot_reload.rs index 6256cc935..e8b4e42e4 100644 --- a/packages/web/src/hot_reload.rs +++ b/packages/web/src/hot_reload.rs @@ -5,7 +5,6 @@ use futures_channel::mpsc::UnboundedReceiver; use dioxus_core::Template; pub(crate) fn init() -> UnboundedReceiver> { - use std::convert::TryInto; use wasm_bindgen::closure::Closure; use wasm_bindgen::JsCast; use web_sys::{MessageEvent, WebSocket}; @@ -31,14 +30,12 @@ pub(crate) fn init() -> UnboundedReceiver> { // change the rsx when new data is received let cl = Closure::wrap(Box::new(move |e: MessageEvent| { if let Ok(text) = e.data().dyn_into::() { - let text: Result = text.try_into(); - if let Ok(string) = text { - let val = serde_json::from_str::(&string).unwrap(); - // leak the value - let val: &'static serde_json::Value = Box::leak(Box::new(val)); - let template: Template<'_> = Template::deserialize(val).unwrap(); - tx.unbounded_send(template).unwrap(); - } + let string: String = text.into(); + let val = serde_json::from_str::(&string).unwrap(); + // leak the value + let val: &'static serde_json::Value = Box::leak(Box::new(val)); + let template: Template<'_> = Template::deserialize(val).unwrap(); + tx.unbounded_send(template).unwrap(); } }) as Box); From 9e388c3c51931d42787283dece0ce6532080d70c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 15 Dec 2023 08:50:54 -0600 Subject: [PATCH 022/377] refactor generational box into multiple files --- packages/generational-box/src/error.rs | 104 ++++ packages/generational-box/src/lib.rs | 637 ++++---------------- packages/generational-box/src/references.rs | 169 ++++++ packages/generational-box/src/sync.rs | 135 +++++ packages/generational-box/src/unsync.rs | 137 +++++ packages/signals/src/signal.rs | 3 +- 6 files changed, 651 insertions(+), 534 deletions(-) create mode 100644 packages/generational-box/src/error.rs create mode 100644 packages/generational-box/src/references.rs create mode 100644 packages/generational-box/src/sync.rs create mode 100644 packages/generational-box/src/unsync.rs diff --git a/packages/generational-box/src/error.rs b/packages/generational-box/src/error.rs new file mode 100644 index 000000000..9aef73901 --- /dev/null +++ b/packages/generational-box/src/error.rs @@ -0,0 +1,104 @@ +use std::error::Error; +use std::fmt::Debug; +use std::fmt::Display; + +#[derive(Debug, Clone)] +/// An error that can occur when trying to borrow a value. +pub enum BorrowError { + /// The value was dropped. + Dropped(ValueDroppedError), + /// The value was already borrowed mutably. + AlreadyBorrowedMut(AlreadyBorrowedMutError), +} + +impl Display for BorrowError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BorrowError::Dropped(error) => Display::fmt(error, f), + BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f), + } + } +} + +impl Error for BorrowError {} + +#[derive(Debug, Clone)] +/// An error that can occur when trying to borrow a value mutably. +pub enum BorrowMutError { + /// The value was dropped. + Dropped(ValueDroppedError), + /// The value was already borrowed. + AlreadyBorrowed(AlreadyBorrowedError), + /// The value was already borrowed mutably. + AlreadyBorrowedMut(AlreadyBorrowedMutError), +} + +impl Display for BorrowMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + BorrowMutError::Dropped(error) => Display::fmt(error, f), + BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f), + BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f), + } + } +} + +impl Error for BorrowMutError {} + +/// An error that can occur when trying to use a value that has been dropped. +#[derive(Debug, Copy, Clone)] +pub struct ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + pub(crate) created_at: &'static std::panic::Location<'static>, +} + +impl Display for ValueDroppedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow because the value was dropped.")?; + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + f.write_fmt(format_args!("created_at: {}", self.created_at))?; + Ok(()) + } +} + +impl std::error::Error for ValueDroppedError {} + +/// An error that can occur when trying to borrow a value that has already been borrowed mutably. +#[derive(Debug, Copy, Clone)] +pub struct AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + pub(crate) borrowed_mut_at: &'static std::panic::Location<'static>, +} + +impl Display for AlreadyBorrowedMutError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow because the value was already borrowed mutably.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?; + Ok(()) + } +} + +impl std::error::Error for AlreadyBorrowedMutError {} + +/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably. +#[derive(Debug, Clone)] +pub struct AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + pub(crate) borrowed_at: Vec<&'static std::panic::Location<'static>>, +} + +impl Display for AlreadyBorrowedError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + f.write_str("borrowed_at:")?; + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + for location in self.borrowed_at.iter() { + f.write_fmt(format_args!("\t{}", location))?; + } + Ok(()) + } +} + +impl std::error::Error for AlreadyBorrowedError {} diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 293bf6c5b..9fe47ba2a 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -1,21 +1,25 @@ #![doc = include_str!("../README.md")] #![warn(missing_docs)] -use parking_lot::{ - MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, -}; +use error::*; +use parking_lot::{Mutex, RwLock}; +use references::{GenerationalRefBorrowInfo, GenerationalRefMutBorrowInfo}; use std::any::Any; -use std::error::Error; -use std::fmt::Display; use std::sync::atomic::AtomicU32; use std::{ - cell::{Ref, RefCell, RefMut}, fmt::Debug, marker::PhantomData, ops::{Deref, DerefMut}, - sync::{Arc, OnceLock}, + sync::Arc, }; +use unsync::UnsyncStorage; + +mod error; +mod references; +mod sync; +mod unsync; + /// # Example /// /// ```compile_fail @@ -229,12 +233,15 @@ impl> GenerationalBox { created_at: self.created_at, })); } - self.raw.0.data.try_read().ok_or_else(|| { - BorrowError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: self.created_at, - }) - }) + self.raw.0.data.try_read( + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + self.created_at, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + GenerationalRefBorrowInfo { + borrowed_at: std::panic::Location::caller(), + borrowed_from: &self.raw.0.borrow, + }, + ) } /// Read the value. Panics if the value is no longer valid. @@ -252,12 +259,14 @@ impl> GenerationalBox { created_at: self.created_at, })); } - self.raw.0.data.try_write().ok_or_else(|| { - BorrowMutError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: self.created_at, - }) - }) + self.raw.0.data.try_write( + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + self.created_at, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + GenerationalRefMutBorrowInfo { + borrowed_from: &self.raw.0.borrow, + }, + ) } /// Write the value. Panics if the value is no longer valid. @@ -295,25 +304,6 @@ impl Clone for GenerationalBox { } } -/// A unsync storage. This is the default storage type. -pub struct UnsyncStorage(RefCell>>); - -impl Default for UnsyncStorage { - fn default() -> Self { - Self(RefCell::new(None)) - } -} - -/// A thread safe storage. This is slower than the unsync storage, but allows you to share the value between threads. -#[derive(Clone, Copy)] -pub struct SyncStorage(&'static RwLock>>); - -impl Default for SyncStorage { - fn default() -> Self { - Self(Box::leak(Box::new(RwLock::new(None)))) - } -} - /// A trait for types that can be mapped. pub trait Mappable: Deref { /// The type after the mapping. @@ -329,36 +319,6 @@ pub trait Mappable: Deref { ) -> Option>; } -impl Mappable for Ref<'static, T> { - type Mapped = Ref<'static, U>; - - fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { - Ref::map(_self, f) - } - - fn try_map( - _self: Self, - f: impl FnOnce(&T) -> Option<&U>, - ) -> Option> { - Ref::filter_map(_self, f).ok() - } -} - -impl Mappable for MappedRwLockReadGuard<'static, T> { - type Mapped = MappedRwLockReadGuard<'static, U>; - - fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { - MappedRwLockReadGuard::map(_self, f) - } - - fn try_map( - _self: Self, - f: impl FnOnce(&T) -> Option<&U>, - ) -> Option> { - MappedRwLockReadGuard::try_map(_self, f).ok() - } -} - /// A trait for types that can be mapped mutably. pub trait MappableMut: DerefMut { /// The type after the mapping. @@ -374,36 +334,6 @@ pub trait MappableMut: DerefMut { ) -> Option>; } -impl MappableMut for RefMut<'static, T> { - type Mapped = RefMut<'static, U>; - - fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { - RefMut::map(_self, f) - } - - fn try_map( - _self: Self, - f: impl FnOnce(&mut T) -> Option<&mut U>, - ) -> Option> { - RefMut::filter_map(_self, f).ok() - } -} - -impl MappableMut for MappedRwLockWriteGuard<'static, T> { - type Mapped = MappedRwLockWriteGuard<'static, U>; - - fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { - MappedRwLockWriteGuard::map(_self, f) - } - - fn try_map( - _self: Self, - f: impl FnOnce(&mut T) -> Option<&mut U>, - ) -> Option> { - MappedRwLockWriteGuard::try_map(_self, f).ok() - } -} - /// A trait for a storage backing type. (RefCell, RwLock, etc.) pub trait Storage: AnyStorage + 'static { /// The reference this storage type returns. @@ -412,22 +342,23 @@ pub trait Storage: AnyStorage + 'static { type Mut: MappableMut + DerefMut; /// Try to read the value. Returns None if the value is no longer valid. - fn try_read(&self) -> Option; - /// Read the value. Panics if the value is no longer valid. - fn read(&self) -> Self::Ref { - self.try_read() - .expect("generational box has been invalidated or the type has changed") - } - /// Try to write the value. Returns None if the value is no longer valid. - fn try_write(&self) -> Option; - /// Write the value. Panics if the value is no longer valid. - fn write(&self) -> Self::Mut { - self.try_write() - .expect("generational box has been invalidated or the type has changed") - } + fn try_read( + &'static self, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo, + ) -> Result; - /// Set the value. Panics if the value is no longer valid. - fn set(&self, value: Data); + /// Try to write the value. Returns None if the value is no longer valid. + fn try_write( + &'static self, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo, + ) -> Result; + + /// Set the value + fn set(&'static self, value: Data); } /// A trait for any storage backing type. @@ -453,127 +384,29 @@ pub trait AnyStorage: Default { } } -impl Storage for UnsyncStorage { - type Ref = Ref<'static, T>; - type Mut = RefMut<'static, T>; - - fn try_read(&self) -> Option { - Ref::filter_map(self.0.borrow(), |any| any.as_ref()?.downcast_ref()).ok() - } - - fn try_write(&self) -> Option { - RefMut::filter_map(self.0.borrow_mut(), |any| any.as_mut()?.downcast_mut()).ok() - } - - fn set(&self, value: T) { - *self.0.borrow_mut() = Some(Box::new(value)); - } -} - -thread_local! { - static UNSYNC_RUNTIME: RefCell>> = RefCell::new(Vec::new()); -} - -impl AnyStorage for UnsyncStorage { - fn data_ptr(&self) -> *const () { - self.0.as_ptr() as *const () - } - - fn take(&self) -> bool { - self.0.borrow_mut().take().is_some() - } - - fn claim() -> MemoryLocation { - UNSYNC_RUNTIME.with(|runtime| { - if let Some(location) = runtime.borrow_mut().pop() { - location - } else { - let data: &'static MemoryLocationInner = - &*Box::leak(Box::new(MemoryLocationInner { - data: Self::default(), - #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: 0.into(), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_at: Default::default(), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_mut_at: Default::default(), - })); - MemoryLocation(data) - } - }) - } - - fn recycle(location: &MemoryLocation) { - location.drop(); - UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(*location)); - } -} - -impl Storage for SyncStorage { - type Ref = MappedRwLockReadGuard<'static, T>; - type Mut = MappedRwLockWriteGuard<'static, T>; - - fn try_read(&self) -> Option { - RwLockReadGuard::try_map(self.0.read(), |any| any.as_ref()?.downcast_ref()).ok() - } - - fn try_write(&self) -> Option { - RwLockWriteGuard::try_map(self.0.write(), |any| any.as_mut()?.downcast_mut()).ok() - } - - fn set(&self, value: T) { - *self.0.write() = Some(Box::new(value)); - } -} - -static SYNC_RUNTIME: OnceLock>>>> = OnceLock::new(); - -fn sync_runtime() -> &'static Arc>>> { - SYNC_RUNTIME.get_or_init(|| Arc::new(Mutex::new(Vec::new()))) -} - -impl AnyStorage for SyncStorage { - fn data_ptr(&self) -> *const () { - self.0.data_ptr() as *const () - } - - fn take(&self) -> bool { - self.0.write().take().is_some() - } - - fn claim() -> MemoryLocation { - sync_runtime().lock().pop().unwrap_or_else(|| { - let data: &'static MemoryLocationInner = - &*Box::leak(Box::new(MemoryLocationInner { - data: Self::default(), - #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: 0.into(), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_at: Default::default(), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_mut_at: Default::default(), - })); - MemoryLocation(data) - }) - } - - fn recycle(location: &MemoryLocation) { - location.drop(); - sync_runtime().lock().push(*location); - } -} - -#[derive(Clone, Copy)] struct MemoryLocation(&'static MemoryLocationInner); +impl Clone for MemoryLocation { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for MemoryLocation {} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +#[derive(Debug, Default)] +struct MemoryLocationBorrowInfo { + borrowed_at: RwLock>>, + borrowed_mut_at: RwLock>>, +} + struct MemoryLocationInner { data: S, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: AtomicU32, #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_at: RwLock>>, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_mut_at: RwLock>>, + borrow: MemoryLocationBorrowInfo, } impl MemoryLocation { @@ -617,36 +450,29 @@ impl MemoryLocation { &self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - ) -> Result, BorrowError> + ) -> std::result::Result<>::Ref, error::BorrowError> where S: Storage, { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - self.0 - .borrowed_at - .write() - .push(std::panic::Location::caller()); - match self.0.data.try_read() { - Some(borrow) => { - match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref::()) { - Ok(reference) => Ok(GenerationalRef { - inner: reference, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefBorrowInfo { - borrowed_at: std::panic::Location::caller(), - borrowed_from: self.0, - }, - }), - Err(_) => Err(BorrowError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at, - })), - } - } - None => Err(BorrowError::AlreadyBorrowedMut(AlreadyBorrowedMutError { + match self.0.data.try_read( + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + GenerationalRefBorrowInfo { + borrowed_at: std::panic::Location::caller(), + borrowed_from: &self.0.borrow, + }, + ) { + Ok(read) => { #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_mut_at: self.0.borrowed_mut_at.read().unwrap(), - })), + self.0 + .borrow + .borrowed_at + .write() + .push(std::panic::Location::caller()); + Ok(read) + } + Err(err) => Err(err), } } @@ -655,270 +481,30 @@ impl MemoryLocation { &self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - ) -> Result, BorrowMutError> { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - { - self.0.borrowed_mut_at.write().unwrap() = Some(std::panic::Location::caller()); - } - match self.0.data.try_borrow_mut() { - Ok(borrow_mut) => { - match RefMut::filter_map(borrow_mut, |any| any.as_mut()?.downcast_mut::()) { - Ok(reference) => Ok(GenerationalRefMut { - inner: reference, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefMutBorrowInfo { - borrowed_from: self.0, - }, - }), - Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at, - })), + ) -> std::result::Result<>::Mut, error::BorrowMutError> + where + S: Storage, + { + match self.0.data.try_write( + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + GenerationalRefMutBorrowInfo { + borrowed_from: &self.0.borrow, + }, + ) { + Ok(read) => { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + { + *self.0.borrow.borrowed_mut_at.write() = Some(std::panic::Location::caller()); } + Ok(read) } - Err(_) => Err(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_at: self.0.borrowed_at.read().clone(), - })), + Err(err) => Err(err), } } } -#[derive(Debug, Clone)] -/// An error that can occur when trying to borrow a value. -pub enum BorrowError { - /// The value was dropped. - Dropped(ValueDroppedError), - /// The value was already borrowed mutably. - AlreadyBorrowedMut(AlreadyBorrowedMutError), -} - -impl Display for BorrowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BorrowError::Dropped(error) => Display::fmt(error, f), - BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f), - } - } -} - -impl Error for BorrowError {} - -#[derive(Debug, Clone)] -/// An error that can occur when trying to borrow a value mutably. -pub enum BorrowMutError { - /// The value was dropped. - Dropped(ValueDroppedError), - /// The value was already borrowed. - AlreadyBorrowed(AlreadyBorrowedError), - /// The value was already borrowed mutably. - AlreadyBorrowedMut(AlreadyBorrowedMutError), -} - -impl Display for BorrowMutError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BorrowMutError::Dropped(error) => Display::fmt(error, f), - BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f), - BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f), - } - } -} - -impl Error for BorrowMutError {} - -/// An error that can occur when trying to use a value that has been dropped. -#[derive(Debug, Copy, Clone)] -pub struct ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: &'static std::panic::Location<'static>, -} - -impl Display for ValueDroppedError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Failed to borrow because the value was dropped.")?; - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - f.write_fmt(format_args!("created_at: {}", self.created_at))?; - Ok(()) - } -} - -impl std::error::Error for ValueDroppedError {} - -/// An error that can occur when trying to borrow a value that has already been borrowed mutably. -#[derive(Debug, Copy, Clone)] -pub struct AlreadyBorrowedMutError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_mut_at: &'static std::panic::Location<'static>, -} - -impl Display for AlreadyBorrowedMutError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Failed to borrow because the value was already borrowed mutably.")?; - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?; - Ok(()) - } -} - -impl std::error::Error for AlreadyBorrowedMutError {} - -/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably. -#[derive(Debug, Clone)] -pub struct AlreadyBorrowedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_at: Vec<&'static std::panic::Location<'static>>, -} - -impl Display for AlreadyBorrowedError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?; - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - f.write_str("borrowed_at:")?; - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - for location in self.borrowed_at.iter() { - f.write_fmt(format_args!("\t{}", location))?; - } - Ok(()) - } -} - -impl std::error::Error for AlreadyBorrowedError {} - -/// A reference to a value in a generational box. -pub struct GenerationalRef { - inner: Ref<'static, T>, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefBorrowInfo, -} - -impl GenerationalRef { - /// Map one ref type to another. - pub fn map(orig: GenerationalRef, f: F) -> GenerationalRef - where - F: FnOnce(&T) -> &U, - { - GenerationalRef { - inner: Ref::map(orig.inner, f), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefBorrowInfo { - borrowed_at: orig.borrow.borrowed_at, - borrowed_from: orig.borrow.borrowed_from, - }, - } - } - - /// Filter one ref type to another. - pub fn filter_map(orig: GenerationalRef, f: F) -> Option> - where - F: FnOnce(&T) -> Option<&U>, - { - let Self { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow, - } = orig; - Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefBorrowInfo { - borrowed_at: borrow.borrowed_at, - borrowed_from: borrow.borrowed_from, - }, - }) - } -} - -impl Deref for GenerationalRef { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.inner.deref() - } -} - -#[cfg(any(debug_assertions, feature = "debug_borrows"))] -struct GenerationalRefBorrowInfo { - borrowed_at: &'static std::panic::Location<'static>, - borrowed_from: &'static MemoryLocationInner, -} - -#[cfg(any(debug_assertions, feature = "debug_borrows"))] -impl Drop for GenerationalRefBorrowInfo { - fn drop(&mut self) { - self.borrowed_from - .borrowed_at - .borrow_mut() - .retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _)); - } -} - -/// A mutable reference to a value in a generational box. -pub struct GenerationalRefMut { - inner: RefMut<'static, T>, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefMutBorrowInfo, -} - -impl GenerationalRefMut { - /// Map one ref type to another. - pub fn map(orig: GenerationalRefMut, f: F) -> GenerationalRefMut - where - F: FnOnce(&mut T) -> &mut U, - { - GenerationalRefMut { - inner: RefMut::map(orig.inner, f), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: orig.borrow, - } - } - - /// Filter one ref type to another. - pub fn filter_map(orig: GenerationalRefMut, f: F) -> Option> - where - F: FnOnce(&mut T) -> Option<&mut U>, - { - let Self { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow, - } = orig; - RefMut::filter_map(inner, f) - .ok() - .map(|inner| GenerationalRefMut { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow, - }) - } -} - -impl Deref for GenerationalRefMut { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.inner.deref() - } -} - -impl DerefMut for GenerationalRefMut { - fn deref_mut(&mut self) -> &mut Self::Target { - self.inner.deref_mut() - } -} - -#[cfg(any(debug_assertions, feature = "debug_borrows"))] -struct GenerationalRefMutBorrowInfo { - borrowed_from: &'static MemoryLocationInner, -} - -#[cfg(any(debug_assertions, feature = "debug_borrows"))] -impl Drop for GenerationalRefMutBorrowInfo { - fn drop(&mut self) { - self.borrowed_from.borrowed_mut_at.take(); - } -} - /// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped. pub struct Owner { owned: Arc>>>, @@ -926,36 +512,23 @@ pub struct Owner { } impl Owner { - /// Insert a value into the store. The value will be dropped when the owner is dropped. - #[track_caller] - pub fn insert(&self, value: T) -> GenerationalBox + /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped. + pub fn insert_with_caller( + &self, + value: T, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + caller: &'static std::panic::Location<'static>, + ) -> GenerationalBox where S: Storage, { let mut location = S::claim(); - let key = location.replace_with_caller( - value, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - std::panic::Location::caller(), - ); - self.owned.lock().push(location); - key - } - - /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped. - pub fn insert_with_caller( - &self, - value: T, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - caller: &'static std::panic::Location<'static>, - ) -> GenerationalBox { - let mut location = self.store.claim(); let key = location.replace_with_caller( value, #[cfg(any(debug_assertions, feature = "debug_borrows"))] caller, ); - self.owned.borrow_mut().push(location); + self.owned.lock().push(location); key } diff --git a/packages/generational-box/src/references.rs b/packages/generational-box/src/references.rs new file mode 100644 index 000000000..013541bd7 --- /dev/null +++ b/packages/generational-box/src/references.rs @@ -0,0 +1,169 @@ +use std::{ + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + +use crate::{Mappable, MappableMut, MemoryLocationBorrowInfo}; + +/// A reference to a value in a generational box. +pub struct GenerationalRef> { + inner: R, + phantom: PhantomData, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo, +} + +impl> GenerationalRef { + pub(crate) fn new( + inner: R, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] borrow: GenerationalRefBorrowInfo, + ) -> Self { + Self { + inner, + phantom: PhantomData, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + } + } +} + +impl> Mappable for GenerationalRef { + type Mapped = GenerationalRef>; + + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { + GenerationalRef { + inner: R::map(_self.inner, f), + phantom: PhantomData, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: _self.borrow.borrowed_at, + borrowed_from: _self.borrow.borrowed_from, + }, + } + } + + fn try_map( + _self: Self, + f: impl FnOnce(&T) -> Option<&U>, + ) -> Option> { + let Self { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + .. + } = _self; + R::try_map(inner, f).map(|inner| GenerationalRef { + inner, + phantom: PhantomData, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefBorrowInfo { + borrowed_at: borrow.borrowed_at, + borrowed_from: borrow.borrowed_from, + }, + }) + } +} + +impl> Deref for GenerationalRef { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +pub(crate) struct GenerationalRefBorrowInfo { + pub(crate) borrowed_at: &'static std::panic::Location<'static>, + pub(crate) borrowed_from: &'static MemoryLocationBorrowInfo, +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +impl Drop for GenerationalRefBorrowInfo { + fn drop(&mut self) { + self.borrowed_from + .borrowed_at + .write() + .retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _)); + } +} + +/// A mutable reference to a value in a generational box. +pub struct GenerationalRefMut> { + inner: W, + phantom: PhantomData, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefMutBorrowInfo, +} + +impl> GenerationalRefMut { + pub(crate) fn new( + inner: R, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: GenerationalRefMutBorrowInfo, + ) -> Self { + Self { + inner, + phantom: PhantomData, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + } + } +} + +impl> MappableMut for GenerationalRefMut { + type Mapped = GenerationalRefMut>; + + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { + GenerationalRefMut { + inner: W::map(_self.inner, f), + phantom: PhantomData, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: _self.borrow, + } + } + + fn try_map( + _self: Self, + f: impl FnOnce(&mut T) -> Option<&mut U>, + ) -> Option> { + let Self { + inner, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + .. + } = _self; + W::try_map(inner, f).map(|inner| GenerationalRefMut { + inner, + phantom: PhantomData, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow, + }) + } +} + +impl> Deref for GenerationalRefMut { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner.deref() + } +} + +impl> DerefMut for GenerationalRefMut { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner.deref_mut() + } +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +pub(crate) struct GenerationalRefMutBorrowInfo { + pub(crate) borrowed_from: &'static MemoryLocationBorrowInfo, +} + +#[cfg(any(debug_assertions, feature = "debug_borrows"))] +impl Drop for GenerationalRefMutBorrowInfo { + fn drop(&mut self) { + self.borrowed_from.borrowed_mut_at.write().take(); + } +} diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs new file mode 100644 index 000000000..1432ca6d8 --- /dev/null +++ b/packages/generational-box/src/sync.rs @@ -0,0 +1,135 @@ +use parking_lot::{ + MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, +}; +use std::sync::{Arc, OnceLock}; + +use crate::{ + error::{self, ValueDroppedError}, + references::{ + GenerationalRef, GenerationalRefBorrowInfo, GenerationalRefMut, + GenerationalRefMutBorrowInfo, + }, + AnyStorage, Mappable, MappableMut, MemoryLocation, MemoryLocationInner, Storage, +}; + +/// A thread safe storage. This is slower than the unsync storage, but allows you to share the value between threads. +#[derive(Default)] +pub struct SyncStorage(RwLock>>); + +static SYNC_RUNTIME: OnceLock>>>> = OnceLock::new(); + +fn sync_runtime() -> &'static Arc>>> { + SYNC_RUNTIME.get_or_init(|| Arc::new(Mutex::new(Vec::new()))) +} + +impl AnyStorage for SyncStorage { + fn data_ptr(&self) -> *const () { + self.0.data_ptr() as *const () + } + + fn take(&self) -> bool { + self.0.write().take().is_some() + } + + fn claim() -> MemoryLocation { + sync_runtime().lock().pop().unwrap_or_else(|| { + let data: &'static MemoryLocationInner = + &*Box::leak(Box::new(MemoryLocationInner { + data: Self::default(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: 0.into(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: Default::default(), + })); + MemoryLocation(data) + }) + } + + fn recycle(location: &MemoryLocation) { + location.drop(); + sync_runtime().lock().push(*location); + } +} + +impl Mappable for MappedRwLockReadGuard<'static, T> { + type Mapped = MappedRwLockReadGuard<'static, U>; + + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { + MappedRwLockReadGuard::map(_self, f) + } + + fn try_map( + _self: Self, + f: impl FnOnce(&T) -> Option<&U>, + ) -> Option> { + MappedRwLockReadGuard::try_map(_self, f).ok() + } +} + +impl MappableMut for MappedRwLockWriteGuard<'static, T> { + type Mapped = MappedRwLockWriteGuard<'static, U>; + + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { + MappedRwLockWriteGuard::map(_self, f) + } + + fn try_map( + _self: Self, + f: impl FnOnce(&mut T) -> Option<&mut U>, + ) -> Option> { + MappedRwLockWriteGuard::try_map(_self, f).ok() + } +} + +impl Storage for SyncStorage { + type Ref = GenerationalRef>; + type Mut = GenerationalRefMut>; + + fn try_read( + &'static self, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo, + ) -> Result { + RwLockReadGuard::try_map(self.0.read(), |any| any.as_ref()?.downcast_ref()) + .map_err(|_| { + error::BorrowError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at, + }) + }) + .map(|guard| { + GenerationalRef::new( + guard, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at, + ) + }) + } + + fn try_write( + &'static self, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo, + ) -> Result { + RwLockWriteGuard::try_map(self.0.write(), |any| any.as_mut()?.downcast_mut()) + .map_err(|_| { + error::BorrowMutError::Dropped(ValueDroppedError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at, + }) + }) + .map(|guard| { + GenerationalRefMut::new( + guard, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at, + ) + }) + } + + fn set(&self, value: T) { + *self.0.write() = Some(Box::new(value)); + } +} diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs new file mode 100644 index 000000000..83c670480 --- /dev/null +++ b/packages/generational-box/src/unsync.rs @@ -0,0 +1,137 @@ +use crate::{ + references::{ + GenerationalRef, GenerationalRefBorrowInfo, GenerationalRefMut, + GenerationalRefMutBorrowInfo, + }, + AnyStorage, Mappable, MappableMut, MemoryLocation, MemoryLocationInner, Storage, +}; +use std::cell::{Ref, RefCell, RefMut}; + +/// A unsync storage. This is the default storage type. +pub struct UnsyncStorage(RefCell>>); + +impl Default for UnsyncStorage { + fn default() -> Self { + Self(RefCell::new(None)) + } +} + +impl Mappable for Ref<'static, T> { + type Mapped = Ref<'static, U>; + + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { + Ref::map(_self, f) + } + + fn try_map( + _self: Self, + f: impl FnOnce(&T) -> Option<&U>, + ) -> Option> { + Ref::filter_map(_self, f).ok() + } +} + +impl MappableMut for RefMut<'static, T> { + type Mapped = RefMut<'static, U>; + + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { + RefMut::map(_self, f) + } + + fn try_map( + _self: Self, + f: impl FnOnce(&mut T) -> Option<&mut U>, + ) -> Option> { + RefMut::filter_map(_self, f).ok() + } +} + +impl Storage for UnsyncStorage { + type Ref = GenerationalRef>; + type Mut = GenerationalRefMut>; + + fn try_read( + &'static self, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo, + ) -> Result { + Ref::filter_map(self.0.borrow(), |any| any.as_ref()?.downcast_ref()) + .map_err(|_| { + crate::error::BorrowError::Dropped( + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + crate::error::ValueDroppedError { created_at }, + ) + }) + .map(|guard| { + GenerationalRef::new( + guard, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at, + ) + }) + } + + fn try_write( + &'static self, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + created_at: &'static std::panic::Location<'static>, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo, + ) -> Result { + RefMut::filter_map(self.0.borrow_mut(), |any| any.as_mut()?.downcast_mut()) + .map_err(|_| { + crate::error::BorrowMutError::Dropped( + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + crate::error::ValueDroppedError { created_at }, + ) + }) + .map(|guard| { + GenerationalRefMut::new( + guard, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at, + ) + }) + } + + fn set(&self, value: T) { + *self.0.borrow_mut() = Some(Box::new(value)); + } +} + +thread_local! { + static UNSYNC_RUNTIME: RefCell>> = RefCell::new(Vec::new()); +} + +impl AnyStorage for UnsyncStorage { + fn data_ptr(&self) -> *const () { + self.0.as_ptr() as *const () + } + + fn take(&self) -> bool { + self.0.borrow_mut().take().is_some() + } + + fn claim() -> MemoryLocation { + UNSYNC_RUNTIME.with(|runtime| { + if let Some(location) = runtime.borrow_mut().pop() { + location + } else { + let data: &'static MemoryLocationInner = + &*Box::leak(Box::new(MemoryLocationInner { + data: Self::default(), + #[cfg(any(debug_assertions, feature = "check_generation"))] + generation: 0.into(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrow: Default::default(), + })); + MemoryLocation(data) + } + }) + } + + fn recycle(location: &MemoryLocation) { + location.drop(); + UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(*location)); + } +} diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 8feb7fe95..d99807f0a 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -235,10 +235,9 @@ impl>> Signal { inner: CopyValue::new_with_caller( SignalData { subscribers: Default::default(), - effect_subscribers: Default::default(), update_any: schedule_update_any().expect("in a virtual dom"), value, - effect_stack: get_effect_stack(), + effect_ref: get_effect_ref(), }, #[cfg(debug_assertions)] caller, From 6b17d3db1e98dd0a33149ff3d4c7296b572cb98b Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 15 Dec 2023 18:35:48 -0600 Subject: [PATCH 023/377] fix signals crate --- packages/generational-box/README.md | 26 ++-- packages/generational-box/src/lib.rs | 147 ++++++++++---------- packages/generational-box/src/references.rs | 13 +- packages/generational-box/src/sync.rs | 23 +-- packages/generational-box/src/unsync.rs | 35 +++-- packages/signals/examples/errors.rs | 90 ++++++++++++ packages/signals/src/effect.rs | 2 +- packages/signals/src/impls.rs | 32 ++++- packages/signals/src/rt.rs | 12 +- packages/signals/src/selector.rs | 6 + packages/signals/src/signal.rs | 20 ++- 11 files changed, 277 insertions(+), 129 deletions(-) create mode 100644 packages/signals/examples/errors.rs diff --git a/packages/generational-box/README.md b/packages/generational-box/README.md index 95aab8402..14dc4e921 100644 --- a/packages/generational-box/README.md +++ b/packages/generational-box/README.md @@ -11,26 +11,20 @@ Three main types manage state in Generational Box: Example: ```rust -use generational_box::Store; +use generational_box::{UnsyncStorage, AnyStorage}; -// Create a store for this thread -let store = Store::default(); +// Create an owner for some state for a scope +let owner = UnsyncStorage::owner(); -{ - // Create an owner for some state for a scope - let owner = store.owner(); +// Create some non-copy data, move it into a owner, and work with copy data +let data: String = "hello world".to_string(); +let key = owner.insert(data); - // Create some non-copy data, move it into a owner, and work with copy data - let data: String = "hello world".to_string(); - let key = owner.insert(data); - - // The generational box can be read from and written to like a RefCell - let value = key.read(); - assert_eq!(*value, "hello world"); -} -// Reading value at this point will cause a panic +// The generational box can be read from and written to like a RefCell +let value = key.read(); +assert_eq!(*value, "hello world"); ``` ## How it works -Internally, `generational-box` creates an arena of generational RefCell's that are recyled when the owner is dropped. You can think of the cells as something like `&'static RefCell>` with a generational check to make recyling a cell easier to debug. Then GenerationalBox's are `Copy` because the `&'static` pointer is `Copy` +Internally, `generational-box` creates an arena of generational `RefCell`'s that are recycled when the owner is dropped. You can think of the cells as something like `&'static RefCell>` with a generational check to make recycling a cell easier to debug. Then GenerationalBox's are `Copy` because the `&'static` pointer is `Copy` diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 9fe47ba2a..b8fdee94b 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -1,10 +1,7 @@ #![doc = include_str!("../README.md")] #![warn(missing_docs)] -use error::*; -use parking_lot::{Mutex, RwLock}; -use references::{GenerationalRefBorrowInfo, GenerationalRefMutBorrowInfo}; -use std::any::Any; +use parking_lot::Mutex; use std::sync::atomic::AtomicU32; use std::{ fmt::Debug, @@ -13,7 +10,10 @@ use std::{ sync::Arc, }; -use unsync::UnsyncStorage; +pub use error::*; +pub use references::*; +pub use sync::SyncStorage; +pub use unsync::UnsyncStorage; mod error; mod references; @@ -37,12 +37,12 @@ fn reused() { let first_ptr; { let owner = UnsyncStorage::owner(); - first_ptr = owner.insert(1).raw.data.data_ptr(); + first_ptr = owner.insert(1).raw.0.data.data_ptr(); drop(owner); } { let owner = UnsyncStorage::owner(); - let second_ptr = owner.insert(1234).raw.data.data_ptr(); + let second_ptr = owner.insert(1234).raw.0.data.data_ptr(); assert_eq!(first_ptr, second_ptr); drop(owner); } @@ -233,7 +233,7 @@ impl> GenerationalBox { created_at: self.created_at, })); } - self.raw.0.data.try_read( + let result = self.raw.0.data.try_read( #[cfg(any(debug_assertions, feature = "debug_ownership"))] self.created_at, #[cfg(any(debug_assertions, feature = "debug_ownership"))] @@ -241,7 +241,18 @@ impl> GenerationalBox { borrowed_at: std::panic::Location::caller(), borrowed_from: &self.raw.0.borrow, }, - ) + ); + + if result.is_ok() { + self.raw + .0 + .borrow + .borrowed_at + .write() + .push(std::panic::Location::caller()); + } + + result } /// Read the value. Panics if the value is no longer valid. @@ -259,14 +270,20 @@ impl> GenerationalBox { created_at: self.created_at, })); } - self.raw.0.data.try_write( + let result = self.raw.0.data.try_write( #[cfg(any(debug_assertions, feature = "debug_ownership"))] self.created_at, #[cfg(any(debug_assertions, feature = "debug_ownership"))] GenerationalRefMutBorrowInfo { borrowed_from: &self.raw.0.borrow, }, - ) + ); + + if result.is_ok() { + *self.raw.0.borrow.borrowed_mut_at.write() = Some(std::panic::Location::caller()); + } + + result } /// Write the value. Panics if the value is no longer valid. @@ -296,9 +313,9 @@ impl> GenerationalBox { } } -impl Copy for GenerationalBox {} +impl Copy for GenerationalBox {} -impl Clone for GenerationalBox { +impl Clone for GenerationalBox { fn clone(&self) -> Self { *self } @@ -384,7 +401,8 @@ pub trait AnyStorage: Default { } } -struct MemoryLocation(&'static MemoryLocationInner); +/// A dynamic memory location that can be used in a generational box. +pub struct MemoryLocation(&'static MemoryLocationInner); impl Clone for MemoryLocation { fn clone(&self) -> Self { @@ -397,8 +415,31 @@ impl Copy for MemoryLocation {} #[cfg(any(debug_assertions, feature = "debug_borrows"))] #[derive(Debug, Default)] struct MemoryLocationBorrowInfo { - borrowed_at: RwLock>>, - borrowed_mut_at: RwLock>>, + pub(crate) borrowed_at: parking_lot::RwLock>>, + pub(crate) borrowed_mut_at: parking_lot::RwLock>>, +} + +impl MemoryLocationBorrowInfo { + fn borrow_mut_error(&self) -> BorrowMutError { + if let Some(borrowed_mut_at) = self.borrowed_mut_at.read().as_ref() { + BorrowMutError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_mut_at, + }) + } else { + BorrowMutError::AlreadyBorrowed(crate::error::AlreadyBorrowedError { + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + borrowed_at: self.borrowed_at.read().clone(), + }) + } + } + + fn borrow_error(&self) -> BorrowError { + BorrowError::AlreadyBorrowedMut(crate::error::AlreadyBorrowedMutError { + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + borrowed_mut_at: self.borrowed_mut_at.read().unwrap(), + }) + } } struct MemoryLocationInner { @@ -444,65 +485,6 @@ impl MemoryLocation { _marker: PhantomData, } } - - #[track_caller] - fn try_borrow( - &self, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: &'static std::panic::Location<'static>, - ) -> std::result::Result<>::Ref, error::BorrowError> - where - S: Storage, - { - match self.0.data.try_read( - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - GenerationalRefBorrowInfo { - borrowed_at: std::panic::Location::caller(), - borrowed_from: &self.0.borrow, - }, - ) { - Ok(read) => { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - self.0 - .borrow - .borrowed_at - .write() - .push(std::panic::Location::caller()); - Ok(read) - } - Err(err) => Err(err), - } - } - - #[track_caller] - fn try_borrow_mut( - &self, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: &'static std::panic::Location<'static>, - ) -> std::result::Result<>::Mut, error::BorrowMutError> - where - S: Storage, - { - match self.0.data.try_write( - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - GenerationalRefMutBorrowInfo { - borrowed_from: &self.0.borrow, - }, - ) { - Ok(read) => { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - { - *self.0.borrow.borrowed_mut_at.write() = Some(std::panic::Location::caller()); - } - Ok(read) - } - Err(err) => Err(err), - } - } } /// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped. @@ -511,7 +493,20 @@ pub struct Owner { phantom: PhantomData, } -impl Owner { +impl Owner { + /// Insert a value into the store. The value will be dropped when the owner is dropped. + #[track_caller] + pub fn insert(&self, value: T) -> GenerationalBox + where + S: Storage, + { + self.insert_with_caller( + value, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + std::panic::Location::caller(), + ) + } + /// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped. pub fn insert_with_caller( &self, diff --git a/packages/generational-box/src/references.rs b/packages/generational-box/src/references.rs index 013541bd7..c2f52946c 100644 --- a/packages/generational-box/src/references.rs +++ b/packages/generational-box/src/references.rs @@ -3,7 +3,7 @@ use std::{ ops::{Deref, DerefMut}, }; -use crate::{Mappable, MappableMut, MemoryLocationBorrowInfo}; +use crate::{Mappable, MappableMut}; /// A reference to a value in a generational box. pub struct GenerationalRef> { @@ -73,9 +73,10 @@ impl> Deref for GenerationalRef { } #[cfg(any(debug_assertions, feature = "debug_borrows"))] -pub(crate) struct GenerationalRefBorrowInfo { +/// Information about a borrow. +pub struct GenerationalRefBorrowInfo { pub(crate) borrowed_at: &'static std::panic::Location<'static>, - pub(crate) borrowed_from: &'static MemoryLocationBorrowInfo, + pub(crate) borrowed_from: &'static crate::MemoryLocationBorrowInfo, } #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -157,8 +158,10 @@ impl> DerefMut for GenerationalRefMut { } #[cfg(any(debug_assertions, feature = "debug_borrows"))] -pub(crate) struct GenerationalRefMutBorrowInfo { - pub(crate) borrowed_from: &'static MemoryLocationBorrowInfo, +/// Information about a mutable borrow. +pub struct GenerationalRefMutBorrowInfo { + /// The location where the borrow occurred. + pub(crate) borrowed_from: &'static crate::MemoryLocationBorrowInfo, } #[cfg(any(debug_assertions, feature = "debug_borrows"))] diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index 1432ca6d8..e11c17418 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -5,10 +5,7 @@ use std::sync::{Arc, OnceLock}; use crate::{ error::{self, ValueDroppedError}, - references::{ - GenerationalRef, GenerationalRefBorrowInfo, GenerationalRefMut, - GenerationalRefMutBorrowInfo, - }, + references::{GenerationalRef, GenerationalRefMut}, AnyStorage, Mappable, MappableMut, MemoryLocation, MemoryLocationInner, Storage, }; @@ -89,9 +86,14 @@ impl Storage for SyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at: crate::GenerationalRefBorrowInfo, ) -> Result { - RwLockReadGuard::try_map(self.0.read(), |any| any.as_ref()?.downcast_ref()) + let read = self + .0 + .try_read() + .ok_or_else(|| at.borrowed_from.borrow_error())?; + RwLockReadGuard::try_map(read, |any| any.as_ref()?.downcast_ref()) .map_err(|_| { error::BorrowError::Dropped(ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_ownership"))] @@ -111,9 +113,14 @@ impl Storage for SyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at: crate::GenerationalRefMutBorrowInfo, ) -> Result { - RwLockWriteGuard::try_map(self.0.write(), |any| any.as_mut()?.downcast_mut()) + let write = self + .0 + .try_write() + .ok_or_else(|| at.borrowed_from.borrow_mut_error())?; + RwLockWriteGuard::try_map(write, |any| any.as_mut()?.downcast_mut()) .map_err(|_| { error::BorrowMutError::Dropped(ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_ownership"))] diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs index 83c670480..dac9967f0 100644 --- a/packages/generational-box/src/unsync.rs +++ b/packages/generational-box/src/unsync.rs @@ -1,8 +1,5 @@ use crate::{ - references::{ - GenerationalRef, GenerationalRefBorrowInfo, GenerationalRefMut, - GenerationalRefMutBorrowInfo, - }, + references::{GenerationalRef, GenerationalRefMut}, AnyStorage, Mappable, MappableMut, MemoryLocation, MemoryLocationInner, Storage, }; use std::cell::{Ref, RefCell, RefMut}; @@ -54,14 +51,19 @@ impl Storage for UnsyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefBorrowInfo, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at: crate::GenerationalRefBorrowInfo, ) -> Result { - Ref::filter_map(self.0.borrow(), |any| any.as_ref()?.downcast_ref()) + let borrow = self + .0 + .try_borrow() + .map_err(|_| at.borrowed_from.borrow_error())?; + Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref()) .map_err(|_| { - crate::error::BorrowError::Dropped( + crate::error::BorrowError::Dropped(crate::error::ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_ownership"))] - crate::error::ValueDroppedError { created_at }, - ) + created_at, + }) }) .map(|guard| { GenerationalRef::new( @@ -76,14 +78,19 @@ impl Storage for UnsyncStorage { &'static self, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] at: GenerationalRefMutBorrowInfo, + #[cfg(any(debug_assertions, feature = "debug_ownership"))] + at: crate::GenerationalRefMutBorrowInfo, ) -> Result { - RefMut::filter_map(self.0.borrow_mut(), |any| any.as_mut()?.downcast_mut()) + let borrow = self + .0 + .try_borrow_mut() + .map_err(|_| at.borrowed_from.borrow_mut_error())?; + RefMut::filter_map(borrow, |any| any.as_mut()?.downcast_mut()) .map_err(|_| { - crate::error::BorrowMutError::Dropped( + crate::error::BorrowMutError::Dropped(crate::error::ValueDroppedError { #[cfg(any(debug_assertions, feature = "debug_ownership"))] - crate::error::ValueDroppedError { created_at }, - ) + created_at, + }) }) .map(|guard| { GenerationalRefMut::new( diff --git a/packages/signals/examples/errors.rs b/packages/signals/examples/errors.rs new file mode 100644 index 000000000..d082e55bb --- /dev/null +++ b/packages/signals/examples/errors.rs @@ -0,0 +1,90 @@ +#![allow(non_snake_case)] + +use dioxus::prelude::*; +use dioxus_signals::{use_signal, use_signal_sync, Signal}; +use generational_box::SyncStorage; + +fn main() { + dioxus_desktop::launch(app); +} + +#[derive(Clone, Copy)] +enum ErrorComponent { + ReadsSignal, + ReadsMutSignal, + ReadDroppedSignal, +} + +fn app(cx: Scope) -> Element { + let error = use_signal(cx, || None); + + render! { + match *error() { + Some(ErrorComponent::ReadsSignal) => render! { ReadsSignal {} }, + Some(ErrorComponent::ReadsMutSignal) => render! { ReadsMutSignal {} }, + Some(ErrorComponent::ReadDroppedSignal) => render! { ReadDroppedSignal {} }, + None => render! { + button { + onclick: move |_| error.set(Some(ErrorComponent::ReadsSignal)), + "ReadsSignal" + } + button { + onclick: move |_| error.set(Some(ErrorComponent::ReadsMutSignal)), + "ReadsMutSignal" + } + button { + onclick: move |_| error.set(Some(ErrorComponent::ReadDroppedSignal)), + "ReadDroppedSignal" + } + } + } + } +} + +fn ReadsSignal(cx: Scope) -> Element { + let signal = use_signal_sync(cx, || 0); + + let _write = signal.write(); + let _read = signal.read(); + + todo!() +} + +fn ReadsMutSignal(cx: Scope) -> Element { + let signal = use_signal_sync(cx, || 0); + + let _read = signal.read(); + let _write = signal.write(); + + todo!() +} + +fn ReadDroppedSignal(cx: Scope) -> Element { + let signal = use_signal_sync(cx, || None); + if cx.generation() < 4 { + cx.needs_update(); + } + render! { + if let Some(value) = &*signal() { + render!{"{value:?}"} + } else { + render! { + ReadDroppedSignalChild { parent_signal: signal } + } + } + } +} + +#[component] +fn ReadDroppedSignalChild( + cx: Scope, + parent_signal: Signal>, SyncStorage>, +) -> Element { + let signal = use_signal_sync(cx, || 0); + cx.use_hook(move || { + parent_signal.set(Some(signal.clone())); + }); + render! { + "{signal}" + } +} diff --git a/packages/signals/src/effect.rs b/packages/signals/src/effect.rs index 7e61e8014..84c19ee9c 100644 --- a/packages/signals/src/effect.rs +++ b/packages/signals/src/effect.rs @@ -170,7 +170,7 @@ impl Effect { /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. pub fn try_run(&self) { tracing::trace!("Running effect: {:?}", self); - if let Some(mut inner) = self.inner.try_write() { + if let Ok(mut inner) = self.inner.try_write() { { EFFECT_STACK.with(|stack| { stack.effects.write().push(*self); diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 676101b20..898fd98d2 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -12,32 +12,37 @@ use std::{ macro_rules! read_impls { ($ty:ident, $bound:path) => { impl Default for $ty { + #[track_caller] fn default() -> Self { Self::new_maybe_sync(Default::default()) } } impl std::clone::Clone for $ty { + #[track_caller] fn clone(&self) -> Self { *self } } - impl Copy for $ty {} + impl Copy for $ty {} impl Display for $ty { + #[track_caller] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Display::fmt(v, f)) } } impl Debug for $ty { + #[track_caller] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Debug::fmt(v, f)) } } impl PartialEq for $ty { + #[track_caller] fn eq(&self, other: &T) -> bool { self.with(|v| *v == *other) } @@ -50,18 +55,21 @@ macro_rules! write_impls { impl + Copy + 'static, S: $bound> std::ops::Add for $ty { type Output = T; + #[track_caller] fn add(self, rhs: T) -> Self::Output { self.with(|v| *v + rhs) } } impl + Copy + 'static, S: $bound> std::ops::AddAssign for $ty { + #[track_caller] fn add_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v + rhs) } } impl + Copy + 'static, S: $bound> std::ops::SubAssign for $ty { + #[track_caller] fn sub_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v - rhs) } @@ -70,12 +78,14 @@ macro_rules! write_impls { impl + Copy + 'static, S: $bound> std::ops::Sub for $ty { type Output = T; + #[track_caller] fn sub(self, rhs: T) -> Self::Output { self.with(|v| *v - rhs) } } impl + Copy + 'static, S: $bound> std::ops::MulAssign for $ty { + #[track_caller] fn mul_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v * rhs) } @@ -84,12 +94,14 @@ macro_rules! write_impls { impl + Copy + 'static, S: $bound> std::ops::Mul for $ty { type Output = T; + #[track_caller] fn mul(self, rhs: T) -> Self::Output { self.with(|v| *v * rhs) } } impl + Copy + 'static, S: $bound> std::ops::DivAssign for $ty { + #[track_caller] fn div_assign(&mut self, rhs: T) { self.with_mut(|v| *v = *v / rhs) } @@ -98,6 +110,7 @@ macro_rules! write_impls { impl + Copy + 'static, S: $bound> std::ops::Div for $ty { type Output = T; + #[track_caller] fn div(self, rhs: T) -> Self::Output { self.with(|v| *v / rhs) } @@ -105,51 +118,61 @@ macro_rules! write_impls { impl $ty, S> { /// Pushes a new value to the end of the vector. + #[track_caller] pub fn push(&self, value: T) { self.with_mut(|v| v.push(value)) } /// Pops the last value from the vector. + #[track_caller] pub fn pop(&self) -> Option { self.with_mut(|v| v.pop()) } /// Inserts a new value at the given index. + #[track_caller] pub fn insert(&self, index: usize, value: T) { self.with_mut(|v| v.insert(index, value)) } /// Removes the value at the given index. + #[track_caller] pub fn remove(&self, index: usize) -> T { self.with_mut(|v| v.remove(index)) } /// Clears the vector, removing all values. + #[track_caller] pub fn clear(&self) { self.with_mut(|v| v.clear()) } /// Extends the vector with the given iterator. + #[track_caller] pub fn extend(&self, iter: impl IntoIterator) { self.with_mut(|v| v.extend(iter)) } /// Truncates the vector to the given length. + #[track_caller] pub fn truncate(&self, len: usize) { self.with_mut(|v| v.truncate(len)) } /// Swaps two values in the vector. + #[track_caller] pub fn swap_remove(&self, index: usize) -> T { self.with_mut(|v| v.swap_remove(index)) } /// Retains only the values that match the given predicate. + #[track_caller] pub fn retain(&self, f: impl FnMut(&T) -> bool) { self.with_mut(|v| v.retain(f)) } /// Splits the vector into two at the given index. + #[track_caller] pub fn split_off(&self, at: usize) -> Vec { self.with_mut(|v| v.split_off(at)) } @@ -161,6 +184,7 @@ read_impls!(CopyValue, Storage); impl>> CopyValue, S> { /// Read a value from the inner vector. + #[track_caller] pub fn get(&self, index: usize) -> Option<>>::Mapped> { S::Ref::try_map(self.read(), move |v| v.get(index)) } @@ -168,6 +192,7 @@ impl>> CopyValue, S> { impl>> CopyValue, S> { /// Unwraps the inner value and clones it. + #[track_caller] pub fn unwrap(&self) -> T where T: Clone, @@ -176,6 +201,7 @@ impl>> CopyValue, S> { } /// Attempts to read the inner value of the Option. + #[track_caller] pub fn as_ref(&self) -> Option<>>::Mapped> { S::Ref::try_map(self.read(), |v| v.as_ref()) } @@ -185,21 +211,25 @@ write_impls!(CopyValue, Storage, Storage>); impl>> CopyValue, S> { /// Takes the value out of the Option. + #[track_caller] pub fn take(&self) -> Option { self.with_mut(|v| v.take()) } /// Replace the value in the Option. + #[track_caller] pub fn replace(&self, value: T) -> Option { self.with_mut(|v| v.replace(value)) } /// Gets the value out of the Option, or inserts the given value if the Option is empty. + #[track_caller] pub fn get_or_insert(&self, default: T) -> >>::Mapped { self.get_or_insert_with(|| default) } /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. + #[track_caller] pub fn get_or_insert_with( &self, default: impl FnOnce() -> T, diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index d9b178e54..2a8135e3a 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -7,10 +7,7 @@ use std::rc::Rc; use dioxus_core::prelude::*; use dioxus_core::ScopeId; -use generational_box::{ - BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, - Storage, Store, -}; +use generational_box::{GenerationalBox, Owner, Storage}; use crate::Effect; @@ -82,6 +79,7 @@ impl CopyValue { } /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. + #[track_caller] pub fn new_in_scope(value: T, scope: ScopeId) -> Self { Self::new_maybe_sync_in_scope(value, scope) } @@ -91,6 +89,7 @@ impl> CopyValue { /// Create a new CopyValue. The value will be stored in the current component. /// /// Once the component this value is created in is dropped, the value will be dropped. + #[track_caller] pub fn new_maybe_sync(value: T) -> Self { let owner = current_owner(); @@ -117,6 +116,7 @@ impl> CopyValue { } /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. + #[track_caller] pub fn new_maybe_sync_in_scope(value: T, scope: ScopeId) -> Self { let owner = owner_in_scope(scope); @@ -143,7 +143,7 @@ impl> CopyValue { /// Try to read the value. If the value has been dropped, this will return None. #[track_caller] - pub fn try_read(&self) -> Option { + pub fn try_read(&self) -> Result { self.value.try_read() } @@ -155,7 +155,7 @@ impl> CopyValue { /// Try to write the value. If the value has been dropped, this will return None. #[track_caller] - pub fn try_write(&self) -> Option { + pub fn try_write(&self) -> Result { self.value.try_write() } diff --git a/packages/signals/src/selector.rs b/packages/signals/src/selector.rs index 560c2c94b..c9564b08a 100644 --- a/packages/signals/src/selector.rs +++ b/packages/signals/src/selector.rs @@ -22,6 +22,7 @@ use crate::{use_signal, EffectInner, EFFECT_STACK}; /// render! { "{double}" } /// } /// ``` +#[track_caller] #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] pub fn use_selector( cx: &ScopeState, @@ -47,6 +48,7 @@ pub fn use_selector( /// render! { "{double}" } /// } /// ``` +#[track_caller] #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] pub fn use_maybe_sync_selector>>( cx: &ScopeState, @@ -71,6 +73,7 @@ pub fn use_maybe_sync_selector>>( /// render! { "{double}" } /// } /// ``` +#[track_caller] #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] pub fn use_selector_with_dependencies( cx: &ScopeState, @@ -99,6 +102,7 @@ where /// render! { "{double}" } /// } /// ``` +#[track_caller] #[must_use = "Consider using `use_effect` to rerun a callback when dependencies change"] pub fn use_maybe_sync_selector_with_dependencies< R: PartialEq, @@ -129,6 +133,7 @@ where /// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. +#[track_caller] pub fn selector(f: impl FnMut() -> R + 'static) -> ReadOnlySignal { maybe_sync_selector(f) } @@ -136,6 +141,7 @@ pub fn selector(f: impl FnMut() -> R + 'static) -> ReadOnlySignal< /// Creates a new Selector that may be Sync + Send. The selector will be run immediately and whenever any signal it reads changes. /// /// Selectors can be used to efficiently compute derived data from signals. +#[track_caller] pub fn maybe_sync_selector>>( mut f: impl FnMut() -> R + 'static, ) -> ReadOnlySignal { diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index d99807f0a..700d9cb1f 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -99,11 +99,20 @@ pub fn use_signal(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal< /// } /// ``` #[must_use] +#[track_caller] pub fn use_signal_sync( cx: &ScopeState, f: impl FnOnce() -> T, ) -> Signal { - *cx.use_hook(|| Signal::new_maybe_sync(f())) + #[cfg(debug_assertions)] + let caller = std::panic::Location::caller(); + *cx.use_hook(|| { + Signal::new_with_caller( + f(), + #[cfg(debug_assertions)] + caller, + ) + }) } #[derive(Clone)] @@ -207,6 +216,7 @@ impl Signal { } /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. + #[track_caller] pub fn new_in_scope(value: T, owner: ScopeId) -> Self { Self::new_maybe_sync_in_scope(value, owner) } @@ -214,6 +224,7 @@ impl Signal { impl>> Signal { /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. + #[track_caller] #[tracing::instrument(skip(value))] pub fn new_maybe_sync(value: T) -> Self { Self { @@ -246,6 +257,7 @@ impl>> Signal { } /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. + #[track_caller] #[tracing::instrument(skip(value))] pub fn new_maybe_sync_in_scope(value: T, owner: ScopeId) -> Self { Self { @@ -513,6 +525,7 @@ pub struct ReadOnlySignal> = UnsyncStorage> impl ReadOnlySignal { /// Create a new read-only signal. + #[track_caller] pub fn new(signal: Signal) -> Self { Self::new_maybe_sync(signal) } @@ -520,6 +533,7 @@ impl ReadOnlySignal { impl>> ReadOnlySignal { /// Create a new read-only signal that is maybe sync. + #[track_caller] pub fn new_maybe_sync(signal: Signal) -> Self { Self { inner: signal } } @@ -542,7 +556,9 @@ impl>> ReadOnlySignal { /// Get the current value of the signal. **Unlike read, this will not subscribe the current scope to the signal which can cause parts of your UI to not update.** /// /// If the signal has been dropped, this will panic. - pub fn peek(&self) -> GenerationalRef { + pub fn peek( + &self, + ) -> <>>::Ref as Mappable>>::Mapped { self.inner.peek() } From e0ff6ce4dad3052ec8d07f2033fb6f3b06ab2749 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Dec 2023 17:42:34 -0600 Subject: [PATCH 024/377] fix some merge changes in generational box --- packages/generational-box/src/lib.rs | 303 --------------------------- 1 file changed, 303 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 467868fb1..b8fdee94b 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -485,309 +485,6 @@ impl MemoryLocation { _marker: PhantomData, } } - - #[track_caller] - fn try_borrow( - &self, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: &'static std::panic::Location<'static>, - ) -> Result, BorrowError> { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - self.0 - .borrowed_at - .borrow_mut() - .push(std::panic::Location::caller()); - match self.0.data.try_borrow() { - Ok(borrow) => match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref::()) { - Ok(reference) => Ok(GenerationalRef { - inner: reference, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefBorrowInfo { - borrowed_at: std::panic::Location::caller(), - borrowed_from: self.0, - }, - }), - Err(_) => Err(BorrowError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at, - })), - }, - Err(_) => Err(BorrowError::AlreadyBorrowedMut(AlreadyBorrowedMutError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_mut_at: self.0.borrowed_mut_at.get().unwrap(), - })), - } - } - - #[track_caller] - fn try_borrow_mut( - &self, - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: &'static std::panic::Location<'static>, - ) -> Result, BorrowMutError> { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - { - self.0 - .borrowed_mut_at - .set(Some(std::panic::Location::caller())); - } - match self.0.data.try_borrow_mut() { - Ok(borrow_mut) => { - match RefMut::filter_map(borrow_mut, |any| any.as_mut()?.downcast_mut::()) { - Ok(reference) => Ok(GenerationalRefMut { - inner: reference, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefMutBorrowInfo { - borrowed_from: self.0, - }, - }), - Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at, - })), - } - } - Err(_) => Err(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_at: self.0.borrowed_at.borrow().clone(), - })), - } - } -} - -#[derive(Debug, Clone)] -/// An error that can occur when trying to borrow a value. -pub enum BorrowError { - /// The value was dropped. - Dropped(ValueDroppedError), - /// The value was already borrowed mutably. - AlreadyBorrowedMut(AlreadyBorrowedMutError), -} - -impl Display for BorrowError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BorrowError::Dropped(error) => Display::fmt(error, f), - BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f), - } - } -} - -impl Error for BorrowError {} - -#[derive(Debug, Clone)] -/// An error that can occur when trying to borrow a value mutably. -pub enum BorrowMutError { - /// The value was dropped. - Dropped(ValueDroppedError), - /// The value was already borrowed. - AlreadyBorrowed(AlreadyBorrowedError), - /// The value was already borrowed mutably. - AlreadyBorrowedMut(AlreadyBorrowedMutError), -} - -impl Display for BorrowMutError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - BorrowMutError::Dropped(error) => Display::fmt(error, f), - BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f), - BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f), - } - } -} - -impl Error for BorrowMutError {} - -/// An error that can occur when trying to use a value that has been dropped. -#[derive(Debug, Copy, Clone)] -pub struct ValueDroppedError { - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - created_at: &'static std::panic::Location<'static>, -} - -impl Display for ValueDroppedError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Failed to borrow because the value was dropped.")?; - #[cfg(any(debug_assertions, feature = "debug_ownership"))] - f.write_fmt(format_args!("created_at: {}", self.created_at))?; - Ok(()) - } -} - -impl std::error::Error for ValueDroppedError {} - -/// An error that can occur when trying to borrow a value that has already been borrowed mutably. -#[derive(Debug, Copy, Clone)] -pub struct AlreadyBorrowedMutError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_mut_at: &'static std::panic::Location<'static>, -} - -impl Display for AlreadyBorrowedMutError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Failed to borrow because the value was already borrowed mutably.")?; - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?; - Ok(()) - } -} - -impl std::error::Error for AlreadyBorrowedMutError {} - -/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably. -#[derive(Debug, Clone)] -pub struct AlreadyBorrowedError { - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrowed_at: Vec<&'static std::panic::Location<'static>>, -} - -impl Display for AlreadyBorrowedError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?; - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - f.write_str("borrowed_at:")?; - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - for location in self.borrowed_at.iter() { - f.write_fmt(format_args!("\t{}", location))?; - } - Ok(()) - } -} - -impl std::error::Error for AlreadyBorrowedError {} - -/// A reference to a value in a generational box. -pub struct GenerationalRef { - inner: Ref<'static, T>, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefBorrowInfo, -} - -impl GenerationalRef { - /// Map one ref type to another. - pub fn map(orig: GenerationalRef, f: F) -> GenerationalRef - where - F: FnOnce(&T) -> &U, - { - GenerationalRef { - inner: Ref::map(orig.inner, f), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefBorrowInfo { - borrowed_at: orig.borrow.borrowed_at, - borrowed_from: orig.borrow.borrowed_from, - }, - } - } - - /// Filter one ref type to another. - pub fn filter_map(orig: GenerationalRef, f: F) -> Option> - where - F: FnOnce(&T) -> Option<&U>, - { - let Self { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow, - } = orig; - Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefBorrowInfo { - borrowed_at: borrow.borrowed_at, - borrowed_from: borrow.borrowed_from, - }, - }) - } -} - -impl Deref for GenerationalRef { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.inner.deref() - } -} - -#[cfg(any(debug_assertions, feature = "debug_borrows"))] -struct GenerationalRefBorrowInfo { - borrowed_at: &'static std::panic::Location<'static>, - borrowed_from: &'static MemoryLocationInner, -} - -#[cfg(any(debug_assertions, feature = "debug_borrows"))] -impl Drop for GenerationalRefBorrowInfo { - fn drop(&mut self) { - self.borrowed_from - .borrowed_at - .borrow_mut() - .retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _)); - } -} - -/// A mutable reference to a value in a generational box. -pub struct GenerationalRefMut { - inner: RefMut<'static, T>, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: GenerationalRefMutBorrowInfo, -} - -impl GenerationalRefMut { - /// Map one ref type to another. - pub fn map(orig: GenerationalRefMut, f: F) -> GenerationalRefMut - where - F: FnOnce(&mut T) -> &mut U, - { - GenerationalRefMut { - inner: RefMut::map(orig.inner, f), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow: orig.borrow, - } - } - - /// Filter one ref type to another. - pub fn filter_map(orig: GenerationalRefMut, f: F) -> Option> - where - F: FnOnce(&mut T) -> Option<&mut U>, - { - let Self { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow, - } = orig; - RefMut::filter_map(inner, f) - .ok() - .map(|inner| GenerationalRefMut { - inner, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - borrow, - }) - } -} - -impl Deref for GenerationalRefMut { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.inner.deref() - } -} - -impl DerefMut for GenerationalRefMut { - fn deref_mut(&mut self) -> &mut Self::Target { - self.inner.deref_mut() - } -} - -#[cfg(any(debug_assertions, feature = "debug_borrows"))] -struct GenerationalRefMutBorrowInfo { - borrowed_from: &'static MemoryLocationInner, -} - -#[cfg(any(debug_assertions, feature = "debug_borrows"))] -impl Drop for GenerationalRefMutBorrowInfo { - fn drop(&mut self) { - self.borrowed_from.borrowed_mut_at.take(); - } } /// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped. From 5ab0c48dddd86578bfe260a28446254621823daa Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sun, 17 Dec 2023 17:55:02 -0600 Subject: [PATCH 025/377] fix clippy --- packages/cli/src/cli/autoformat.rs | 2 -- packages/signals/examples/errors.rs | 32 ++++++++++++++--------------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/packages/cli/src/cli/autoformat.rs b/packages/cli/src/cli/autoformat.rs index 8cbb86269..2c0691d6c 100644 --- a/packages/cli/src/cli/autoformat.rs +++ b/packages/cli/src/cli/autoformat.rs @@ -147,8 +147,6 @@ async fn autoformat_project(check: bool) -> Result<()> { return Ok(()); } - let indent = indentation_for(&files_to_format[0])?; - if files_to_format.is_empty() { return Ok(()); } diff --git a/packages/signals/examples/errors.rs b/packages/signals/examples/errors.rs index d082e55bb..8155f9192 100644 --- a/packages/signals/examples/errors.rs +++ b/packages/signals/examples/errors.rs @@ -10,9 +10,9 @@ fn main() { #[derive(Clone, Copy)] enum ErrorComponent { - ReadsSignal, - ReadsMutSignal, - ReadDroppedSignal, + Read, + ReadMut, + ReadDropped, } fn app(cx: Scope) -> Element { @@ -20,28 +20,28 @@ fn app(cx: Scope) -> Element { render! { match *error() { - Some(ErrorComponent::ReadsSignal) => render! { ReadsSignal {} }, - Some(ErrorComponent::ReadsMutSignal) => render! { ReadsMutSignal {} }, - Some(ErrorComponent::ReadDroppedSignal) => render! { ReadDroppedSignal {} }, + Some(ErrorComponent::Read) => render! { Read {} }, + Some(ErrorComponent::ReadMut) => render! { ReadMut {} }, + Some(ErrorComponent::ReadDropped) => render! { ReadDropped {} }, None => render! { button { - onclick: move |_| error.set(Some(ErrorComponent::ReadsSignal)), - "ReadsSignal" + onclick: move |_| error.set(Some(ErrorComponent::Read)), + "Read" } button { - onclick: move |_| error.set(Some(ErrorComponent::ReadsMutSignal)), - "ReadsMutSignal" + onclick: move |_| error.set(Some(ErrorComponent::ReadMut)), + "ReadMut" } button { - onclick: move |_| error.set(Some(ErrorComponent::ReadDroppedSignal)), - "ReadDroppedSignal" + onclick: move |_| error.set(Some(ErrorComponent::ReadDropped)), + "ReadDropped" } } } } } -fn ReadsSignal(cx: Scope) -> Element { +fn Read(cx: Scope) -> Element { let signal = use_signal_sync(cx, || 0); let _write = signal.write(); @@ -50,7 +50,7 @@ fn ReadsSignal(cx: Scope) -> Element { todo!() } -fn ReadsMutSignal(cx: Scope) -> Element { +fn ReadMut(cx: Scope) -> Element { let signal = use_signal_sync(cx, || 0); let _read = signal.read(); @@ -59,7 +59,7 @@ fn ReadsMutSignal(cx: Scope) -> Element { todo!() } -fn ReadDroppedSignal(cx: Scope) -> Element { +fn ReadDropped(cx: Scope) -> Element { let signal = use_signal_sync(cx, || None); if cx.generation() < 4 { cx.needs_update(); @@ -82,7 +82,7 @@ fn ReadDroppedSignalChild( ) -> Element { let signal = use_signal_sync(cx, || 0); cx.use_hook(move || { - parent_signal.set(Some(signal.clone())); + parent_signal.set(Some(signal)); }); render! { "{signal}" From a2fadc8d114d6a4402e4c66b12a653011e7977e3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 23 Dec 2023 13:43:27 -0600 Subject: [PATCH 026/377] Make the function bounds for map less restrictive --- packages/signals/examples/iterator.rs | 28 +++++++++++++++++++++++++++ packages/signals/src/map.rs | 2 +- packages/signals/src/signal.rs | 2 +- 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 packages/signals/examples/iterator.rs diff --git a/packages/signals/examples/iterator.rs b/packages/signals/examples/iterator.rs new file mode 100644 index 000000000..75875cde2 --- /dev/null +++ b/packages/signals/examples/iterator.rs @@ -0,0 +1,28 @@ +use dioxus::prelude::*; +use dioxus_signals::*; + +fn main() { + dioxus_desktop::launch(App); +} + +#[component] +fn App(cx: Scope) -> Element { + let signal = use_signal(cx, || vec![String::from("Hello"), String::from("World")]); + + render! { + button { + onclick: move |_| { + signal.write().push(String::from("Hello")); + }, + "Add one" + } + for i in 0..signal().len() { + Child { signal: signal.map(move |v| v.get(i).unwrap()) } + } + } +} + +#[component] +fn Child(cx: Scope, signal: SignalMap) -> Element { + render! {"{signal:?}"} +} diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index 1442309e1..2673fabad 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -13,7 +13,7 @@ pub struct SignalMap { impl SignalMap { /// Create a new mapped signal. - pub fn new(signal: Signal, mapping: fn(&T) -> &U) -> Self { + pub fn new(signal: Signal, mapping: impl Fn(&T) -> &U + 'static) -> Self { Self { origin_scope: signal.origin_scope(), mapping: CopyValue::new(Box::new(move || Ref::map(signal.read(), |v| (mapping)(v)))), diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index ad025c839..564a644e7 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -257,7 +257,7 @@ impl Signal { } /// Map the signal to a new type. - pub fn map(self, f: fn(&T) -> &O) -> SignalMap { + pub fn map(self, f: impl Fn(&T) -> &O + 'static) -> SignalMap { SignalMap::new(self, f) } } From 31489167f7cde672dae94f3eb30b67ab6b6739dc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 23 Dec 2023 13:52:23 -0600 Subject: [PATCH 027/377] use map in the signal iterator --- packages/generational-box/src/lib.rs | 8 ++--- packages/signals/examples/iterator.rs | 4 +-- packages/signals/src/impls.rs | 47 ++++++++++++++++++------ packages/signals/src/map.rs | 51 ++++++++++++++++++++++----- 4 files changed, 86 insertions(+), 24 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index f56904ede..836256d73 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -479,7 +479,7 @@ impl Display for AlreadyBorrowedError { impl std::error::Error for AlreadyBorrowedError {} /// A reference to a value in a generational box. -pub struct GenerationalRef { +pub struct GenerationalRef { inner: Ref<'static, T>, #[cfg(any(debug_assertions, feature = "debug_borrows"))] borrow: GenerationalRefBorrowInfo, @@ -487,7 +487,7 @@ pub struct GenerationalRef { impl GenerationalRef { /// Map one ref type to another. - pub fn map(orig: GenerationalRef, f: F) -> GenerationalRef + pub fn map(orig: GenerationalRef, f: F) -> GenerationalRef where F: FnOnce(&T) -> &U, { @@ -502,7 +502,7 @@ impl GenerationalRef { } /// Filter one ref type to another. - pub fn filter_map(orig: GenerationalRef, f: F) -> Option> + pub fn filter_map(orig: GenerationalRef, f: F) -> Option> where F: FnOnce(&T) -> Option<&U>, { @@ -522,7 +522,7 @@ impl GenerationalRef { } } -impl Deref for GenerationalRef { +impl Deref for GenerationalRef { type Target = T; fn deref(&self) -> &Self::Target { diff --git a/packages/signals/examples/iterator.rs b/packages/signals/examples/iterator.rs index 75875cde2..24db7712f 100644 --- a/packages/signals/examples/iterator.rs +++ b/packages/signals/examples/iterator.rs @@ -16,8 +16,8 @@ fn App(cx: Scope) -> Element { }, "Add one" } - for i in 0..signal().len() { - Child { signal: signal.map(move |v| v.get(i).unwrap()) } + for item in signal.iter_signals() { + Child { signal: item } } } } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 077777c40..f741b05ed 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,5 +1,6 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; +use crate::SignalMap; use generational_box::GenerationalRef; use generational_box::GenerationalRefMut; @@ -213,20 +214,20 @@ pub struct CopyValueIterator { value: CopyValue>, } -impl Iterator for CopyValueIterator { - type Item = T; +impl Iterator for CopyValueIterator { + type Item = GenerationalRef; fn next(&mut self) -> Option { let index = self.index; self.index += 1; - self.value.get(index).map(|v| v.clone()) + self.value.get(index) } } -impl IntoIterator for CopyValue> { +impl IntoIterator for CopyValue> { type IntoIter = CopyValueIterator; - type Item = T; + type Item = GenerationalRef; fn into_iter(self) -> Self::IntoIter { CopyValueIterator { @@ -256,20 +257,20 @@ pub struct SignalIterator { value: Signal>, } -impl Iterator for SignalIterator { - type Item = T; +impl Iterator for SignalIterator { + type Item = GenerationalRef; fn next(&mut self) -> Option { let index = self.index; self.index += 1; - self.value.get(index).map(|v| v.clone()) + self.value.get(index) } } -impl IntoIterator for Signal> { +impl IntoIterator for Signal> { type IntoIter = SignalIterator; - type Item = T; + type Item = GenerationalRef; fn into_iter(self) -> Self::IntoIter { SignalIterator { @@ -279,11 +280,37 @@ impl IntoIterator for Signal> { } } +/// An iterator over items in a `Signal>` that yields [`SignalMap`]s. +pub struct MappedSignalIterator { + index: usize, + length: usize, + value: Signal>, +} + +impl Iterator for MappedSignalIterator { + type Item = SignalMap; + + fn next(&mut self) -> Option { + let index = self.index; + self.index += 1; + (index < self.length).then(|| self.value.map(move |v| v.get(index).unwrap())) + } +} + impl Signal> { /// Returns a reference to an element or `None` if out of bounds. pub fn get_mut(&self, index: usize) -> Option>> { Write::filter_map(self.write(), |v| v.get_mut(index)) } + + /// Create an iterator of [`SignalMap`]s over the inner vector. + pub fn iter_signals(&self) -> MappedSignalIterator { + MappedSignalIterator { + index: 0, + length: self.read().len(), + value: *self, + } + } } impl Signal> { diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index 2673fabad..5a9a4a817 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -1,14 +1,14 @@ use crate::CopyValue; use crate::Signal; use dioxus_core::ScopeId; -use std::cell::Ref; +use generational_box::GenerationalRef; use std::fmt::Debug; use std::fmt::Display; /// A read only signal that has been mapped to a new type. pub struct SignalMap { origin_scope: ScopeId, - mapping: CopyValue Ref<'static, U>>>, + mapping: CopyValue GenerationalRef>>, } impl SignalMap { @@ -16,7 +16,9 @@ impl SignalMap { pub fn new(signal: Signal, mapping: impl Fn(&T) -> &U + 'static) -> Self { Self { origin_scope: signal.origin_scope(), - mapping: CopyValue::new(Box::new(move || Ref::map(signal.read(), |v| (mapping)(v)))), + mapping: CopyValue::new(Box::new(move || { + GenerationalRef::map(signal.read(), |v| (mapping)(v)) + })), } } @@ -26,7 +28,7 @@ impl SignalMap { } /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn read(&self) -> Ref<'static, U> { + pub fn read(&self) -> GenerationalRef { (self.mapping.read())() } @@ -71,8 +73,8 @@ impl Debug for SignalMap { impl SignalMap> { /// Read a value from the inner vector. - pub fn get(&self, index: usize) -> Option> { - Ref::filter_map(self.read(), |v| v.get(index)).ok() + pub fn get(&self, index: usize) -> Option> { + GenerationalRef::filter_map(self.read(), |v| v.get(index)) } } @@ -86,7 +88,40 @@ impl SignalMap> { } /// Attemps to read the inner value of the Option. - pub fn as_ref(&self) -> Option> { - Ref::filter_map(self.read(), |v| v.as_ref()).ok() + pub fn as_ref(&self) -> Option> { + GenerationalRef::filter_map(self.read(), |v| v.as_ref()) + } +} + +impl std::ops::Deref for SignalMap { + type Target = dyn Fn() -> GenerationalRef; + + 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). + let uninit_callable = std::mem::MaybeUninit::::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::()); + + // 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 } } From e45739ac04b1e3d84bdfbc9caffdca22bfef5309 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 23 Dec 2023 13:57:49 -0600 Subject: [PATCH 028/377] fix examples --- packages/signals/examples/split_subscriptions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/signals/examples/split_subscriptions.rs b/packages/signals/examples/split_subscriptions.rs index bb286b4b7..b259a6ecb 100644 --- a/packages/signals/examples/split_subscriptions.rs +++ b/packages/signals/examples/split_subscriptions.rs @@ -108,7 +108,7 @@ fn ReadsManySignals(cx: Scope) -> Element { "Increase First Item" } for signal in data.many_signals { - Child { count: signal } + Child { count: *signal } } } } From b9d179c4941a9b35511dee3df93399fa85e8b414 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 1 Jan 2024 10:44:21 -0600 Subject: [PATCH 029/377] add as_mapped_ref --- packages/signals/examples/iterator.rs | 2 +- packages/signals/examples/option.rs | 39 +++++++++++++++++++++++++++ packages/signals/src/impls.rs | 17 +++++++++--- packages/signals/src/map.rs | 22 +++++++-------- packages/signals/src/signal.rs | 6 ++--- packages/signals/tests/map.rs | 2 +- 6 files changed, 68 insertions(+), 20 deletions(-) create mode 100644 packages/signals/examples/option.rs diff --git a/packages/signals/examples/iterator.rs b/packages/signals/examples/iterator.rs index 24db7712f..2f6484b55 100644 --- a/packages/signals/examples/iterator.rs +++ b/packages/signals/examples/iterator.rs @@ -23,6 +23,6 @@ fn App(cx: Scope) -> Element { } #[component] -fn Child(cx: Scope, signal: SignalMap) -> Element { +fn Child(cx: Scope, signal: MappedSignal) -> Element { render! {"{signal:?}"} } diff --git a/packages/signals/examples/option.rs b/packages/signals/examples/option.rs new file mode 100644 index 000000000..10f41034c --- /dev/null +++ b/packages/signals/examples/option.rs @@ -0,0 +1,39 @@ +use dioxus::prelude::*; +use dioxus_signals::*; + +fn main() { + dioxus_desktop::launch(App); +} + +#[component] +fn App(cx: Scope) -> Element { + let signal = use_signal(cx, || Some(String::from("Hello"))); + + render! { + button { + onclick: move |_| { + let new_value = (!signal.read().is_some()).then(|| String::from("Hello")); + signal.set(new_value); + }, + "Swap" + } + button { + onclick: move |_| { + if let Some(value) = &mut *signal.write() { + value.push_str(" World"); + } + }, + "Change" + } + if let Some(item) = signal.as_mapped_ref(){ + render! { + Child { signal: item } + } + } + } +} + +#[component] +fn Child(cx: Scope, signal: MappedSignal) -> Element { + render! {"{signal:?}"} +} diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index f741b05ed..dc91eeae5 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,6 +1,6 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; -use crate::SignalMap; +use crate::MappedSignal; use generational_box::GenerationalRef; use generational_box::GenerationalRefMut; @@ -280,7 +280,7 @@ impl IntoIterator for Signal> { } } -/// An iterator over items in a `Signal>` that yields [`SignalMap`]s. +/// An iterator over items in a `Signal>` that yields [`MappedSignal`]s. pub struct MappedSignalIterator { index: usize, length: usize, @@ -288,7 +288,7 @@ pub struct MappedSignalIterator { } impl Iterator for MappedSignalIterator { - type Item = SignalMap; + type Item = MappedSignal; fn next(&mut self) -> Option { let index = self.index; @@ -303,7 +303,7 @@ impl Signal> { Write::filter_map(self.write(), |v| v.get_mut(index)) } - /// Create an iterator of [`SignalMap`]s over the inner vector. + /// Create an iterator of [`MappedSignal`]s over the inner vector. pub fn iter_signals(&self) -> MappedSignalIterator { MappedSignalIterator { index: 0, @@ -318,4 +318,13 @@ impl Signal> { pub fn as_mut(&self) -> Option>> { Write::filter_map(self.write(), |v| v.as_mut()) } + + /// Try to create a [`MappedSignal`] over the inner value. + pub fn as_mapped_ref(&self) -> Option> { + if self.read().is_some() { + Some(self.map(|v| v.as_ref().unwrap())) + } else { + None + } + } } diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index 5a9a4a817..13555e265 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -6,12 +6,12 @@ use std::fmt::Debug; use std::fmt::Display; /// A read only signal that has been mapped to a new type. -pub struct SignalMap { +pub struct MappedSignal { origin_scope: ScopeId, mapping: CopyValue GenerationalRef>>, } -impl SignalMap { +impl MappedSignal { /// Create a new mapped signal. pub fn new(signal: Signal, mapping: impl Fn(&T) -> &U + 'static) -> Self { Self { @@ -38,47 +38,47 @@ impl SignalMap { } } -impl SignalMap { +impl MappedSignal { /// Get the current value of the signal. This will subscribe the current scope to the signal. pub fn value(&self) -> U { self.read().clone() } } -impl PartialEq for SignalMap { +impl PartialEq for MappedSignal { fn eq(&self, other: &Self) -> bool { self.mapping == other.mapping } } -impl std::clone::Clone for SignalMap { +impl std::clone::Clone for MappedSignal { fn clone(&self) -> Self { *self } } -impl Copy for SignalMap {} +impl Copy for MappedSignal {} -impl Display for SignalMap { +impl Display for MappedSignal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Display::fmt(v, f)) } } -impl Debug for SignalMap { +impl Debug for MappedSignal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.with(|v| Debug::fmt(v, f)) } } -impl SignalMap> { +impl MappedSignal> { /// Read a value from the inner vector. pub fn get(&self, index: usize) -> Option> { GenerationalRef::filter_map(self.read(), |v| v.get(index)) } } -impl SignalMap> { +impl MappedSignal> { /// Unwraps the inner value and clones it. pub fn unwrap(&self) -> U where @@ -93,7 +93,7 @@ impl SignalMap> { } } -impl std::ops::Deref for SignalMap { +impl std::ops::Deref for MappedSignal { type Target = dyn Fn() -> GenerationalRef; fn deref(&self) -> &Self::Target { diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index aa94fbd4a..8a03af2a4 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,4 +1,4 @@ -use crate::SignalMap; +use crate::MappedSignal; use std::{ cell::RefCell, mem::MaybeUninit, @@ -307,8 +307,8 @@ impl Signal { } /// Map the signal to a new type. - pub fn map(self, f: impl Fn(&T) -> &O + 'static) -> SignalMap { - SignalMap::new(self, f) + pub fn map(self, f: impl Fn(&T) -> &O + 'static) -> MappedSignal { + MappedSignal::new(self, f) } } diff --git a/packages/signals/tests/map.rs b/packages/signals/tests/map.rs index 449e5e599..e93d2fed0 100644 --- a/packages/signals/tests/map.rs +++ b/packages/signals/tests/map.rs @@ -16,7 +16,7 @@ fn create_signals_global() { fn Child(cx: Scope) -> Element { let signal = create_without_cx(); - let mapped = SignalMap::new(signal, |v| v.as_bytes()); + let mapped = MappedSignal::new(signal, |v| v.as_bytes()); render! { "{signal:?}" From ccd0ccffa727d08a03c9d0d0d4632834a74aad4a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 1 Jan 2024 10:47:45 -0600 Subject: [PATCH 030/377] fix formatting --- packages/signals/examples/option.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/signals/examples/option.rs b/packages/signals/examples/option.rs index 10f41034c..c0d1746b7 100644 --- a/packages/signals/examples/option.rs +++ b/packages/signals/examples/option.rs @@ -25,7 +25,7 @@ fn App(cx: Scope) -> Element { }, "Change" } - if let Some(item) = signal.as_mapped_ref(){ + if let Some(item) = signal.as_mapped_ref() { render! { Child { signal: item } } From 73b7f450a5f315087f09fc09516dfb69e509478e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 2 Jan 2024 14:55:46 -0600 Subject: [PATCH 031/377] fix merge errors --- packages/signals/examples/iterator.rs | 28 ------------ packages/signals/examples/option.rs | 39 ---------------- packages/signals/src/impls.rs | 50 ++++----------------- packages/signals/src/map.rs | 64 ++++++++++++--------------- packages/signals/src/signal.rs | 9 +++- 5 files changed, 45 insertions(+), 145 deletions(-) delete mode 100644 packages/signals/examples/iterator.rs delete mode 100644 packages/signals/examples/option.rs diff --git a/packages/signals/examples/iterator.rs b/packages/signals/examples/iterator.rs deleted file mode 100644 index 2f6484b55..000000000 --- a/packages/signals/examples/iterator.rs +++ /dev/null @@ -1,28 +0,0 @@ -use dioxus::prelude::*; -use dioxus_signals::*; - -fn main() { - dioxus_desktop::launch(App); -} - -#[component] -fn App(cx: Scope) -> Element { - let signal = use_signal(cx, || vec![String::from("Hello"), String::from("World")]); - - render! { - button { - onclick: move |_| { - signal.write().push(String::from("Hello")); - }, - "Add one" - } - for item in signal.iter_signals() { - Child { signal: item } - } - } -} - -#[component] -fn Child(cx: Scope, signal: MappedSignal) -> Element { - render! {"{signal:?}"} -} diff --git a/packages/signals/examples/option.rs b/packages/signals/examples/option.rs deleted file mode 100644 index c0d1746b7..000000000 --- a/packages/signals/examples/option.rs +++ /dev/null @@ -1,39 +0,0 @@ -use dioxus::prelude::*; -use dioxus_signals::*; - -fn main() { - dioxus_desktop::launch(App); -} - -#[component] -fn App(cx: Scope) -> Element { - let signal = use_signal(cx, || Some(String::from("Hello"))); - - render! { - button { - onclick: move |_| { - let new_value = (!signal.read().is_some()).then(|| String::from("Hello")); - signal.set(new_value); - }, - "Swap" - } - button { - onclick: move |_| { - if let Some(value) = &mut *signal.write() { - value.push_str(" World"); - } - }, - "Change" - } - if let Some(item) = signal.as_mapped_ref() { - render! { - Child { signal: item } - } - } - } -} - -#[component] -fn Child(cx: Scope, signal: MappedSignal) -> Element { - render! {"{signal:?}"} -} diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 7429f5886..27ac8849b 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,6 +1,5 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; -use crate::MappedSignal; use crate::SignalData; use generational_box::Mappable; use generational_box::{MappableMut, Storage}; @@ -333,7 +332,7 @@ pub struct CopyValueIterator>> { } impl>> Iterator for CopyValueIterator { - type Item = S::Ref; + type Item = >>::Mapped; fn next(&mut self) -> Option { let index = self.index; @@ -343,9 +342,9 @@ impl>> Iterator for CopyValueIterator { } impl>> IntoIterator for CopyValue, S> { - type IntoIter = S::Ref; + type IntoIter = CopyValueIterator; - type Item = GenerationalRef; + type Item = >>::Mapped; fn into_iter(self) -> Self::IntoIter { CopyValueIterator { @@ -378,7 +377,9 @@ pub struct SignalIterator>>> { } impl>>> Iterator for SignalIterator { - type Item = S::Ref; + type Item = <<>>>::Ref as Mappable>>>::Mapped< + Vec, + > as Mappable>>::Mapped; fn next(&mut self) -> Option { let index = self.index; @@ -390,7 +391,9 @@ impl>>> Iterator for SignalIterator { impl>>> IntoIterator for Signal, S> { type IntoIter = SignalIterator; - type Item = GenerationalRef; + type Item = <<>>>::Ref as Mappable>>>::Mapped< + Vec, + > as Mappable>>::Mapped; fn into_iter(self) -> Self::IntoIter { SignalIterator { @@ -400,23 +403,6 @@ impl>>> IntoIterator for Signal, } } -/// An iterator over items in a `Signal>` that yields [`MappedSignal`]s. -pub struct MappedSignalIterator>>> { - index: usize, - length: usize, - value: Signal, S>, -} - -impl>>> Iterator for MappedSignalIterator { - type Item = MappedSignal; - - fn next(&mut self) -> Option { - let index = self.index; - self.index += 1; - (index < self.length).then(|| self.value.map(move |v| v.get(index).unwrap())) - } -} - impl>>> Signal, S> where <>>>::Mut as MappableMut< @@ -439,15 +425,6 @@ where > { Write::filter_map(self.write(), |v| v.get_mut(index)) } - - /// Create an iterator of [`MappedSignal`]s over the inner vector. - pub fn iter_signals(&self) -> MappedSignalIterator { - MappedSignalIterator { - index: 0, - length: self.read().len(), - value: *self, - } - } } impl>>> Signal, S> { @@ -455,13 +432,4 @@ impl>>> Signal, S> { pub fn as_mut(&self) -> Option>>>::Mut as MappableMut>>>::Mapped> as MappableMut>>::Mapped, S, Option>>{ Write::filter_map(self.write(), |v| v.as_mut()) } - - /// Try to create a [`MappedSignal`] over the inner value. - pub fn as_mapped_ref(&self) -> Option> { - if self.read().is_some() { - Some(self.map(|v| v.as_ref().unwrap())) - } else { - None - } - } } diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index 13555e265..6c02b8553 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -1,40 +1,54 @@ use crate::CopyValue; use crate::Signal; +use crate::SignalData; use dioxus_core::ScopeId; -use generational_box::GenerationalRef; +use generational_box::Mappable; +use generational_box::Storage; use std::fmt::Debug; use std::fmt::Display; /// A read only signal that has been mapped to a new type. pub struct MappedSignal { origin_scope: ScopeId, - mapping: CopyValue GenerationalRef>>, + mapping: CopyValue U>>, } -impl MappedSignal { +impl MappedSignal<()> { /// Create a new mapped signal. - pub fn new(signal: Signal, mapping: impl Fn(&T) -> &U + 'static) -> Self { - Self { + pub fn new>, U>( + signal: Signal, + mapping: impl Fn(&T) -> &U + 'static, + ) -> MappedSignal< + <<>>::Ref as Mappable>>::Mapped< + T, + > as generational_box::Mappable>::Mapped, + > { + MappedSignal { origin_scope: signal.origin_scope(), mapping: CopyValue::new(Box::new(move || { - GenerationalRef::map(signal.read(), |v| (mapping)(v)) + <<>>::Ref as Mappable>>::Mapped>::map( + signal.read(), + &mapping, + ) })), } } +} +impl MappedSignal { /// Get the scope that the signal was created in. pub fn origin_scope(&self) -> ScopeId { self.origin_scope } /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn read(&self) -> GenerationalRef { + pub fn read(&self) -> U { (self.mapping.read())() } /// Run a closure with a reference to the signal's value. - pub fn with(&self, f: impl FnOnce(&U) -> O) -> O { - f(&*self.read()) + pub fn with(&self, f: impl FnOnce(U) -> O) -> O { + f(self.read()) } } @@ -59,42 +73,20 @@ impl std::clone::Clone for MappedSignal { impl Copy for MappedSignal {} -impl Display for MappedSignal { +impl Display for MappedSignal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.with(|v| Display::fmt(v, f)) + self.with(|v| Display::fmt(&v, f)) } } -impl Debug for MappedSignal { +impl Debug for MappedSignal { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.with(|v| Debug::fmt(v, f)) - } -} - -impl MappedSignal> { - /// Read a value from the inner vector. - pub fn get(&self, index: usize) -> Option> { - GenerationalRef::filter_map(self.read(), |v| v.get(index)) - } -} - -impl MappedSignal> { - /// Unwraps the inner value and clones it. - pub fn unwrap(&self) -> U - where - U: Clone, - { - self.with(|v| v.clone()).unwrap() - } - - /// Attemps to read the inner value of the Option. - pub fn as_ref(&self) -> Option> { - GenerationalRef::filter_map(self.read(), |v| v.as_ref()) + self.with(|v| Debug::fmt(&v, f)) } } impl std::ops::Deref for MappedSignal { - type Target = dyn Fn() -> GenerationalRef; + type Target = dyn Fn() -> T; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index bdb426761..96c455b25 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -394,7 +394,14 @@ impl>> Signal { } /// Map the signal to a new type. - pub fn map(self, f: impl Fn(&T) -> &O + 'static) -> MappedSignal { + pub fn map( + self, + f: impl Fn(&T) -> &O + 'static, + ) -> MappedSignal< + <<>>::Ref as generational_box::Mappable< + SignalData, + >>::Mapped as generational_box::Mappable>::Mapped, + > { MappedSignal::new(self, f) } From b7c9817cf31bbc960620a9738e62270b888c40bb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 2 Jan 2024 15:19:01 -0600 Subject: [PATCH 032/377] fix tests --- packages/generational-box/src/lib.rs | 16 ++++----- packages/generational-box/src/references.rs | 39 ++++++++++++++------- packages/generational-box/src/sync.rs | 16 ++++----- packages/generational-box/src/unsync.rs | 16 ++++----- packages/signals/src/map.rs | 2 +- packages/signals/tests/map.rs | 5 +-- 6 files changed, 52 insertions(+), 42 deletions(-) diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index b8fdee94b..4bbe06d8a 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -322,30 +322,30 @@ impl Clone for GenerationalBox { } /// A trait for types that can be mapped. -pub trait Mappable: Deref { +pub trait Mappable: Deref { /// The type after the mapping. - type Mapped: Mappable + Deref; + type Mapped: Mappable + Deref; /// Map the value. - fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped; + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped; /// Try to map the value. - fn try_map( + fn try_map( _self: Self, f: impl FnOnce(&T) -> Option<&U>, ) -> Option>; } /// A trait for types that can be mapped mutably. -pub trait MappableMut: DerefMut { +pub trait MappableMut: DerefMut { /// The type after the mapping. - type Mapped: MappableMut + DerefMut; + type Mapped: MappableMut + DerefMut; /// Map the value. - fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped; + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped; /// Try to map the value. - fn try_map( + fn try_map( _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, ) -> Option>; diff --git a/packages/generational-box/src/references.rs b/packages/generational-box/src/references.rs index c2f52946c..115babf7a 100644 --- a/packages/generational-box/src/references.rs +++ b/packages/generational-box/src/references.rs @@ -1,4 +1,5 @@ use std::{ + fmt::{Debug, Display}, marker::PhantomData, ops::{Deref, DerefMut}, }; @@ -6,7 +7,7 @@ use std::{ use crate::{Mappable, MappableMut}; /// A reference to a value in a generational box. -pub struct GenerationalRef> { +pub struct GenerationalRef> { inner: R, phantom: PhantomData, #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -27,10 +28,10 @@ impl> GenerationalRef { } } -impl> Mappable for GenerationalRef { - type Mapped = GenerationalRef>; +impl> Mappable for GenerationalRef { + type Mapped = GenerationalRef>; - fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { GenerationalRef { inner: R::map(_self.inner, f), phantom: PhantomData, @@ -42,7 +43,7 @@ impl> Mappable for GenerationalRef { } } - fn try_map( + fn try_map( _self: Self, f: impl FnOnce(&T) -> Option<&U>, ) -> Option> { @@ -64,7 +65,19 @@ impl> Mappable for GenerationalRef { } } -impl> Deref for GenerationalRef { +impl> Debug for GenerationalRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.deref().fmt(f) + } +} + +impl> Display for GenerationalRef { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.inner.deref().fmt(f) + } +} + +impl> Deref for GenerationalRef { type Target = T; fn deref(&self) -> &Self::Target { @@ -90,7 +103,7 @@ impl Drop for GenerationalRefBorrowInfo { } /// A mutable reference to a value in a generational box. -pub struct GenerationalRefMut> { +pub struct GenerationalRefMut> { inner: W, phantom: PhantomData, #[cfg(any(debug_assertions, feature = "debug_borrows"))] @@ -112,10 +125,10 @@ impl> GenerationalRefMut { } } -impl> MappableMut for GenerationalRefMut { - type Mapped = GenerationalRefMut>; +impl> MappableMut for GenerationalRefMut { + type Mapped = GenerationalRefMut>; - fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { GenerationalRefMut { inner: W::map(_self.inner, f), phantom: PhantomData, @@ -124,7 +137,7 @@ impl> MappableMut for GenerationalRefMut } } - fn try_map( + fn try_map( _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, ) -> Option> { @@ -143,7 +156,7 @@ impl> MappableMut for GenerationalRefMut } } -impl> Deref for GenerationalRefMut { +impl> Deref for GenerationalRefMut { type Target = T; fn deref(&self) -> &Self::Target { @@ -151,7 +164,7 @@ impl> Deref for GenerationalRefMut { } } -impl> DerefMut for GenerationalRefMut { +impl> DerefMut for GenerationalRefMut { fn deref_mut(&mut self) -> &mut Self::Target { self.inner.deref_mut() } diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index e11c17418..7d940278f 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -48,14 +48,14 @@ impl AnyStorage for SyncStorage { } } -impl Mappable for MappedRwLockReadGuard<'static, T> { - type Mapped = MappedRwLockReadGuard<'static, U>; +impl Mappable for MappedRwLockReadGuard<'static, T> { + type Mapped = MappedRwLockReadGuard<'static, U>; - fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { MappedRwLockReadGuard::map(_self, f) } - fn try_map( + fn try_map( _self: Self, f: impl FnOnce(&T) -> Option<&U>, ) -> Option> { @@ -63,14 +63,14 @@ impl Mappable for MappedRwLockReadGuard<'static, T> { } } -impl MappableMut for MappedRwLockWriteGuard<'static, T> { - type Mapped = MappedRwLockWriteGuard<'static, U>; +impl MappableMut for MappedRwLockWriteGuard<'static, T> { + type Mapped = MappedRwLockWriteGuard<'static, U>; - fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { MappedRwLockWriteGuard::map(_self, f) } - fn try_map( + fn try_map( _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, ) -> Option> { diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs index dac9967f0..d941dd00b 100644 --- a/packages/generational-box/src/unsync.rs +++ b/packages/generational-box/src/unsync.rs @@ -13,14 +13,14 @@ impl Default for UnsyncStorage { } } -impl Mappable for Ref<'static, T> { - type Mapped = Ref<'static, U>; +impl Mappable for Ref<'static, T> { + type Mapped = Ref<'static, U>; - fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { Ref::map(_self, f) } - fn try_map( + fn try_map( _self: Self, f: impl FnOnce(&T) -> Option<&U>, ) -> Option> { @@ -28,14 +28,14 @@ impl Mappable for Ref<'static, T> { } } -impl MappableMut for RefMut<'static, T> { - type Mapped = RefMut<'static, U>; +impl MappableMut for RefMut<'static, T> { + type Mapped = RefMut<'static, U>; - fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { + fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { RefMut::map(_self, f) } - fn try_map( + fn try_map( _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, ) -> Option> { diff --git a/packages/signals/src/map.rs b/packages/signals/src/map.rs index 6c02b8553..b7093e27b 100644 --- a/packages/signals/src/map.rs +++ b/packages/signals/src/map.rs @@ -15,7 +15,7 @@ pub struct MappedSignal { impl MappedSignal<()> { /// Create a new mapped signal. - pub fn new>, U>( + pub fn new>, U: ?Sized>( signal: Signal, mapping: impl Fn(&T) -> &U + 'static, ) -> MappedSignal< diff --git a/packages/signals/tests/map.rs b/packages/signals/tests/map.rs index e93d2fed0..bea13009a 100644 --- a/packages/signals/tests/map.rs +++ b/packages/signals/tests/map.rs @@ -18,10 +18,7 @@ fn create_signals_global() { let signal = create_without_cx(); let mapped = MappedSignal::new(signal, |v| v.as_bytes()); - render! { - "{signal:?}" - "{mapped:?}" - } + render! { "{signal:?}", "{mapped:?}" } } let _edits = dom.rebuild().santize(); From c9ff449e45a101ce2db35cdc55c9ffdc3227f182 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 4 Jan 2024 17:33:22 -0600 Subject: [PATCH 033/377] remove the bump allocator --- packages/core/src/fragment.rs | 15 +++++++-------- packages/core/src/nodes.rs | 20 ++++++++++---------- packages/core/src/properties.rs | 10 +++------- packages/core/src/scopes.rs | 3 --- 4 files changed, 20 insertions(+), 28 deletions(-) diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index dbc8e8131..bfab2f98f 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -39,15 +39,15 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { }) } -pub struct FragmentProps<'a>(Element<'a>); -pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>); -impl<'a> FragmentBuilder<'a, false> { - pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> { +pub struct FragmentProps(Element); +pub struct FragmentBuilder(Element); +impl FragmentBuilder { + pub fn children(self, children: Element) -> FragmentBuilder { FragmentBuilder(children) } } -impl<'a, const A: bool> FragmentBuilder<'a, A> { - pub fn build(self) -> FragmentProps<'a> { +impl FragmentBuilder { + pub fn build(self) -> FragmentProps { FragmentProps(self.0) } } @@ -94,11 +94,10 @@ impl<'a, const A: bool> FragmentBuilder<'a, A> { /// ``` impl<'a> Properties for FragmentProps<'a> { type Builder = FragmentBuilder<'a, false>; - const IS_STATIC: bool = false; fn builder() -> Self::Builder { FragmentBuilder(None) } - unsafe fn memoize(&self, _other: &Self) -> bool { + fn memoize(&self, _other: &Self) -> bool { false } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index d3ad1875f..3d3afaeb3 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -41,11 +41,11 @@ impl<'a> Default for RenderReturn<'a> { /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping /// static parts of the template. #[derive(Debug, Clone)] -pub struct VNode<'a> { +pub struct VNode { /// The key given to the root of this template. /// /// In fragments, this is the key of the first child. In other cases, it is the key of the root. - pub key: Option<&'a str>, + pub key: Option, /// When rendered, this template will be linked to its parent manually pub(crate) parent: Cell>, @@ -58,25 +58,25 @@ pub struct VNode<'a> { /// The IDs for the roots of this template - to be used when moving the template around and removing it from /// the actual Dom - pub root_ids: RefCell>, + pub root_ids: RefCell>, /// The dynamic parts of the template - pub dynamic_nodes: &'a [DynamicNode<'a>], + pub dynamic_nodes: Vec, /// The dynamic parts of the template - pub dynamic_attrs: &'a [Attribute<'a>], + pub dynamic_attrs: Vec, } -impl<'a> VNode<'a> { +impl VNode { /// Create a template with no nodes that will be skipped over during diffing - pub fn empty(cx: &'a ScopeState) -> Element<'a> { + pub fn empty() -> Element { Some(VNode { key: None, parent: Default::default(), stable_id: Default::default(), - root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())), - dynamic_nodes: &[], - dynamic_attrs: &[], + root_ids: Default::default(), + dynamic_nodes: Vec::new(), + dynamic_attrs: Vec::new(), template: Cell::new(Template { name: "dioxus-empty", roots: &[], diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 3f0d9ef97..9a36158fc 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -32,14 +32,11 @@ use crate::innerlude::*; /// data: &'a str /// } /// ``` -pub trait Properties: Sized { +pub trait Properties: Sized + 'static { /// The type of the builder for this component. /// Used to create "in-progress" versions of the props. type Builder; - /// An indication if these props are can be memoized automatically. - const IS_STATIC: bool; - /// Create a builder for this component. fn builder() -> Self::Builder; @@ -48,16 +45,15 @@ pub trait Properties: Sized { /// # Safety /// The user must know if their props are static, but if they make a mistake, UB happens /// Therefore it's unsafe to memoize. - unsafe fn memoize(&self, other: &Self) -> bool; + fn memoize(&self, other: &Self) -> bool; } impl Properties for () { type Builder = EmptyBuilder; - const IS_STATIC: bool = true; fn builder() -> Self::Builder { EmptyBuilder {} } - unsafe fn memoize(&self, _other: &Self) -> bool { + fn memoize(&self, _other: &Self) -> bool { true } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 1a26d3847..552a9d5b9 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -87,9 +87,6 @@ pub struct ScopeState { pub(crate) render_cnt: Cell, - pub(crate) node_arena_1: BumpFrame, - pub(crate) node_arena_2: BumpFrame, - pub(crate) hooks: RefCell>>>, pub(crate) hook_idx: Cell, From 3ad16ddd375a8a2926980ee815a8bb12a09918f0 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 4 Jan 2024 18:33:01 -0600 Subject: [PATCH 034/377] rip out more lifetimes --- packages/core/Cargo.toml | 3 - packages/core/src/any_props.rs | 6 +- packages/core/src/bump_frame.rs | 76 ------------- packages/core/src/create.rs | 18 ++-- packages/core/src/diff.rs | 93 ++++++---------- packages/core/src/error_boundary.rs | 30 +++--- packages/core/src/events.rs | 2 +- packages/core/src/fragment.rs | 6 +- packages/core/src/lazynodes.rs | 101 ------------------ packages/core/src/lib.rs | 9 +- packages/core/src/nodes.rs | 160 +++++++++++++--------------- packages/core/src/properties.rs | 4 +- packages/core/src/scope_arena.rs | 6 +- packages/core/src/scope_context.rs | 2 +- packages/core/src/scopes.rs | 95 ++++------------- packages/core/src/virtual_dom.rs | 6 +- packages/html/src/events.rs | 2 +- packages/html/src/events/mouse.rs | 2 +- 18 files changed, 165 insertions(+), 456 deletions(-) delete mode 100644 packages/core/src/bump_frame.rs delete mode 100644 packages/core/src/lazynodes.rs diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index ce7c6bccf..f73696f72 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -10,9 +10,6 @@ homepage = "https://dioxuslabs.com" keywords = ["dom", "ui", "gui", "react"] [dependencies] -# Bumpalo is used as a micro heap backing each component -bumpalo = { version = "3.6", features = ["collections", "boxed"] } - # faster hashmaps rustc-hash = { workspace = true } diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index f14a78065..2cc9c6559 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -11,9 +11,9 @@ use std::panic::AssertUnwindSafe; /// # Safety /// /// This should not be implemented outside this module -pub(crate) unsafe trait AnyProps<'a> { +pub(crate) unsafe trait AnyProps { fn props_ptr(&self) -> *const (); - fn render(&'a self, bump: &'a ScopeState) -> RenderReturn<'a>; + fn render<'a>(&'a self, bump: &'a ScopeState) -> RenderReturn<'a>; unsafe fn memoize(&self, other: &dyn AnyProps) -> bool; } @@ -37,7 +37,7 @@ impl<'a, P> VProps<'a, P> { } } -unsafe impl<'a, P> AnyProps<'a> for VProps<'a, P> { +unsafe impl<'a, P> AnyProps for VProps<'a, P> { fn props_ptr(&self) -> *const () { &self.props as *const _ as *const () } diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs deleted file mode 100644 index 6927bd8bc..000000000 --- a/packages/core/src/bump_frame.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::nodes::RenderReturn; -use crate::{Attribute, AttributeValue, VComponent}; -use bumpalo::Bump; -use std::cell::RefCell; -use std::cell::{Cell, UnsafeCell}; - -pub(crate) struct BumpFrame { - pub bump: UnsafeCell, - pub node: Cell<*const RenderReturn<'static>>, - - // The bump allocator will not call the destructor of the objects it allocated. Attributes and props need to have there destructor called, so we keep a list of them to drop before the bump allocator is reset. - pub(crate) attributes_to_drop_before_reset: RefCell>>, - pub(crate) props_to_drop_before_reset: RefCell>>, -} - -impl BumpFrame { - pub(crate) fn new(capacity: usize) -> Self { - let bump = Bump::with_capacity(capacity); - Self { - bump: UnsafeCell::new(bump), - node: Cell::new(std::ptr::null()), - attributes_to_drop_before_reset: Default::default(), - props_to_drop_before_reset: Default::default(), - } - } - - /// Creates a new lifetime out of thin air - pub(crate) unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> { - let node = self.node.get(); - - if node.is_null() { - return None; - } - - unsafe { std::mem::transmute(&*node) } - } - - pub(crate) fn bump(&self) -> &Bump { - unsafe { &*self.bump.get() } - } - - pub(crate) fn add_attribute_to_drop(&self, attribute: *const Attribute<'static>) { - self.attributes_to_drop_before_reset - .borrow_mut() - .push(attribute); - } - - /// Reset the bump allocator and drop all the attributes and props that were allocated in it. - /// - /// # Safety - /// The caller must insure that no reference to anything allocated in the bump allocator is available after this function is called. - pub(crate) unsafe fn reset(&self) { - let mut attributes = self.attributes_to_drop_before_reset.borrow_mut(); - attributes.drain(..).for_each(|attribute| { - let attribute = unsafe { &*attribute }; - if let AttributeValue::Any(l) = &attribute.value { - _ = l.take(); - } - }); - let mut props = self.props_to_drop_before_reset.borrow_mut(); - props.drain(..).for_each(|prop| { - let prop = unsafe { &*prop }; - _ = prop.props.borrow_mut().take(); - }); - unsafe { - let bump = &mut *self.bump.get(); - bump.reset(); - } - } -} - -impl Drop for BumpFrame { - fn drop(&mut self) { - unsafe { self.reset() } - } -} diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index f3b89ae9e..55334b900 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -67,7 +67,7 @@ impl<'b> VirtualDom { /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. /// /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created. - pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize { + pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode) -> usize { self.runtime.scope_stack.borrow_mut().push(scope); let nodes = self.create(template); self.runtime.scope_stack.borrow_mut().pop(); @@ -75,7 +75,7 @@ impl<'b> VirtualDom { } /// Create this template and write its mutations - pub(crate) fn create(&mut self, node: &'b VNode<'b>) -> usize { + pub(crate) fn create(&mut self, node: &'b VNode) -> usize { // check for a overriden template #[cfg(debug_assertions)] { @@ -182,7 +182,7 @@ impl<'b> VirtualDom { 1 } - fn write_dynamic_root(&mut self, template: &'b VNode<'b>, idx: usize) -> usize { + fn write_dynamic_root(&mut self, template: &'b VNode, idx: usize) -> usize { use DynamicNode::*; match &template.dynamic_nodes[idx] { node @ Component { .. } | node @ Fragment(_) => { @@ -232,7 +232,7 @@ impl<'b> VirtualDom { /// We want to make sure we write these nodes while on top of the root fn write_element_root( &mut self, - template: &'b VNode<'b>, + template: &'b VNode, root_idx: usize, dynamic_attrs: &mut Peekable>, dynamic_nodes_iter: &mut Peekable>, @@ -269,7 +269,7 @@ impl<'b> VirtualDom { dynamic_nodes_iter: &mut Peekable>, dynamic_nodes: &[(usize, &'static [u8])], root_idx: u8, - template: &'b VNode<'b>, + template: &'b VNode, ) { let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) { Some((a, b)) => (a, b), @@ -306,7 +306,7 @@ impl<'b> VirtualDom { attrs: &mut Peekable>, root_idx: u8, root: ElementId, - node: &'b VNode<'b>, + node: &'b VNode, ) { while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx)) @@ -327,7 +327,7 @@ impl<'b> VirtualDom { fn write_attribute( &mut self, - template: &'b VNode<'b>, + template: &'b VNode, idx: usize, attribute: &'b crate::Attribute<'b>, id: ElementId, @@ -490,7 +490,7 @@ impl<'b> VirtualDom { } } - fn create_dynamic_text(&mut self, parent: ElementRef, text: &'b VText<'b>) -> usize { + fn create_dynamic_text(&mut self, parent: ElementRef, text: &'b VText) -> usize { // Allocate a dynamic element reference for this text node let new_id = self.next_element(); @@ -538,7 +538,7 @@ impl<'b> VirtualDom { pub(super) fn create_component_node( &mut self, parent: Option, - component: &'b VComponent<'b>, + component: &'b VComponent, ) -> usize { use RenderReturn::*; diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 43b27f2e7..971c3977e 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -59,7 +59,7 @@ impl<'b> VirtualDom { self.runtime.scope_stack.borrow_mut().pop(); } - fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) { + fn diff_ok_to_err(&mut self, l: &'b VNode, p: &'b VPlaceholder) { let id = self.next_element(); p.id.set(Some(id)); p.parent.set(l.parent.get()); @@ -82,7 +82,7 @@ impl<'b> VirtualDom { }; } - fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) { + fn diff_node(&mut self, left_template: &'b VNode, right_template: &'b VNode) { // If hot reloading is enabled, we need to make sure we're using the latest template #[cfg(debug_assertions)] { @@ -198,8 +198,8 @@ impl<'b> VirtualDom { fn diff_vcomponent( &mut self, - left: &'b VComponent<'b>, - right: &'b VComponent<'b>, + left: &'b VComponent, + right: &'b VComponent, parent: Option, ) { if std::ptr::eq(left, right) { @@ -249,8 +249,8 @@ impl<'b> VirtualDom { fn replace_vcomponent( &mut self, - right: &'b VComponent<'b>, - left: &'b VComponent<'b>, + right: &'b VComponent, + left: &'b VComponent, parent: Option, ) { let m = self.create_component_node(parent, right); @@ -307,7 +307,7 @@ impl<'b> VirtualDom { /// Component { ..props } /// } /// ``` - fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) { + fn light_diff_templates(&mut self, left: &'b VNode, right: &'b VNode) { let parent = left.parent.take(); match matching_components(left, right) { None => self.replace(left, [right], parent), @@ -321,7 +321,7 @@ impl<'b> VirtualDom { /// /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's /// different. - fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) { + fn diff_vtext(&mut self, left: &'b VText, right: &'b VText) { let id = left.id.get().unwrap_or_else(|| self.next_element()); right.id.set(Some(id)); @@ -331,12 +331,7 @@ impl<'b> VirtualDom { } } - fn diff_non_empty_fragment( - &mut self, - old: &'b [VNode<'b>], - new: &'b [VNode<'b>], - parent: ElementRef, - ) { + fn diff_non_empty_fragment(&mut self, old: &'b [VNode], new: &'b [VNode], parent: ElementRef) { let new_is_keyed = new[0].key.is_some(); let old_is_keyed = old[0].key.is_some(); debug_assert!( @@ -363,12 +358,7 @@ impl<'b> VirtualDom { // [... parent] // // the change list stack is in the same state when this function returns. - fn diff_non_keyed_children( - &mut self, - old: &'b [VNode<'b>], - new: &'b [VNode<'b>], - parent: ElementRef, - ) { + fn diff_non_keyed_children(&mut self, old: &'b [VNode], new: &'b [VNode], parent: ElementRef) { use std::cmp::Ordering; // Handled these cases in `diff_children` before calling this function. @@ -404,15 +394,10 @@ impl<'b> VirtualDom { // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 // // The stack is empty upon entry. - fn diff_keyed_children( - &mut self, - old: &'b [VNode<'b>], - new: &'b [VNode<'b>], - parent: ElementRef, - ) { + fn diff_keyed_children(&mut self, old: &'b [VNode], new: &'b [VNode], parent: ElementRef) { if cfg!(debug_assertions) { let mut keys = rustc_hash::FxHashSet::default(); - let mut assert_unique_keys = |children: &'b [VNode<'b>]| { + let mut assert_unique_keys = |children: &'b [VNode]| { keys.clear(); for child in children { let key = child.key; @@ -485,8 +470,8 @@ impl<'b> VirtualDom { /// If there is no offset, then this function returns None and the diffing is complete. fn diff_keyed_ends( &mut self, - old: &'b [VNode<'b>], - new: &'b [VNode<'b>], + old: &'b [VNode], + new: &'b [VNode], parent: ElementRef, ) -> Option<(usize, usize)> { let mut left_offset = 0; @@ -542,12 +527,7 @@ impl<'b> VirtualDom { // // Upon exit from this function, it will be restored to that same self. #[allow(clippy::too_many_lines)] - fn diff_keyed_middle( - &mut self, - old: &'b [VNode<'b>], - new: &'b [VNode<'b>], - parent: ElementRef, - ) { + fn diff_keyed_middle(&mut self, old: &'b [VNode], new: &'b [VNode], parent: ElementRef) { /* 1. Map the old keys into a numerical ordering based on indices. 2. Create a map of old key to its index @@ -729,7 +709,7 @@ impl<'b> VirtualDom { } /// Push all the real nodes on the stack - fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize { + fn push_all_real_nodes(&mut self, node: &'b VNode) -> usize { node.template .get() .roots @@ -783,7 +763,7 @@ impl<'b> VirtualDom { pub(crate) fn create_children( &mut self, - nodes: impl IntoIterator>, + nodes: impl IntoIterator, parent: Option, ) -> usize { nodes @@ -797,8 +777,8 @@ impl<'b> VirtualDom { fn create_and_insert_before( &mut self, - new: &'b [VNode<'b>], - before: &'b VNode<'b>, + new: &'b [VNode], + before: &'b VNode, parent: ElementRef, ) { let m = self.create_children(new, Some(parent)); @@ -806,12 +786,7 @@ impl<'b> VirtualDom { self.mutations.push(Mutation::InsertBefore { id, m }) } - fn create_and_insert_after( - &mut self, - new: &'b [VNode<'b>], - after: &'b VNode<'b>, - parent: ElementRef, - ) { + fn create_and_insert_after(&mut self, new: &'b [VNode], after: &'b VNode, parent: ElementRef) { let m = self.create_children(new, Some(parent)); let id = self.find_last_element(after); self.mutations.push(Mutation::InsertAfter { id, m }) @@ -821,7 +796,7 @@ impl<'b> VirtualDom { fn replace_placeholder( &mut self, l: &'b VPlaceholder, - r: impl IntoIterator>, + r: impl IntoIterator, parent: ElementRef, ) { let m = self.create_children(r, Some(parent)); @@ -832,8 +807,8 @@ impl<'b> VirtualDom { fn replace( &mut self, - left: &'b VNode<'b>, - right: impl IntoIterator>, + left: &'b VNode, + right: impl IntoIterator, parent: Option, ) { let m = self.create_children(right, parent); @@ -855,7 +830,7 @@ impl<'b> VirtualDom { }; } - fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder, parent: ElementRef) { + fn node_to_placeholder(&mut self, l: &'b [VNode], r: &'b VPlaceholder, parent: ElementRef) { // Create the placeholder first, ensuring we get a dedicated ID for the placeholder let placeholder = self.next_element(); @@ -878,14 +853,14 @@ impl<'b> VirtualDom { /// Remove these nodes from the dom /// Wont generate mutations for the inner nodes - fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) { + fn remove_nodes(&mut self, nodes: &'b [VNode]) { nodes .iter() .rev() .for_each(|node| self.remove_node(node, true)); } - fn remove_node(&mut self, node: &'b VNode<'b>, gen_muts: bool) { + fn remove_node(&mut self, node: &'b VNode, gen_muts: bool) { // Clean up any attributes that have claimed a static node as dynamic for mount/unmounta // Will not generate mutations! self.reclaim_attributes(node); @@ -903,7 +878,7 @@ impl<'b> VirtualDom { self.reclaim_vnode_id(node); } - fn reclaim_vnode_id(&mut self, node: &'b VNode<'b>) { + fn reclaim_vnode_id(&mut self, node: &'b VNode) { // Clean up the vnode id if let Some(id) = node.stable_id() { self.element_refs.remove(id.0); @@ -1025,7 +1000,7 @@ impl<'b> VirtualDom { self.drop_scope(scope, false); } - fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId { + fn find_first_element(&self, node: &'b VNode) -> ElementId { match node.dynamic_root(0) { None => node.root_ids.borrow()[0], Some(Text(t)) => t.id.get().unwrap(), @@ -1046,7 +1021,7 @@ impl<'b> VirtualDom { } } - fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId { + fn find_last_element(&self, node: &'b VNode) -> ElementId { match node.dynamic_root(node.template.get().roots.len() - 1) { None => *node.root_ids.borrow().last().unwrap(), Some(Text(t)) => t.id.get().unwrap(), @@ -1067,7 +1042,7 @@ impl<'b> VirtualDom { } } - pub(crate) fn assign_boundary_ref(&mut self, parent: Option, child: &'b VNode<'b>) { + pub(crate) fn assign_boundary_ref(&mut self, parent: Option, child: &'b VNode) { if let Some(parent) = parent { // assign the parent of the child child.parent.set(Some(parent)); @@ -1080,7 +1055,7 @@ impl<'b> VirtualDom { /// We need to check for the obvious case, and the non-obvious case where the template as cloned /// /// We use the pointer of the dynamic_node list in this case -fn templates_are_the_same<'b>(left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) -> bool { +fn templates_are_the_same<'b>(left_template: &'b VNode, right_template: &'b VNode) -> bool { std::ptr::eq(left_template, right_template) } @@ -1092,9 +1067,9 @@ fn templates_are_different(left_template: &VNode, right_template: &VNode) -> boo } fn matching_components<'a>( - left: &'a VNode<'a>, - right: &'a VNode<'a>, -) -> Option, &'a VComponent<'a>)>> { + left: &'a VNode, + right: &'a VNode, +) -> Option> { let left_template = left.template.get(); let right_template = right.template.get(); if left_template.roots.len() != right_template.roots.len() { diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index d42f59395..30e31b7b8 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -262,7 +262,7 @@ impl Throw for Option { } } -pub struct ErrorHandler<'a>(Box LazyNodes<'a, 'a> + 'a>); +pub struct ErrorHandler(Box LazyNodes<'a, 'a>>); impl<'a, F: Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a> From for ErrorHandler<'a> { fn from(value: F) -> Self { Self(Box::new(value)) @@ -290,7 +290,7 @@ fn default_handler<'a>(error: CapturedError) -> LazyNodes<'a, 'a> { stable_id: Default::default(), key: None, template: std::cell::Cell::new(TEMPLATE), - root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(), + root_ids: Vec::with_capacity(1usize).into(), dynamic_nodes: __cx .bump() .alloc([__cx.text_node(format_args!("{0}", error))]), @@ -298,45 +298,39 @@ fn default_handler<'a>(error: CapturedError) -> LazyNodes<'a, 'a> { } }) } -pub struct ErrorBoundaryProps<'a> { - children: Element<'a>, - handle_error: ErrorHandler<'a>, +pub struct ErrorBoundaryProps { + children: Element, + handle_error: ErrorHandler, } -impl<'a> ErrorBoundaryProps<'a> { +impl ErrorBoundaryProps { /** Create a builder for building `ErrorBoundaryProps`. On the builder, call `.children(...)`(optional), `.handle_error(...)`(optional) to set the values of the fields. Finally, call `.build()` to create the instance of `ErrorBoundaryProps`. */ #[allow(dead_code)] - pub fn builder() -> ErrorBoundaryPropsBuilder<'a, ((), ())> { - ErrorBoundaryPropsBuilder { - fields: ((), ()), - _phantom: ::core::default::Default::default(), - } + pub fn builder() -> ErrorBoundaryPropsBuilder<((), ())> { + ErrorBoundaryPropsBuilder { fields: ((), ()) } } } #[must_use] #[doc(hidden)] #[allow(dead_code, non_camel_case_types, non_snake_case)] -pub struct ErrorBoundaryPropsBuilder<'a, TypedBuilderFields> { +pub struct ErrorBoundaryPropsBuilder { fields: TypedBuilderFields, - _phantom: ::core::marker::PhantomData<&'a ()>, } -impl<'a, TypedBuilderFields> Clone for ErrorBoundaryPropsBuilder<'a, TypedBuilderFields> +impl Clone for ErrorBoundaryPropsBuilder where TypedBuilderFields: Clone, { fn clone(&self) -> Self { Self { fields: self.fields.clone(), - _phantom: ::core::default::Default::default(), } } } -impl<'a> Properties for ErrorBoundaryProps<'a> { - type Builder = ErrorBoundaryPropsBuilder<'a, ((), ())>; - const IS_STATIC: bool = false; +impl Properties for ErrorBoundaryProps { + type Builder = ErrorBoundaryPropsBuilder<((), ())>; fn builder() -> Self::Builder { ErrorBoundaryProps::builder() } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 784a8c865..74e07c914 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -147,7 +147,7 @@ impl Default for EventHandler<'_, T> { } } -type ExternalListenerCallback<'bump, T> = bumpalo::boxed::Box<'bump, dyn FnMut(T) + 'bump>; +type ExternalListenerCallback<'bump, T> = Box; impl EventHandler<'_, T> { /// Call this event handler with the appropriate event type diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index bfab2f98f..e4e1d8660 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -26,7 +26,7 @@ use crate::innerlude::*; /// /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element { +pub fn Fragment<'a>(cx: Scope<'a, FragmentProps>) -> Element { let children = cx.props.0.as_ref()?; Some(VNode { key: children.key, @@ -92,8 +92,8 @@ impl FragmentBuilder { /// }) /// } /// ``` -impl<'a> Properties for FragmentProps<'a> { - type Builder = FragmentBuilder<'a, false>; +impl<'a> Properties for FragmentProps { + type Builder = FragmentBuilder; fn builder() -> Self::Builder { FragmentBuilder(None) } diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs deleted file mode 100644 index ae1bd2109..000000000 --- a/packages/core/src/lazynodes.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Support for storing lazy-nodes on the stack -//! -//! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls -//! to `rsx!` more efficient. -//! -//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`ScopeState`] closures. -//! -//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case, -//! we build a tiny alloactor in the stack and allocate the closure into that. -//! -//! The logic for this was borrowed from . Unfortunately, this crate does not -//! support non-static closures, so we've implemented the core logic of `ValueA` in this module. - -#[allow(unused_imports)] -use smallbox::{smallbox, space::S16, SmallBox}; - -use crate::{innerlude::VNode, ScopeState}; - -/// A concrete type provider for closures that build [`VNode`] structures. -/// -/// This struct wraps lazy structs that build [`VNode`] trees. Normally, we cannot perform a blanket implementation over -/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching. -/// -/// -/// ```rust, ignore -/// LazyNodes::new(|f| { -/// static TEMPLATE: dioxus::core::Template = dioxus::core::Template { -/// name: "main.rs:5:5:20", // Source location of the template for hot reloading -/// roots: &[ -/// dioxus::core::TemplateNode::Element { -/// tag: dioxus_elements::div::TAG_NAME, -/// namespace: dioxus_elements::div::NAME_SPACE, -/// attrs: &[], -/// children: &[], -/// }, -/// ], -/// node_paths: &[], -/// attr_paths: &[], -/// }; -/// dioxus::core::VNode { -/// parent: None, -/// key: None, -/// template: std::cell::Cell::new(TEMPLATE), -/// root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in( -/// 1usize, -/// f.bump(), -/// ) -/// .into(), -/// dynamic_nodes: f.bump().alloc([]), -/// dynamic_attrs: f.bump().alloc([]), -/// }) -/// } -/// ``` -/// -/// Find more information about how to construct [`VNode`] at - -pub struct LazyNodes<'a, 'b> { - #[cfg(not(miri))] - inner: SmallBox VNode<'a> + 'b, S16>, - - #[cfg(miri)] - inner: Box VNode<'a> + 'b>, -} - -impl<'a, 'b> LazyNodes<'a, 'b> { - /// Create a new [`LazyNodes`] closure, optimistically placing it onto the stack. - /// - /// If the closure cannot fit into the stack allocation (16 bytes), then it - /// is placed on the heap. Most closures will fit into the stack, and is - /// the most optimal way to use the creation function. - pub fn new(val: impl FnOnce(&'a ScopeState) -> VNode<'a> + 'b) -> Self { - // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch - let mut slot = Some(val); - - Self { - #[cfg(not(miri))] - inner: smallbox!(move |f| { - let val = slot.take().expect("cannot call LazyNodes twice"); - val(f) - }), - - #[cfg(miri)] - inner: Box::new(move |f| { - let val = slot.take().expect("cannot call LazyNodes twice"); - val(f) - }), - } - } - - /// Call the closure with the given factory to produce real [`VNode`]. - /// - /// ```rust, ignore - /// let f = LazyNodes::new(/* Closure for creating VNodes */); - /// - /// let node = f.call(cac); - /// ``` - #[must_use] - pub fn call(mut self, f: &'a ScopeState) -> VNode<'a> { - (self.inner)(f) - } -} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 13d55826b..8690d9a31 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -5,7 +5,6 @@ mod any_props; mod arena; -mod bump_frame; mod create; mod diff; mod dirty_scope; @@ -43,7 +42,7 @@ pub(crate) mod innerlude { /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`]. /// /// An Errored [`Element`] will propagate the error to the nearest error boundary. - pub type Element<'a> = Option>; + pub type Element = Option; /// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`]. /// @@ -97,9 +96,3 @@ pub mod prelude { VNode, VirtualDom, }; } - -pub mod exports { - //! Important dependencies that are used by the rest of the library - //! Feel free to just add the dependencies in your own Crates.toml - pub use bumpalo; -} diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 3d3afaeb3..692520b3e 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -19,9 +19,9 @@ pub type TemplateId = &'static str; /// /// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor /// you might need to handle the case where there's no node immediately ready. -pub enum RenderReturn<'a> { +pub enum RenderReturn { /// A currently-available element - Ready(VNode<'a>), + Ready(VNode), /// The component aborted rendering early. It might've thrown an error. /// @@ -30,7 +30,7 @@ pub enum RenderReturn<'a> { Aborted(VPlaceholder), } -impl<'a> Default for RenderReturn<'a> { +impl Default for RenderReturn { fn default() -> Self { RenderReturn::Aborted(VPlaceholder::default()) } @@ -88,11 +88,11 @@ impl VNode { /// Create a new VNode pub fn new( - key: Option<&'a str>, + key: Option, template: Template<'static>, - root_ids: bumpalo::collections::Vec<'a, ElementId>, - dynamic_nodes: &'a [DynamicNode<'a>], - dynamic_attrs: &'a [Attribute<'a>], + root_ids: Vec, + dynamic_nodes: &'a [DynamicNode], + dynamic_attrs: &'a [Attribute], ) -> Self { Self { key, @@ -113,7 +113,7 @@ impl VNode { /// Load a dynamic root at the given index /// /// Returns [`None`] if the root is actually a static node (Element/Text) - pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> { + pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode> { match &self.template.get().roots[idx] { TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None, TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => { @@ -147,7 +147,7 @@ pub struct Template<'a> { /// /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm. #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))] - pub roots: &'a [TemplateNode<'a>], + pub roots: &'a [TemplateNode], /// The paths of each node relative to the root of the template. /// @@ -242,7 +242,7 @@ impl<'a> Template<'a> { derive(serde::Serialize, serde::Deserialize), serde(tag = "type") )] -pub enum TemplateNode<'a> { +pub enum TemplateNode { /// An statically known element in the dom. /// /// In HTML this would be something like `
` @@ -250,7 +250,7 @@ pub enum TemplateNode<'a> { /// The name of the element /// /// IE for a div, it would be the string "div" - tag: &'a str, + tag: &'static str, /// The namespace of the element /// @@ -260,23 +260,23 @@ pub enum TemplateNode<'a> { feature = "serialize", serde(deserialize_with = "deserialize_option_leaky") )] - namespace: Option<&'a str>, + namespace: Option<&'static str>, /// A list of possibly dynamic attribues for this element /// /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`. #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))] - attrs: &'a [TemplateAttribute<'a>], + attrs: &'static [TemplateAttribute], /// A list of template nodes that define another set of template nodes #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))] - children: &'a [TemplateNode<'a>], + children: &'static [TemplateNode], }, /// This template node is just a piece of static text Text { /// The actual text - text: &'a str, + text: &'static str, }, /// This template node is unknown, and needs to be created at runtime. @@ -298,7 +298,7 @@ pub enum TemplateNode<'a> { /// /// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index #[derive(Debug)] -pub enum DynamicNode<'a> { +pub enum DynamicNode { /// A component node /// /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and @@ -306,10 +306,10 @@ pub enum DynamicNode<'a> { /// /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap /// the render function at runtime - Component(VComponent<'a>), + Component(VComponent), /// A text node - Text(VText<'a>), + Text(VText), /// A placeholder /// @@ -322,27 +322,20 @@ pub enum DynamicNode<'a> { /// /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering /// or iterators. - Fragment(&'a [VNode<'a>]), + Fragment(&'static [VNode]), } -impl Default for DynamicNode<'_> { +impl Default for DynamicNode { fn default() -> Self { Self::Placeholder(Default::default()) } } /// An instance of a child component -pub struct VComponent<'a> { +pub struct VComponent { /// The name of this component pub name: &'static str, - /// Are the props valid for the 'static lifetime? - /// - /// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it. - /// - /// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement - pub(crate) static_props: bool, - /// The assigned Scope for this component pub(crate) scope: Cell>, @@ -351,17 +344,17 @@ pub struct VComponent<'a> { /// It is possible that components get folded at compile time, so these shouldn't be really used as a key pub(crate) render_fn: *const (), - pub(crate) props: RefCell + 'a>>>, + pub(crate) props: RefCell>>, } -impl<'a> VComponent<'a> { +impl<'a> VComponent { /// Get the scope that this component is mounted to pub fn mounted_scope(&self) -> Option { self.scope.get() } } -impl<'a> std::fmt::Debug for VComponent<'a> { +impl<'a> std::fmt::Debug for VComponent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("VComponent") .field("name", &self.name) @@ -373,17 +366,17 @@ impl<'a> std::fmt::Debug for VComponent<'a> { /// An instance of some text, mounted to the DOM #[derive(Debug)] -pub struct VText<'a> { +pub struct VText { /// The actual text itself - pub value: &'a str, + pub value: String, /// The ID of this node in the real DOM pub(crate) id: Cell>, } -impl<'a> VText<'a> { +impl<'a> VText { /// Create a new VText - pub fn new(value: &'a str) -> Self { + pub fn new(value: String) -> Self { Self { value, id: Default::default(), @@ -419,21 +412,21 @@ impl VPlaceholder { derive(serde::Serialize, serde::Deserialize), serde(tag = "type") )] -pub enum TemplateAttribute<'a> { +pub enum TemplateAttribute { /// This attribute is entirely known at compile time, enabling Static { /// The name of this attribute. /// /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href" - name: &'a str, + name: &'static str, /// The value of this attribute, known at compile time /// /// Currently this only accepts &str, so values, even if they're known at compile time, are not known - value: &'a str, + value: &'static str, /// The namespace of this attribute. Does not exist in the HTML spec - namespace: Option<&'a str>, + namespace: Option<&'static str>, }, /// The attribute in this position is actually determined dynamically at runtime @@ -447,12 +440,12 @@ pub enum TemplateAttribute<'a> { /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` #[derive(Debug)] -pub struct Attribute<'a> { +pub struct Attribute { /// The name of the attribute. - pub name: &'a str, + pub name: &'static str, /// The value of the attribute - pub value: AttributeValue<'a>, + pub value: AttributeValue, /// The namespace of the attribute. /// @@ -466,11 +459,11 @@ pub struct Attribute<'a> { pub(crate) mounted_element: Cell, } -impl<'a> Attribute<'a> { +impl Attribute { /// Create a new attribute pub fn new( - name: &'a str, - value: AttributeValue<'a>, + name: &'static str, + value: AttributeValue, namespace: Option<&'static str>, volatile: bool, ) -> Self { @@ -493,9 +486,9 @@ impl<'a> Attribute<'a> { /// /// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`] /// variant. -pub enum AttributeValue<'a> { +pub enum AttributeValue { /// Text attribute - Text(&'a str), + Text(String), /// A float Float(f64), @@ -507,16 +500,16 @@ pub enum AttributeValue<'a> { Bool(bool), /// A listener, like "onclick" - Listener(RefCell>>), + Listener(RefCell>), /// An arbitrary value that implements PartialEq and is static - Any(RefCell>>), + Any(RefCell>>), /// A "none" value, resulting in the removal of an attribute from the dom None, } -pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event) + 'a>; +pub type ListenerCb = Box)>; /// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed /// @@ -550,8 +543,8 @@ pub enum BorrowedAttributeValue<'a> { None, } -impl<'a> From<&'a AttributeValue<'a>> for BorrowedAttributeValue<'a> { - fn from(value: &'a AttributeValue<'a>) -> Self { +impl<'a> From<&'a AttributeValue> for BorrowedAttributeValue<'a> { + fn from(value: &'a AttributeValue) -> Self { match value { AttributeValue::Text(value) => BorrowedAttributeValue::Text(value), AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value), @@ -613,7 +606,7 @@ where panic!("Any cannot be deserialized") } -impl<'a> std::fmt::Debug for AttributeValue<'a> { +impl std::fmt::Debug for AttributeValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(), @@ -627,7 +620,7 @@ impl<'a> std::fmt::Debug for AttributeValue<'a> { } } -impl<'a> PartialEq for AttributeValue<'a> { +impl PartialEq for AttributeValue { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Text(l0), Self::Text(r0)) => l0 == r0, @@ -668,42 +661,33 @@ impl AnyValue for T { } } -impl<'a> RenderReturn<'a> { - pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> { - unsafe { std::mem::transmute(self) } - } - pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> { - unsafe { std::mem::transmute(self) } - } -} - /// A trait that allows various items to be converted into a dynamic node for the rsx macro pub trait IntoDynNode<'a, A = ()> { /// Consume this item along with a scopestate and produce a DynamicNode /// /// You can use the bump alloactor of the scopestate to creat the dynamic node - fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode<'a>; + fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode; } impl<'a> IntoDynNode<'a> for () { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> { + fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { DynamicNode::default() } } -impl<'a> IntoDynNode<'a> for VNode<'a> { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> { +impl<'a> IntoDynNode<'a> for VNode { + fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { DynamicNode::Fragment(_cx.bump().alloc([self])) } } -impl<'a> IntoDynNode<'a> for DynamicNode<'a> { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> { +impl<'a> IntoDynNode<'a> for DynamicNode { + fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { self } } impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> { + fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { match self { Some(val) => val.into_dyn_node(_cx), None => DynamicNode::default(), @@ -711,8 +695,8 @@ impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option { } } -impl<'a> IntoDynNode<'a> for &Element<'a> { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> { +impl<'a> IntoDynNode<'a> for &Element { + fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { match self.as_ref() { Some(val) => val.clone().into_dyn_node(_cx), _ => DynamicNode::default(), @@ -721,13 +705,13 @@ impl<'a> IntoDynNode<'a> for &Element<'a> { } impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> { - fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode<'a> { + fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode { DynamicNode::Fragment(cx.bump().alloc([cx.render(self).unwrap()])) } } impl<'a, 'b> IntoDynNode<'b> for &'a str { - fn into_dyn_node(self, cx: &'b ScopeState) -> DynamicNode<'b> { + fn into_dyn_node(self, cx: &'b ScopeState) -> DynamicNode { DynamicNode::Text(VText { value: cx.bump().alloc_str(self), id: Default::default(), @@ -745,13 +729,13 @@ impl IntoDynNode<'_> for String { } impl<'b> IntoDynNode<'b> for Arguments<'_> { - fn into_dyn_node(self, cx: &'b ScopeState) -> DynamicNode<'b> { + fn into_dyn_node(self, cx: &'b ScopeState) -> DynamicNode { cx.text_node(self) } } -impl<'a> IntoDynNode<'a> for &'a VNode<'a> { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> { +impl<'a> IntoDynNode<'a> for &'a VNode { + fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { DynamicNode::Fragment(_cx.bump().alloc([VNode { parent: self.parent.clone(), stable_id: self.stable_id.clone(), @@ -764,24 +748,24 @@ impl<'a> IntoDynNode<'a> for &'a VNode<'a> { } } -pub trait IntoVNode<'a> { - fn into_vnode(self, _cx: &'a ScopeState) -> VNode<'a>; +pub trait IntoVNode { + fn into_vnode(self) -> VNode; } -impl<'a> IntoVNode<'a> for VNode<'a> { - fn into_vnode(self, _cx: &'a ScopeState) -> VNode<'a> { +impl<'a> IntoVNode for VNode { + fn into_vnode(self) -> VNode { self } } -impl<'a> IntoVNode<'a> for Element<'a> { - fn into_vnode(self, cx: &'a ScopeState) -> VNode<'a> { +impl<'a> IntoVNode for Element { + fn into_vnode(self) -> VNode { match self { Some(val) => val.into_vnode(cx), - _ => VNode::empty(cx).unwrap(), + _ => VNode::empty().unwrap(), } } } -impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> { - fn into_vnode(self, cx: &'a ScopeState) -> VNode<'a> { +impl<'a, 'b> IntoVNode for LazyNodes<'a, 'b> { + fn into_vnode(self) -> VNode { cx.render(self).unwrap() } } @@ -791,9 +775,9 @@ pub struct FromNodeIterator; impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T where T: Iterator, - I: IntoVNode<'a>, + I: IntoVNode, { - fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode<'a> { + fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode { let mut nodes = bumpalo::collections::Vec::new_in(cx.bump()); nodes.extend(self.into_iter().map(|node| node.into_vnode(cx))); diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index 9a36158fc..ada5e58d5 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -32,7 +32,7 @@ use crate::innerlude::*; /// data: &'a str /// } /// ``` -pub trait Properties: Sized + 'static { +pub trait Properties: Clone + Sized + 'static { /// The type of the builder for this component. /// Used to create "in-progress" versions of the props. type Builder; @@ -66,7 +66,7 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element<'a>) -> T::Builder { +pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { T::builder() } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 962a55e86..b8daeb7cb 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -1,6 +1,5 @@ use crate::{ any_props::AnyProps, - bump_frame::BumpFrame, innerlude::DirtyScope, nodes::RenderReturn, scope_context::ScopeContext, @@ -11,7 +10,7 @@ use crate::{ impl VirtualDom { pub(super) fn new_scope( &mut self, - props: Box>, + props: Box, name: &'static str, ) -> &ScopeState { let parent_id = self.runtime.current_scope_id(); @@ -27,9 +26,6 @@ impl VirtualDom { props: Some(props), - node_arena_1: BumpFrame::new(0), - node_arena_2: BumpFrame::new(0), - render_cnt: Default::default(), hooks: Default::default(), hook_idx: Default::default(), diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index e4cb256d2..e06fe5eba 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -301,7 +301,7 @@ pub fn provide_root_context(value: T) -> Option { } /// Suspends the current component -pub fn suspend() -> Option> { +pub fn suspend() -> Option { with_current_scope(|cx| { cx.suspend(); }); diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 552a9d5b9..ffdd895f5 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,16 +1,13 @@ use crate::{ any_props::AnyProps, any_props::VProps, - bump_frame::BumpFrame, innerlude::ErrorBoundary, innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText}, - lazynodes::LazyNodes, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, runtime::Runtime, scope_context::ScopeContext, AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId, }; -use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ any::Any, cell::{Cell, Ref, RefCell, UnsafeCell}, @@ -90,11 +87,11 @@ pub struct ScopeState { pub(crate) hooks: RefCell>>>, pub(crate) hook_idx: Cell, - pub(crate) borrowed_props: RefCell>>, + pub(crate) borrowed_props: RefCell>, pub(crate) element_refs_to_drop: RefCell>, - pub(crate) attributes_to_drop_before_render: RefCell>>, + pub(crate) attributes_to_drop_before_render: RefCell>, - pub(crate) props: Option>>, + pub(crate) props: Option>, } impl Drop for ScopeState { @@ -108,22 +105,6 @@ impl<'src> ScopeState { self.runtime.get_context(self.context_id).unwrap() } - pub(crate) fn current_frame(&self) -> &BumpFrame { - match self.render_cnt.get() % 2 { - 0 => &self.node_arena_1, - 1 => &self.node_arena_2, - _ => unreachable!(), - } - } - - pub(crate) fn previous_frame(&self) -> &BumpFrame { - match self.render_cnt.get() % 2 { - 1 => &self.node_arena_1, - 0 => &self.node_arena_2, - _ => unreachable!(), - } - } - /// Get the name of this component pub fn name(&self) -> &str { self.context().name @@ -136,17 +117,6 @@ impl<'src> ScopeState { self.render_cnt.get() } - /// Get a handle to the currently active bump arena for this Scope - /// - /// This is a bump memory allocator. Be careful using this directly since the contents will be wiped on the next render. - /// It's easy to leak memory here since the drop implementation will not be called for any objects allocated in this arena. - /// - /// If you need to allocate items that need to be dropped, use bumpalo's box. - pub fn bump(&self) -> &Bump { - // note that this is actually the previous frame since we use that as scratch space while the component is rendering - self.previous_frame().bump() - } - /// Get a handle to the currently active head node arena for this Scope /// /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR. @@ -343,7 +313,7 @@ impl<'src> ScopeState { /// cx.render(lazy_tree) /// } ///``` - pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> { + pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element { let element = rsx.call(self); let mut listeners = self.attributes_to_drop_before_render.borrow_mut(); @@ -383,25 +353,13 @@ impl<'src> ScopeState { } /// Create a dynamic text node using [`Arguments`] and the [`ScopeState`]'s internal [`Bump`] allocator - pub fn text_node(&'src self, args: Arguments) -> DynamicNode<'src> { + pub fn text_node(&'src self, args: Arguments) -> DynamicNode { DynamicNode::Text(VText { value: self.raw_text(args), id: Default::default(), }) } - /// Allocate some text inside the [`ScopeState`] from [`Arguments`] - /// - /// Uses the currently active [`Bump`] allocator - pub fn raw_text(&'src self, args: Arguments) -> &'src str { - args.as_str().unwrap_or_else(|| { - use bumpalo::core_alloc::fmt::Write; - let mut str_buf = bumpalo::collections::String::new_in(self.bump()); - str_buf.write_fmt(args).unwrap(); - str_buf.into_bump_str() - }) - } - /// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`] using the internal [`Bump`] allocator pub fn make_node<'c, I>(&'src self, into: impl IntoDynNode<'src, I> + 'c) -> DynamicNode { into.into_dyn_node(self) @@ -417,7 +375,7 @@ impl<'src> ScopeState { value: impl IntoAttributeValue<'src>, namespace: Option<&'static str>, volatile: bool, - ) -> Attribute<'src> { + ) -> Attribute { Attribute { name, namespace, @@ -443,10 +401,10 @@ impl<'src> ScopeState { /// ``` pub fn component<'child, P>( &'src self, - component: fn(Scope<'child, P>) -> Element<'child>, + component: fn(Scope<'child, P>) -> Element, props: P, fn_name: &'static str, - ) -> DynamicNode<'src> + ) -> DynamicNode where // The properties must be valid until the next bump frame P: Properties + 'src, @@ -456,13 +414,12 @@ impl<'src> ScopeState { let vcomp = VProps::new(component, P::memoize, props); // cast off the lifetime of the render return - let as_dyn: Box + '_> = Box::new(vcomp); - let extended: Box + 'src> = unsafe { std::mem::transmute(as_dyn) }; + let as_dyn: Box = Box::new(vcomp); + let extended: Box = unsafe { std::mem::transmute(as_dyn) }; DynamicNode::Component(VComponent { name: fn_name, render_fn: component as *const (), - static_props: P::IS_STATIC, props: RefCell::new(Some(extended)), scope: Default::default(), }) @@ -470,9 +427,9 @@ impl<'src> ScopeState { /// Create a new [`EventHandler`] from an [`FnMut`] pub fn event_handler(&'src self, f: impl FnMut(T) + 'src) -> EventHandler<'src, T> { - let handler: &mut dyn FnMut(T) = self.bump().alloc(f); - let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) }; - let callback = RefCell::new(Some(caller)); + let callback = RefCell::new(Some(Box::new(move |event: Event| { + f(event.data); + }))); EventHandler { callback, origin: self.context().id, @@ -485,34 +442,22 @@ impl<'src> ScopeState { pub fn listener( &'src self, mut callback: impl FnMut(Event) + 'src, - ) -> AttributeValue<'src> { - // safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw - // This is the suggested way to build a bumpbox - // - // In theory, we could just use regular boxes - let boxed: BumpBox<'src, dyn FnMut(_) + 'src> = unsafe { - BumpBox::from_raw(self.bump().alloc(move |event: Event| { + ) -> AttributeValue { + AttributeValue::Listener(RefCell::new(Some(Box::new( + move |event: Event| { if let Ok(data) = event.data.downcast::() { callback(Event { propagates: event.propagates, data, }); } - })) - }; - - AttributeValue::Listener(RefCell::new(Some(boxed))) + }, + )))) } /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`] - pub fn any_value(&'src self, value: T) -> AttributeValue<'src> { - // safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw - // This is the suggested way to build a bumpbox - // - // In theory, we could just use regular boxes - let boxed: BumpBox<'src, dyn AnyValue> = - unsafe { BumpBox::from_raw(self.bump().alloc(value)) }; - AttributeValue::Any(RefCell::new(Some(boxed))) + pub fn any_value(&'src self, value: T) -> AttributeValue { + AttributeValue::Any(RefCell::new(Some(Box::new(value)))) } /// Mark this component as suspended and then return None diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index f469cd9e2..ec1f0dfe5 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -16,7 +16,9 @@ use crate::{ use futures_util::{pin_mut, StreamExt}; use rustc_hash::{FxHashMap, FxHashSet}; use slab::Slab; -use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc, sync::Arc}; +use std::{ + any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc, sync::Arc, +}; /// A virtual node system that progresses user events and diffs UI trees. /// @@ -186,7 +188,7 @@ pub struct VirtualDom { pub(crate) templates: FxHashMap>>, // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template - pub(crate) element_refs: Slab>>>, + pub(crate) element_refs: Slab>>, // The element ids that are used in the renderer pub(crate) elements: Slab>, diff --git a/packages/html/src/events.rs b/packages/html/src/events.rs index c59149493..32c48aafb 100644 --- a/packages/html/src/events.rs +++ b/packages/html/src/events.rs @@ -9,7 +9,7 @@ macro_rules! impl_event { $( $( #[$attr] )* #[inline] - pub fn $name<'a, E: crate::EventReturn, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'a) -> ::dioxus_core::Attribute<'a> { + pub fn $name<'a, E: crate::EventReturn, T>(_cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event<$data>) -> E + 'a) -> ::dioxus_core::Attribute { ::dioxus_core::Attribute::new( stringify!($name), _cx.listener(move |e: ::dioxus_core::Event<$data>| { diff --git a/packages/html/src/events/mouse.rs b/packages/html/src/events/mouse.rs index 17128f4e4..890d56546 100644 --- a/packages/html/src/events/mouse.rs +++ b/packages/html/src/events/mouse.rs @@ -151,7 +151,7 @@ impl_event! { pub fn ondoubleclick<'a, E: crate::EventReturn, T>( _cx: &'a ::dioxus_core::ScopeState, mut _f: impl FnMut(::dioxus_core::Event) -> E + 'a, -) -> ::dioxus_core::Attribute<'a> { +) -> ::dioxus_core::Attribute { ::dioxus_core::Attribute::new( "ondblclick", _cx.listener(move |e: ::dioxus_core::Event| { From f42ef3ef9d93133cb785e8809be3d5f158378dcb Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 5 Jan 2024 08:39:22 -0600 Subject: [PATCH 035/377] remove more lifetimes --- packages/core/compile_tests/props_safety.rs | 4 +- .../props_safety_temporary_values.rs | 2 +- packages/core/src/any_props.rs | 49 ++--- packages/core/src/arena.rs | 6 +- packages/core/src/create.rs | 42 ++--- packages/core/src/diff.rs | 111 +++++------- packages/core/src/error_boundary.rs | 102 +++++------ packages/core/src/events.rs | 10 +- packages/core/src/fragment.rs | 8 +- packages/core/src/lib.rs | 14 +- packages/core/src/nodes.rs | 167 ++++++++---------- packages/core/src/properties.rs | 2 +- packages/core/src/scope_arena.rs | 4 +- packages/core/src/scopes.rs | 121 ++----------- packages/core/src/virtual_dom.rs | 10 +- packages/core/tests/create_dom.rs | 2 +- packages/core/tests/create_passthru.rs | 4 +- packages/core/tests/fuzzing.rs | 4 +- 18 files changed, 241 insertions(+), 421 deletions(-) diff --git a/packages/core/compile_tests/props_safety.rs b/packages/core/compile_tests/props_safety.rs index a9729cec9..485ef7900 100644 --- a/packages/core/compile_tests/props_safety.rs +++ b/packages/core/compile_tests/props_safety.rs @@ -14,10 +14,10 @@ fn app(cx: Scope) -> Element { #[derive(Props)] struct Testing<'a> { - borrowed: &'a RefCell>>, + borrowed: &'a RefCell>, } -fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> { +fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element { let Testing { borrowed } = cx.props; let borrowed_temporary_data = cx.use_hook(|| String::from("This data is only valid for the lifetime of the child")); diff --git a/packages/core/compile_tests/props_safety_temporary_values.rs b/packages/core/compile_tests/props_safety_temporary_values.rs index c332f63c4..962c9e6e6 100644 --- a/packages/core/compile_tests/props_safety_temporary_values.rs +++ b/packages/core/compile_tests/props_safety_temporary_values.rs @@ -17,7 +17,7 @@ struct Testing<'a> { borrowed: &'a Vec, } -fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element<'a> { +fn unsafe_child_component<'a>(cx: Scope<'a, Testing<'a>>) -> Element { cx.render(rsx! { div { "{cx.props.borrowed:?}" } }) diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index 2cc9c6559..420bf9d32 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -1,9 +1,4 @@ -use crate::{ - innerlude::Scoped, - nodes::RenderReturn, - scopes::{Scope, ScopeState}, - Element, -}; +use crate::{nodes::RenderReturn, scopes::ScopeState, Element}; use std::panic::AssertUnwindSafe; /// A trait that essentially allows VComponentProps to be used generically @@ -11,24 +6,19 @@ use std::panic::AssertUnwindSafe; /// # Safety /// /// This should not be implemented outside this module -pub(crate) unsafe trait AnyProps { - fn props_ptr(&self) -> *const (); - fn render<'a>(&'a self, bump: &'a ScopeState) -> RenderReturn<'a>; - unsafe fn memoize(&self, other: &dyn AnyProps) -> bool; +pub(crate) trait AnyProps { + fn render<'a>(&'a self, bump: &'a ScopeState) -> RenderReturn; + fn memoize(&self, other: &dyn AnyProps) -> bool; } -pub(crate) struct VProps<'a, P> { - pub render_fn: fn(Scope<'a, P>) -> Element<'a>, - pub memo: unsafe fn(&P, &P) -> bool, +pub(crate) struct VProps

{ + pub render_fn: fn(P) -> Element, + pub memo: fn(&P, &P) -> bool, pub props: P, } -impl<'a, P> VProps<'a, P> { - pub(crate) fn new( - render_fn: fn(Scope<'a, P>) -> Element<'a>, - memo: unsafe fn(&P, &P) -> bool, - props: P, - ) -> Self { +impl

VProps

{ + pub(crate) fn new(render_fn: fn(P) -> Element, memo: fn(&P, &P) -> bool, props: P) -> Self { Self { render_fn, memo, @@ -37,30 +27,19 @@ impl<'a, P> VProps<'a, P> { } } -unsafe impl<'a, P> AnyProps for VProps<'a, P> { - fn props_ptr(&self) -> *const () { - &self.props as *const _ as *const () - } - +impl AnyProps for VProps

{ // Safety: // this will downcast the other ptr as our swallowed type! // you *must* make this check *before* calling this method // if your functions are not the same, then you will downcast a pointer into a different type (UB) - unsafe fn memoize(&self, other: &dyn AnyProps) -> bool { - let real_other: &P = &*(other.props_ptr() as *const _ as *const P); - let real_us: &P = &*(self.props_ptr() as *const _ as *const P); - (self.memo)(real_us, real_other) + fn memoize(&self, other: &dyn AnyProps) -> bool { + (self.memo)(self, other) } - fn render(&'a self, cx: &'a ScopeState) -> RenderReturn<'a> { + fn render(&self, cx: &ScopeState) -> RenderReturn { let res = std::panic::catch_unwind(AssertUnwindSafe(move || { // Call the render function directly - let scope: &mut Scoped

= cx.bump().alloc(Scoped { - props: &self.props, - scope: cx, - }); - - (self.render_fn)(scope) + (self.render_fn)(self.props.clone()) })); match res { diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index ffdd4656b..205060b32 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -102,7 +102,7 @@ impl VirtualDom { if recursive { if let Some(root) = self.scopes[id.0].try_root_node() { - if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } { + if let RenderReturn::Ready(node) = root { self.drop_scope_inner(node) } } @@ -131,7 +131,6 @@ impl VirtualDom { if let Some(f) = c.scope.get() { self.drop_scope(f, true); } - c.props.take(); } DynamicNode::Fragment(nodes) => { nodes.iter().for_each(|node| self.drop_scope_inner(node)) @@ -166,9 +165,6 @@ impl VirtualDom { Some(child) if child != scope_id => self.ensure_drop_safety(child), _ => (), } - if let Ok(mut props) = comp.props.try_borrow_mut() { - *props = None; - } } let scope = &self.scopes[scope_id.0]; scope.borrowed_props.borrow_mut().clear(); diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 55334b900..c4390c212 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -63,11 +63,11 @@ fn sorting() { ); } -impl<'b> VirtualDom { +impl VirtualDom { /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. /// /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created. - pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode) -> usize { + pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &VNode) -> usize { self.runtime.scope_stack.borrow_mut().push(scope); let nodes = self.create(template); self.runtime.scope_stack.borrow_mut().pop(); @@ -75,7 +75,7 @@ impl<'b> VirtualDom { } /// Create this template and write its mutations - pub(crate) fn create(&mut self, node: &'b VNode) -> usize { + pub(crate) fn create(&mut self, node: &VNode) -> usize { // check for a overriden template #[cfg(debug_assertions)] { @@ -182,7 +182,7 @@ impl<'b> VirtualDom { 1 } - fn write_dynamic_root(&mut self, template: &'b VNode, idx: usize) -> usize { + fn write_dynamic_root(&mut self, template: &VNode, idx: usize) -> usize { use DynamicNode::*; match &template.dynamic_nodes[idx] { node @ Component { .. } | node @ Fragment(_) => { @@ -232,7 +232,7 @@ impl<'b> VirtualDom { /// We want to make sure we write these nodes while on top of the root fn write_element_root( &mut self, - template: &'b VNode, + template: &VNode, root_idx: usize, dynamic_attrs: &mut Peekable>, dynamic_nodes_iter: &mut Peekable>, @@ -269,7 +269,7 @@ impl<'b> VirtualDom { dynamic_nodes_iter: &mut Peekable>, dynamic_nodes: &[(usize, &'static [u8])], root_idx: u8, - template: &'b VNode, + template: &VNode, ) { let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) { Some((a, b)) => (a, b), @@ -306,7 +306,7 @@ impl<'b> VirtualDom { attrs: &mut Peekable>, root_idx: u8, root: ElementId, - node: &'b VNode, + node: &VNode, ) { while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx)) @@ -327,9 +327,9 @@ impl<'b> VirtualDom { fn write_attribute( &mut self, - template: &'b VNode, + template: &VNode, idx: usize, - attribute: &'b crate::Attribute<'b>, + attribute: &crate::Attribute, id: ElementId, ) { // Make sure we set the attribute's associated id @@ -355,7 +355,7 @@ impl<'b> VirtualDom { } _ => { // Safety: we promise not to re-alias this text later on after committing it to the mutation - let value: BorrowedAttributeValue<'b> = (&attribute.value).into(); + let value: BorrowedAttributeValue = (&attribute.value).into(); let unbounded_value = unsafe { std::mem::transmute(value) }; self.mutations.push(SetAttribute { @@ -476,35 +476,28 @@ impl<'b> VirtualDom { } } - pub(crate) fn create_dynamic_node( - &mut self, - parent: ElementRef, - node: &'b DynamicNode<'b>, - ) -> usize { + pub(crate) fn create_dynamic_node(&mut self, parent: ElementRef, node: &DynamicNode) -> usize { use DynamicNode::*; match node { Text(text) => self.create_dynamic_text(parent, text), Placeholder(place) => self.create_placeholder(place, parent), Component(component) => self.create_component_node(Some(parent), component), - Fragment(frag) => self.create_children(*frag, Some(parent)), + Fragment(frag) => self.create_children(frag, Some(parent)), } } - fn create_dynamic_text(&mut self, parent: ElementRef, text: &'b VText) -> usize { + fn create_dynamic_text(&mut self, parent: ElementRef, text: &VText) -> usize { // Allocate a dynamic element reference for this text node let new_id = self.next_element(); // Make sure the text node is assigned to the correct element text.id.set(Some(new_id)); - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let value = unsafe { std::mem::transmute(text.value) }; - // Add the mutation to the list self.mutations.push(HydrateText { id: new_id, path: &parent.path.path[1..], - value, + value: &text.value, }); // Since we're hydrating an existing node, we don't create any new nodes @@ -538,7 +531,7 @@ impl<'b> VirtualDom { pub(super) fn create_component_node( &mut self, parent: Option, - component: &'b VComponent, + component: &VComponent, ) -> usize { use RenderReturn::*; @@ -547,7 +540,7 @@ impl<'b> VirtualDom { component.scope.set(Some(scope)); - match unsafe { self.run_scope(scope).extend_lifetime_ref() } { + match self.run_scope(scope) { // Create the component's root element Ready(t) => { self.assign_boundary_ref(parent, t); @@ -561,7 +554,6 @@ impl<'b> VirtualDom { fn load_scope_from_vcomponent(&mut self, component: &VComponent) -> ScopeId { component .props - .take() .map(|props| { let unbounded_props: Box = unsafe { std::mem::transmute(props) }; self.new_scope(unbounded_props, component.name).context().id @@ -578,7 +570,7 @@ impl<'b> VirtualDom { 1 } - fn set_slot(&mut self, slot: &'b Cell>) -> ElementId { + fn set_slot(&mut self, slot: &Cell>) -> ElementId { let id = self.next_element(); slot.set(Some(id)); id diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 971c3977e..fefb9512e 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -16,7 +16,7 @@ use crate::{ use rustc_hash::{FxHashMap, FxHashSet}; use DynamicNode::*; -impl<'b> VirtualDom { +impl VirtualDom { pub(super) fn diff_scope(&mut self, scope: ScopeId) { self.runtime.scope_stack.borrow_mut().push(scope); let scope_state = &mut self.get_scope(scope).unwrap(); @@ -59,7 +59,7 @@ impl<'b> VirtualDom { self.runtime.scope_stack.borrow_mut().pop(); } - fn diff_ok_to_err(&mut self, l: &'b VNode, p: &'b VPlaceholder) { + fn diff_ok_to_err(&mut self, l: &VNode, p: &VPlaceholder) { let id = self.next_element(); p.id.set(Some(id)); p.parent.set(l.parent.get()); @@ -82,7 +82,7 @@ impl<'b> VirtualDom { }; } - fn diff_node(&mut self, left_template: &'b VNode, right_template: &'b VNode) { + fn diff_node(&mut self, left_template: &VNode, right_template: &VNode) { // If hot reloading is enabled, we need to make sure we're using the latest template #[cfg(debug_assertions)] { @@ -166,8 +166,8 @@ impl<'b> VirtualDom { fn diff_dynamic_node( &mut self, - left_node: &'b DynamicNode<'b>, - right_node: &'b DynamicNode<'b>, + left_node: &DynamicNode, + right_node: &DynamicNode, parent: ElementRef, ) { match (left_node, right_node) { @@ -184,9 +184,9 @@ impl<'b> VirtualDom { }; } - fn update_attribute(&mut self, right_attr: &'b Attribute<'b>, left_attr: &'b Attribute) { + fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) { let name = unsafe { std::mem::transmute(left_attr.name) }; - let value: BorrowedAttributeValue<'b> = (&right_attr.value).into(); + let value: BorrowedAttributeValue = (&right_attr.value).into(); let value = unsafe { std::mem::transmute(value) }; self.mutations.push(Mutation::SetAttribute { id: left_attr.mounted_element.get(), @@ -198,8 +198,8 @@ impl<'b> VirtualDom { fn diff_vcomponent( &mut self, - left: &'b VComponent, - right: &'b VComponent, + left: &VComponent, + right: &VComponent, parent: Option, ) { if std::ptr::eq(left, right) { @@ -249,8 +249,8 @@ impl<'b> VirtualDom { fn replace_vcomponent( &mut self, - right: &'b VComponent, - left: &'b VComponent, + right: &VComponent, + left: &VComponent, parent: Option, ) { let m = self.create_component_node(parent, right); @@ -307,7 +307,7 @@ impl<'b> VirtualDom { /// Component { ..props } /// } /// ``` - fn light_diff_templates(&mut self, left: &'b VNode, right: &'b VNode) { + fn light_diff_templates(&mut self, left: &VNode, right: &VNode) { let parent = left.parent.take(); match matching_components(left, right) { None => self.replace(left, [right], parent), @@ -321,7 +321,7 @@ impl<'b> VirtualDom { /// /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's /// different. - fn diff_vtext(&mut self, left: &'b VText, right: &'b VText) { + fn diff_vtext(&mut self, left: &VText, right: &VText) { let id = left.id.get().unwrap_or_else(|| self.next_element()); right.id.set(Some(id)); @@ -331,7 +331,7 @@ impl<'b> VirtualDom { } } - fn diff_non_empty_fragment(&mut self, old: &'b [VNode], new: &'b [VNode], parent: ElementRef) { + fn diff_non_empty_fragment(&mut self, old: &[VNode], new: &[VNode], parent: ElementRef) { let new_is_keyed = new[0].key.is_some(); let old_is_keyed = old[0].key.is_some(); debug_assert!( @@ -358,7 +358,7 @@ impl<'b> VirtualDom { // [... parent] // // the change list stack is in the same state when this function returns. - fn diff_non_keyed_children(&mut self, old: &'b [VNode], new: &'b [VNode], parent: ElementRef) { + fn diff_non_keyed_children(&mut self, old: &[VNode], new: &[VNode], parent: ElementRef) { use std::cmp::Ordering; // Handled these cases in `diff_children` before calling this function. @@ -394,10 +394,10 @@ impl<'b> VirtualDom { // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 // // The stack is empty upon entry. - fn diff_keyed_children(&mut self, old: &'b [VNode], new: &'b [VNode], parent: ElementRef) { + fn diff_keyed_children(&mut self, old: &[VNode], new: &[VNode], parent: ElementRef) { if cfg!(debug_assertions) { let mut keys = rustc_hash::FxHashSet::default(); - let mut assert_unique_keys = |children: &'b [VNode]| { + let mut assert_unique_keys = |children: &[VNode]| { keys.clear(); for child in children { let key = child.key; @@ -470,8 +470,8 @@ impl<'b> VirtualDom { /// If there is no offset, then this function returns None and the diffing is complete. fn diff_keyed_ends( &mut self, - old: &'b [VNode], - new: &'b [VNode], + old: &[VNode], + new: &[VNode], parent: ElementRef, ) -> Option<(usize, usize)> { let mut left_offset = 0; @@ -527,7 +527,7 @@ impl<'b> VirtualDom { // // Upon exit from this function, it will be restored to that same self. #[allow(clippy::too_many_lines)] - fn diff_keyed_middle(&mut self, old: &'b [VNode], new: &'b [VNode], parent: ElementRef) { + fn diff_keyed_middle(&mut self, old: &[VNode], new: &[VNode], parent: ElementRef) { /* 1. Map the old keys into a numerical ordering based on indices. 2. Create a map of old key to its index @@ -709,7 +709,7 @@ impl<'b> VirtualDom { } /// Push all the real nodes on the stack - fn push_all_real_nodes(&mut self, node: &'b VNode) -> usize { + fn push_all_real_nodes(&mut self, node: &VNode) -> usize { node.template .get() .roots @@ -746,12 +746,7 @@ impl<'b> VirtualDom { Component(comp) => { let scope = comp.scope.get().unwrap(); - match unsafe { - self.get_scope(scope) - .unwrap() - .root_node() - .extend_lifetime_ref() - } { + match self.get_scope(scope).unwrap().root_node() { RenderReturn::Ready(node) => self.push_all_real_nodes(node), RenderReturn::Aborted(_node) => todo!(), } @@ -761,9 +756,9 @@ impl<'b> VirtualDom { .sum() } - pub(crate) fn create_children( + pub(crate) fn create_children<'a>( &mut self, - nodes: impl IntoIterator, + nodes: impl IntoIterator, parent: Option, ) -> usize { nodes @@ -775,28 +770,23 @@ impl<'b> VirtualDom { .sum() } - fn create_and_insert_before( - &mut self, - new: &'b [VNode], - before: &'b VNode, - parent: ElementRef, - ) { + fn create_and_insert_before(&mut self, new: &[VNode], before: &VNode, parent: ElementRef) { let m = self.create_children(new, Some(parent)); let id = self.find_first_element(before); self.mutations.push(Mutation::InsertBefore { id, m }) } - fn create_and_insert_after(&mut self, new: &'b [VNode], after: &'b VNode, parent: ElementRef) { + fn create_and_insert_after(&mut self, new: &[VNode], after: &VNode, parent: ElementRef) { let m = self.create_children(new, Some(parent)); let id = self.find_last_element(after); self.mutations.push(Mutation::InsertAfter { id, m }) } /// Simply replace a placeholder with a list of nodes - fn replace_placeholder( + fn replace_placeholder<'a>( &mut self, - l: &'b VPlaceholder, - r: impl IntoIterator, + l: &VPlaceholder, + r: impl IntoIterator, parent: ElementRef, ) { let m = self.create_children(r, Some(parent)); @@ -805,10 +795,10 @@ impl<'b> VirtualDom { self.reclaim(id); } - fn replace( + fn replace<'a>( &mut self, - left: &'b VNode, - right: impl IntoIterator, + left: &VNode, + right: impl IntoIterator, parent: Option, ) { let m = self.create_children(right, parent); @@ -830,7 +820,7 @@ impl<'b> VirtualDom { }; } - fn node_to_placeholder(&mut self, l: &'b [VNode], r: &'b VPlaceholder, parent: ElementRef) { + fn node_to_placeholder(&mut self, l: &[VNode], r: &VPlaceholder, parent: ElementRef) { // Create the placeholder first, ensuring we get a dedicated ID for the placeholder let placeholder = self.next_element(); @@ -853,14 +843,14 @@ impl<'b> VirtualDom { /// Remove these nodes from the dom /// Wont generate mutations for the inner nodes - fn remove_nodes(&mut self, nodes: &'b [VNode]) { + fn remove_nodes(&mut self, nodes: &[VNode]) { nodes .iter() .rev() .for_each(|node| self.remove_node(node, true)); } - fn remove_node(&mut self, node: &'b VNode, gen_muts: bool) { + fn remove_node(&mut self, node: &VNode, gen_muts: bool) { // Clean up any attributes that have claimed a static node as dynamic for mount/unmounta // Will not generate mutations! self.reclaim_attributes(node); @@ -878,7 +868,7 @@ impl<'b> VirtualDom { self.reclaim_vnode_id(node); } - fn reclaim_vnode_id(&mut self, node: &'b VNode) { + fn reclaim_vnode_id(&mut self, node: &VNode) { // Clean up the vnode id if let Some(id) = node.stable_id() { self.element_refs.remove(id.0); @@ -982,12 +972,7 @@ impl<'b> VirtualDom { .expect("VComponents to always have a scope"); // Remove the component from the dom - match unsafe { - self.get_scope(scope) - .unwrap() - .root_node() - .extend_lifetime_ref() - } { + match unsafe { self.get_scope(scope).unwrap().root_node() } { RenderReturn::Ready(t) => self.remove_node(t, gen_muts), RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts), }; @@ -1000,7 +985,7 @@ impl<'b> VirtualDom { self.drop_scope(scope, false); } - fn find_first_element(&self, node: &'b VNode) -> ElementId { + fn find_first_element(&self, node: &VNode) -> ElementId { match node.dynamic_root(0) { None => node.root_ids.borrow()[0], Some(Text(t)) => t.id.get().unwrap(), @@ -1008,12 +993,7 @@ impl<'b> VirtualDom { Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); - match unsafe { - self.get_scope(scope) - .unwrap() - .root_node() - .extend_lifetime_ref() - } { + match unsafe { self.get_scope(scope).unwrap().root_node() } { RenderReturn::Ready(t) => self.find_first_element(t), _ => todo!("cannot handle nonstandard nodes"), } @@ -1021,7 +1001,7 @@ impl<'b> VirtualDom { } } - fn find_last_element(&self, node: &'b VNode) -> ElementId { + fn find_last_element(&self, node: &VNode) -> ElementId { match node.dynamic_root(node.template.get().roots.len() - 1) { None => *node.root_ids.borrow().last().unwrap(), Some(Text(t)) => t.id.get().unwrap(), @@ -1029,12 +1009,7 @@ impl<'b> VirtualDom { Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); - match unsafe { - self.get_scope(scope) - .unwrap() - .root_node() - .extend_lifetime_ref() - } { + match unsafe { self.get_scope(scope).unwrap().root_node() } { RenderReturn::Ready(t) => self.find_last_element(t), _ => todo!("cannot handle nonstandard nodes"), } @@ -1042,7 +1017,7 @@ impl<'b> VirtualDom { } } - pub(crate) fn assign_boundary_ref(&mut self, parent: Option, child: &'b VNode) { + pub(crate) fn assign_boundary_ref(&mut self, parent: Option, child: &VNode) { if let Some(parent) = parent { // assign the parent of the child child.parent.set(Some(parent)); @@ -1055,7 +1030,7 @@ impl<'b> VirtualDom { /// We need to check for the obvious case, and the non-obvious case where the template as cloned /// /// We use the pointer of the dynamic_node list in this case -fn templates_are_the_same<'b>(left_template: &'b VNode, right_template: &'b VNode) -> bool { +fn templates_are_the_same(left_template: &VNode, right_template: &VNode) -> bool { std::ptr::eq(left_template, right_template) } diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index 30e31b7b8..2901acbca 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -1,7 +1,7 @@ use crate::{ scope_context::{consume_context, current_scope_id, schedule_update_any}, - Element, IntoDynNode, LazyNodes, Properties, Scope, ScopeId, ScopeState, Template, - TemplateAttribute, TemplateNode, VNode, + Element, IntoDynNode, Properties, ScopeId, ScopeState, Template, TemplateAttribute, + TemplateNode, VNode, }; use std::{ any::{Any, TypeId}, @@ -262,42 +262,40 @@ impl Throw for Option { } } -pub struct ErrorHandler(Box LazyNodes<'a, 'a>>); -impl<'a, F: Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a> From for ErrorHandler<'a> { +pub struct ErrorHandler(Box Element>); +impl Element> From for ErrorHandler { fn from(value: F) -> Self { Self(Box::new(value)) } } -fn default_handler<'a>(error: CapturedError) -> LazyNodes<'a, 'a> { - LazyNodes::new(move |__cx: &ScopeState| -> VNode { - static TEMPLATE: Template = Template { - name: "error_handle.rs:42:5:884", - roots: &[TemplateNode::Element { - tag: "pre", - namespace: None, - attrs: &[TemplateAttribute::Static { - name: "color", - namespace: Some("style"), - value: "red", - }], - children: &[TemplateNode::DynamicText { id: 0usize }], +fn default_handler(error: CapturedError) -> Element { + static TEMPLATE: Template = Template { + name: "error_handle.rs:42:5:884", + roots: &[TemplateNode::Element { + tag: "pre", + namespace: None, + attrs: &[TemplateAttribute::Static { + name: "color", + namespace: Some("style"), + value: "red", }], - node_paths: &[&[0u8, 0u8]], - attr_paths: &[], - }; - VNode { - parent: Default::default(), - stable_id: Default::default(), - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: Vec::with_capacity(1usize).into(), - dynamic_nodes: __cx - .bump() - .alloc([__cx.text_node(format_args!("{0}", error))]), - dynamic_attrs: __cx.bump().alloc([]), - } + children: &[TemplateNode::DynamicText { id: 0usize }], + }], + node_paths: &[&[0u8, 0u8]], + attr_paths: &[], + }; + Some(VNode { + parent: Default::default(), + stable_id: Default::default(), + key: None, + template: std::cell::Cell::new(TEMPLATE), + root_ids: Vec::with_capacity(1usize).into(), + dynamic_nodes: vec![error.to_string().into_dyn_node()], + dynamic_attrs: Default::default(), }) } + +#[derive(Clone)] pub struct ErrorBoundaryProps { children: Element, handle_error: ErrorHandler, @@ -334,7 +332,7 @@ impl Properties for ErrorBoundaryProps { fn builder() -> Self::Builder { ErrorBoundaryProps::builder() } - unsafe fn memoize(&self, _: &Self) -> bool { + fn memoize(&self, _: &Self) -> bool { false } } @@ -354,16 +352,15 @@ impl ErrorBoundaryPropsBuilder_Optional for (T,) { } } #[allow(dead_code, non_camel_case_types, missing_docs)] -impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((), __handle_error)> { +impl<__handle_error> ErrorBoundaryPropsBuilder<((), __handle_error)> { pub fn children( self, - children: Element<'a>, - ) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> { + children: Element, + ) -> ErrorBoundaryPropsBuilder<((Element,), __handle_error)> { let children = (children,); let (_, handle_error) = self.fields; ErrorBoundaryPropsBuilder { fields: (children, handle_error), - _phantom: self._phantom, } } } @@ -372,26 +369,25 @@ impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((), __handle_error)> { pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_children {} #[doc(hidden)] #[allow(dead_code, non_camel_case_types, missing_docs)] -impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> { +impl<__handle_error> ErrorBoundaryPropsBuilder<((Element,), __handle_error)> { #[deprecated(note = "Repeated field children")] pub fn children( self, _: ErrorBoundaryPropsBuilder_Error_Repeated_field_children, - ) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> { + ) -> ErrorBoundaryPropsBuilder<((Element,), __handle_error)> { self } } #[allow(dead_code, non_camel_case_types, missing_docs)] -impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, ())> { +impl<__children> ErrorBoundaryPropsBuilder<(__children, ())> { pub fn handle_error( self, - handle_error: impl ::core::convert::Into>, - ) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> { + handle_error: impl ::core::convert::Into, + ) -> ErrorBoundaryPropsBuilder<(__children, (ErrorHandler,))> { let handle_error = (handle_error.into(),); let (children, _) = self.fields; ErrorBoundaryPropsBuilder { fields: (children, handle_error), - _phantom: self._phantom, } } } @@ -400,23 +396,22 @@ impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, ())> { pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error {} #[doc(hidden)] #[allow(dead_code, non_camel_case_types, missing_docs)] -impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> { +impl<__children> ErrorBoundaryPropsBuilder<(__children, (ErrorHandler,))> { #[deprecated(note = "Repeated field handle_error")] pub fn handle_error( self, _: ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error, - ) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> { + ) -> ErrorBoundaryPropsBuilder<(__children, (ErrorHandler,))> { self } } #[allow(dead_code, non_camel_case_types, missing_docs)] impl< - 'a, - __handle_error: ErrorBoundaryPropsBuilder_Optional>, - __children: ErrorBoundaryPropsBuilder_Optional>, - > ErrorBoundaryPropsBuilder<'a, (__children, __handle_error)> + __handle_error: ErrorBoundaryPropsBuilder_Optional, + __children: ErrorBoundaryPropsBuilder_Optional, + > ErrorBoundaryPropsBuilder<(__children, __handle_error)> { - pub fn build(self) -> ErrorBoundaryProps<'a> { + pub fn build(self) -> ErrorBoundaryProps { let (children, handle_error) = self.fields; let children = ErrorBoundaryPropsBuilder_Optional::into_value(children, || { ::core::default::Default::default() @@ -453,7 +448,7 @@ impl< /// They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them. /// Error boundaries are quick to implement, but it can be useful to individually handle errors in your components to provide a better user experience when you know that an error is likely to occur. #[allow(non_upper_case_globals, non_snake_case)] -pub fn ErrorBoundary<'a>(cx: Scope<'a, ErrorBoundaryProps<'a>>) -> Element { +pub fn ErrorBoundary(cx: ErrorBoundaryProps) -> Element { let error_boundary = use_error_boundary(cx); match error_boundary.take_error() { Some(error) => cx.render((cx.props.handle_error.0)(error)), @@ -470,11 +465,8 @@ pub fn ErrorBoundary<'a>(cx: Scope<'a, ErrorBoundaryProps<'a>>) -> Element { stable_id: Default::default(), key: None, template: std::cell::Cell::new(TEMPLATE), - root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(), - dynamic_nodes: __cx.bump().alloc([{ - let ___nodes = (&cx.props.children).into_dyn_node(__cx); - ___nodes - }]), + root_ids: Vec::with_capacity(1usize).into(), + dynamic_nodes: vec![(&cx.props.children).into_dyn_node()], dynamic_attrs: __cx.bump().alloc([]), } }), diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 74e07c914..7e208ba35 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -133,12 +133,12 @@ impl std::fmt::Debug for Event { /// } /// /// ``` -pub struct EventHandler<'bump, T = ()> { +pub struct EventHandler { pub(crate) origin: ScopeId, - pub(super) callback: RefCell>>, + pub(super) callback: RefCell>>, } -impl Default for EventHandler<'_, T> { +impl Default for EventHandler { fn default() -> Self { Self { origin: ScopeId::ROOT, @@ -147,9 +147,9 @@ impl Default for EventHandler<'_, T> { } } -type ExternalListenerCallback<'bump, T> = Box; +type ExternalListenerCallback = Box; -impl EventHandler<'_, T> { +impl EventHandler< T> { /// Call this event handler with the appropriate event type /// /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic. diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index e4e1d8660..9f5ad7e16 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -26,8 +26,8 @@ use crate::innerlude::*; /// /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>(cx: Scope<'a, FragmentProps>) -> Element { - let children = cx.props.0.as_ref()?; +pub fn Fragment(cx: FragmentProps) -> Element { + let children = cx.0.as_ref()?; Some(VNode { key: children.key, parent: children.parent.clone(), @@ -39,7 +39,9 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps>) -> Element { }) } +#[derive(Clone, PartialEq)] pub struct FragmentProps(Element); + pub struct FragmentBuilder(Element); impl FragmentBuilder { pub fn children(self, children: Element) -> FragmentBuilder { @@ -92,7 +94,7 @@ impl FragmentBuilder { /// }) /// } /// ``` -impl<'a> Properties for FragmentProps { +impl Properties for FragmentProps { type Builder = FragmentBuilder; fn builder() -> Self::Builder { FragmentBuilder(None) diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 8690d9a31..feecc0590 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -11,7 +11,6 @@ mod dirty_scope; mod error_boundary; mod events; mod fragment; -mod lazynodes; mod mutations; mod nodes; mod properties; @@ -28,7 +27,6 @@ pub(crate) mod innerlude { pub use crate::error_boundary::*; pub use crate::events::*; pub use crate::fragment::*; - pub use crate::lazynodes::*; pub use crate::mutations::*; pub use crate::nodes::RenderReturn; pub use crate::nodes::*; @@ -71,15 +69,14 @@ pub(crate) mod innerlude { /// Example {} /// ) /// ``` - pub type Component

= fn(Scope

) -> Element; + pub type Component

= fn(P) -> Element; } pub use crate::innerlude::{ fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, - LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, - TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, - VirtualDom, + Mutation, Mutations, Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template, + TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types @@ -91,8 +88,7 @@ pub mod prelude { provide_context, provide_context_to_scope, provide_root_context, push_future, remove_future, schedule_update_any, spawn, spawn_forever, suspend, use_error_boundary, AnyValue, Component, Element, ErrorBoundary, Event, EventHandler, Fragment, - IntoAttributeValue, IntoDynNode, LazyNodes, Properties, Runtime, RuntimeGuard, Scope, - ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw, - VNode, VirtualDom, + IntoAttributeValue, IntoDynNode, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, + TaskId, Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom, }; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 692520b3e..67403b9ec 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,9 +1,5 @@ use crate::innerlude::{ElementRef, VNodeId}; -use crate::{ - any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState, -}; -use bumpalo::boxed::Box as BumpBox; -use bumpalo::Bump; +use crate::{any_props::AnyProps, arena::ElementId, Element, Event, ScopeId, ScopeState}; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, @@ -91,8 +87,8 @@ impl VNode { key: Option, template: Template<'static>, root_ids: Vec, - dynamic_nodes: &'a [DynamicNode], - dynamic_attrs: &'a [Attribute], + dynamic_nodes: Vec, + dynamic_attrs: Vec, ) -> Self { Self { key, @@ -113,7 +109,7 @@ impl VNode { /// Load a dynamic root at the given index /// /// Returns [`None`] if the root is actually a static node (Element/Text) - pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode> { + pub fn dynamic_root(&self, idx: usize) -> Option<&DynamicNode> { match &self.template.get().roots[idx] { TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None, TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => { @@ -297,7 +293,7 @@ pub enum TemplateNode { /// A node created at runtime /// /// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum DynamicNode { /// A component node /// @@ -322,7 +318,7 @@ pub enum DynamicNode { /// /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering /// or iterators. - Fragment(&'static [VNode]), + Fragment(Vec), } impl Default for DynamicNode { @@ -331,6 +327,7 @@ impl Default for DynamicNode { } } +#[derive(Clone)] /// An instance of a child component pub struct VComponent { /// The name of this component @@ -344,7 +341,7 @@ pub struct VComponent { /// It is possible that components get folded at compile time, so these shouldn't be really used as a key pub(crate) render_fn: *const (), - pub(crate) props: RefCell>>, + pub(crate) props: Box, } impl<'a> VComponent { @@ -662,89 +659,78 @@ impl AnyValue for T { } /// A trait that allows various items to be converted into a dynamic node for the rsx macro -pub trait IntoDynNode<'a, A = ()> { +pub trait IntoDynNode { /// Consume this item along with a scopestate and produce a DynamicNode /// /// You can use the bump alloactor of the scopestate to creat the dynamic node - fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode; + fn into_dyn_node(self) -> DynamicNode; } -impl<'a> IntoDynNode<'a> for () { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { +impl IntoDynNode for () { + fn into_dyn_node(self) -> DynamicNode { DynamicNode::default() } } -impl<'a> IntoDynNode<'a> for VNode { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { - DynamicNode::Fragment(_cx.bump().alloc([self])) +impl IntoDynNode for VNode { + fn into_dyn_node(self) -> DynamicNode { + DynamicNode::Fragment(vec![self]) } } -impl<'a> IntoDynNode<'a> for DynamicNode { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { +impl IntoDynNode for DynamicNode { + fn into_dyn_node(self) -> DynamicNode { self } } -impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { +impl IntoDynNode for Option { + fn into_dyn_node(self) -> DynamicNode { match self { - Some(val) => val.into_dyn_node(_cx), + Some(val) => val.into_dyn_node(), None => DynamicNode::default(), } } } -impl<'a> IntoDynNode<'a> for &Element { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { +impl IntoDynNode for &Element { + fn into_dyn_node(self) -> DynamicNode { match self.as_ref() { - Some(val) => val.clone().into_dyn_node(_cx), + Some(val) => val.clone().into_dyn_node(), _ => DynamicNode::default(), } } } -impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> { - fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode { - DynamicNode::Fragment(cx.bump().alloc([cx.render(self).unwrap()])) - } -} - -impl<'a, 'b> IntoDynNode<'b> for &'a str { - fn into_dyn_node(self, cx: &'b ScopeState) -> DynamicNode { +impl IntoDynNode for &str { + fn into_dyn_node(self) -> DynamicNode { DynamicNode::Text(VText { - value: cx.bump().alloc_str(self), + value: self.to_string(), id: Default::default(), }) } } -impl IntoDynNode<'_> for String { - fn into_dyn_node(self, cx: &ScopeState) -> DynamicNode { +impl IntoDynNode for String { + fn into_dyn_node(self) -> DynamicNode { DynamicNode::Text(VText { - value: cx.bump().alloc_str(&self), + value: self, id: Default::default(), }) } } -impl<'b> IntoDynNode<'b> for Arguments<'_> { - fn into_dyn_node(self, cx: &'b ScopeState) -> DynamicNode { - cx.text_node(self) +impl<'b> IntoDynNode for Arguments<'_> { + fn into_dyn_node(self) -> DynamicNode { + DynamicNode::Text(VText { + value: self.to_string(), + id: Default::default(), + }) } } -impl<'a> IntoDynNode<'a> for &'a VNode { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode { - DynamicNode::Fragment(_cx.bump().alloc([VNode { - parent: self.parent.clone(), - stable_id: self.stable_id.clone(), - template: self.template.clone(), - root_ids: self.root_ids.clone(), - key: self.key, - dynamic_nodes: self.dynamic_nodes, - dynamic_attrs: self.dynamic_attrs, - }])) +impl IntoDynNode for &VNode { + fn into_dyn_node(self) -> DynamicNode { + DynamicNode::Fragment(vec![self.clone()]) } } @@ -759,97 +745,88 @@ impl<'a> IntoVNode for VNode { impl<'a> IntoVNode for Element { fn into_vnode(self) -> VNode { match self { - Some(val) => val.into_vnode(cx), + Some(val) => val.into_vnode(), _ => VNode::empty().unwrap(), } } } -impl<'a, 'b> IntoVNode for LazyNodes<'a, 'b> { - fn into_vnode(self) -> VNode { - cx.render(self).unwrap() - } -} // Note that we're using the E as a generic but this is never crafted anyways. pub struct FromNodeIterator; -impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T +impl IntoDynNode for T where T: Iterator, I: IntoVNode, { - fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode { - let mut nodes = bumpalo::collections::Vec::new_in(cx.bump()); + fn into_dyn_node(self) -> DynamicNode { + let mut children: Vec<_> = self.into_iter().map(|node| node.into_vnode()).collect(); - nodes.extend(self.into_iter().map(|node| node.into_vnode(cx))); - - match nodes.into_bump_slice() { - children if children.is_empty() => DynamicNode::default(), - children => DynamicNode::Fragment(children), + if children.is_empty() { + DynamicNode::default() + } else { + DynamicNode::Fragment(children) } } } /// A value that can be converted into an attribute value -pub trait IntoAttributeValue<'a> { +pub trait IntoAttributeValue { /// Convert into an attribute value - fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>; + fn into_value(self) -> AttributeValue; } -impl<'a> IntoAttributeValue<'a> for AttributeValue<'a> { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { +impl<'a> IntoAttributeValue for AttributeValue { + fn into_value(self) -> AttributeValue { self } } -impl<'a> IntoAttributeValue<'a> for &'a str { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { +impl<'a> IntoAttributeValue for &'a str { + fn into_value(self) -> AttributeValue { + AttributeValue::Text(self.to_string()) + } +} + +impl<'a> IntoAttributeValue for String { + fn into_value(self) -> AttributeValue { AttributeValue::Text(self) } } -impl<'a> IntoAttributeValue<'a> for String { - fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Text(cx.alloc_str(&self)) - } -} - -impl<'a> IntoAttributeValue<'a> for f64 { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { +impl<'a> IntoAttributeValue for f64 { + fn into_value(self) -> AttributeValue { AttributeValue::Float(self) } } -impl<'a> IntoAttributeValue<'a> for i64 { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { +impl<'a> IntoAttributeValue for i64 { + fn into_value(self) -> AttributeValue { AttributeValue::Int(self) } } -impl<'a> IntoAttributeValue<'a> for bool { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { +impl<'a> IntoAttributeValue for bool { + fn into_value(self) -> AttributeValue { AttributeValue::Bool(self) } } -impl<'a> IntoAttributeValue<'a> for Arguments<'_> { - fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> { - use bumpalo::core_alloc::fmt::Write; - let mut str_buf = bumpalo::collections::String::new_in(bump); - str_buf.write_fmt(self).unwrap(); - AttributeValue::Text(str_buf.into_bump_str()) +impl<'a> IntoAttributeValue for Arguments<'_> { + fn into_value(self) -> AttributeValue { + AttributeValue::Text(self.to_string()) } } -impl<'a> IntoAttributeValue<'a> for BumpBox<'a, dyn AnyValue> { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { +impl IntoAttributeValue for Box { + fn into_value(self) -> AttributeValue { AttributeValue::Any(RefCell::new(Some(self))) } } -impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option { - fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> { +impl<'a, T: IntoAttributeValue> IntoAttributeValue for Option { + fn into_value(self) -> AttributeValue { match self { - Some(val) => val.into_value(bump), + Some(val) => val.into_value(), None => AttributeValue::None, } } diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs index ada5e58d5..7eb3ea85e 100644 --- a/packages/core/src/properties.rs +++ b/packages/core/src/properties.rs @@ -66,7 +66,7 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { +pub fn fc_to_builder(_: fn(T) -> Element) -> T::Builder { T::builder() } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index b8daeb7cb..3666a40a5 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -62,7 +62,7 @@ impl VirtualDom { let props: &dyn AnyProps = std::mem::transmute(props); let _span = tracing::trace_span!("render", scope = %scope.context().name); - props.render(scope).extend_lifetime() + props.render(scope) }; let scope = &self.scopes[scope_id.0]; @@ -93,7 +93,7 @@ impl VirtualDom { } // rebind the lifetime now that its stored internally - let result = unsafe { allocated.extend_lifetime_ref() }; + let result = unsafe { allocated }; self.runtime.scope_stack.borrow_mut().pop(); diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index ffdd895f5..980a85fe1 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,7 +1,6 @@ use crate::{ any_props::AnyProps, any_props::VProps, - innerlude::ErrorBoundary, innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText}, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, runtime::Runtime, @@ -17,40 +16,6 @@ use std::{ sync::Arc, }; -/// A wrapper around the [`Scoped`] object that contains a reference to the [`ScopeState`] and properties for a given -/// component. -/// -/// The [`Scope`] is your handle to the [`crate::VirtualDom`] and the component state. Every component is given its own -/// [`ScopeState`] and merged with its properties to create a [`Scoped`]. -/// -/// The [`Scope`] handle specifically exists to provide a stable reference to these items for the lifetime of the -/// component render. -pub type Scope<'a, T = ()> = &'a Scoped<'a, T>; - -// This ScopedType exists because we want to limit the amount of monomorphization that occurs when making inner -// state type generic over props. When the state is generic, it causes every method to be monomorphized for every -// instance of Scope in the codebase. -// -// -/// A wrapper around a component's [`ScopeState`] and properties. The [`ScopeState`] provides the majority of methods -/// for the VirtualDom and component state. -pub struct Scoped<'a, T = ()> { - /// The component's state and handle to the scheduler. - /// - /// Stores things like the custom bump arena, spawn functions, hooks, and the scheduler. - pub scope: &'a ScopeState, - - /// The component's properties. - pub props: &'a T, -} - -impl<'a, T> std::ops::Deref for Scoped<'a, T> { - type Target = &'a ScopeState; - fn deref(&self) -> &Self::Target { - &self.scope - } -} - /// A component's unique identifier. /// /// `ScopeId` is a `usize` that acts a key for the internal slab of Scopes. This means that the key is not unqiue across @@ -300,69 +265,17 @@ impl<'src> ScopeState { self.context().remove_future(id); } - /// Take a lazy [`crate::VNode`] structure and actually build it with the context of the efficient [`bumpalo::Bump`] allocator. - /// - /// ## Example - /// - /// ```ignore - /// fn Component(cx: Scope) -> Element { - /// // Lazy assemble the VNode tree - /// let lazy_nodes = rsx!("hello world"); - /// - /// // Actually build the tree and allocate it - /// cx.render(lazy_tree) - /// } - ///``` - pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element { - let element = rsx.call(self); - - let mut listeners = self.attributes_to_drop_before_render.borrow_mut(); - for attr in element.dynamic_attrs { - match attr.value { - // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped - AttributeValue::Listener(_) => { - let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; - listeners.push(unbounded); - } - // We need to drop any values manually to make sure that their drop implementation is called before the next render - AttributeValue::Any(_) => { - let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; - self.previous_frame().add_attribute_to_drop(unbounded); - } - - _ => (), - } - } - - let mut props = self.borrowed_props.borrow_mut(); - let mut drop_props = self - .previous_frame() - .props_to_drop_before_reset - .borrow_mut(); - for node in element.dynamic_nodes { - if let DynamicNode::Component(comp) = node { - let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) }; - if !comp.static_props { - props.push(unbounded); - } - drop_props.push(unbounded); - } - } - - Some(element) - } - /// Create a dynamic text node using [`Arguments`] and the [`ScopeState`]'s internal [`Bump`] allocator pub fn text_node(&'src self, args: Arguments) -> DynamicNode { DynamicNode::Text(VText { - value: self.raw_text(args), + value: args.to_string(), id: Default::default(), }) } /// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`] using the internal [`Bump`] allocator - pub fn make_node<'c, I>(&'src self, into: impl IntoDynNode<'src, I> + 'c) -> DynamicNode { - into.into_dyn_node(self) + pub fn make_node<'c, I>(&'src self, into: impl IntoDynNode + 'c) -> DynamicNode { + into.into_dyn_node() } /// Create a new [`Attribute`] from a name, value, namespace, and volatile bool @@ -372,7 +285,7 @@ impl<'src> ScopeState { pub fn attr( &'src self, name: &'static str, - value: impl IntoAttributeValue<'src>, + value: impl IntoAttributeValue, namespace: Option<&'static str>, volatile: bool, ) -> Attribute { @@ -381,7 +294,7 @@ impl<'src> ScopeState { namespace, volatile, mounted_element: Default::default(), - value: value.into_value(self.bump()), + value: value.into_value(), } } @@ -399,37 +312,35 @@ impl<'src> ScopeState { /// fn(Scope) -> Element; /// async fn(Scope>) -> Element; /// ``` - pub fn component<'child, P>( - &'src self, - component: fn(Scope<'child, P>) -> Element, + pub fn component

( + &self, + component: fn(P) -> Element, props: P, fn_name: &'static str, ) -> DynamicNode where // The properties must be valid until the next bump frame - P: Properties + 'src, - // The current bump allocator frame must outlive the child's borrowed props - 'src: 'child, + P: Properties, { let vcomp = VProps::new(component, P::memoize, props); // cast off the lifetime of the render return - let as_dyn: Box = Box::new(vcomp); - let extended: Box = unsafe { std::mem::transmute(as_dyn) }; + let as_dyn: Box = Box::new(vcomp); + let extended: Box = unsafe { std::mem::transmute(as_dyn) }; DynamicNode::Component(VComponent { name: fn_name, render_fn: component as *const (), - props: RefCell::new(Some(extended)), + props: extended, scope: Default::default(), }) } /// Create a new [`EventHandler`] from an [`FnMut`] - pub fn event_handler(&'src self, f: impl FnMut(T) + 'src) -> EventHandler<'src, T> { - let callback = RefCell::new(Some(Box::new(move |event: Event| { - f(event.data); - }))); + pub fn event_handler(&self, f: impl FnMut(T)) -> EventHandler { + let callback = RefCell::new(Some(Box::new(move |event: T| { + f(event); + }) as Box)); EventHandler { callback, origin: self.context().id, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index ec1f0dfe5..a66e4aa20 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -11,7 +11,7 @@ use crate::{ nodes::{Template, TemplateId}, runtime::{Runtime, RuntimeGuard}, scopes::{ScopeId, ScopeState}, - AttributeValue, Element, Event, Scope, VNode, + AttributeValue, Element, Event, VNode, }; use futures_util::{pin_mut, StreamExt}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -76,7 +76,7 @@ use std::{ /// } /// /// #[component] -/// fn Title<'a>(cx: Scope<'a>, children: Element<'a>) -> Element { +/// fn Title<'a>(cx: Scope<'a>, children: Element) -> Element { /// cx.render(rsx! { /// div { id: "title", children } /// }) @@ -224,7 +224,7 @@ impl VirtualDom { /// ``` /// /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it. - pub fn new(app: fn(Scope) -> Element) -> Self { + pub fn new(app: fn() -> Element) -> Self { Self::new_with_props(app, ()) } @@ -258,7 +258,7 @@ impl VirtualDom { /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" }); /// let mutations = dom.rebuild(); /// ``` - pub fn new_with_props(root: fn(Scope

) -> Element, root_props: P) -> Self { + pub fn new_with_props(root: fn(P) -> Element, root_props: P) -> Self { let (tx, rx) = futures_channel::mpsc::unbounded(); let scheduler = Scheduler::new(tx); let mut dom = Self { @@ -567,7 +567,7 @@ impl VirtualDom { /// ``` pub fn rebuild(&mut self) -> Mutations { let _runtime = RuntimeGuard::new(self.runtime.clone()); - match unsafe { self.run_scope(ScopeId::ROOT).extend_lifetime_ref() } { + match unsafe { self.run_scope(ScopeId::ROOT) } { // Rebuilding implies we append the created elements to the root RenderReturn::Ready(node) => { let m = self.create_scope(ScopeId::ROOT, node); diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 5e5005d3f..4bd232b98 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -142,7 +142,7 @@ fn create_components() { #[derive(Props)] struct ChildProps<'a> { - children: Element<'a>, + children: Element, } fn Child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element { diff --git a/packages/core/tests/create_passthru.rs b/packages/core/tests/create_passthru.rs index 4311cf1b1..01830f6ed 100644 --- a/packages/core/tests/create_passthru.rs +++ b/packages/core/tests/create_passthru.rs @@ -19,7 +19,7 @@ fn nested_passthru_creates() { } #[component] - fn PassThru<'a>(cx: Scope<'a>, children: Element<'a>) -> Element { + fn PassThru<'a>(cx: Scope<'a>, children: Element) -> Element { cx.render(rsx!(children)) } @@ -59,7 +59,7 @@ fn nested_passthru_creates_add() { } #[component] - fn ChildComp<'a>(cx: Scope, children: Element<'a>) -> Element { + fn ChildComp<'a>(cx: Scope, children: Element) -> Element { cx.render(rsx! { children }) } diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index 65d131eb8..02017307f 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -15,7 +15,7 @@ fn random_ns() -> Option<&'static str> { } } -fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute<'static> { +fn create_random_attribute(attr_idx: &mut usize) -> TemplateAttribute { match rand::random::() % 2 { 0 => TemplateAttribute::Static { name: Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), @@ -238,7 +238,7 @@ struct BorrowedDepthProps<'a> { inner: DepthProps, } -fn create_random_element_borrowed<'a>(cx: Scope<'a, BorrowedDepthProps<'a>>) -> Element<'a> { +fn create_random_element_borrowed<'a>(cx: Scope<'a, BorrowedDepthProps<'a>>) -> Element { println!("{}", cx.props.borrow); let bump = cx.bump(); let allocated = bump.alloc(Scoped { scope: cx, props: &cx.props.inner }); From c70e2bfcb6b0421127b1584a5d46c4232e41e039 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 5 Jan 2024 09:32:50 -0600 Subject: [PATCH 036/377] move diffing into the global runtime --- packages/core/src/any_props.rs | 50 +++++++++++++--- packages/core/src/arena.rs | 74 +---------------------- packages/core/src/diff.rs | 7 +-- packages/core/src/nodes.rs | 11 ++-- packages/core/src/scope_arena.rs | 52 +++++----------- packages/core/src/scope_context.rs | 67 +++++++++++++++++++++ packages/core/src/scopes.rs | 95 +++++------------------------- packages/core/src/virtual_dom.rs | 6 +- 8 files changed, 152 insertions(+), 210 deletions(-) diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index 420bf9d32..ff9331942 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -1,14 +1,40 @@ -use crate::{nodes::RenderReturn, scopes::ScopeState, Element}; -use std::panic::AssertUnwindSafe; +use crate::{nodes::RenderReturn, Element}; +use std::{ops::Deref, panic::AssertUnwindSafe}; + +/// A boxed version of AnyProps that can be cloned +pub(crate) struct BoxedAnyProps { + inner: Box, +} + +impl BoxedAnyProps { + fn new(inner: impl AnyProps + 'static) -> Self { + Self { + inner: Box::new(inner), + } + } +} + +impl Deref for BoxedAnyProps { + type Target = dyn AnyProps; + + fn deref(&self) -> &Self::Target { + &*self.inner + } +} + +impl Clone for BoxedAnyProps { + fn clone(&self) -> Self { + Self { + inner: self.inner.duplicate(), + } + } +} /// A trait that essentially allows VComponentProps to be used generically -/// -/// # Safety -/// -/// This should not be implemented outside this module pub(crate) trait AnyProps { - fn render<'a>(&'a self, bump: &'a ScopeState) -> RenderReturn; + fn render<'a>(&'a self) -> RenderReturn; fn memoize(&self, other: &dyn AnyProps) -> bool; + fn duplicate(&self) -> Box; } pub(crate) struct VProps

{ @@ -36,7 +62,7 @@ impl AnyProps for VProps

{ (self.memo)(self, other) } - fn render(&self, cx: &ScopeState) -> RenderReturn { + fn render(&self) -> RenderReturn { let res = std::panic::catch_unwind(AssertUnwindSafe(move || { // Call the render function directly (self.render_fn)(self.props.clone()) @@ -52,4 +78,12 @@ impl AnyProps for VProps

{ } } } + + fn duplicate(&self) -> Box { + Box::new(Self { + render_fn: self.render_fn, + memo: self.memo, + props: self.props.clone(), + }) + } } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 205060b32..7ad3986ec 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,8 +1,8 @@ use std::ptr::NonNull; use crate::{ - innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, - AttributeValue, DynamicNode, ScopeId, + innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, + ScopeId, }; /// An Element's unique identifier. @@ -48,14 +48,6 @@ impl VirtualDom { std::mem::transmute::, _>(vnode.into()) }))); - // Set this id to be dropped when the scope is rerun - if let Some(scope) = self.runtime.current_scope_id() { - self.scopes[scope.0] - .element_refs_to_drop - .borrow_mut() - .push(new_id); - } - new_id } @@ -89,17 +81,6 @@ impl VirtualDom { id, }); - // Remove all VNode ids from the scope - for id in self.scopes[id.0] - .element_refs_to_drop - .borrow_mut() - .drain(..) - { - self.element_refs.try_remove(id.0); - } - - self.ensure_drop_safety(id); - if recursive { if let Some(root) = self.scopes[id.0].try_root_node() { if let RenderReturn::Ready(node) = root { @@ -110,18 +91,6 @@ impl VirtualDom { let scope = &mut self.scopes[id.0]; - // Drop all the hooks once the children are dropped - // this means we'll drop hooks bottom-up - scope.hooks.get_mut().clear(); - { - let context = scope.context(); - - // Drop all the futures once the hooks are dropped - for task_id in context.spawned_tasks.borrow_mut().drain() { - context.tasks.remove(task_id); - } - } - self.scopes.remove(id.0); } @@ -139,45 +108,6 @@ impl VirtualDom { DynamicNode::Text(_) => {} }); } - - /// Descend through the tree, removing any borrowed props and listeners - pub(crate) fn ensure_drop_safety(&mut self, scope_id: ScopeId) { - let scope = &self.scopes[scope_id.0]; - - { - // Drop all element refs that could be invalidated when the component was rerun - let mut element_refs = self.scopes[scope_id.0].element_refs_to_drop.borrow_mut(); - let element_refs_slab = &mut self.element_refs; - for element_ref in element_refs.drain(..) { - if let Some(element_ref) = element_refs_slab.get_mut(element_ref.0) { - *element_ref = None; - } - } - } - - // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we - // run the hooks (which hold an &mut Reference) - // recursively call ensure_drop_safety on all children - let props = { scope.borrowed_props.borrow_mut().clone() }; - for comp in props { - let comp = unsafe { &*comp }; - match comp.scope.get() { - Some(child) if child != scope_id => self.ensure_drop_safety(child), - _ => (), - } - } - let scope = &self.scopes[scope_id.0]; - scope.borrowed_props.borrow_mut().clear(); - - // Now that all the references are gone, we can safely drop our own references in our listeners. - let mut listeners = scope.attributes_to_drop_before_render.borrow_mut(); - listeners.drain(..).for_each(|listener| { - let listener = unsafe { &*listener }; - if let AttributeValue::Listener(l) = &listener.value { - _ = l.take(); - } - }); - } } impl ElementPath { diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index fefb9512e..924536ef0 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -219,13 +219,12 @@ impl VirtualDom { // copy out the box for both let old_scope = &self.scopes[scope_id.0]; let old = old_scope.props.as_ref(); - let new: Box = right.props.take().unwrap(); - let new: Box = unsafe { std::mem::transmute(new) }; + let new: &dyn AnyProps = right.props.as_ref(); // If the props are static, then we try to memoize by setting the new with the old // The target scopestate still has the reference to the old props, so there's no need to update anything // This also implicitly drops the new props since they're not used - if left.static_props && unsafe { old.as_ref().unwrap().memoize(new.as_ref()) } { + if old.memoize(new) { tracing::trace!( "Memoized props for component {:#?} ({})", scope_id, @@ -235,7 +234,7 @@ impl VirtualDom { } // First, move over the props from the old to the new, dropping old props in the process - self.scopes[scope_id.0].props = Some(new); + self.scopes[scope_id.0].props = new; // Now run the component and diff it self.run_scope(scope_id); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 67403b9ec..767b81da3 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -341,7 +341,7 @@ pub struct VComponent { /// It is possible that components get folded at compile time, so these shouldn't be really used as a key pub(crate) render_fn: *const (), - pub(crate) props: Box, + pub(crate) props: BoxedAnyProps, } impl<'a> VComponent { @@ -362,7 +362,7 @@ impl<'a> std::fmt::Debug for VComponent { } /// An instance of some text, mounted to the DOM -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct VText { /// The actual text itself pub value: String, @@ -436,7 +436,7 @@ pub enum TemplateAttribute { } /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Attribute { /// The name of the attribute. pub name: &'static str, @@ -483,6 +483,7 @@ impl Attribute { /// /// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`] /// variant. +#[derive(Clone)] pub enum AttributeValue { /// Text attribute Text(String), @@ -497,10 +498,10 @@ pub enum AttributeValue { Bool(bool), /// A listener, like "onclick" - Listener(RefCell>), + Listener(ListenerCb), /// An arbitrary value that implements PartialEq and is static - Any(RefCell>>), + Any(Box), /// A "none" value, resulting in the removal of an attribute from the dom None, diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 3666a40a5..694f9811d 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -24,15 +24,8 @@ impl VirtualDom { runtime: self.runtime.clone(), context_id: id, - props: Some(props), - - render_cnt: Default::default(), - hooks: Default::default(), - hook_idx: Default::default(), - - borrowed_props: Default::default(), - attributes_to_drop_before_render: Default::default(), - element_refs_to_drop: Default::default(), + props, + last_rendered_node: Default::default(), })); let context = @@ -42,42 +35,30 @@ impl VirtualDom { scope } - pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn { + pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn { self.runtime.scope_stack.borrow_mut().push(scope_id); - // Cycle to the next frame and then reset it - // This breaks any latent references, invalidating every pointer referencing into it. - // Remove all the outdated listeners - self.ensure_drop_safety(scope_id); let new_nodes = unsafe { let scope = &self.scopes[scope_id.0]; - scope.previous_frame().reset(); - scope.context().suspended.set(false); - - scope.hook_idx.set(0); + let context = scope.context(); + context.suspended.set(false); + context.hook_index.set(0); // safety: due to how we traverse the tree, we know that the scope is not currently aliased - let props: &dyn AnyProps = scope.props.as_ref().unwrap().as_ref(); - let props: &dyn AnyProps = std::mem::transmute(props); + let props: &dyn AnyProps = &*scope.props; let _span = tracing::trace_span!("render", scope = %scope.context().name); - props.render(scope) + props.render() }; - let scope = &self.scopes[scope_id.0]; - - // We write on top of the previous frame and then make it the current by pushing the generation forward - let frame = scope.previous_frame(); - - // set the new head of the bump frame - let allocated = &*frame.bump().alloc(new_nodes); - frame.node.set(allocated); - - // And move the render generation forward by one - scope.render_cnt.set(scope.render_cnt.get() + 1); + let scope = &mut self.scopes[scope_id.0]; let context = scope.context(); + + // And move the render generation forward by one + context.render_count.set(context.render_count.get() + 1); + // remove this scope from dirty scopes self.dirty_scopes.remove(&DirtyScope { height: context.height, @@ -85,18 +66,15 @@ impl VirtualDom { }); if context.suspended.get() { - if matches!(allocated, RenderReturn::Aborted(_)) { + if matches!(new_nodes, RenderReturn::Aborted(_)) { self.suspended_scopes.insert(context.id); } } else if !self.suspended_scopes.is_empty() { _ = self.suspended_scopes.remove(&context.id); } - // rebind the lifetime now that its stored internally - let result = unsafe { allocated }; - self.runtime.scope_stack.borrow_mut().pop(); - result + new_nodes } } diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index e06fe5eba..d1cece18c 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -22,10 +22,15 @@ pub(crate) struct ScopeContext { pub(crate) parent_id: Option, pub(crate) height: u32, + pub(crate) render_count: Cell, + pub(crate) suspended: Cell, pub(crate) shared_contexts: RefCell>>, + pub(crate) hooks: RefCell>>, + pub(crate) hook_index: Cell, + pub(crate) tasks: Rc, pub(crate) spawned_tasks: RefCell>, } @@ -43,10 +48,13 @@ impl ScopeContext { id, parent_id, height, + render_count: Cell::new(0), suspended: Cell::new(false), shared_contexts: RefCell::new(vec![]), tasks, spawned_tasks: RefCell::new(FxHashSet::default()), + hooks: RefCell::new(vec![]), + hook_index: Cell::new(0), } } @@ -244,6 +252,65 @@ impl ScopeContext { self.suspended.set(true); None } + + /// Store a value between renders. The foundational hook for all other hooks. + /// + /// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`. + /// + /// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait + /// + /// # Example + /// + /// ``` + /// use dioxus_core::ScopeState; + /// + /// // prints a greeting on the initial render + /// pub fn use_hello_world(cx: &ScopeState) { + /// cx.use_hook(|| println!("Hello, world!")); + /// } + /// ``` + #[allow(clippy::mut_from_ref)] + pub fn use_hook(&self, initializer: impl FnOnce() -> State) -> &mut State { + let cur_hook = self.hook_index.get(); + let mut hooks = self.hooks.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks."); + + if cur_hook >= hooks.len() { + hooks.push(Box::new(initializer())); + } + + hooks + .get(cur_hook) + .and_then(|inn| { + self.hook_index.set(cur_hook + 1); + let raw_ref: &mut dyn Any = inn.as_mut(); + raw_ref.downcast_mut::() + }) + .expect( + r#" + Unable to retrieve the hook that was initialized at this index. + Consult the `rules of hooks` to understand how to use hooks properly. + + You likely used the hook in a conditional. Hooks rely on consistent ordering between renders. + Functions prefixed with "use" should never be called conditionally. + "#, + ) + } + + /// Get the current render since the inception of this component + /// + /// This can be used as a helpful diagnostic when debugging hooks/renders, etc + pub fn generation(&self) -> usize { + self.render_count.get() + } +} + +impl Drop for ScopeContext { + fn drop(&mut self) { + // Drop all spawned tasks + for id in self.spawned_tasks.borrow().iter() { + self.tasks.remove(*id); + } + } } /// Schedule an update for any component given its [`ScopeId`]. diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 980a85fe1..e26f38ca9 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,7 +1,7 @@ use crate::{ any_props::AnyProps, any_props::VProps, - innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText}, + innerlude::{DynamicNode, EventHandler, VComponent, VText}, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, runtime::Runtime, scope_context::ScopeContext, @@ -9,7 +9,7 @@ use crate::{ }; use std::{ any::Any, - cell::{Cell, Ref, RefCell, UnsafeCell}, + cell::{Ref, RefCell}, fmt::{Arguments, Debug}, future::Future, rc::Rc, @@ -47,16 +47,9 @@ pub struct ScopeState { pub(crate) runtime: Rc, pub(crate) context_id: ScopeId, - pub(crate) render_cnt: Cell, + pub(crate) last_rendered_node: Option, - pub(crate) hooks: RefCell>>>, - pub(crate) hook_idx: Cell, - - pub(crate) borrowed_props: RefCell>, - pub(crate) element_refs_to_drop: RefCell>, - pub(crate) attributes_to_drop_before_render: RefCell>, - - pub(crate) props: Option>, + pub(crate) props: Box, } impl Drop for ScopeState { @@ -75,13 +68,6 @@ impl<'src> ScopeState { self.context().name } - /// Get the current render since the inception of this component - /// - /// This can be used as a helpful diagnostic when debugging hooks/renders, etc - pub fn generation(&self) -> usize { - self.render_cnt.get() - } - /// Get a handle to the currently active head node arena for this Scope /// /// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR. @@ -98,15 +84,7 @@ impl<'src> ScopeState { /// /// Returns [`None`] if the tree has not been built yet. pub fn try_root_node(&self) -> Option<&RenderReturn> { - let ptr = self.current_frame().node.get(); - - if ptr.is_null() { - return None; - } - - let r: &RenderReturn = unsafe { &*ptr }; - - unsafe { std::mem::transmute(r) } + self.last_rendered_node.as_ref() } /// Get the height of this Scope - IE the number of scopes above it. @@ -354,21 +332,19 @@ impl<'src> ScopeState { &'src self, mut callback: impl FnMut(Event) + 'src, ) -> AttributeValue { - AttributeValue::Listener(RefCell::new(Some(Box::new( - move |event: Event| { - if let Ok(data) = event.data.downcast::() { - callback(Event { - propagates: event.propagates, - data, - }); - } - }, - )))) + AttributeValue::Listener(Box::new(move |event: Event| { + if let Ok(data) = event.data.downcast::() { + callback(Event { + propagates: event.propagates, + data, + }); + } + })) } /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`] pub fn any_value(&'src self, value: T) -> AttributeValue { - AttributeValue::Any(RefCell::new(Some(Box::new(value)))) + AttributeValue::Any(Box::new(value)) } /// Mark this component as suspended and then return None @@ -377,47 +353,4 @@ impl<'src> ScopeState { cx.suspend(); None } - - /// Store a value between renders. The foundational hook for all other hooks. - /// - /// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`. - /// - /// When the component is unmounted (removed from the UI), the value is dropped. This means you can return a custom type and provide cleanup code by implementing the [`Drop`] trait - /// - /// # Example - /// - /// ``` - /// use dioxus_core::ScopeState; - /// - /// // prints a greeting on the initial render - /// pub fn use_hello_world(cx: &ScopeState) { - /// cx.use_hook(|| println!("Hello, world!")); - /// } - /// ``` - #[allow(clippy::mut_from_ref)] - pub fn use_hook(&self, initializer: impl FnOnce() -> State) -> &mut State { - let cur_hook = self.hook_idx.get(); - let mut hooks = self.hooks.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks."); - - if cur_hook >= hooks.len() { - hooks.push(Box::new(UnsafeCell::new(initializer()))); - } - - hooks - .get(cur_hook) - .and_then(|inn| { - self.hook_idx.set(cur_hook + 1); - let raw_ref = unsafe { &mut *inn.get() }; - raw_ref.downcast_mut::() - }) - .expect( - r#" - Unable to retrieve the hook that was initialized at this index. - Consult the `rules of hooks` to understand how to use hooks properly. - - You likely used the hook in a conditional. Hooks rely on consistent ordering between renders. - Functions prefixed with "use" should never be called conditionally. - "#, - ) - } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a66e4aa20..5c8246355 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -224,7 +224,7 @@ impl VirtualDom { /// ``` /// /// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it. - pub fn new(app: fn() -> Element) -> Self { + pub fn new(app: fn(()) -> Element) -> Self { Self::new_with_props(app, ()) } @@ -258,7 +258,7 @@ impl VirtualDom { /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" }); /// let mutations = dom.rebuild(); /// ``` - pub fn new_with_props(root: fn(P) -> Element, root_props: P) -> Self { + pub fn new_with_props(root: fn(P) -> Element, root_props: P) -> Self { let (tx, rx) = futures_channel::mpsc::unbounded(); let scheduler = Scheduler::new(tx); let mut dom = Self { @@ -450,7 +450,7 @@ impl VirtualDom { let origin = path.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); - if let Some(cb) = listener.borrow_mut().as_deref_mut() { + if let Some(cb) = listener.as_deref_mut() { cb(uievent.clone()); } self.runtime.scope_stack.borrow_mut().pop(); From 0c76770da078bee98b69565630c906fcb700eaa6 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 5 Jan 2024 10:57:05 -0600 Subject: [PATCH 037/377] remove a lot of unsafe --- packages/core/src/any_props.rs | 34 ++++-- packages/core/src/arena.rs | 25 +---- packages/core/src/create.rs | 55 ++++------ packages/core/src/diff.rs | 121 ++++++++------------- packages/core/src/error_boundary.rs | 54 +++++----- packages/core/src/fragment.rs | 11 +- packages/core/src/lib.rs | 8 +- packages/core/src/mutations.rs | 10 +- packages/core/src/nodes.rs | 160 ++++++++-------------------- packages/core/src/scope_arena.rs | 8 +- packages/core/src/scopes.rs | 17 ++- packages/core/src/virtual_dom.rs | 61 ++++------- 12 files changed, 201 insertions(+), 363 deletions(-) diff --git a/packages/core/src/any_props.rs b/packages/core/src/any_props.rs index ff9331942..52a235972 100644 --- a/packages/core/src/any_props.rs +++ b/packages/core/src/any_props.rs @@ -1,5 +1,5 @@ use crate::{nodes::RenderReturn, Element}; -use std::{ops::Deref, panic::AssertUnwindSafe}; +use std::{any::Any, ops::Deref, panic::AssertUnwindSafe}; /// A boxed version of AnyProps that can be cloned pub(crate) struct BoxedAnyProps { @@ -7,7 +7,7 @@ pub(crate) struct BoxedAnyProps { } impl BoxedAnyProps { - fn new(inner: impl AnyProps + 'static) -> Self { + pub fn new(inner: impl AnyProps + 'static) -> Self { Self { inner: Box::new(inner), } @@ -33,7 +33,8 @@ impl Clone for BoxedAnyProps { /// A trait that essentially allows VComponentProps to be used generically pub(crate) trait AnyProps { fn render<'a>(&'a self) -> RenderReturn; - fn memoize(&self, other: &dyn AnyProps) -> bool; + fn memoize(&self, other: &dyn Any) -> bool; + fn props(&self) -> &dyn Any; fn duplicate(&self) -> Box; } @@ -41,25 +42,35 @@ pub(crate) struct VProps

{ pub render_fn: fn(P) -> Element, pub memo: fn(&P, &P) -> bool, pub props: P, + pub name: &'static str, } impl

VProps

{ - pub(crate) fn new(render_fn: fn(P) -> Element, memo: fn(&P, &P) -> bool, props: P) -> Self { + pub(crate) fn new( + render_fn: fn(P) -> Element, + memo: fn(&P, &P) -> bool, + props: P, + name: &'static str, + ) -> Self { Self { render_fn, memo, props, + name, } } } impl AnyProps for VProps

{ - // Safety: - // this will downcast the other ptr as our swallowed type! - // you *must* make this check *before* calling this method - // if your functions are not the same, then you will downcast a pointer into a different type (UB) - fn memoize(&self, other: &dyn AnyProps) -> bool { - (self.memo)(self, other) + fn memoize(&self, other: &dyn Any) -> bool { + match other.downcast_ref::() { + Some(other) => (self.memo)(&self.props, &other.props), + None => false, + } + } + + fn props(&self) -> &dyn Any { + &self.props } fn render(&self) -> RenderReturn { @@ -72,7 +83,7 @@ impl AnyProps for VProps

{ Ok(Some(e)) => RenderReturn::Ready(e), Ok(None) => RenderReturn::default(), Err(err) => { - let component_name = cx.name(); + let component_name = self.name; tracing::error!("Error while rendering component `{component_name}`: {err:?}"); RenderReturn::default() } @@ -84,6 +95,7 @@ impl AnyProps for VProps

{ render_fn: self.render_fn, memo: self.memo, props: self.props.clone(), + name: self.name, }) } } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 7ad3986ec..3aa82712f 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,5 +1,3 @@ -use std::ptr::NonNull; - use crate::{ innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, ScopeId, @@ -21,16 +19,16 @@ pub struct ElementId(pub usize); #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct VNodeId(pub usize); -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct ElementRef { // the pathway of the real element inside the template pub(crate) path: ElementPath, - // The actual template - pub(crate) template: VNodeId, - - // The scope the element belongs to + // the scope that this element belongs to pub(crate) scope: ScopeId, + + // The actual element + pub(crate) element: VNode, } #[derive(Clone, Copy, Debug)] @@ -43,14 +41,6 @@ impl VirtualDom { ElementId(self.elements.insert(None)) } - pub(crate) fn next_vnode_ref(&mut self, vnode: &VNode) -> VNodeId { - let new_id = VNodeId(self.element_refs.insert(Some(unsafe { - std::mem::transmute::, _>(vnode.into()) - }))); - - new_id - } - pub(crate) fn reclaim(&mut self, el: ElementId) { self.try_reclaim(el) .unwrap_or_else(|| panic!("cannot reclaim {:?}", el)); @@ -67,11 +57,6 @@ impl VirtualDom { self.elements.try_remove(el.0).map(|_| ()) } - pub(crate) fn set_template(&mut self, id: VNodeId, vnode: &VNode) { - self.element_refs[id.0] = - Some(unsafe { std::mem::transmute::, _>(vnode.into()) }); - } - // Drop a scope and all its children // // Note: This will not remove any ids from the arena diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index c4390c212..64196a969 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,7 +1,4 @@ -use crate::any_props::AnyProps; -use crate::innerlude::{ - BorrowedAttributeValue, ElementPath, ElementRef, VComponent, VPlaceholder, VText, -}; +use crate::innerlude::{ElementPath, ElementRef, VComponent, VPlaceholder, VText}; use crate::mutations::Mutation; use crate::mutations::Mutation::*; use crate::nodes::VNode; @@ -96,9 +93,6 @@ impl VirtualDom { nodes_mut.resize(len, ElementId::default()); }; - // Set this node id - node.stable_id.set(Some(self.next_vnode_ref(node))); - // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways self.register_template(node.template.get()); @@ -190,7 +184,7 @@ impl VirtualDom { path: ElementPath { path: template.template.get().node_paths[idx], }, - template: template.stable_id().unwrap(), + element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; self.create_dynamic_node(template_ref, node) @@ -200,10 +194,10 @@ impl VirtualDom { path: ElementPath { path: template.template.get().node_paths[idx], }, - template: template.stable_id().unwrap(), + element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; - parent.set(Some(template_ref)); + *parent.borrow_mut() = Some(template_ref); let id = self.set_slot(id); self.mutations.push(CreatePlaceholder { id }); 1 @@ -217,12 +211,7 @@ impl VirtualDom { } fn create_static_text(&mut self, value: &str, id: ElementId) { - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let unbounded_text: &str = unsafe { std::mem::transmute(value) }; - self.mutations.push(CreateTextNode { - value: unbounded_text, - id, - }); + self.mutations.push(CreateTextNode { value, id }); } /// We write all the descendent data for this element @@ -289,7 +278,7 @@ impl VirtualDom { path: ElementPath { path: template.template.get().node_paths[idx], }, - template: template.stable_id().unwrap(), + element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[idx]); @@ -336,14 +325,14 @@ impl VirtualDom { attribute.mounted_element.set(id); // Safety: we promise not to re-alias this text later on after committing it to the mutation - let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) }; + let unbounded_name: &str = &attribute.name; match &attribute.value { AttributeValue::Listener(_) => { let path = &template.template.get().attr_paths[idx]; let element_ref = ElementRef { path: ElementPath { path }, - template: template.stable_id().unwrap(), + element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; self.elements[id.0] = Some(element_ref); @@ -354,9 +343,7 @@ impl VirtualDom { }) } _ => { - // Safety: we promise not to re-alias this text later on after committing it to the mutation - let value: BorrowedAttributeValue = (&attribute.value).into(); - let unbounded_value = unsafe { std::mem::transmute(value) }; + let unbounded_value = &attribute.value; self.mutations.push(SetAttribute { name: unbounded_name, @@ -516,7 +503,7 @@ impl VirtualDom { placeholder.id.set(Some(id)); // Assign the placeholder's parent - placeholder.parent.set(Some(parent)); + *placeholder.parent.borrow_mut() = Some(parent); // Assign the ID to the existing node in the template self.mutations.push(AssignId { @@ -540,7 +527,11 @@ impl VirtualDom { component.scope.set(Some(scope)); - match self.run_scope(scope) { + let new = self.run_scope(scope); + + self.scopes[scope.0].last_rendered_node = Some(new.clone()); + + match &new { // Create the component's root element Ready(t) => { self.assign_boundary_ref(parent, t); @@ -550,22 +541,20 @@ impl VirtualDom { } } - /// Load a scope from a vcomponent. If the props don't exist, that means the component is currently "live" + /// Load a scope from a vcomponent. If the scope id doesn't exist, that means the component is currently "live" fn load_scope_from_vcomponent(&mut self, component: &VComponent) -> ScopeId { - component - .props - .map(|props| { - let unbounded_props: Box = unsafe { std::mem::transmute(props) }; - self.new_scope(unbounded_props, component.name).context().id - }) - .unwrap_or_else(|| component.scope.get().unwrap()) + component.scope.get().unwrap_or_else(|| { + self.new_scope(component.props.clone(), component.name) + .context() + .id + }) } fn mount_aborted(&mut self, placeholder: &VPlaceholder, parent: Option) -> usize { let id = self.next_element(); self.mutations.push(Mutation::CreatePlaceholder { id }); placeholder.id.set(Some(id)); - placeholder.parent.set(parent); + *placeholder.parent.borrow_mut() = parent; 1 } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 924536ef0..90f2d3d79 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -1,10 +1,9 @@ +use std::ops::Deref; + use crate::{ any_props::AnyProps, arena::ElementId, - innerlude::{ - BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, - VText, - }, + innerlude::{DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText}, mutations::Mutation, nodes::RenderReturn, nodes::{DynamicNode, VNode}, @@ -17,52 +16,43 @@ use rustc_hash::{FxHashMap, FxHashSet}; use DynamicNode::*; impl VirtualDom { - pub(super) fn diff_scope(&mut self, scope: ScopeId) { + pub(super) fn diff_scope(&mut self, scope: ScopeId, new_nodes: RenderReturn) { self.runtime.scope_stack.borrow_mut().push(scope); - let scope_state = &mut self.get_scope(scope).unwrap(); - unsafe { - // Load the old and new bump arenas - let old = scope_state - .previous_frame() - .try_load_node() - .expect("Call rebuild before diffing"); + let scope_state = &mut self.scopes[scope.0]; + // Load the old and new bump arenas + let old = std::mem::replace(&mut scope_state.last_rendered_node, Some(new_nodes)).unwrap(); + let new = scope_state.last_rendered_node.as_ref().unwrap(); - let new = scope_state - .current_frame() - .try_load_node() - .expect("Call rebuild before diffing"); + use RenderReturn::{Aborted, Ready}; - use RenderReturn::{Aborted, Ready}; + match (&old, new) { + // Normal pathway + (Ready(l), Ready(r)) => self.diff_node(l, r), - match (old, new) { - // Normal pathway - (Ready(l), Ready(r)) => self.diff_node(l, r), + // Unwind the mutations if need be + (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p), - // Unwind the mutations if need be - (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p), + // Just move over the placeholder + (Aborted(l), Aborted(r)) => { + r.id.set(l.id.get()); + *r.parent.borrow_mut() = l.parent.borrow().clone(); + } - // Just move over the placeholder - (Aborted(l), Aborted(r)) => { - r.id.set(l.id.get()); - r.parent.set(l.parent.get()) - } - - // Placeholder becomes something - // We should also clear the error now - (Aborted(l), Ready(r)) => self.replace_placeholder( - l, - [r], - l.parent.get().expect("root node should not be none"), - ), - }; - } + // Placeholder becomes something + // We should also clear the error now + (Aborted(l), Ready(r)) => self.replace_placeholder( + l, + [r], + l.parent.borrow().expect("root node should not be none"), + ), + }; self.runtime.scope_stack.borrow_mut().pop(); } fn diff_ok_to_err(&mut self, l: &VNode, p: &VPlaceholder) { let id = self.next_element(); p.id.set(Some(id)); - p.parent.set(l.parent.get()); + *p.parent.borrow_mut() = l.parent.borrow().clone(); self.mutations.push(Mutation::CreatePlaceholder { id }); let pre_edits = self.mutations.edits.len(); @@ -101,13 +91,7 @@ impl VirtualDom { // Copy over the parent { - right_template.parent.set(left_template.parent.get()); - } - - // Update the bubble id pointer - right_template.stable_id.set(left_template.stable_id.get()); - if let Some(bubble_id) = right_template.stable_id.get() { - self.set_template(bubble_id, right_template); + *right_template.parent.borrow_mut() = left_template.parent.borrow().clone(); } // If the templates are the same, we don't need to do anything, nor do we want to @@ -145,7 +129,7 @@ impl VirtualDom { .enumerate() .for_each(|(dyn_node_idx, (left_node, right_node))| { let current_ref = ElementRef { - template: right_template.stable_id().unwrap(), + element: right_template.clone(), path: ElementPath { path: left_template.template.get().node_paths[dyn_node_idx], }, @@ -175,19 +159,18 @@ impl VirtualDom { (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent), (Placeholder(left), Placeholder(right)) => { right.id.set(left.id.get()); - right.parent.set(left.parent.get()); + *right.parent.borrow_mut() = left.parent.borrow().clone(); }, (Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent)), - (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right, parent), + (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right, parent), (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent), _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."), }; } fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) { - let name = unsafe { std::mem::transmute(left_attr.name) }; - let value: BorrowedAttributeValue = (&right_attr.value).into(); - let value = unsafe { std::mem::transmute(value) }; + let name = &left_attr.name; + let value = &right_attr.value; self.mutations.push(Mutation::SetAttribute { id: left_attr.mounted_element.get(), ns: right_attr.namespace, @@ -218,13 +201,13 @@ impl VirtualDom { // copy out the box for both let old_scope = &self.scopes[scope_id.0]; - let old = old_scope.props.as_ref(); - let new: &dyn AnyProps = right.props.as_ref(); + let old = old_scope.props.deref(); + let new: &dyn AnyProps = right.props.deref(); // If the props are static, then we try to memoize by setting the new with the old // The target scopestate still has the reference to the old props, so there's no need to update anything // This also implicitly drops the new props since they're not used - if old.memoize(new) { + if old.memoize(new.props()) { tracing::trace!( "Memoized props for component {:#?} ({})", scope_id, @@ -234,11 +217,11 @@ impl VirtualDom { } // First, move over the props from the old to the new, dropping old props in the process - self.scopes[scope_id.0].props = new; + self.scopes[scope_id.0].props = right.props.clone(); // Now run the component and diff it - self.run_scope(scope_id); - self.diff_scope(scope_id); + let new = self.run_scope(scope_id); + self.diff_scope(scope_id, new); self.dirty_scopes.remove(&DirtyScope { height: self.runtime.get_context(scope_id).unwrap().height, @@ -325,8 +308,10 @@ impl VirtualDom { right.id.set(Some(id)); if left.value != right.value { - let value = unsafe { std::mem::transmute(right.value) }; - self.mutations.push(Mutation::SetText { id, value }); + self.mutations.push(Mutation::SetText { + id, + value: &right.value, + }); } } @@ -824,7 +809,7 @@ impl VirtualDom { let placeholder = self.next_element(); r.id.set(Some(placeholder)); - r.parent.set(Some(parent)); + r.parent.borrow_mut().replace(parent); self.mutations .push(Mutation::CreatePlaceholder { id: placeholder }); @@ -862,16 +847,6 @@ impl VirtualDom { // Clean up the roots, assuming we need to generate mutations for these // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim) self.reclaim_roots(node, gen_muts); - - // Clean up the vnode id - self.reclaim_vnode_id(node); - } - - fn reclaim_vnode_id(&mut self, node: &VNode) { - // Clean up the vnode id - if let Some(id) = node.stable_id() { - self.element_refs.remove(id.0); - } } fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) { @@ -976,10 +951,6 @@ impl VirtualDom { RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts), }; - // Restore the props back to the vcomponent in case it gets rendered again - let props = self.scopes[scope.0].props.take(); - *comp.props.borrow_mut() = unsafe { std::mem::transmute(props) }; - // Now drop all the resouces self.drop_scope(scope, false); } @@ -1019,7 +990,7 @@ impl VirtualDom { pub(crate) fn assign_boundary_ref(&mut self, parent: Option, child: &VNode) { if let Some(parent) = parent { // assign the parent of the child - child.parent.set(Some(parent)); + child.parent.borrow_mut().replace(parent); } } } diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index 2901acbca..44c42606c 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -1,7 +1,6 @@ use crate::{ scope_context::{consume_context, current_scope_id, schedule_update_any}, - Element, IntoDynNode, Properties, ScopeId, ScopeState, Template, TemplateAttribute, - TemplateNode, VNode, + Element, IntoDynNode, Properties, ScopeId, Template, TemplateAttribute, TemplateNode, VNode, }; use std::{ any::{Any, TypeId}, @@ -14,8 +13,9 @@ use std::{ }; /// Provide an error boundary to catch errors from child components -pub fn use_error_boundary(cx: &ScopeState) -> &ErrorBoundary { - cx.use_hook(|| cx.provide_context(ErrorBoundary::new())) +pub fn use_error_boundary() -> ErrorBoundary { + // use_hook(|| cx.provide_context(ErrorBoundary::new())) + todo!() } /// A boundary that will capture any errors from child components @@ -262,10 +262,11 @@ impl Throw for Option { } } -pub struct ErrorHandler(Box Element>); +#[derive(Clone)] +pub struct ErrorHandler(Rc Element>); impl Element> From for ErrorHandler { fn from(value: F) -> Self { - Self(Box::new(value)) + Self(Rc::new(value)) } } fn default_handler(error: CapturedError) -> Element { @@ -284,15 +285,13 @@ fn default_handler(error: CapturedError) -> Element { node_paths: &[&[0u8, 0u8]], attr_paths: &[], }; - Some(VNode { - parent: Default::default(), - stable_id: Default::default(), - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: Vec::with_capacity(1usize).into(), - dynamic_nodes: vec![error.to_string().into_dyn_node()], - dynamic_attrs: Default::default(), - }) + Some(VNode::new( + None, + TEMPLATE, + Vec::with_capacity(1usize), + vec![error.to_string().into_dyn_node()], + Default::default(), + )) } #[derive(Clone)] @@ -417,7 +416,7 @@ impl< ::core::default::Default::default() }); let handle_error = ErrorBoundaryPropsBuilder_Optional::into_value(handle_error, || { - ErrorHandler(Box::new(default_handler)) + ErrorHandler(Rc::new(default_handler)) }); ErrorBoundaryProps { children, @@ -448,27 +447,24 @@ impl< /// They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them. /// Error boundaries are quick to implement, but it can be useful to individually handle errors in your components to provide a better user experience when you know that an error is likely to occur. #[allow(non_upper_case_globals, non_snake_case)] -pub fn ErrorBoundary(cx: ErrorBoundaryProps) -> Element { - let error_boundary = use_error_boundary(cx); +pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element { + let error_boundary = use_error_boundary(); match error_boundary.take_error() { - Some(error) => cx.render((cx.props.handle_error.0)(error)), + Some(error) => (props.handle_error.0)(error), None => Some({ - let __cx = cx; static TEMPLATE: Template = Template { name: "examples/error_handle.rs:81:17:2342", roots: &[TemplateNode::Dynamic { id: 0usize }], node_paths: &[&[0u8]], attr_paths: &[], }; - VNode { - parent: Default::default(), - stable_id: Default::default(), - key: None, - template: std::cell::Cell::new(TEMPLATE), - root_ids: Vec::with_capacity(1usize).into(), - dynamic_nodes: vec![(&cx.props.children).into_dyn_node()], - dynamic_attrs: __cx.bump().alloc([]), - } + VNode::new( + None, + TEMPLATE, + Vec::with_capacity(1usize), + vec![(props.children).into_dyn_node()], + Default::default(), + ) }), } } diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index 9f5ad7e16..577c04a65 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -27,16 +27,7 @@ use crate::innerlude::*; /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. #[allow(non_upper_case_globals, non_snake_case)] pub fn Fragment(cx: FragmentProps) -> Element { - let children = cx.0.as_ref()?; - Some(VNode { - key: children.key, - parent: children.parent.clone(), - stable_id: children.stable_id.clone(), - template: children.template.clone(), - root_ids: children.root_ids.clone(), - dynamic_nodes: children.dynamic_nodes, - dynamic_attrs: children.dynamic_attrs, - }) + cx.0.clone() } #[derive(Clone, PartialEq)] diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index feecc0590..e5576310f 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -73,10 +73,10 @@ pub(crate) mod innerlude { } pub use crate::innerlude::{ - fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, - CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, - Mutation, Mutations, Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template, - TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, + fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, CapturedError, + Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, Mutation, Mutations, + Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, + TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 25e64cecc..021eb1406 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -1,6 +1,6 @@ use rustc_hash::FxHashSet; -use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Template}; +use crate::{arena::ElementId, AttributeValue, ScopeId, Template}; /// A container for all the relevant steps to modify the Real DOM /// @@ -13,7 +13,6 @@ use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Templa /// Templates, however, apply to all subtrees, not just target subtree. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. -#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[derive(Debug, Default)] #[must_use = "not handling edits can lead to visual inconsistencies in UI"] pub struct Mutations<'a> { @@ -56,11 +55,6 @@ impl<'a> Mutations<'a> { /// of the Dioxus VirtualDom. /// /// These edits can be serialized and sent over the network or through any interface -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - serde(tag = "type") -)] #[derive(Debug, PartialEq)] pub enum Mutation<'a> { /// Add these m children to the target element @@ -195,7 +189,7 @@ pub enum Mutation<'a> { name: &'a str, /// The value of the attribute. - value: BorrowedAttributeValue<'a>, + value: &'a AttributeValue, /// The ID of the node to set the attribute of. id: ElementId, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 767b81da3..e25a69209 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -1,5 +1,8 @@ -use crate::innerlude::{ElementRef, VNodeId}; -use crate::{any_props::AnyProps, arena::ElementId, Element, Event, ScopeId, ScopeState}; +use crate::any_props::BoxedAnyProps; +use crate::innerlude::ElementRef; +use crate::{arena::ElementId, Element, Event, ScopeId}; +use std::ops::Deref; +use std::rc::Rc; use std::{ any::{Any, TypeId}, cell::{Cell, RefCell}, @@ -15,6 +18,7 @@ pub type TemplateId = &'static str; /// /// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor /// you might need to handle the case where there's no node immediately ready. +#[derive(Clone)] pub enum RenderReturn { /// A currently-available element Ready(VNode), @@ -36,26 +40,23 @@ impl Default for RenderReturn { /// /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping /// static parts of the template. -#[derive(Debug, Clone)] -pub struct VNode { +#[derive(Debug)] +pub struct VNodeInner { /// The key given to the root of this template. /// /// In fragments, this is the key of the first child. In other cases, it is the key of the root. pub key: Option, /// When rendered, this template will be linked to its parent manually - pub(crate) parent: Cell>, - - /// The bubble id assigned to the child that we need to update and drop when diffing happens - pub(crate) stable_id: Cell>, - - /// The static nodes and static descriptor of the template - pub template: Cell>, + pub(crate) parent: RefCell>, /// The IDs for the roots of this template - to be used when moving the template around and removing it from /// the actual Dom pub root_ids: RefCell>, + /// The static nodes and static descriptor of the template + pub template: Cell>, + /// The dynamic parts of the template pub dynamic_nodes: Vec, @@ -63,13 +64,33 @@ pub struct VNode { pub dynamic_attrs: Vec, } +/// A reference to a template along with any context needed to hydrate it +/// +/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping +/// static parts of the template. +#[derive(Clone, Debug)] +pub struct VNode(Rc); + +impl PartialEq for VNode { + fn eq(&self, other: &Self) -> bool { + Rc::ptr_eq(&self.0, &other.0) + } +} + +impl Deref for VNode { + type Target = VNodeInner; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + impl VNode { /// Create a template with no nodes that will be skipped over during diffing pub fn empty() -> Element { - Some(VNode { + Some(Self(Rc::new(VNodeInner { key: None, parent: Default::default(), - stable_id: Default::default(), root_ids: Default::default(), dynamic_nodes: Vec::new(), dynamic_attrs: Vec::new(), @@ -79,7 +100,7 @@ impl VNode { node_paths: &[], attr_paths: &[], }), - }) + }))) } /// Create a new VNode @@ -90,20 +111,14 @@ impl VNode { dynamic_nodes: Vec, dynamic_attrs: Vec, ) -> Self { - Self { + Self(Rc::new(VNodeInner { key, - parent: Cell::new(None), - stable_id: Cell::new(None), + parent: Default::default(), template: Cell::new(template), root_ids: RefCell::new(root_ids), dynamic_nodes, dynamic_attrs, - } - } - - /// Get the stable id of this node used for bubbling events - pub(crate) fn stable_id(&self) -> Option { - self.stable_id.get() + })) } /// Load a dynamic root at the given index @@ -293,7 +308,7 @@ pub enum TemplateNode { /// A node created at runtime /// /// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index -#[derive(Debug, Clone)] +#[derive(Debug)] pub enum DynamicNode { /// A component node /// @@ -355,7 +370,6 @@ impl<'a> std::fmt::Debug for VComponent { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("VComponent") .field("name", &self.name) - .field("static_props", &self.static_props) .field("scope", &self.scope) .finish() } @@ -387,12 +401,12 @@ impl<'a> VText { } /// A placeholder node, used by suspense and fragments -#[derive(Debug, Default)] +#[derive(Clone, Debug, Default)] pub struct VPlaceholder { /// The ID of this node in the real DOM pub(crate) id: Cell>, /// The parent of this node - pub(crate) parent: Cell>, + pub(crate) parent: RefCell>, } impl VPlaceholder { @@ -436,7 +450,7 @@ pub enum TemplateAttribute { } /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"` -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct Attribute { /// The name of the attribute. pub name: &'static str, @@ -483,7 +497,6 @@ impl Attribute { /// /// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`] /// variant. -#[derive(Clone)] pub enum AttributeValue { /// Text attribute Text(String), @@ -498,7 +511,7 @@ pub enum AttributeValue { Bool(bool), /// A listener, like "onclick" - Listener(ListenerCb), + Listener(RefCell), /// An arbitrary value that implements PartialEq and is static Any(Box), @@ -509,85 +522,6 @@ pub enum AttributeValue { pub type ListenerCb = Box)>; -/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed -/// -/// These varients are used to communicate what the value of an attribute is that needs to be updated -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serialize", serde(untagged))] -pub enum BorrowedAttributeValue<'a> { - /// Text attribute - Text(&'a str), - - /// A float - Float(f64), - - /// Signed integer - Int(i64), - - /// Boolean - Bool(bool), - - /// An arbitrary value that implements PartialEq and is static - #[cfg_attr( - feature = "serialize", - serde( - deserialize_with = "deserialize_any_value", - serialize_with = "serialize_any_value" - ) - )] - Any(std::cell::Ref<'a, dyn AnyValue>), - - /// A "none" value, resulting in the removal of an attribute from the dom - None, -} - -impl<'a> From<&'a AttributeValue> for BorrowedAttributeValue<'a> { - fn from(value: &'a AttributeValue) -> Self { - match value { - AttributeValue::Text(value) => BorrowedAttributeValue::Text(value), - AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value), - AttributeValue::Int(value) => BorrowedAttributeValue::Int(*value), - AttributeValue::Bool(value) => BorrowedAttributeValue::Bool(*value), - AttributeValue::Listener(_) => { - panic!("A listener cannot be turned into a borrowed value") - } - AttributeValue::Any(value) => { - let value = value.borrow(); - BorrowedAttributeValue::Any(std::cell::Ref::map(value, |value| { - &**value.as_ref().unwrap() - })) - } - AttributeValue::None => BorrowedAttributeValue::None, - } - } -} - -impl Debug for BorrowedAttributeValue<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(), - Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(), - Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(), - Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(), - Self::Any(_) => f.debug_tuple("Any").field(&"...").finish(), - Self::None => write!(f, "None"), - } - } -} - -impl PartialEq for BorrowedAttributeValue<'_> { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Text(l0), Self::Text(r0)) => l0 == r0, - (Self::Float(l0), Self::Float(r0)) => l0 == r0, - (Self::Int(l0), Self::Int(r0)) => l0 == r0, - (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, - (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(&**r0), - _ => core::mem::discriminant(self) == core::mem::discriminant(other), - } - } -} - #[cfg(feature = "serialize")] fn serialize_any_value(_: &std::cell::Ref<'_, dyn AnyValue>, _: S) -> Result where @@ -626,11 +560,7 @@ impl PartialEq for AttributeValue { (Self::Int(l0), Self::Int(r0)) => l0 == r0, (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, (Self::Listener(_), Self::Listener(_)) => true, - (Self::Any(l0), Self::Any(r0)) => { - let l0 = l0.borrow(); - let r0 = r0.borrow(); - l0.as_ref().unwrap().any_cmp(&**r0.as_ref().unwrap()) - } + (Self::Any(l0), Self::Any(r0)) => l0.as_ref().any_cmp(r0.as_ref()), _ => false, } } @@ -820,7 +750,7 @@ impl<'a> IntoAttributeValue for Arguments<'_> { impl IntoAttributeValue for Box { fn into_value(self) -> AttributeValue { - AttributeValue::Any(RefCell::new(Some(self))) + AttributeValue::Any(self) } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 694f9811d..a1d5d8b89 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -1,5 +1,5 @@ use crate::{ - any_props::AnyProps, + any_props::{AnyProps, BoxedAnyProps}, innerlude::DirtyScope, nodes::RenderReturn, scope_context::ScopeContext, @@ -8,11 +8,7 @@ use crate::{ }; impl VirtualDom { - pub(super) fn new_scope( - &mut self, - props: Box, - name: &'static str, - ) -> &ScopeState { + pub(super) fn new_scope(&mut self, props: BoxedAnyProps, name: &'static str) -> &ScopeState { let parent_id = self.runtime.current_scope_id(); let height = parent_id .and_then(|parent_id| self.get_scope(parent_id).map(|f| f.context().height + 1)) diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index e26f38ca9..5537553a5 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -1,6 +1,5 @@ use crate::{ - any_props::AnyProps, - any_props::VProps, + any_props::{BoxedAnyProps, VProps}, innerlude::{DynamicNode, EventHandler, VComponent, VText}, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, runtime::Runtime, @@ -49,7 +48,7 @@ pub struct ScopeState { pub(crate) last_rendered_node: Option, - pub(crate) props: Box, + pub(crate) props: BoxedAnyProps, } impl Drop for ScopeState { @@ -300,16 +299,12 @@ impl<'src> ScopeState { // The properties must be valid until the next bump frame P: Properties, { - let vcomp = VProps::new(component, P::memoize, props); - - // cast off the lifetime of the render return - let as_dyn: Box = Box::new(vcomp); - let extended: Box = unsafe { std::mem::transmute(as_dyn) }; + let vcomp = VProps::new(component, P::memoize, props, fn_name); DynamicNode::Component(VComponent { name: fn_name, render_fn: component as *const (), - props: extended, + props: BoxedAnyProps::new(vcomp), scope: Default::default(), }) } @@ -332,14 +327,14 @@ impl<'src> ScopeState { &'src self, mut callback: impl FnMut(Event) + 'src, ) -> AttributeValue { - AttributeValue::Listener(Box::new(move |event: Event| { + AttributeValue::Listener(RefCell::new(Box::new(move |event: Event| { if let Ok(data) = event.data.downcast::() { callback(Event { propagates: event.propagates, data, }); } - })) + }))) } /// Create a new [`AttributeValue`] with a value that implements [`AnyValue`] diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 5c8246355..98f510384 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -3,7 +3,7 @@ //! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust. use crate::{ - any_props::VProps, + any_props::{BoxedAnyProps, VProps}, arena::ElementId, innerlude::{DirtyScope, ElementRef, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, mutations::Mutation, @@ -11,14 +11,12 @@ use crate::{ nodes::{Template, TemplateId}, runtime::{Runtime, RuntimeGuard}, scopes::{ScopeId, ScopeState}, - AttributeValue, Element, Event, VNode, + AttributeValue, Element, Event, }; use futures_util::{pin_mut, StreamExt}; use rustc_hash::{FxHashMap, FxHashSet}; use slab::Slab; -use std::{ - any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc, sync::Arc, -}; +use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc, sync::Arc}; /// A virtual node system that progresses user events and diffs UI trees. /// @@ -187,9 +185,6 @@ pub struct VirtualDom { // Maps a template path to a map of byteindexes to templates pub(crate) templates: FxHashMap>>, - // Every element is actually a dual reference - one to the template and the other to the dynamic node in that template - pub(crate) element_refs: Slab>>, - // The element ids that are used in the renderer pub(crate) elements: Slab>, @@ -268,13 +263,12 @@ impl VirtualDom { dirty_scopes: Default::default(), templates: Default::default(), elements: Default::default(), - element_refs: Default::default(), mutations: Mutations::default(), suspended_scopes: Default::default(), }; let root = dom.new_scope( - Box::new(VProps::new(root, |_, _| unreachable!(), root_props)), + BoxedAnyProps::new(VProps::new(root, |_, _| unreachable!(), root_props, "root")), "app", ); @@ -363,14 +357,10 @@ impl VirtualDom { | <-- no, broke early */ let parent_path = match self.elements.get(element.0) { - Some(Some(el)) => el, + Some(Some(el)) => el.clone(), _ => return, }; - let mut parent_node = self - .element_refs - .get(parent_path.template.0) - .cloned() - .map(|el| (*parent_path, el)); + let mut parent_node = Some(parent_path); let mut listeners = vec![]; // We will clone this later. The data itself is wrapped in RC to be used in callbacks if required @@ -382,13 +372,12 @@ impl VirtualDom { // If the event bubbles, we traverse through the tree until we find the target element. if bubbles { // Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent. - while let Some((path, el_ref)) = parent_node { - // safety: we maintain references of all vnodes in the element slab - let template = unsafe { el_ref.unwrap().as_ref() }; - let node_template = template.template.get(); + while let Some(path) = parent_node { + let el_ref = &path.element; + let node_template = el_ref.template.get(); let target_path = path.path; - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() { let this_path = node_template.attr_paths[idx]; // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one @@ -413,9 +402,7 @@ impl VirtualDom { let origin = path.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); - if let Some(cb) = listener.borrow_mut().as_deref_mut() { - cb(uievent.clone()); - } + (listener.borrow_mut())(uievent.clone()); self.runtime.scope_stack.borrow_mut().pop(); self.runtime.rendering.set(true); @@ -425,22 +412,16 @@ impl VirtualDom { } } - parent_node = template.parent.get().and_then(|element_ref| { - self.element_refs - .get(element_ref.template.0) - .cloned() - .map(|el| (element_ref, el)) - }); + parent_node = el_ref.parent.borrow().clone(); } } else { // Otherwise, we just call the listener on the target element - if let Some((path, el_ref)) = parent_node { - // safety: we maintain references of all vnodes in the element slab - let template = unsafe { el_ref.unwrap().as_ref() }; - let node_template = template.template.get(); + if let Some(path) = parent_node { + let el_ref = &path.element; + let node_template = el_ref.template.get(); let target_path = path.path; - for (idx, attr) in template.dynamic_attrs.iter().enumerate() { + for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() { let this_path = node_template.attr_paths[idx]; // Remove the "on" prefix if it exists, TODO, we should remove this and settle on one @@ -450,9 +431,7 @@ impl VirtualDom { let origin = path.scope; self.runtime.scope_stack.borrow_mut().push(origin); self.runtime.rendering.set(false); - if let Some(cb) = listener.as_deref_mut() { - cb(uievent.clone()); - } + (listener.borrow_mut())(uievent.clone()); self.runtime.scope_stack.borrow_mut().pop(); self.runtime.rendering.set(true); @@ -570,7 +549,7 @@ impl VirtualDom { match unsafe { self.run_scope(ScopeId::ROOT) } { // Rebuilding implies we append the created elements to the root RenderReturn::Ready(node) => { - let m = self.create_scope(ScopeId::ROOT, node); + let m = self.create_scope(ScopeId::ROOT, &node); self.mutations.edits.push(Mutation::AppendChildren { id: ElementId(0), m, @@ -645,8 +624,8 @@ impl VirtualDom { { let _runtime = RuntimeGuard::new(self.runtime.clone()); // Run the scope and get the mutations - self.run_scope(dirty.id); - self.diff_scope(dirty.id); + let new_nodes = self.run_scope(dirty.id); + self.diff_scope(dirty.id, new_nodes); } } From 35b461cd091b7a957795df59b589edd8fb8f4ee3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 5 Jan 2024 11:44:07 -0600 Subject: [PATCH 038/377] switch to a mutations trait to fix some lifetime issues --- packages/core/src/create.rs | 190 +++++++++------- packages/core/src/diff.rs | 329 ++++++++++++++++------------ packages/core/src/lib.rs | 6 +- packages/core/src/mutations.rs | 359 +++++++++++++++++++++++++++---- packages/core/src/virtual_dom.rs | 40 ++-- 5 files changed, 634 insertions(+), 290 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 64196a969..3da568634 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,6 +1,4 @@ -use crate::innerlude::{ElementPath, ElementRef, VComponent, VPlaceholder, VText}; -use crate::mutations::Mutation; -use crate::mutations::Mutation::*; +use crate::innerlude::{ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations}; use crate::nodes::VNode; use crate::nodes::{DynamicNode, TemplateNode}; use crate::virtual_dom::VirtualDom; @@ -64,15 +62,20 @@ impl VirtualDom { /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer. /// /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created. - pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &VNode) -> usize { + pub(crate) fn create_scope( + &mut self, + scope: ScopeId, + template: &VNode, + to: &mut impl WriteMutations, + ) -> usize { self.runtime.scope_stack.borrow_mut().push(scope); - let nodes = self.create(template); + let nodes = self.create(template, to); self.runtime.scope_stack.borrow_mut().pop(); nodes } /// Create this template and write its mutations - pub(crate) fn create(&mut self, node: &VNode) -> usize { + pub(crate) fn create(&mut self, node: &VNode, to: &mut impl WriteMutations) -> usize { // check for a overriden template #[cfg(debug_assertions)] { @@ -95,12 +98,7 @@ impl VirtualDom { // The best renderers will have templates prehydrated and registered // Just in case, let's create the template using instructions anyways - self.register_template(node.template.get()); - - // we know that this will generate at least one mutation per node - self.mutations - .edits - .reserve(node.template.get().roots.len()); + self.register_template(node.template.get(), to); // Walk the roots, creating nodes and assigning IDs // nodes in an iterator of ((dynamic_node_index, sorted_index), path) @@ -153,30 +151,46 @@ impl VirtualDom { .map(|(idx, root)| match root { DynamicText { id } | Dynamic { id } => { nodes.next().unwrap(); - self.write_dynamic_root(node, *id) + self.write_dynamic_root(node, *id, to) } Element { .. } => { #[cfg(not(debug_assertions))] let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[]); #[cfg(debug_assertions)] - let id = - self.write_element_root(node, idx, &mut attrs, &mut nodes, &nodes_sorted); + let id = self.write_element_root( + node, + idx, + &mut attrs, + &mut nodes, + &nodes_sorted, + to, + ); id } - Text { .. } => self.write_static_text_root(node, idx), + Text { .. } => self.write_static_text_root(node, idx, to), }) .sum() } - fn write_static_text_root(&mut self, node: &VNode, idx: usize) -> usize { + fn write_static_text_root( + &mut self, + node: &VNode, + idx: usize, + to: &mut impl WriteMutations, + ) -> usize { // Simply just load the template root, no modifications needed - self.load_template_root(node, idx); + self.load_template_root(node, idx, to); // Text producs just one node on the stack 1 } - fn write_dynamic_root(&mut self, template: &VNode, idx: usize) -> usize { + fn write_dynamic_root( + &mut self, + template: &VNode, + idx: usize, + to: &mut impl WriteMutations, + ) -> usize { use DynamicNode::*; match &template.dynamic_nodes[idx] { node @ Component { .. } | node @ Fragment(_) => { @@ -187,7 +201,7 @@ impl VirtualDom { element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; - self.create_dynamic_node(template_ref, node) + self.create_dynamic_node(template_ref, node, to) } Placeholder(VPlaceholder { id, parent }) => { let template_ref = ElementRef { @@ -199,21 +213,17 @@ impl VirtualDom { }; *parent.borrow_mut() = Some(template_ref); let id = self.set_slot(id); - self.mutations.push(CreatePlaceholder { id }); + to.create_placeholder(id); 1 } Text(VText { id, value }) => { let id = self.set_slot(id); - self.create_static_text(value, id); + to.create_text_node(value, id); 1 } } } - fn create_static_text(&mut self, value: &str, id: ElementId) { - self.mutations.push(CreateTextNode { value, id }); - } - /// We write all the descendent data for this element /// /// Elements can contain other nodes - and those nodes can be dynamic or static @@ -226,15 +236,22 @@ impl VirtualDom { dynamic_attrs: &mut Peekable>, dynamic_nodes_iter: &mut Peekable>, dynamic_nodes: &[(usize, &'static [u8])], + to: &mut impl WriteMutations, ) -> usize { // Load the template root and get the ID for the node on the stack - let root_on_stack = self.load_template_root(template, root_idx); + let root_on_stack = self.load_template_root(template, root_idx, to); // Write all the attributes below this root - self.write_attrs_on_root(dynamic_attrs, root_idx as u8, root_on_stack, template); + self.write_attrs_on_root(dynamic_attrs, root_idx as u8, root_on_stack, template, to); // Load in all of the placeholder or dynamic content under this root too - self.load_placeholders(dynamic_nodes_iter, dynamic_nodes, root_idx as u8, template); + self.load_placeholders( + dynamic_nodes_iter, + dynamic_nodes, + root_idx as u8, + template, + to, + ); 1 } @@ -259,6 +276,7 @@ impl VirtualDom { dynamic_nodes: &[(usize, &'static [u8])], root_idx: u8, template: &VNode, + to: &mut impl WriteMutations, ) { let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) { Some((a, b)) => (a, b), @@ -281,11 +299,11 @@ impl VirtualDom { element: template.clone(), scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; - let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[idx]); + let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[idx], to); if m > 0 { // The path is one shorter because the top node is the root let path = &template.template.get().node_paths[idx][1..]; - self.mutations.push(ReplacePlaceholder { m, path }); + to.replace_placeholder_with_nodes(path, m); } } } @@ -296,14 +314,15 @@ impl VirtualDom { root_idx: u8, root: ElementId, node: &VNode, + to: &mut impl WriteMutations, ) { while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx)) { - let id = self.assign_static_node_as_dynamic(path, root); + let id = self.assign_static_node_as_dynamic(path, root, to); loop { - self.write_attribute(node, attr_id, &node.dynamic_attrs[attr_id], id); + self.write_attribute(node, attr_id, &node.dynamic_attrs[attr_id], id, to); // Only push the dynamic attributes forward if they match the current path (same element) match attrs.next_if(|(_, p)| *p == path) { @@ -320,6 +339,7 @@ impl VirtualDom { idx: usize, attribute: &crate::Attribute, id: ElementId, + to: &mut impl WriteMutations, ) { // Make sure we set the attribute's associated id attribute.mounted_element.set(id); @@ -336,35 +356,27 @@ impl VirtualDom { scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)), }; self.elements[id.0] = Some(element_ref); - self.mutations.push(NewEventListener { - // all listeners start with "on" - name: &unbounded_name[2..], - id, - }) + to.create_event_listener(&unbounded_name[2..], id); } _ => { let unbounded_value = &attribute.value; - self.mutations.push(SetAttribute { - name: unbounded_name, - value: unbounded_value, - ns: attribute.namespace, - id, - }) + to.set_attribute(unbounded_name, attribute.namespace, unbounded_value, id); } } } - fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId { + fn load_template_root( + &mut self, + template: &VNode, + root_idx: usize, + to: &mut impl WriteMutations, + ) -> ElementId { // Get an ID for this root since it's a real root let this_id = self.next_element(); template.root_ids.borrow_mut()[root_idx] = this_id; - self.mutations.push(LoadTemplate { - name: template.template.get().name, - index: root_idx, - id: this_id, - }); + to.load_template(template.template.get().name, root_idx, this_id); this_id } @@ -380,6 +392,7 @@ impl VirtualDom { &mut self, path: &'static [u8], this_id: ElementId, + to: &mut impl WriteMutations, ) -> ElementId { if path.len() == 1 { return this_id; @@ -389,16 +402,17 @@ impl VirtualDom { // Else, it's deep in the template and we should create a new id for it let id = self.next_element(); - self.mutations.push(Mutation::AssignId { - path: &path[1..], - id, - }); + to.assign_node_id(&path[1..], id); id } /// Insert a new template into the VirtualDom's template registry - pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template<'static>) { + pub(crate) fn register_template_first_byte_index( + &mut self, + mut template: Template<'static>, + to: &mut impl WriteMutations, + ) { // First, make sure we mark the template as seen, regardless if we process it let (path, _) = template.name.rsplit_once(':').unwrap(); if let Some((_, old_template)) = self @@ -421,14 +435,18 @@ impl VirtualDom { // If it's all dynamic nodes, then we don't need to register it if !template.is_completely_dynamic() { - self.mutations.templates.push(template); + to.register_template(template); } } /// Insert a new template into the VirtualDom's template registry // used in conditional compilation #[allow(unused_mut)] - pub(crate) fn register_template(&mut self, mut template: Template<'static>) { + pub(crate) fn register_template( + &mut self, + mut template: Template<'static>, + to: &mut impl WriteMutations, + ) { let (path, byte_index) = template.name.rsplit_once(':').unwrap(); let byte_index = byte_index.parse::().unwrap(); @@ -458,22 +476,32 @@ impl VirtualDom { // If it's all dynamic nodes, then we don't need to register it if !template.is_completely_dynamic() { - self.mutations.templates.push(template); + to.register_template(template) } } } - pub(crate) fn create_dynamic_node(&mut self, parent: ElementRef, node: &DynamicNode) -> usize { + pub(crate) fn create_dynamic_node( + &mut self, + parent: ElementRef, + node: &DynamicNode, + to: &mut impl WriteMutations, + ) -> usize { use DynamicNode::*; match node { - Text(text) => self.create_dynamic_text(parent, text), - Placeholder(place) => self.create_placeholder(place, parent), - Component(component) => self.create_component_node(Some(parent), component), - Fragment(frag) => self.create_children(frag, Some(parent)), + Text(text) => self.create_dynamic_text(parent, text, to), + Placeholder(place) => self.create_placeholder(place, parent, to), + Component(component) => self.create_component_node(Some(parent), component, to), + Fragment(frag) => self.create_children(frag, Some(parent), to), } } - fn create_dynamic_text(&mut self, parent: ElementRef, text: &VText) -> usize { + fn create_dynamic_text( + &mut self, + parent: ElementRef, + text: &VText, + to: &mut impl WriteMutations, + ) -> usize { // Allocate a dynamic element reference for this text node let new_id = self.next_element(); @@ -481,11 +509,7 @@ impl VirtualDom { text.id.set(Some(new_id)); // Add the mutation to the list - self.mutations.push(HydrateText { - id: new_id, - path: &parent.path.path[1..], - value: &text.value, - }); + to.hydrate_text_node(&parent.path.path[1..], &text.value, new_id); // Since we're hydrating an existing node, we don't create any new nodes 0 @@ -495,6 +519,7 @@ impl VirtualDom { &mut self, placeholder: &VPlaceholder, parent: ElementRef, + to: &mut impl WriteMutations, ) -> usize { // Allocate a dynamic element reference for this text node let id = self.next_element(); @@ -506,10 +531,7 @@ impl VirtualDom { *placeholder.parent.borrow_mut() = Some(parent); // Assign the ID to the existing node in the template - self.mutations.push(AssignId { - path: &parent.path.path[1..], - id, - }); + to.assign_node_id(&parent.path.path[1..], id); // Since the placeholder is already in the DOM, we don't create any new nodes 0 @@ -519,11 +541,12 @@ impl VirtualDom { &mut self, parent: Option, component: &VComponent, + to: &mut impl WriteMutations, ) -> usize { use RenderReturn::*; // Load up a ScopeId for this vcomponent - let scope = self.load_scope_from_vcomponent(component); + let scope = self.load_scope_from_vcomponent(component, to); component.scope.set(Some(scope)); @@ -535,14 +558,18 @@ impl VirtualDom { // Create the component's root element Ready(t) => { self.assign_boundary_ref(parent, t); - self.create_scope(scope, t) + self.create_scope(scope, t, to) } - Aborted(t) => self.mount_aborted(t, parent), + Aborted(t) => self.mount_aborted(t, parent, to), } } /// Load a scope from a vcomponent. If the scope id doesn't exist, that means the component is currently "live" - fn load_scope_from_vcomponent(&mut self, component: &VComponent) -> ScopeId { + fn load_scope_from_vcomponent( + &mut self, + component: &VComponent, + to: &mut impl WriteMutations, + ) -> ScopeId { component.scope.get().unwrap_or_else(|| { self.new_scope(component.props.clone(), component.name) .context() @@ -550,9 +577,14 @@ impl VirtualDom { }) } - fn mount_aborted(&mut self, placeholder: &VPlaceholder, parent: Option) -> usize { + fn mount_aborted( + &mut self, + placeholder: &VPlaceholder, + parent: Option, + to: &mut impl WriteMutations, + ) -> usize { let id = self.next_element(); - self.mutations.push(Mutation::CreatePlaceholder { id }); + to.create_placeholder(id); placeholder.id.set(Some(id)); *placeholder.parent.borrow_mut() = parent; diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 90f2d3d79..385f4a18e 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -3,7 +3,9 @@ use std::ops::Deref; use crate::{ any_props::AnyProps, arena::ElementId, - innerlude::{DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText}, + innerlude::{ + DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations, + }, mutations::Mutation, nodes::RenderReturn, nodes::{DynamicNode, VNode}, @@ -16,7 +18,12 @@ use rustc_hash::{FxHashMap, FxHashSet}; use DynamicNode::*; impl VirtualDom { - pub(super) fn diff_scope(&mut self, scope: ScopeId, new_nodes: RenderReturn) { + pub(super) fn diff_scope( + &mut self, + scope: ScopeId, + new_nodes: RenderReturn, + to: &mut impl WriteMutations, + ) { self.runtime.scope_stack.borrow_mut().push(scope); let scope_state = &mut self.scopes[scope.0]; // Load the old and new bump arenas @@ -27,10 +34,10 @@ impl VirtualDom { match (&old, new) { // Normal pathway - (Ready(l), Ready(r)) => self.diff_node(l, r), + (Ready(l), Ready(r)) => self.diff_node(l, r, to), // Unwind the mutations if need be - (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p), + (Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p, to), // Just move over the placeholder (Aborted(l), Aborted(r)) => { @@ -44,24 +51,19 @@ impl VirtualDom { l, [r], l.parent.borrow().expect("root node should not be none"), + to, ), }; self.runtime.scope_stack.borrow_mut().pop(); } - fn diff_ok_to_err(&mut self, l: &VNode, p: &VPlaceholder) { + fn diff_ok_to_err(&mut self, l: &VNode, p: &VPlaceholder, to: &mut impl WriteMutations) { let id = self.next_element(); p.id.set(Some(id)); *p.parent.borrow_mut() = l.parent.borrow().clone(); - self.mutations.push(Mutation::CreatePlaceholder { id }); + to.create_placeholder(id); - let pre_edits = self.mutations.edits.len(); - - self.remove_node(l, true); - - // We should always have a remove mutation - // Eventually we don't want to generate placeholders, so this might not be true. But it's true today - assert!(self.mutations.edits.len() > pre_edits); + self.remove_node(l, true, to); // We want to optimize the replace case to use one less mutation if possible // Since mutations are done in reverse, the last node removed will be the first in the stack @@ -72,7 +74,12 @@ impl VirtualDom { }; } - fn diff_node(&mut self, left_template: &VNode, right_template: &VNode) { + fn diff_node( + &mut self, + left_template: &VNode, + right_template: &VNode, + to: &mut impl WriteMutations, + ) { // If hot reloading is enabled, we need to make sure we're using the latest template #[cfg(debug_assertions)] { @@ -83,7 +90,7 @@ impl VirtualDom { right_template.template.set(template); if template != left_template.template.get() { let parent = left_template.parent.take(); - return self.replace(left_template, [right_template], parent); + return self.replace(left_template, [right_template], parent, to); } } } @@ -101,7 +108,7 @@ impl VirtualDom { // If the templates are different by name, we need to replace the entire template if templates_are_different(left_template, right_template) { - return self.light_diff_templates(left_template, right_template); + return self.light_diff_templates(left_template, right_template, to); } // If the templates are the same, we can diff the attributes and children @@ -117,7 +124,7 @@ impl VirtualDom { // If the attributes are different (or volatile), we need to update them if left_attr.value != right_attr.value || left_attr.volatile { - self.update_attribute(right_attr, left_attr); + self.update_attribute(right_attr, left_attr, to); } }); @@ -135,7 +142,7 @@ impl VirtualDom { }, scope: self.runtime.scope_stack.borrow().last().copied().unwrap(), }; - self.diff_dynamic_node(left_node, right_node, current_ref); + self.diff_dynamic_node(left_node, right_node, current_ref, to); }); // Make sure the roots get transferred over while we're here @@ -153,30 +160,36 @@ impl VirtualDom { left_node: &DynamicNode, right_node: &DynamicNode, parent: ElementRef, + to: &mut impl WriteMutations, ) { match (left_node, right_node) { - (Text(left), Text(right)) => self.diff_vtext(left, right), - (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent), + (Text(left), Text(right)) => self.diff_vtext(left, right, to), + (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent, to), (Placeholder(left), Placeholder(right)) => { right.id.set(left.id.get()); *right.parent.borrow_mut() = left.parent.borrow().clone(); }, - (Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent)), - (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right, parent), - (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent), + (Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent), to), + (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right, parent, to), + (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent, to), _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."), }; } - fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) { + fn update_attribute( + &mut self, + right_attr: &Attribute, + left_attr: &Attribute, + to: &mut impl WriteMutations, + ) { let name = &left_attr.name; let value = &right_attr.value; - self.mutations.push(Mutation::SetAttribute { - id: left_attr.mounted_element.get(), - ns: right_attr.namespace, + to.set_attribute( name, + right_attr.namespace, value, - }); + left_attr.mounted_element.get(), + ); } fn diff_vcomponent( @@ -184,6 +197,7 @@ impl VirtualDom { left: &VComponent, right: &VComponent, parent: Option, + to: &mut impl WriteMutations, ) { if std::ptr::eq(left, right) { return; @@ -191,7 +205,7 @@ impl VirtualDom { // Replace components that have different render fns if left.render_fn != right.render_fn { - return self.replace_vcomponent(right, left, parent); + return self.replace_vcomponent(right, left, parent, to); } // Make sure the new vcomponent has the right scopeid associated to it @@ -221,7 +235,7 @@ impl VirtualDom { // Now run the component and diff it let new = self.run_scope(scope_id); - self.diff_scope(scope_id, new); + self.diff_scope(scope_id, new, to); self.dirty_scopes.remove(&DirtyScope { height: self.runtime.get_context(scope_id).unwrap().height, @@ -234,14 +248,11 @@ impl VirtualDom { right: &VComponent, left: &VComponent, parent: Option, + to: &mut impl WriteMutations, ) { let m = self.create_component_node(parent, right); - let pre_edits = self.mutations.edits.len(); - - self.remove_component_node(left, true); - - assert!(self.mutations.edits.len() > pre_edits); + self.remove_component_node(left, true, to); // We want to optimize the replace case to use one less mutation if possible // Since mutations are done in reverse, the last node removed will be the first in the stack @@ -289,13 +300,13 @@ impl VirtualDom { /// Component { ..props } /// } /// ``` - fn light_diff_templates(&mut self, left: &VNode, right: &VNode) { + fn light_diff_templates(&mut self, left: &VNode, right: &VNode, to: &mut impl WriteMutations) { let parent = left.parent.take(); match matching_components(left, right) { - None => self.replace(left, [right], parent), + None => self.replace(left, [right], parent, to), Some(components) => components .into_iter() - .for_each(|(l, r)| self.diff_vcomponent(l, r, parent)), + .for_each(|(l, r)| self.diff_vcomponent(l, r, parent, to)), } } @@ -303,19 +314,22 @@ impl VirtualDom { /// /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's /// different. - fn diff_vtext(&mut self, left: &VText, right: &VText) { + fn diff_vtext(&mut self, left: &VText, right: &VText, to: &mut impl WriteMutations) { let id = left.id.get().unwrap_or_else(|| self.next_element()); right.id.set(Some(id)); if left.value != right.value { - self.mutations.push(Mutation::SetText { - id, - value: &right.value, - }); + to.set_node_text(&right.value, id); } } - fn diff_non_empty_fragment(&mut self, old: &[VNode], new: &[VNode], parent: ElementRef) { + fn diff_non_empty_fragment( + &mut self, + old: &[VNode], + new: &[VNode], + parent: ElementRef, + to: &mut impl WriteMutations, + ) { let new_is_keyed = new[0].key.is_some(); let old_is_keyed = old[0].key.is_some(); debug_assert!( @@ -328,9 +342,9 @@ impl VirtualDom { ); if new_is_keyed && old_is_keyed { - self.diff_keyed_children(old, new, parent); + self.diff_keyed_children(old, new, parent, to); } else { - self.diff_non_keyed_children(old, new, parent); + self.diff_non_keyed_children(old, new, parent, to); } } @@ -342,7 +356,13 @@ impl VirtualDom { // [... parent] // // the change list stack is in the same state when this function returns. - fn diff_non_keyed_children(&mut self, old: &[VNode], new: &[VNode], parent: ElementRef) { + fn diff_non_keyed_children( + &mut self, + old: &[VNode], + new: &[VNode], + parent: ElementRef, + to: &mut impl WriteMutations, + ) { use std::cmp::Ordering; // Handled these cases in `diff_children` before calling this function. @@ -350,15 +370,15 @@ impl VirtualDom { debug_assert!(!old.is_empty()); match old.len().cmp(&new.len()) { - Ordering::Greater => self.remove_nodes(&old[new.len()..]), + Ordering::Greater => self.remove_nodes(&old[new.len()..], to), Ordering::Less => { - self.create_and_insert_after(&new[old.len()..], old.last().unwrap(), parent) + self.create_and_insert_after(&new[old.len()..], old.last().unwrap(), parent, to) } Ordering::Equal => {} } for (new, old) in new.iter().zip(old.iter()) { - self.diff_node(old, new); + self.diff_node(old, new, to); } } @@ -378,7 +398,13 @@ impl VirtualDom { // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739 // // The stack is empty upon entry. - fn diff_keyed_children(&mut self, old: &[VNode], new: &[VNode], parent: ElementRef) { + fn diff_keyed_children( + &mut self, + old: &[VNode], + new: &[VNode], + parent: ElementRef, + to: &mut impl WriteMutations, + ) { if cfg!(debug_assertions) { let mut keys = rustc_hash::FxHashSet::default(); let mut assert_unique_keys = |children: &[VNode]| { @@ -406,7 +432,7 @@ impl VirtualDom { // // `shared_prefix_count` is the count of how many nodes at the start of // `new` and `old` share the same keys. - let (left_offset, right_offset) = match self.diff_keyed_ends(old, new, parent) { + let (left_offset, right_offset) = match self.diff_keyed_ends(old, new, parent, to) { Some(count) => count, None => return, }; @@ -425,25 +451,25 @@ impl VirtualDom { if new_middle.is_empty() { // remove the old elements - self.remove_nodes(old_middle); + self.remove_nodes(old_middle, to); } else if old_middle.is_empty() { // there were no old elements, so just create the new elements // we need to find the right "foothold" though - we shouldn't use the "append" at all if left_offset == 0 { // insert at the beginning of the old list let foothold = &old[old.len() - right_offset]; - self.create_and_insert_before(new_middle, foothold, parent); + self.create_and_insert_before(new_middle, foothold, parent, to); } else if right_offset == 0 { // insert at the end the old list let foothold = old.last().unwrap(); - self.create_and_insert_after(new_middle, foothold, parent); + self.create_and_insert_after(new_middle, foothold, parent, to); } else { // inserting in the middle let foothold = &old[left_offset - 1]; - self.create_and_insert_after(new_middle, foothold, parent); + self.create_and_insert_after(new_middle, foothold, parent, to); } } else { - self.diff_keyed_middle(old_middle, new_middle, parent); + self.diff_keyed_middle(old_middle, new_middle, parent, to); } } @@ -457,6 +483,7 @@ impl VirtualDom { old: &[VNode], new: &[VNode], parent: ElementRef, + to: &mut impl WriteMutations, ) -> Option<(usize, usize)> { let mut left_offset = 0; @@ -465,21 +492,21 @@ impl VirtualDom { if old.key != new.key { break; } - self.diff_node(old, new); + self.diff_node(old, new, to); left_offset += 1; } // If that was all of the old children, then create and append the remaining // new children and we're finished. if left_offset == old.len() { - self.create_and_insert_after(&new[left_offset..], old.last().unwrap(), parent); + self.create_and_insert_after(&new[left_offset..], old.last().unwrap(), parent, to); return None; } // And if that was all of the new children, then remove all of the remaining // old children and we're finished. if left_offset == new.len() { - self.remove_nodes(&old[left_offset..]); + self.remove_nodes(&old[left_offset..], to); return None; } @@ -490,7 +517,7 @@ impl VirtualDom { if old.key != new.key { break; } - self.diff_node(old, new); + self.diff_node(old, new, to); right_offset += 1; } @@ -511,7 +538,13 @@ impl VirtualDom { // // Upon exit from this function, it will be restored to that same self. #[allow(clippy::too_many_lines)] - fn diff_keyed_middle(&mut self, old: &[VNode], new: &[VNode], parent: ElementRef) { + fn diff_keyed_middle( + &mut self, + old: &[VNode], + new: &[VNode], + parent: ElementRef, + to: &mut impl WriteMutations, + ) { /* 1. Map the old keys into a numerical ordering based on indices. 2. Create a map of old key to its index @@ -567,8 +600,8 @@ impl VirtualDom { // create the new children afresh. if shared_keys.is_empty() { if !old.is_empty() { - self.remove_nodes(&old[1..]); - self.replace(&old[0], new, Some(parent)); + self.remove_nodes(&old[1..], to); + self.replace(&old[0], new, Some(parent), to); } else { // I think this is wrong - why are we appending? // only valid of the if there are no trailing elements @@ -584,7 +617,7 @@ impl VirtualDom { for child in old { let key = child.key.unwrap(); if !shared_keys.contains(&key) { - self.remove_node(child, true); + self.remove_node(child, true, to); } } @@ -611,7 +644,7 @@ impl VirtualDom { } for idx in &lis_sequence { - self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]); + self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx], to); } let mut nodes_created = 0; @@ -625,17 +658,14 @@ impl VirtualDom { if old_index == u32::MAX as usize { nodes_created += self.create(new_node); } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_real_nodes(new_node); + self.diff_node(&old[old_index], new_node, to); + nodes_created += self.push_all_real_nodes(new_node, to); } } let id = self.find_last_element(&new[last]); if nodes_created > 0 { - self.mutations.push(Mutation::InsertAfter { - id, - m: nodes_created, - }) + to.insert_nodes_after(id, nodes_created) } nodes_created = 0; } @@ -651,17 +681,14 @@ impl VirtualDom { if old_index == u32::MAX as usize { nodes_created += self.create(new_node); } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_real_nodes(new_node); + self.diff_node(&old[old_index], new_node, to); + nodes_created += self.push_all_real_nodes(new_node, to); } } let id = self.find_first_element(&new[last]); if nodes_created > 0 { - self.mutations.push(Mutation::InsertBefore { - id, - m: nodes_created, - }); + to.insert_nodes_before(id, nodes_created); } nodes_created = 0; @@ -677,23 +704,20 @@ impl VirtualDom { if old_index == u32::MAX as usize { nodes_created += self.create(new_node); } else { - self.diff_node(&old[old_index], new_node); - nodes_created += self.push_all_real_nodes(new_node); + self.diff_node(&old[old_index], new_node, to); + nodes_created += self.push_all_real_nodes(new_node, to); } } let id = self.find_first_element(&new[first_lis]); if nodes_created > 0 { - self.mutations.push(Mutation::InsertBefore { - id, - m: nodes_created, - }); + to.insert_nodes_before(id, nodes_created); } } } /// Push all the real nodes on the stack - fn push_all_real_nodes(&mut self, node: &VNode) -> usize { + fn push_all_real_nodes(&mut self, node: &VNode, to: &mut impl WriteMutations) -> usize { node.template .get() .roots @@ -703,35 +727,29 @@ impl VirtualDom { let node = match node.dynamic_root(idx) { Some(node) => node, None => { - self.mutations.push(Mutation::PushRoot { - id: node.root_ids.borrow()[idx], - }); + to.push_root(node.root_ids.borrow()[idx]); return 1; } }; match node { Text(t) => { - self.mutations.push(Mutation::PushRoot { - id: t.id.get().unwrap(), - }); + to.push_root(t.id.get().unwrap()); 1 } Placeholder(t) => { - self.mutations.push(Mutation::PushRoot { - id: t.id.get().unwrap(), - }); + to.push_root(t.id.get().unwrap()); 1 } Fragment(nodes) => nodes .iter() - .map(|node| self.push_all_real_nodes(node)) + .map(|node| self.push_all_real_nodes(node, to)) .sum(), Component(comp) => { let scope = comp.scope.get().unwrap(); match self.get_scope(scope).unwrap().root_node() { - RenderReturn::Ready(node) => self.push_all_real_nodes(node), + RenderReturn::Ready(node) => self.push_all_real_nodes(node, to), RenderReturn::Aborted(_node) => todo!(), } } @@ -744,6 +762,7 @@ impl VirtualDom { &mut self, nodes: impl IntoIterator, parent: Option, + to: &mut impl WriteMutations, ) -> usize { nodes .into_iter() @@ -754,16 +773,28 @@ impl VirtualDom { .sum() } - fn create_and_insert_before(&mut self, new: &[VNode], before: &VNode, parent: ElementRef) { - let m = self.create_children(new, Some(parent)); + fn create_and_insert_before( + &mut self, + new: &[VNode], + before: &VNode, + parent: ElementRef, + to: &mut impl WriteMutations, + ) { + let m = self.create_children(new, Some(parent), to); let id = self.find_first_element(before); - self.mutations.push(Mutation::InsertBefore { id, m }) + to.insert_nodes_before(id, m); } - fn create_and_insert_after(&mut self, new: &[VNode], after: &VNode, parent: ElementRef) { - let m = self.create_children(new, Some(parent)); + fn create_and_insert_after( + &mut self, + new: &[VNode], + after: &VNode, + parent: ElementRef, + to: &mut impl WriteMutations, + ) { + let m = self.create_children(new, Some(parent), to); let id = self.find_last_element(after); - self.mutations.push(Mutation::InsertAfter { id, m }) + to.insert_nodes_after(id, m); } /// Simply replace a placeholder with a list of nodes @@ -772,10 +803,11 @@ impl VirtualDom { l: &VPlaceholder, r: impl IntoIterator, parent: ElementRef, + to: &mut impl WriteMutations, ) { - let m = self.create_children(r, Some(parent)); + let m = self.create_children(r, Some(parent), to); let id = l.id.get().unwrap(); - self.mutations.push(Mutation::ReplaceWith { id, m }); + to.replace_with(id, m); self.reclaim(id); } @@ -784,16 +816,11 @@ impl VirtualDom { left: &VNode, right: impl IntoIterator, parent: Option, + to: &mut impl WriteMutations, ) { - let m = self.create_children(right, parent); + let m = self.create_children(right, parent, to); - let pre_edits = self.mutations.edits.len(); - - self.remove_node(left, true); - - // We should always have a remove mutation - // Eventually we don't want to generate placeholders, so this might not be true. But it's true today - assert!(self.mutations.edits.len() > pre_edits); + self.remove_node(left, true, to); // We want to optimize the replace case to use one less mutation if possible // Since mutations are done in reverse, the last node removed will be the first in the stack @@ -804,17 +831,22 @@ impl VirtualDom { }; } - fn node_to_placeholder(&mut self, l: &[VNode], r: &VPlaceholder, parent: ElementRef) { + fn node_to_placeholder( + &mut self, + l: &[VNode], + r: &VPlaceholder, + parent: ElementRef, + to: &mut impl WriteMutations, + ) { // Create the placeholder first, ensuring we get a dedicated ID for the placeholder let placeholder = self.next_element(); r.id.set(Some(placeholder)); r.parent.borrow_mut().replace(parent); - self.mutations - .push(Mutation::CreatePlaceholder { id: placeholder }); + to.create_placeholder(placeholder); - self.remove_nodes(l); + self.remove_nodes(l, to); // We want to optimize the replace case to use one less mutation if possible // Since mutations are done in reverse, the last node removed will be the first in the stack @@ -827,43 +859,43 @@ impl VirtualDom { /// Remove these nodes from the dom /// Wont generate mutations for the inner nodes - fn remove_nodes(&mut self, nodes: &[VNode]) { + fn remove_nodes(&mut self, nodes: &[VNode], to: &mut impl WriteMutations) { nodes .iter() .rev() - .for_each(|node| self.remove_node(node, true)); + .for_each(|node| self.remove_node(node, true, to)); } - fn remove_node(&mut self, node: &VNode, gen_muts: bool) { + fn remove_node(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) { // Clean up any attributes that have claimed a static node as dynamic for mount/unmounta // Will not generate mutations! - self.reclaim_attributes(node); + self.reclaim_attributes(node, to); // Remove the nested dynamic nodes // We don't generate mutations for these, as they will be removed by the parent (in the next line) // But we still need to make sure to reclaim them from the arena and drop their hooks, etc - self.remove_nested_dyn_nodes(node); + self.remove_nested_dyn_nodes(node, to); // Clean up the roots, assuming we need to generate mutations for these // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim) - self.reclaim_roots(node, gen_muts); + self.reclaim_roots(node, gen_muts, to); } - fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) { + fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) { for (idx, _) in node.template.get().roots.iter().enumerate() { if let Some(dy) = node.dynamic_root(idx) { - self.remove_dynamic_node(dy, gen_muts); + self.remove_dynamic_node(dy, gen_muts, to); } else { let id = node.root_ids.borrow()[idx]; if gen_muts { - self.mutations.push(Mutation::Remove { id }); + to.remove_node(id); } self.reclaim(id); } } } - fn reclaim_attributes(&mut self, node: &VNode) { + fn reclaim_attributes(&mut self, node: &VNode, to: &mut impl WriteMutations) { let mut id = None; for (idx, attr) in node.dynamic_attrs.iter().enumerate() { // We'll clean up the root nodes either way, so don't worry @@ -894,7 +926,7 @@ impl VirtualDom { } } - fn remove_nested_dyn_nodes(&mut self, node: &VNode) { + fn remove_nested_dyn_nodes(&mut self, node: &VNode, to: &mut impl WriteMutations) { for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() { let path_len = node .template @@ -904,41 +936,56 @@ impl VirtualDom { .map(|path| path.len()); // Roots are cleaned up automatically above and nodes with a empty path are placeholders if let Some(2..) = path_len { - self.remove_dynamic_node(dyn_node, false) + self.remove_dynamic_node(dyn_node, false, to) } } } - fn remove_dynamic_node(&mut self, node: &DynamicNode, gen_muts: bool) { + fn remove_dynamic_node( + &mut self, + node: &DynamicNode, + gen_muts: bool, + to: &mut impl WriteMutations, + ) { match node { - Component(comp) => self.remove_component_node(comp, gen_muts), - Text(t) => self.remove_text_node(t, gen_muts), - Placeholder(t) => self.remove_placeholder(t, gen_muts), + Component(comp) => self.remove_component_node(comp, gen_muts, to), + Text(t) => self.remove_text_node(t, gen_muts, to), + Placeholder(t) => self.remove_placeholder(t, gen_muts, to), Fragment(nodes) => nodes .iter() - .for_each(|node| self.remove_node(node, gen_muts)), + .for_each(|node| self.remove_node(node, gen_muts, to)), }; } - fn remove_placeholder(&mut self, t: &VPlaceholder, gen_muts: bool) { + fn remove_placeholder( + &mut self, + t: &VPlaceholder, + gen_muts: bool, + to: &mut impl WriteMutations, + ) { if let Some(id) = t.id.take() { if gen_muts { - self.mutations.push(Mutation::Remove { id }); + to.remove_node(id); } self.reclaim(id) } } - fn remove_text_node(&mut self, t: &VText, gen_muts: bool) { + fn remove_text_node(&mut self, t: &VText, gen_muts: bool, to: &mut impl WriteMutations) { if let Some(id) = t.id.take() { if gen_muts { - self.mutations.push(Mutation::Remove { id }); + to.remove_node(id); } self.reclaim(id) } } - fn remove_component_node(&mut self, comp: &VComponent, gen_muts: bool) { + fn remove_component_node( + &mut self, + comp: &VComponent, + gen_muts: bool, + to: &mut impl WriteMutations, + ) { // Remove the component reference from the vcomponent so they're not tied together let scope = comp .scope @@ -947,8 +994,10 @@ impl VirtualDom { // Remove the component from the dom match unsafe { self.get_scope(scope).unwrap().root_node() } { - RenderReturn::Ready(t) => self.remove_node(t, gen_muts), - RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts), + RenderReturn::Ready(t) => self.remove_node(t, gen_muts, to), + RenderReturn::Aborted(placeholder) => { + self.remove_placeholder(placeholder, gen_muts, to) + } }; // Now drop all the resouces diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index e5576310f..570713a91 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -74,9 +74,9 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, CapturedError, - Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, Mutation, Mutations, - Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, - TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, + Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, Mutation, + MutationsVec, Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template, + TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 021eb1406..6ac45b690 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -2,7 +2,7 @@ use rustc_hash::FxHashSet; use crate::{arena::ElementId, AttributeValue, ScopeId, Template}; -/// A container for all the relevant steps to modify the Real DOM +/// Something that can handle the mutations that are generated by the diffing process and apply them to the Real DOM /// /// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the /// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes @@ -13,42 +13,128 @@ use crate::{arena::ElementId, AttributeValue, ScopeId, Template}; /// Templates, however, apply to all subtrees, not just target subtree. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. -#[derive(Debug, Default)] -#[must_use = "not handling edits can lead to visual inconsistencies in UI"] -pub struct Mutations<'a> { - /// The ID of the subtree that these edits are targetting - pub subtree: usize, +pub trait WriteMutations { + /// Register a template with the renderer + fn register_template(&mut self, template: Template<'static>); - /// The list of Scopes that were diffed, created, and removed during the Diff process. - pub dirty_scopes: FxHashSet, - - /// Any templates encountered while diffing the DOM. + /// Add these m children to the target element /// - /// These must be loaded into a cache before applying the edits - pub templates: Vec>, + /// Id: The ID of the element being mounted to + /// M: The number of nodes on the stack to append to the target element + fn append_children(&mut self, id: ElementId, m: usize); - /// Any mutations required to patch the renderer to match the layout of the VirtualDom - pub edits: Vec>, -} - -impl<'a> Mutations<'a> { - /// Rewrites IDs to just be "template", so you can compare the mutations + /// Assign the element at the given path the target ElementId. /// - /// Used really only for testing - pub fn santize(mut self) -> Self { - for edit in self.edits.iter_mut() { - if let Mutation::LoadTemplate { name, .. } = edit { - *name = "template" - } - } + /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per + /// element, hence the use of a single byte. + /// + /// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child. + /// Id: The ID we're assigning to this element/placeholder. This will be used later to modify the element or replace it with another element. + fn assign_node_id(&mut self, path: &'static [u8], id: ElementId); - self - } + /// Create a placeholder in the DOM that we will use later. + /// + /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing + /// + /// Id: The ID we're assigning to this element/placeholder. This will be used later to modify the element or replace it with another element. + fn create_placeholder(&mut self, id: ElementId); - /// Push a new mutation into the dom_edits list - pub(crate) fn push(&mut self, mutation: Mutation<'static>) { - self.edits.push(mutation) - } + /// Create a node specifically for text with the given value + /// + /// Value: The text content of this text node + /// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element. + fn create_text_node(&mut self, value: &str, id: ElementId); + + /// Hydrate an existing text node at the given path with the given text. + /// + /// Assign this text node the given ID since we will likely need to modify this text at a later point + /// + /// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child. + /// Value: The value of the textnode that we want to set the placeholder with + /// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element. + fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId); + + /// Load and clone an existing node from a template saved under that specific name + /// + /// Dioxus guarantees that the renderer will have already been provided the template. + /// When the template is picked up in the template list, it should be saved under its "name" - here, the name + /// + /// Name: The unique "name" of the template based on the template location. When paired with `rsx!`, this is autogenerated + /// Index: The index root we loading from the template. The template is stored as a list of nodes. This index represents the position of that root + /// Id: The ID we're assigning to this element being loaded from the template (This will be used later to move the element around in lists) + fn load_template(&mut self, name: &'static str, index: usize, id: ElementId); + + /// Replace the target element (given by its ID) with the topmost m nodes on the stack + /// + /// id: The ID of the node we're going to replace with new nodes + /// m: The number of nodes on the stack to replace the target element with + fn replace_node_with(&mut self, id: ElementId, m: usize); + + /// Replace an existing element in the template at the given path with the m nodes on the stack + /// + /// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child. + /// M: The number of nodes on the stack to replace the target element with + fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize); + + /// Insert a number of nodes after a given node. + /// + /// Id: The ID of the node to insert after. + /// M: The number of nodes on the stack to insert after the target node. + fn insert_nodes_after(&mut self, id: ElementId, m: usize); + + /// Insert a number of nodes before a given node. + /// + /// Id: The ID of the node to insert before. + /// M: The number of nodes on the stack to insert before the target node. + fn insert_nodes_before(&mut self, id: ElementId, m: usize); + + /// Set the value of a node's attribute. + /// + /// Name: The name of the attribute to set. + /// NS: The (optional) namespace of the attribute. For instance, "style" is in the "style" namespace. + /// Value: The value of the attribute. + /// Id: The ID of the node to set the attribute of. + fn set_attribute( + &mut self, + name: &'static str, + ns: Option<&'static str>, + value: &AttributeValue, + id: ElementId, + ); + + /// Set the text content of a node. + /// + /// Value: The textcontent of the node + /// Id: The ID of the node to set the textcontent of. + fn set_node_text(&mut self, value: &str, id: ElementId); + + /// Create a new Event Listener. + /// + /// Name: The name of the event to listen for. + /// Id: The ID of the node to attach the listener to. + fn create_event_listener(&mut self, name: &'static str, id: ElementId); + + /// Remove an existing Event Listener. + /// + /// Name: The name of the event to remove. + /// Id: The ID of the node to remove. + fn remove_event_listener(&mut self, name: &'static str, id: ElementId); + + /// Remove a particular node from the DOM + /// + /// Id: The ID of the node to remove. + fn remove_node(&mut self, id: ElementId); + + /// Push the given root node onto our stack. + /// + /// Id: The ID of the root node to push. + fn push_root(&mut self, id: ElementId) {} + + /// Swap to a new subtree + fn swap_subtree(&mut self, subtree_index: usize) {} + + /// Mark a scope as dirty + fn mark_scope_dirty(&mut self, scope_id: ScopeId) {} } /// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state @@ -56,7 +142,7 @@ impl<'a> Mutations<'a> { /// /// These edits can be serialized and sent over the network or through any interface #[derive(Debug, PartialEq)] -pub enum Mutation<'a> { +pub enum Mutation { /// Add these m children to the target element AppendChildren { /// The ID of the element being mounted to @@ -98,7 +184,7 @@ pub enum Mutation<'a> { /// Create a node specifically for text with the given value CreateTextNode { /// The text content of this text node - value: &'a str, + value: String, /// The ID we're assigning to this specific text nodes /// @@ -117,7 +203,7 @@ pub enum Mutation<'a> { path: &'static [u8], /// The value of the textnode that we want to set the placeholder with - value: &'a str, + value: String, /// The ID we're assigning to this specific text nodes /// @@ -186,23 +272,23 @@ pub enum Mutation<'a> { /// Set the value of a node's attribute. SetAttribute { /// The name of the attribute to set. - name: &'a str, - - /// The value of the attribute. - value: &'a AttributeValue, - - /// The ID of the node to set the attribute of. - id: ElementId, + name: &'static str, /// The (optional) namespace of the attribute. /// For instance, "style" is in the "style" namespace. - ns: Option<&'a str>, + ns: Option<&'static str>, + + /// The value of the attribute. + value: AttributeValue, + + /// The ID of the node to set the attribute of. + id: ElementId, }, /// Set the textcontent of a node. SetText { /// The textcontent of the node - value: &'a str, + value: String, /// The ID of the node to set the textcontent of. id: ElementId, @@ -211,7 +297,7 @@ pub enum Mutation<'a> { /// Create a new Event Listener. NewEventListener { /// The name of the event to listen for. - name: &'a str, + name: String, /// The ID of the node to attach the listener to. id: ElementId, @@ -220,7 +306,7 @@ pub enum Mutation<'a> { /// Remove an existing Event Listener. RemoveEventListener { /// The name of the event to remove. - name: &'a str, + name: String, /// The ID of the node to remove. id: ElementId, @@ -238,3 +324,186 @@ pub enum Mutation<'a> { id: ElementId, }, } + +/// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values +pub struct MutationsVec { + /// The list of Scopes that were diffed, created, and removed during the Diff process. + pub dirty_scopes: FxHashSet, + + /// Any templates encountered while diffing the DOM. + /// + /// These must be loaded into a cache before applying the edits + pub templates: Vec>, + + /// Any mutations required to patch the renderer to match the layout of the VirtualDom + pub edits: Vec, +} + +impl MutationsVec { + /// Rewrites IDs to just be "template", so you can compare the mutations + /// + /// Used really only for testing + pub fn santize(mut self) -> Self { + for edit in self.edits.iter_mut() { + if let Mutation::LoadTemplate { name, .. } = edit { + *name = "template" + } + } + + self + } +} + +impl WriteMutations for MutationsVec { + fn register_template(&mut self, template: Template<'static>) { + self.templates.push(template) + } + + fn append_children(&mut self, id: ElementId, m: usize) { + self.edits.push(Mutation::AppendChildren { id, m }) + } + + fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) { + self.edits.push(Mutation::AssignId { path, id }) + } + + fn create_placeholder(&mut self, id: ElementId) { + self.edits.push(Mutation::CreatePlaceholder { id }) + } + + fn create_text_node(&mut self, value: &str, id: ElementId) { + self.edits.push(Mutation::CreateTextNode { + value: value.into(), + id, + }) + } + + fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId) { + self.edits.push(Mutation::HydrateText { + path, + value: value.into(), + id, + }) + } + + fn load_template(&mut self, name: &'static str, index: usize, id: ElementId) { + self.edits.push(Mutation::LoadTemplate { name, index, id }) + } + + fn replace_node_with(&mut self, id: ElementId, m: usize) { + self.edits.push(Mutation::ReplaceWith { id, m }) + } + + fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) { + self.edits.push(Mutation::ReplacePlaceholder { path, m }) + } + + fn insert_nodes_after(&mut self, id: ElementId, m: usize) { + self.edits.push(Mutation::InsertAfter { id, m }) + } + + fn insert_nodes_before(&mut self, id: ElementId, m: usize) { + self.edits.push(Mutation::InsertBefore { id, m }) + } + + fn set_attribute( + &mut self, + name: &'static str, + ns: Option<&'static str>, + value: &AttributeValue, + id: ElementId, + ) { + self.edits.push(Mutation::SetAttribute { + name, + ns, + value: match value { + AttributeValue::Text(s) => AttributeValue::Text(s.clone()), + AttributeValue::Bool(b) => AttributeValue::Bool(*b), + AttributeValue::Float(n) => AttributeValue::Float(*n), + AttributeValue::Int(n) => AttributeValue::Int(*n), + AttributeValue::None => AttributeValue::None, + _ => panic!("Cannot serialize attribute value"), + }, + id, + }) + } + + fn set_node_text(&mut self, value: &str, id: ElementId) { + self.edits.push(Mutation::SetText { + value: value.into(), + id, + }) + } + + fn create_event_listener(&mut self, name: &'static str, id: ElementId) { + self.edits.push(Mutation::NewEventListener { + name: name.into(), + id, + }) + } + + fn remove_event_listener(&mut self, name: &'static str, id: ElementId) { + self.edits.push(Mutation::RemoveEventListener { + name: name.into(), + id, + }) + } + + fn remove_node(&mut self, id: ElementId) { + self.edits.push(Mutation::Remove { id }) + } + + fn push_root(&mut self, id: ElementId) { + self.edits.push(Mutation::PushRoot { id }) + } + + fn swap_subtree(&mut self, _subtree_index: usize) {} + + fn mark_scope_dirty(&mut self, scope_id: ScopeId) { + self.dirty_scopes.insert(scope_id); + } +} + +/// A struct that ignores all mutations +pub struct NoOpMutations; + +impl WriteMutations for NoOpMutations { + fn register_template(&mut self, template: Template<'static>) {} + + fn append_children(&mut self, id: ElementId, m: usize) {} + + fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {} + + fn create_placeholder(&mut self, id: ElementId) {} + + fn create_text_node(&mut self, value: &str, id: ElementId) {} + + fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId) {} + + fn load_template(&mut self, name: &'static str, index: usize, id: ElementId) {} + + fn replace_node_with(&mut self, id: ElementId, m: usize) {} + + fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {} + + fn insert_nodes_after(&mut self, id: ElementId, m: usize) {} + + fn insert_nodes_before(&mut self, id: ElementId, m: usize) {} + + fn set_attribute( + &mut self, + name: &'static str, + ns: Option<&'static str>, + value: &AttributeValue, + id: ElementId, + ) { + } + + fn set_node_text(&mut self, value: &str, id: ElementId) {} + + fn create_event_listener(&mut self, name: &'static str, id: ElementId) {} + + fn remove_event_listener(&mut self, name: &'static str, id: ElementId) {} + + fn remove_node(&mut self, id: ElementId) {} +} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 98f510384..1e567fae3 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -5,7 +5,10 @@ use crate::{ any_props::{BoxedAnyProps, VProps}, arena::ElementId, - innerlude::{DirtyScope, ElementRef, ErrorBoundary, Mutations, Scheduler, SchedulerMsg}, + innerlude::{ + DirtyScope, ElementRef, ErrorBoundary, Mutations, NoOpMutations, Scheduler, SchedulerMsg, + WriteMutations, + }, mutations::Mutation, nodes::RenderReturn, nodes::{Template, TemplateId}, @@ -188,8 +191,6 @@ pub struct VirtualDom { // The element ids that are used in the renderer pub(crate) elements: Slab>, - pub(crate) mutations: Mutations<'static>, - pub(crate) runtime: Rc, // Currently suspended scopes @@ -263,7 +264,6 @@ impl VirtualDom { dirty_scopes: Default::default(), templates: Default::default(), elements: Default::default(), - mutations: Mutations::default(), suspended_scopes: Default::default(), }; @@ -544,38 +544,33 @@ impl VirtualDom { /// /// apply_edits(edits); /// ``` - pub fn rebuild(&mut self) -> Mutations { + pub fn rebuild(&mut self, to: &mut impl WriteMutations) { let _runtime = RuntimeGuard::new(self.runtime.clone()); match unsafe { self.run_scope(ScopeId::ROOT) } { // Rebuilding implies we append the created elements to the root RenderReturn::Ready(node) => { let m = self.create_scope(ScopeId::ROOT, &node); - self.mutations.edits.push(Mutation::AppendChildren { - id: ElementId(0), - m, - }); + to.append_children(ElementId(0), m); } // If an error occurs, we should try to render the default error component and context where the error occured RenderReturn::Aborted(placeholder) => { tracing::debug!("Ran into suspended or aborted scope during rebuild"); let id = self.next_element(); placeholder.id.set(Some(id)); - self.mutations.push(Mutation::CreatePlaceholder { id }); + to.create_placeholder(id); } } - - self.finalize() } /// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress /// suspended subtrees. - pub fn render_immediate(&mut self) -> Mutations { + pub fn render_immediate(&mut self, to: &mut impl WriteMutations) { // Build a waker that won't wake up since our deadline is already expired when it's polled let waker = futures_util::task::noop_waker(); let mut cx = std::task::Context::from_waker(&waker); // Now run render with deadline but dont even try to poll any async tasks - let fut = self.render_with_deadline(std::future::ready(())); + let fut = self.render_with_deadline(std::future::ready(()), to); pin_mut!(fut); // The root component is not allowed to be async @@ -596,7 +591,7 @@ impl VirtualDom { self.wait_for_work().await; - _ = self.render_immediate(); + _ = self.render_immediate(&mut NoOpMutations); } } @@ -605,7 +600,11 @@ impl VirtualDom { /// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues. /// /// If no suspense trees are present - pub async fn render_with_deadline(&mut self, deadline: impl Future) -> Mutations { + pub async fn render_with_deadline( + &mut self, + deadline: impl Future, + to: &mut impl WriteMutations, + ) { pin_mut!(deadline); self.process_events(); @@ -625,7 +624,7 @@ impl VirtualDom { let _runtime = RuntimeGuard::new(self.runtime.clone()); // Run the scope and get the mutations let new_nodes = self.run_scope(dirty.id); - self.diff_scope(dirty.id, new_nodes); + self.diff_scope(dirty.id, new_nodes, to); } } @@ -645,16 +644,11 @@ impl VirtualDom { if let Either::Left((_, _)) = select(&mut deadline, pinned).await { // release the borrowed drop(work); - return self.finalize(); + return; } } } - /// Swap the current mutations with a new - fn finalize(&mut self) -> Mutations { - std::mem::take(&mut self.mutations) - } - /// Get the current runtime pub fn runtime(&self) -> Rc { self.runtime.clone() From 717f38a5ec33290437fe3cca36aeb9af6afe7362 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 5 Jan 2024 11:45:01 -0600 Subject: [PATCH 039/377] remove some unused unsafe blocks --- packages/core/src/diff.rs | 6 +++--- packages/core/src/scope_arena.rs | 2 +- packages/core/src/virtual_dom.rs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 385f4a18e..0b41f4916 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -993,7 +993,7 @@ impl VirtualDom { .expect("VComponents to always have a scope"); // Remove the component from the dom - match unsafe { self.get_scope(scope).unwrap().root_node() } { + match self.get_scope(scope).unwrap().root_node() { RenderReturn::Ready(t) => self.remove_node(t, gen_muts, to), RenderReturn::Aborted(placeholder) => { self.remove_placeholder(placeholder, gen_muts, to) @@ -1012,7 +1012,7 @@ impl VirtualDom { Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); - match unsafe { self.get_scope(scope).unwrap().root_node() } { + match self.get_scope(scope).unwrap().root_node() { RenderReturn::Ready(t) => self.find_first_element(t), _ => todo!("cannot handle nonstandard nodes"), } @@ -1028,7 +1028,7 @@ impl VirtualDom { Some(Placeholder(t)) => t.id.get().unwrap(), Some(Component(comp)) => { let scope = comp.scope.get().unwrap(); - match unsafe { self.get_scope(scope).unwrap().root_node() } { + match self.get_scope(scope).unwrap().root_node() { RenderReturn::Ready(t) => self.find_last_element(t), _ => todo!("cannot handle nonstandard nodes"), } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index a1d5d8b89..20cba5480 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -34,7 +34,7 @@ impl VirtualDom { pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn { self.runtime.scope_stack.borrow_mut().push(scope_id); - let new_nodes = unsafe { + let new_nodes = { let scope = &self.scopes[scope_id.0]; let context = scope.context(); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1e567fae3..b4209e7aa 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -549,7 +549,7 @@ impl VirtualDom { match unsafe { self.run_scope(ScopeId::ROOT) } { // Rebuilding implies we append the created elements to the root RenderReturn::Ready(node) => { - let m = self.create_scope(ScopeId::ROOT, &node); + let m = self.create_scope(ScopeId::ROOT, &node, to); to.append_children(ElementId(0), m); } // If an error occurs, we should try to render the default error component and context where the error occured From 113b2c1529ca95908590359bff0f7226165bb76e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 5 Jan 2024 12:07:29 -0600 Subject: [PATCH 040/377] port over more diffing + creation logic --- packages/core/src/create.rs | 10 ++--- packages/core/src/diff.rs | 67 ++++++++++++++------------------ packages/core/src/mutations.rs | 8 ++-- packages/core/src/nodes.rs | 16 ++++---- packages/core/src/scopes.rs | 6 +-- packages/core/src/virtual_dom.rs | 28 +++++++++---- packages/core/tests/fuzzing.rs | 2 +- 7 files changed, 69 insertions(+), 68 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 3da568634..06cb07603 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -408,11 +408,7 @@ impl VirtualDom { } /// Insert a new template into the VirtualDom's template registry - pub(crate) fn register_template_first_byte_index( - &mut self, - mut template: Template<'static>, - to: &mut impl WriteMutations, - ) { + pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template) { // First, make sure we mark the template as seen, regardless if we process it let (path, _) = template.name.rsplit_once(':').unwrap(); if let Some((_, old_template)) = self @@ -435,7 +431,7 @@ impl VirtualDom { // If it's all dynamic nodes, then we don't need to register it if !template.is_completely_dynamic() { - to.register_template(template); + self.queued_templates.push(template); } } @@ -444,7 +440,7 @@ impl VirtualDom { #[allow(unused_mut)] pub(crate) fn register_template( &mut self, - mut template: Template<'static>, + mut template: Template, to: &mut impl WriteMutations, ) { let (path, byte_index) = template.name.rsplit_once(':').unwrap(); diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 0b41f4916..61cb8e178 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -6,7 +6,6 @@ use crate::{ innerlude::{ DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText, WriteMutations, }, - mutations::Mutation, nodes::RenderReturn, nodes::{DynamicNode, VNode}, scopes::ScopeId, @@ -63,15 +62,10 @@ impl VirtualDom { *p.parent.borrow_mut() = l.parent.borrow().clone(); to.create_placeholder(id); - self.remove_node(l, true, to); + to.insert_nodes_before(id, 1); - // We want to optimize the replace case to use one less mutation if possible - // Since mutations are done in reverse, the last node removed will be the first in the stack - // Instead of *just* removing it, we can use the replace mutation - match self.mutations.edits.pop().unwrap() { - Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m: 1 }), - _ => panic!("Expected remove mutation from remove_node"), - }; + // TODO: Instead of *just* removing it, we can use the replace mutation + self.remove_node(l, true, to); } fn diff_node( @@ -250,17 +244,12 @@ impl VirtualDom { parent: Option, to: &mut impl WriteMutations, ) { - let m = self.create_component_node(parent, right); + let m = self.create_component_node(parent, right, to); + // TODO: Instead of *just* removing it, we can use the replace mutation self.remove_component_node(left, true, to); - // We want to optimize the replace case to use one less mutation if possible - // Since mutations are done in reverse, the last node removed will be the first in the stack - // Instead of *just* removing it, we can use the replace mutation - match self.mutations.edits.pop().unwrap() { - Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m }), - at => panic!("Expected remove mutation from remove_node {:#?}", at), - }; + todo!() } /// Lightly diff the two templates, checking only their roots. @@ -656,7 +645,7 @@ impl VirtualDom { let new_idx = idx + last + 1; let old_index = new_index_to_old_index[new_idx]; if old_index == u32::MAX as usize { - nodes_created += self.create(new_node); + nodes_created += self.create(new_node, to); } else { self.diff_node(&old[old_index], new_node, to); nodes_created += self.push_all_real_nodes(new_node, to); @@ -679,7 +668,7 @@ impl VirtualDom { let new_idx = idx + next + 1; let old_index = new_index_to_old_index[new_idx]; if old_index == u32::MAX as usize { - nodes_created += self.create(new_node); + nodes_created += self.create(new_node, to); } else { self.diff_node(&old[old_index], new_node, to); nodes_created += self.push_all_real_nodes(new_node, to); @@ -702,7 +691,7 @@ impl VirtualDom { for (idx, new_node) in new[..first_lis].iter().enumerate() { let old_index = new_index_to_old_index[idx]; if old_index == u32::MAX as usize { - nodes_created += self.create(new_node); + nodes_created += self.create(new_node, to); } else { self.diff_node(&old[old_index], new_node, to); nodes_created += self.push_all_real_nodes(new_node, to); @@ -768,7 +757,7 @@ impl VirtualDom { .into_iter() .map(|child| { self.assign_boundary_ref(parent, child); - self.create(child) + self.create(child, to) }) .sum() } @@ -807,7 +796,7 @@ impl VirtualDom { ) { let m = self.create_children(r, Some(parent), to); let id = l.id.get().unwrap(); - to.replace_with(id, m); + to.replace_node_with(id, m); self.reclaim(id); } @@ -820,15 +809,10 @@ impl VirtualDom { ) { let m = self.create_children(right, parent, to); - self.remove_node(left, true, to); + // TODO: Instead of *just* removing it, we can use the replace mutation + to.insert_nodes_before(self.find_first_element(left), m); - // We want to optimize the replace case to use one less mutation if possible - // Since mutations are done in reverse, the last node removed will be the first in the stack - // Instead of *just* removing it, we can use the replace mutation - match self.mutations.edits.pop().unwrap() { - Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m }), - _ => panic!("Expected remove mutation from remove_node"), - }; + self.remove_node(left, true, to); } fn node_to_placeholder( @@ -846,15 +830,22 @@ impl VirtualDom { to.create_placeholder(placeholder); - self.remove_nodes(l, to); + self.replace_nodes(l, 1, to); + } + /// Replace many nodes with a number of nodes on the stack + fn replace_nodes(&mut self, nodes: &[VNode], m: usize, to: &mut impl WriteMutations) { // We want to optimize the replace case to use one less mutation if possible // Since mutations are done in reverse, the last node removed will be the first in the stack - // Instead of *just* removing it, we can use the replace mutation - match self.mutations.edits.pop().unwrap() { - Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m: 1 }), - _ => panic!("Expected remove mutation from remove_node"), - }; + // TODO: Instead of *just* removing it, we can use the replace mutation + to.insert_nodes_before(self.find_first_element(&nodes[0]), m); + + debug_assert!( + !nodes.is_empty(), + "replace_nodes must have at least one node" + ); + + self.remove_nodes(&nodes, to); } /// Remove these nodes from the dom @@ -869,7 +860,7 @@ impl VirtualDom { fn remove_node(&mut self, node: &VNode, gen_muts: bool, to: &mut impl WriteMutations) { // Clean up any attributes that have claimed a static node as dynamic for mount/unmounta // Will not generate mutations! - self.reclaim_attributes(node, to); + self.reclaim_attributes(node); // Remove the nested dynamic nodes // We don't generate mutations for these, as they will be removed by the parent (in the next line) @@ -895,7 +886,7 @@ impl VirtualDom { } } - fn reclaim_attributes(&mut self, node: &VNode, to: &mut impl WriteMutations) { + fn reclaim_attributes(&mut self, node: &VNode) { let mut id = None; for (idx, attr) in node.dynamic_attrs.iter().enumerate() { // We'll clean up the root nodes either way, so don't worry diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 6ac45b690..0493194f2 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -15,7 +15,7 @@ use crate::{arena::ElementId, AttributeValue, ScopeId, Template}; /// Mutations are the only link between the RealDOM and the VirtualDOM. pub trait WriteMutations { /// Register a template with the renderer - fn register_template(&mut self, template: Template<'static>); + fn register_template(&mut self, template: Template); /// Add these m children to the target element /// @@ -333,7 +333,7 @@ pub struct MutationsVec { /// Any templates encountered while diffing the DOM. /// /// These must be loaded into a cache before applying the edits - pub templates: Vec>, + pub templates: Vec