diff --git a/Cargo.lock b/Cargo.lock index f9d3e00ba..c86599f60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4031,6 +4031,7 @@ dependencies = [ "criterion", "parking_lot", "rand 0.8.5", + "tracing", ] [[package]] diff --git a/packages/core-macro/src/props/mod.rs b/packages/core-macro/src/props/mod.rs index f5933aa37..8bbd687b5 100644 --- a/packages/core-macro/src/props/mod.rs +++ b/packages/core-macro/src/props/mod.rs @@ -652,8 +652,11 @@ mod struct_info { let new_value: &_ = &*new.#signal_fields.peek(); (&old_value).compare(&&new_value) }; + // Make the old fields point to the new fields + #signal_fields.point_to(new.#signal_fields).unwrap(); if !field_eq { - (#signal_fields).__set(new.#signal_fields.__take()); + // If the fields are not equal, mark the signal as dirty to rerun any subscribers + (#signal_fields).mark_dirty(); } // Move the old value back self.#signal_fields = #signal_fields; @@ -683,13 +686,13 @@ mod struct_info { quote! { // If the event handler is None, we don't need to update it if let (Some(old_handler), Some(new_handler)) = (self.#name.as_mut(), new.#name.as_ref()) { - old_handler.__set(new_handler.__take()); + old_handler.__point_to(new_handler); } } } else { quote! { // Update the event handlers - self.#name.__set(new.#name.__take()); + self.#name.__point_to(&new.#name); } } }).collect(); @@ -1447,7 +1450,7 @@ Finally, call `.build()` to create the instance of `{name}`. #[derive(Clone)] #vis struct #name #generics_with_bounds #where_clause { inner: #original_name #ty_generics, - owner: Owner, + owner: dioxus_core::internal::generational_box::Owner, } impl #original_impl_generics PartialEq for #name #ty_generics #where_clause { diff --git a/packages/core-macro/tests/values_memoize_in_place.rs b/packages/core-macro/tests/values_memoize_in_place.rs index fa782c422..d29f624b2 100644 --- a/packages/core-macro/tests/values_memoize_in_place.rs +++ b/packages/core-macro/tests/values_memoize_in_place.rs @@ -161,3 +161,52 @@ fn spreads_memorize_in_place() { assert!(props.memoize(&CompProps::builder().build())); assert_eq!(props.attributes, vec![]); } + +// Regression test for https://github.com/DioxusLabs/dioxus/issues/2331 +#[test] +fn cloning_read_only_signal_components_work() { + fn app() -> Element { + if generation() < 5 { + println!("Generating new props"); + needs_update(); + } + + let read_only_signal_rsx = rsx! { + TakesReadOnlySignalNonClone { sig: NonCloneable(generation() as i32) } + TakesReadOnlySignalNum { sig: generation() as i32 } + }; + + rsx! { + {read_only_signal_rsx.clone()} + {read_only_signal_rsx} + } + } + + struct NonCloneable(T); + + #[component] + fn TakesReadOnlySignalNum(sig: ReadOnlySignal) -> Element { + rsx! {} + } + + #[component] + fn TakesReadOnlySignalNonClone(sig: ReadOnlySignal>) -> Element { + rsx! {} + } + + set_event_converter(Box::new(dioxus::html::SerializedHtmlEventConverter)); + let mut dom = VirtualDom::new(app); + + let mutations = dom.rebuild_to_vec(); + println!("{:#?}", mutations); + dom.mark_dirty(ScopeId::APP); + for _ in 0..20 { + let event = Event::new( + Rc::new(PlatformEventData::new(Box::::default())) as Rc, + true, + ); + dom.runtime().handle_event("click", event, ElementId(1)); + dom.render_immediate(&mut dioxus_core::NoOpMutations); + } + dom.render_immediate(&mut dioxus_core::NoOpMutations); +} diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 486d60cf1..0cf19bfd5 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -1,6 +1,6 @@ use crate::{properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId}; use generational_box::GenerationalBox; -use std::{cell::RefCell, marker::PhantomData, rc::Rc}; +use std::{cell::RefCell, marker::PhantomData, panic::Location, rc::Rc}; /// A wrapper around some generic data that handles the event's state /// @@ -431,19 +431,10 @@ impl PartialEq for Callback { } pub(super) struct ExternalListenerCallback { - callback: Rc Ret>>, + callback: Box Ret>, runtime: std::rc::Weak, } -impl Clone for ExternalListenerCallback { - fn clone(&self) -> Self { - Self { - callback: self.callback.clone(), - runtime: self.runtime.clone(), - } - } -} - impl Callback { /// Create a new [`Callback`] from an [`FnMut`]. The callback is owned by the current scope and will be dropped when the scope is dropped. /// This should not be called directly in the body of a component because it will not be dropped until the component is dropped. @@ -456,9 +447,8 @@ impl Callback { .current_scope_id() .unwrap_or_else(|e| panic!("{}", e)); let owner = crate::innerlude::current_owner::(); - let callback = owner.insert(Some(ExternalListenerCallback { - callback: Rc::new(RefCell::new(move |event: Args| f(event).spawn())) - as Rc Ret>>, + let callback = owner.insert_rc(Some(ExternalListenerCallback { + callback: Box::new(move |event: Args| f(event).spawn()), runtime: Rc::downgrade(&runtime), })); Self { callback, origin } @@ -471,11 +461,13 @@ impl Callback { let origin = runtime .current_scope_id() .unwrap_or_else(|e| panic!("{}", e)); - let callback = GenerationalBox::leak(Some(ExternalListenerCallback { - callback: Rc::new(RefCell::new(move |event: Args| f(event).spawn())) - as Rc Ret>>, - runtime: Rc::downgrade(&runtime), - })); + let callback = GenerationalBox::leak_rc( + Some(ExternalListenerCallback { + callback: Box::new(move |event: Args| f(event).spawn()), + runtime: Rc::downgrade(&runtime), + }), + Location::caller(), + ); Self { callback, origin } } @@ -484,16 +476,13 @@ impl Callback { /// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic. #[track_caller] pub fn call(&self, arguments: Args) -> Ret { - if let Some(callback) = self.callback.read().as_ref() { + if let Some(callback) = self.callback.write().as_mut() { let runtime = callback .runtime .upgrade() .expect("Callback was called after the runtime was dropped"); let _guard = RuntimeGuard::new(runtime.clone()); - runtime.with_scope_on_stack(self.origin, || { - let mut callback = callback.callback.borrow_mut(); - callback(arguments) - }) + runtime.with_scope_on_stack(self.origin, || (callback.callback)(arguments)) } else { panic!("Callback was manually dropped") } @@ -511,24 +500,19 @@ impl Callback { self.callback.set(None); } - #[doc(hidden)] - /// This should only be used by the `rsx!` macro. - pub fn __set(&mut self, value: Rc Ret>>) { + /// Replace the function in the callback with a new one + pub fn replace(&mut self, callback: Box Ret>) { + let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e)); self.callback.set(Some(ExternalListenerCallback { - callback: value, - runtime: Rc::downgrade(&Runtime::current().unwrap()), + callback, + runtime: Rc::downgrade(&runtime), })); } #[doc(hidden)] /// This should only be used by the `rsx!` macro. - pub fn __take(&self) -> Rc Ret>> { - self.callback - .read() - .as_ref() - .expect("Callback was manually dropped") - .callback - .clone() + pub fn __point_to(&mut self, other: &Self) { + self.callback.point_to(other.callback).unwrap(); } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 10e7e909f..57c439a5f 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -41,6 +41,9 @@ pub mod internal { HotReloadDynamicAttribute, HotReloadDynamicNode, HotReloadLiteral, HotReloadTemplateWithLocation, HotReloadedTemplate, HotreloadedLiteral, NamedAttribute, }; + + #[doc(hidden)] + pub use generational_box; } pub(crate) mod innerlude { diff --git a/packages/core/src/suspense/component.rs b/packages/core/src/suspense/component.rs index 6af8f05bd..cd9626138 100644 --- a/packages/core/src/suspense/component.rs +++ b/packages/core/src/suspense/component.rs @@ -50,7 +50,7 @@ where } fn memoize(&mut self, new: &Self) -> bool { let equal = self == new; - self.fallback.__set(new.fallback.__take()); + self.fallback.__point_to(&new.fallback); if !equal { let new_clone = new.clone(); self.children = new_clone.children; diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 419eb1704..68d607aee 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -11,6 +11,7 @@ keywords = ["generational", "box", "memory", "allocator"] [dependencies] parking_lot = "0.12.1" +tracing.workspace = true [dev-dependencies] rand = { workspace = true } diff --git a/packages/generational-box/src/entry.rs b/packages/generational-box/src/entry.rs index 0363b63d2..019f02e0e 100644 --- a/packages/generational-box/src/entry.rs +++ b/packages/generational-box/src/entry.rs @@ -2,36 +2,66 @@ use crate::{ BorrowError, BorrowMutError, GenerationalLocation, GenerationalRefBorrowGuard, GenerationalRefBorrowMutGuard, }; +use std::{ + num::NonZeroU64, + sync::atomic::{AtomicU64, Ordering}, +}; -pub(crate) struct StorageEntry { - generation: u64, - - pub data: Option, +pub(crate) struct RcStorageEntry { + ref_count: AtomicU64, + pub data: T, } -impl Default for StorageEntry { - fn default() -> Self { +impl RcStorageEntry { + pub const fn new(data: T) -> Self { Self { - generation: 0, - data: None, + ref_count: AtomicU64::new(0), + data, } } + + pub fn add_ref(&self) { + self.ref_count.fetch_add(1, Ordering::SeqCst); + } + + pub fn drop_ref(&self) -> bool { + let new_ref_count = self.ref_count.fetch_sub(1, Ordering::SeqCst); + new_ref_count == 0 + } +} + +pub(crate) struct StorageEntry { + generation: NonZeroU64, + pub(crate) data: T, } impl StorageEntry { + pub const fn new(data: T) -> Self { + Self { + generation: NonZeroU64::MIN, + data, + } + } + pub fn valid(&self, location: &GenerationalLocation) -> bool { self.generation == location.generation } pub fn increment_generation(&mut self) { - self.generation += 1; + self.generation = self.generation.checked_add(1).unwrap(); } - pub fn generation(&self) -> u64 { + pub fn generation(&self) -> NonZeroU64 { self.generation } } +impl Default for StorageEntry { + fn default() -> Self { + Self::new(T::default()) + } +} + #[derive(Default)] pub(crate) struct MemoryLocationBorrowInfo( #[cfg(any(debug_assertions, feature = "debug_borrows"))] diff --git a/packages/generational-box/src/error.rs b/packages/generational-box/src/error.rs index 74bd8882a..44299766d 100644 --- a/packages/generational-box/src/error.rs +++ b/packages/generational-box/src/error.rs @@ -5,7 +5,10 @@ use std::fmt::Display; use crate::GenerationalLocation; /// A result that can be returned from a borrow operation. -pub type BorrowResult = std::result::Result; +pub type BorrowResult = std::result::Result; + +/// A result that can be returned from a borrow mut operation. +pub type BorrowMutResult = std::result::Result; #[derive(Debug, Clone, PartialEq)] /// An error that can occur when trying to borrow a value. @@ -50,6 +53,15 @@ impl Display for BorrowMutError { impl Error for BorrowMutError {} +impl From for BorrowMutError { + fn from(error: BorrowError) -> Self { + match error { + BorrowError::Dropped(error) => BorrowMutError::Dropped(error), + BorrowError::AlreadyBorrowedMut(error) => BorrowMutError::AlreadyBorrowedMut(error), + } + } +} + /// An error that can occur when trying to use a value that has been dropped. #[derive(Debug, Copy, Clone, PartialEq)] pub struct ValueDroppedError { diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 552c142a9..0d903a9f0 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -5,6 +5,7 @@ use parking_lot::Mutex; use std::{ fmt::Debug, marker::PhantomData, + num::NonZeroU64, ops::{Deref, DerefMut}, sync::Arc, }; @@ -24,7 +25,7 @@ mod unsync; #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct GenerationalBoxId { data_ptr: *const (), - generation: u64, + generation: NonZeroU64, } // Safety: GenerationalBoxId is Send and Sync because there is no way to access the pointer. @@ -54,9 +55,19 @@ impl> GenerationalBox { /// Create a new generational box by leaking a value into the storage. This is useful for creating /// a box that needs to be manually dropped with no owners. #[track_caller] - pub fn leak(value: T) -> Self { - let location = S::claim(std::panic::Location::caller()); - location.set(value); + pub fn leak(value: T, location: &'static std::panic::Location<'static>) -> Self { + let location = S::new(value, location); + Self { + raw: location, + _marker: PhantomData, + } + } + + /// Create a new reference counted generational box by leaking a value into the storage. This is useful for creating + /// a box that needs to be manually dropped with no owners. + #[track_caller] + pub fn leak_rc(value: T, location: &'static std::panic::Location<'static>) -> Self { + let location = S::new_rc(value, location); Self { raw: location, _marker: PhantomData, @@ -98,8 +109,12 @@ impl> GenerationalBox { } /// Set the value. Panics if the value is no longer valid. - pub fn set(&self, value: T) { - S::set(self.raw, value); + #[track_caller] + pub fn set(&self, value: T) + where + T: 'static, + { + *self.write() = value; } /// Returns true if the pointer is equal to the other pointer. @@ -108,24 +123,30 @@ impl> GenerationalBox { } /// Drop the value out of the generational box and invalidate the generational box. - /// This will return the value if the value was taken. - pub fn manually_drop(&self) -> Option + pub fn manually_drop(&self) where T: 'static, { - self.raw.take() + self.raw.recycle(); } /// Try to get the location the generational box was created at. In release mode this will always return None. pub fn created_at(&self) -> Option<&'static std::panic::Location<'static>> { - #[cfg(debug_assertions)] - { - Some(self.raw.location.created_at) - } - #[cfg(not(debug_assertions))] - { - None - } + self.raw.location.created_at() + } + + /// Get a reference to the value + #[track_caller] + pub fn leak_reference(&self) -> BorrowResult> { + Ok(Self { + raw: S::new_reference(self.raw)?, + _marker: std::marker::PhantomData, + }) + } + + /// Change this box to point to another generational box + pub fn point_to(&mut self, other: GenerationalBox) -> BorrowResult { + S::change_reference(self.raw, other.raw) } } @@ -140,17 +161,35 @@ impl Clone for GenerationalBox { /// A trait for a storage backing type. (RefCell, RwLock, etc.) pub trait Storage: AnyStorage + 'static { /// Try to read the value. Returns None if the value is no longer valid. - fn try_read( - location: GenerationalPointer, - ) -> Result, BorrowError>; + fn try_read(pointer: GenerationalPointer) -> BorrowResult>; /// Try to write the value. Returns None if the value is no longer valid. - fn try_write( - location: GenerationalPointer, - ) -> Result, BorrowMutError>; + fn try_write(pointer: GenerationalPointer) -> BorrowMutResult>; - /// Set the value if the location is valid - fn set(location: GenerationalPointer, value: Data); + /// Create a new memory location. This will either create a new memory location or recycle an old one. + fn new( + value: Data, + caller: &'static std::panic::Location<'static>, + ) -> GenerationalPointer; + + /// Create a new reference counted memory location. This will either create a new memory location or recycle an old one. + fn new_rc( + value: Data, + caller: &'static std::panic::Location<'static>, + ) -> GenerationalPointer; + + /// Reference another location if the location is valid + /// + /// This method may return an error if the other box is no longer valid or it is already borrowed mutably. + fn new_reference(inner: GenerationalPointer) -> BorrowResult>; + + /// Change the reference a signal is pointing to + /// + /// This method may return an error if the other box is no longer valid or it is already borrowed mutably. + fn change_reference( + pointer: GenerationalPointer, + rc_pointer: GenerationalPointer, + ) -> BorrowResult; } /// A trait for any storage backing type. @@ -206,10 +245,7 @@ pub trait AnyStorage: Default + 'static { fn data_ptr(&self) -> *const (); /// Recycle a memory location. This will drop the memory location and return it to the runtime. - fn recycle(location: GenerationalPointer) -> Option>; - - /// Claim a new memory location. This will either create a new memory location or recycle an old one. - fn claim(caller: &'static std::panic::Location<'static>) -> GenerationalPointer; + fn recycle(location: GenerationalPointer); /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates. fn owner() -> Owner { @@ -222,11 +258,24 @@ pub trait AnyStorage: Default + 'static { #[derive(Debug, Clone, Copy)] pub(crate) struct GenerationalLocation { /// The generation this location is associated with. Using the location after this generation is invalidated will return errors. - generation: u64, + generation: NonZeroU64, #[cfg(any(debug_assertions, feature = "debug_ownership"))] created_at: &'static std::panic::Location<'static>, } +impl GenerationalLocation { + pub(crate) fn created_at(&self) -> Option<&'static std::panic::Location<'static>> { + #[cfg(debug_assertions)] + { + Some(self.created_at) + } + #[cfg(not(debug_assertions))] + { + None + } + } +} + /// A pointer to a specific generational box and generation in that box. pub struct GenerationalPointer { /// The storage that is backing this location @@ -261,20 +310,6 @@ impl Clone for GenerationalPointer { impl Copy for GenerationalPointer {} impl GenerationalPointer { - fn take(self) -> Option - where - S: Storage, - { - S::recycle(self).map(|value| *(value.downcast().unwrap())) - } - - fn set(self, value: T) - where - S: Storage, - { - S::set(self, value) - } - #[track_caller] fn try_read(self) -> Result, BorrowError> where @@ -346,6 +381,32 @@ impl Owner { self.insert_with_caller(value, std::panic::Location::caller()) } + /// Create a new reference counted box. The box will be dropped when all references are dropped. + #[track_caller] + pub fn insert_rc(&self, value: T) -> GenerationalBox + where + S: Storage, + { + self.insert_rc_with_caller(value, 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_rc_with_caller( + &self, + value: T, + caller: &'static std::panic::Location<'static>, + ) -> GenerationalBox + where + S: Storage, + { + let location = S::new_rc(value, caller); + self.0.lock().owned.push(location); + GenerationalBox { + raw: location, + _marker: std::marker::PhantomData, + } + } + /// 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, @@ -355,8 +416,7 @@ impl Owner { where S: Storage, { - let location = S::claim(caller); - location.set(value); + let location = S::new(value, caller); self.0.lock().owned.push(location); GenerationalBox { raw: location, @@ -364,14 +424,19 @@ impl Owner { } } - /// 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. + /// Create a new reference to an existing box. The reference will be dropped when the owner is dropped. + /// + /// This method may return an error if the other box is no longer valid or it is already borrowed mutably. #[track_caller] - pub fn invalid(&self) -> GenerationalBox { - let location = S::claim(std::panic::Location::caller()); - self.0.lock().owned.push(location); - GenerationalBox { - raw: location, - _marker: PhantomData, - } + pub fn insert_reference( + &self, + other: GenerationalBox, + ) -> BorrowResult> + where + S: Storage, + { + let location = other.leak_reference()?; + self.0.lock().owned.push(location.raw); + Ok(location) } } diff --git a/packages/generational-box/src/sync.rs b/packages/generational-box/src/sync.rs index 6f2c7ca59..fab0373a7 100644 --- a/packages/generational-box/src/sync.rs +++ b/packages/generational-box/src/sync.rs @@ -1,20 +1,174 @@ use parking_lot::{ MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, }; -use std::sync::{Arc, OnceLock}; +use std::{ + any::Any, + fmt::Debug, + num::NonZeroU64, + sync::{Arc, OnceLock}, +}; use crate::{ - entry::{MemoryLocationBorrowInfo, StorageEntry}, + entry::{MemoryLocationBorrowInfo, RcStorageEntry, StorageEntry}, error::{self, ValueDroppedError}, references::{GenerationalRef, GenerationalRefMut}, - AnyStorage, GenerationalLocation, GenerationalPointer, Storage, + AnyStorage, BorrowError, BorrowMutError, BorrowMutResult, BorrowResult, GenerationalLocation, + GenerationalPointer, Storage, }; +type RwLockStorageEntryRef = RwLockReadGuard<'static, StorageEntry>; +type RwLockStorageEntryMut = RwLockWriteGuard<'static, StorageEntry>; + +pub(crate) enum RwLockStorageEntryData { + Reference(GenerationalPointer), + Rc(RcStorageEntry>), + Data(Box), + Empty, +} + +impl Default for RwLockStorageEntryData { + fn default() -> Self { + Self::Empty + } +} + +impl Debug for RwLockStorageEntryData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Reference(location) => write!(f, "Reference({:?})", location), + Self::Rc(_) => write!(f, "Rc"), + Self::Data(_) => write!(f, "Data"), + Self::Empty => write!(f, "Empty"), + } + } +} + +impl RwLockStorageEntryData { + pub const fn new_full(data: Box) -> Self { + Self::Data(data) + } +} + /// 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 { borrow_info: MemoryLocationBorrowInfo, - data: RwLock>>, + data: RwLock>, +} + +impl SyncStorage { + pub(crate) fn read( + pointer: GenerationalPointer, + ) -> BorrowResult>> { + Self::get_split_ref(pointer).map(|(_, guard)| { + RwLockReadGuard::map(guard, |data| match &data.data { + RwLockStorageEntryData::Data(data) => data, + RwLockStorageEntryData::Rc(data) => &data.data, + _ => unreachable!(), + }) + }) + } + + pub(crate) fn get_split_ref( + mut pointer: GenerationalPointer, + ) -> BorrowResult<(GenerationalPointer, RwLockStorageEntryRef)> { + loop { + let borrow = pointer.storage.data.read(); + if !borrow.valid(&pointer.location) { + return Err(BorrowError::Dropped(ValueDroppedError::new_for_location( + pointer.location, + ))); + } + match &borrow.data { + // If this is a reference, keep traversing the pointers + RwLockStorageEntryData::Reference(data) => { + pointer = *data; + } + // Otherwise return the value + RwLockStorageEntryData::Data(_) | RwLockStorageEntryData::Rc(_) => { + return Ok((pointer, borrow)); + } + RwLockStorageEntryData::Empty => { + return Err(BorrowError::Dropped(ValueDroppedError::new_for_location( + pointer.location, + ))); + } + } + } + } + + pub(crate) fn write( + pointer: GenerationalPointer, + ) -> BorrowMutResult>> + { + Self::get_split_mut(pointer).map(|(_, guard)| { + RwLockWriteGuard::map(guard, |data| match &mut data.data { + RwLockStorageEntryData::Data(data) => data, + RwLockStorageEntryData::Rc(data) => &mut data.data, + _ => unreachable!(), + }) + }) + } + + pub(crate) fn get_split_mut( + mut pointer: GenerationalPointer, + ) -> BorrowMutResult<(GenerationalPointer, RwLockStorageEntryMut)> { + loop { + let borrow = pointer.storage.data.write(); + if !borrow.valid(&pointer.location) { + return Err(BorrowMutError::Dropped( + ValueDroppedError::new_for_location(pointer.location), + )); + } + match &borrow.data { + // If this is a reference, keep traversing the pointers + RwLockStorageEntryData::Reference(data) => { + pointer = *data; + } + // Otherwise return the value + RwLockStorageEntryData::Data(_) | RwLockStorageEntryData::Rc(_) => { + return Ok((pointer, borrow)); + } + RwLockStorageEntryData::Empty => { + return Err(BorrowMutError::Dropped( + ValueDroppedError::new_for_location(pointer.location), + )); + } + } + } + } + + fn create_new( + value: RwLockStorageEntryData, + #[allow(unused)] caller: &'static std::panic::Location<'static>, + ) -> GenerationalPointer { + match sync_runtime().lock().pop() { + Some(storage) => { + let mut write = storage.data.write(); + let location = GenerationalLocation { + generation: write.generation(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: caller, + }; + write.data = value; + GenerationalPointer { storage, location } + } + None => { + let storage: &'static Self = &*Box::leak(Box::new(Self { + borrow_info: Default::default(), + data: RwLock::new(StorageEntry::new(value)), + })); + + let location = GenerationalLocation { + generation: NonZeroU64::MIN, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: caller, + }; + + GenerationalPointer { storage, location } + } + } + } } static SYNC_RUNTIME: OnceLock>>> = OnceLock::new(); @@ -71,42 +225,50 @@ impl AnyStorage for SyncStorage { self.data.data_ptr() as *const () } - #[track_caller] - #[allow(unused)] - fn claim(caller: &'static std::panic::Location<'static>) -> GenerationalPointer { - match sync_runtime().lock().pop() { - Some(mut storage) => { - let location = GenerationalLocation { - generation: storage.data.read().generation(), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - created_at: caller, - }; - GenerationalPointer { storage, location } - } - None => { - let storage: &'static Self = &*Box::leak(Box::default()); - - let location = GenerationalLocation { - generation: 0, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - created_at: caller, - }; - - GenerationalPointer { storage, location } - } - } - } - - fn recycle(pointer: GenerationalPointer) -> Option> { + fn recycle(pointer: GenerationalPointer) { let mut borrow_mut = pointer.storage.data.write(); + // First check if the generation is still valid if !borrow_mut.valid(&pointer.location) { - return None; + return; } + borrow_mut.increment_generation(); - let old_data = borrow_mut.data.take(); + + // Then decrement the reference count or drop the value if it's the last reference + match &mut borrow_mut.data { + // If this is the original reference, drop the value + RwLockStorageEntryData::Data(_) => borrow_mut.data = RwLockStorageEntryData::Empty, + // If this is a rc, just ignore the drop + RwLockStorageEntryData::Rc(_) => {} + // If this is a reference, decrement the reference count + RwLockStorageEntryData::Reference(reference) => { + drop_ref(*reference); + } + RwLockStorageEntryData::Empty => {} + } + sync_runtime().lock().push(pointer.storage); - old_data.map(|data| data as Box) + } +} + +fn drop_ref(pointer: GenerationalPointer) { + let mut borrow_mut = pointer.storage.data.write(); + + // First check if the generation is still valid + if !borrow_mut.valid(&pointer.location) { + return; + } + + if let RwLockStorageEntryData::Rc(entry) = &mut borrow_mut.data { + // Decrement the reference count + if entry.drop_ref() { + // If the reference count is now zero, drop the value + borrow_mut.data = RwLockStorageEntryData::Empty; + sync_runtime().lock().push(pointer.storage); + } + } else { + unreachable!("References should always point to a data entry directly"); } } @@ -115,15 +277,11 @@ impl Storage for SyncStorage { fn try_read( pointer: GenerationalPointer, ) -> Result, error::BorrowError> { - let read = pointer.storage.data.read(); + let read = Self::read(pointer)?; - let read = RwLockReadGuard::try_map(read, |any| { - // Verify the generation is still correct - if !any.valid(&pointer.location) { - return None; - } + let read = MappedRwLockReadGuard::try_map(read, |any| { // Then try to downcast - any.data.as_ref()?.downcast_ref() + any.downcast_ref() }); match read { Ok(guard) => Ok(GenerationalRef::new( @@ -140,15 +298,11 @@ impl Storage for SyncStorage { fn try_write( pointer: GenerationalPointer, ) -> Result, error::BorrowMutError> { - let write = pointer.storage.data.write(); + let write = Self::write(pointer)?; - let write = RwLockWriteGuard::try_map(write, |any| { - // Verify the generation is still correct - if !any.valid(&pointer.location) { - return None; - } + let write = MappedRwLockWriteGuard::try_map(write, |any| { // Then try to downcast - any.data.as_mut()?.downcast_mut() + any.downcast_mut() }); match write { Ok(guard) => Ok(GenerationalRefMut::new( @@ -161,12 +315,78 @@ impl Storage for SyncStorage { } } - fn set(pointer: GenerationalPointer, value: T) { - let mut write = pointer.storage.data.write(); - // First check if the generation is still valid - if !write.valid(&pointer.location) { - return; + fn new(value: T, caller: &'static std::panic::Location<'static>) -> GenerationalPointer { + Self::create_new(RwLockStorageEntryData::new_full(Box::new(value)), caller) + } + + fn new_rc( + value: T, + caller: &'static std::panic::Location<'static>, + ) -> GenerationalPointer { + // Create the data that the rc points to + let data = Self::create_new( + RwLockStorageEntryData::Rc(RcStorageEntry::new(Box::new(value))), + caller, + ); + Self::create_new(RwLockStorageEntryData::Reference(data), caller) + } + + fn new_reference( + location: GenerationalPointer, + ) -> BorrowResult> { + // Chase the reference to get the final location + let (location, value) = Self::get_split_ref(location)?; + if let RwLockStorageEntryData::Rc(data) = &value.data { + data.add_ref(); + } else { + unreachable!() } - write.data = Some(Box::new(value)); + Ok(Self::create_new( + RwLockStorageEntryData::Reference(location), + location + .location + .created_at() + .unwrap_or(std::panic::Location::caller()), + )) + } + + fn change_reference( + location: GenerationalPointer, + other: GenerationalPointer, + ) -> BorrowResult { + if location == other { + return Ok(()); + } + + let (other_final, other_write) = Self::get_split_ref(other)?; + + let mut write = location.storage.data.write(); + // First check if the generation is still valid + if !write.valid(&location.location) { + return Err(BorrowError::Dropped(ValueDroppedError::new_for_location( + location.location, + ))); + } + + if let (RwLockStorageEntryData::Reference(reference), RwLockStorageEntryData::Rc(data)) = + (&mut write.data, &other_write.data) + { + if reference == &other_final { + return Ok(()); + } + drop_ref(*reference); + *reference = other_final; + data.add_ref(); + } else { + tracing::trace!( + "References should always point to a data entry directly found {:?} instead", + other_write.data + ); + return Err(BorrowError::Dropped(ValueDroppedError::new_for_location( + other_final.location, + ))); + } + + Ok(()) } } diff --git a/packages/generational-box/src/unsync.rs b/packages/generational-box/src/unsync.rs index 308448e6a..553264857 100644 --- a/packages/generational-box/src/unsync.rs +++ b/packages/generational-box/src/unsync.rs @@ -1,35 +1,175 @@ use crate::{ - entry::{MemoryLocationBorrowInfo, StorageEntry}, + entry::{MemoryLocationBorrowInfo, RcStorageEntry, StorageEntry}, error, references::{GenerationalRef, GenerationalRefMut}, - AnyStorage, BorrowError, BorrowMutError, GenerationalLocation, GenerationalPointer, Storage, + AnyStorage, BorrowError, BorrowMutError, BorrowMutResult, BorrowResult, GenerationalLocation, + GenerationalPointer, Storage, ValueDroppedError, }; -use std::cell::{Ref, RefCell, RefMut}; +use std::{ + any::Any, + cell::{Ref, RefCell, RefMut}, + fmt::Debug, + num::NonZeroU64, +}; + +type RefCellStorageEntryRef = Ref<'static, StorageEntry>; + +type RefCellStorageEntryMut = RefMut<'static, StorageEntry>; thread_local! { static UNSYNC_RUNTIME: RefCell> = const { RefCell::new(Vec::new()) }; } +pub(crate) enum RefCellStorageEntryData { + Reference(GenerationalPointer), + Rc(RcStorageEntry>), + Data(Box), + Empty, +} + +impl Debug for RefCellStorageEntryData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Reference(pointer) => write!(f, "Reference({:?})", pointer.location), + Self::Rc(_) => write!(f, "Rc"), + Self::Data(_) => write!(f, "Data"), + Self::Empty => write!(f, "Empty"), + } + } +} + +impl Default for RefCellStorageEntryData { + fn default() -> Self { + Self::Empty + } +} + /// A unsync storage. This is the default storage type. #[derive(Default)] pub struct UnsyncStorage { borrow_info: MemoryLocationBorrowInfo, - data: RefCell>>, + data: RefCell>, } impl UnsyncStorage { - fn try_borrow_mut( - &self, - ) -> Result>>, BorrowMutError> { - self.data - .try_borrow_mut() - .map_err(|_| self.borrow_info.borrow_mut_error()) + pub(crate) fn read( + pointer: GenerationalPointer, + ) -> BorrowResult>> { + Self::get_split_ref(pointer).map(|(_, guard)| { + Ref::map(guard, |data| match &data.data { + RefCellStorageEntryData::Data(data) => data, + RefCellStorageEntryData::Rc(data) => &data.data, + _ => unreachable!(), + }) + }) } - fn try_borrow(&self) -> Result>>, BorrowError> { - self.data - .try_borrow() - .map_err(|_| self.borrow_info.borrow_error()) + pub(crate) fn get_split_ref( + mut pointer: GenerationalPointer, + ) -> BorrowResult<(GenerationalPointer, RefCellStorageEntryRef)> { + loop { + let borrow = pointer + .storage + .data + .try_borrow() + .map_err(|_| pointer.storage.borrow_info.borrow_error())?; + if !borrow.valid(&pointer.location) { + return Err(BorrowError::Dropped(ValueDroppedError::new_for_location( + pointer.location, + ))); + } + match &borrow.data { + // If this is a reference, keep traversing the pointers + RefCellStorageEntryData::Reference(data) => { + pointer = *data; + } + // Otherwise return the value + RefCellStorageEntryData::Rc(_) | RefCellStorageEntryData::Data(_) => { + return Ok((pointer, borrow)); + } + RefCellStorageEntryData::Empty => { + return Err(BorrowError::Dropped(ValueDroppedError::new_for_location( + pointer.location, + ))); + } + } + } + } + + pub(crate) fn write( + pointer: GenerationalPointer, + ) -> BorrowMutResult>> { + Self::get_split_mut(pointer).map(|(_, guard)| { + RefMut::map(guard, |data| match &mut data.data { + RefCellStorageEntryData::Data(data) => data, + RefCellStorageEntryData::Rc(data) => &mut data.data, + _ => unreachable!(), + }) + }) + } + + pub(crate) fn get_split_mut( + mut pointer: GenerationalPointer, + ) -> BorrowMutResult<(GenerationalPointer, RefCellStorageEntryMut)> { + loop { + let borrow = pointer + .storage + .data + .try_borrow_mut() + .map_err(|_| pointer.storage.borrow_info.borrow_mut_error())?; + if !borrow.valid(&pointer.location) { + return Err(BorrowMutError::Dropped( + ValueDroppedError::new_for_location(pointer.location), + )); + } + match &borrow.data { + // If this is a reference, keep traversing the pointers + RefCellStorageEntryData::Reference(data) => { + pointer = *data; + } + // Otherwise return the value + RefCellStorageEntryData::Data(_) | RefCellStorageEntryData::Rc(_) => { + return Ok((pointer, borrow)); + } + RefCellStorageEntryData::Empty => { + return Err(BorrowMutError::Dropped( + ValueDroppedError::new_for_location(pointer.location), + )); + } + } + } + } + + fn create_new( + value: RefCellStorageEntryData, + #[allow(unused)] caller: &'static std::panic::Location<'static>, + ) -> GenerationalPointer { + UNSYNC_RUNTIME.with(|runtime| match runtime.borrow_mut().pop() { + Some(storage) => { + let mut write = storage.data.borrow_mut(); + let location = GenerationalLocation { + generation: write.generation(), + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: caller, + }; + write.data = value; + GenerationalPointer { storage, location } + } + None => { + let storage: &'static Self = &*Box::leak(Box::new(Self { + borrow_info: Default::default(), + data: RefCell::new(StorageEntry::new(value)), + })); + + let location = GenerationalLocation { + generation: NonZeroU64::MIN, + #[cfg(any(debug_assertions, feature = "debug_borrows"))] + created_at: caller, + }; + + GenerationalPointer { storage, location } + } + }) } } @@ -81,44 +221,51 @@ impl AnyStorage for UnsyncStorage { self.data.as_ptr() as *const () } - #[allow(unused)] - fn claim(caller: &'static std::panic::Location<'static>) -> GenerationalPointer { - UNSYNC_RUNTIME.with(|runtime| { - if let Some(storage) = runtime.borrow_mut().pop() { - let location = GenerationalLocation { - generation: storage.data.borrow().generation(), - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - created_at: caller, - }; - GenerationalPointer { storage, location } - } else { - let data: &'static Self = &*Box::leak(Box::default()); - let location = GenerationalLocation { - generation: 0, - #[cfg(any(debug_assertions, feature = "debug_borrows"))] - created_at: caller, - }; - GenerationalPointer { - storage: data, - location, - } - } - }) - } - - fn recycle(pointer: GenerationalPointer) -> Option> { + fn recycle(pointer: GenerationalPointer) { let mut borrow_mut = pointer.storage.data.borrow_mut(); // First check if the generation is still valid if !borrow_mut.valid(&pointer.location) { - return None; + return; } borrow_mut.increment_generation(); - let old_data = borrow_mut.data.take(); - UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(pointer.storage)); + // Then decrement the reference count or drop the value if it's the last reference + match &mut borrow_mut.data { + // If this is the original reference, drop the value + RefCellStorageEntryData::Data(_) => borrow_mut.data = RefCellStorageEntryData::Empty, + // If this is a rc, just ignore the drop + RefCellStorageEntryData::Rc(_) => {} + // If this is a reference, decrement the reference count + RefCellStorageEntryData::Reference(reference) => { + let reference = *reference; + drop(borrow_mut); + drop_ref(reference); + } + RefCellStorageEntryData::Empty => {} + } - old_data + UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(pointer.storage)); + } +} + +fn drop_ref(pointer: GenerationalPointer) { + let mut borrow_mut = pointer.storage.data.borrow_mut(); + + // First check if the generation is still valid + if !borrow_mut.valid(&pointer.location) { + return; + } + + if let RefCellStorageEntryData::Rc(entry) = &mut borrow_mut.data { + // Decrement the reference count + if entry.drop_ref() { + // If the reference count is now zero, drop the value + borrow_mut.data = RefCellStorageEntryData::Empty; + UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(pointer.storage)); + } + } else { + unreachable!("References should always point to a data entry directly",); } } @@ -127,15 +274,11 @@ impl Storage for UnsyncStorage { fn try_read( pointer: GenerationalPointer, ) -> Result, error::BorrowError> { - let read = pointer.storage.try_borrow()?; + let read = Self::read(pointer)?; let ref_ = Ref::filter_map(read, |any| { - // Verify the generation is still correct - if !any.valid(&pointer.location) { - return None; - } // Then try to downcast - any.data.as_ref()?.downcast_ref() + any.downcast_ref() }); match ref_ { Ok(guard) => Ok(GenerationalRef::new( @@ -152,15 +295,11 @@ impl Storage for UnsyncStorage { fn try_write( pointer: GenerationalPointer, ) -> Result, error::BorrowMutError> { - let write = pointer.storage.try_borrow_mut()?; + let write = Self::write(pointer)?; let ref_mut = RefMut::filter_map(write, |any| { - // Verify the generation is still correct - if !any.valid(&pointer.location) { - return None; - } // Then try to downcast - any.data.as_mut()?.downcast_mut() + any.downcast_mut() }); match ref_mut { Ok(guard) => Ok(GenerationalRefMut::new( @@ -173,12 +312,78 @@ impl Storage for UnsyncStorage { } } - fn set(pointer: GenerationalPointer, value: T) { - let mut borrow_mut = pointer.storage.data.borrow_mut(); - // First check if the generation is still valid - if !borrow_mut.valid(&pointer.location) { - return; + fn new(value: T, caller: &'static std::panic::Location<'static>) -> GenerationalPointer { + Self::create_new(RefCellStorageEntryData::Data(Box::new(value)), caller) + } + + fn new_rc( + value: T, + caller: &'static std::panic::Location<'static>, + ) -> GenerationalPointer { + // Create the data that the rc points to + let data = Self::create_new( + RefCellStorageEntryData::Rc(RcStorageEntry::new(Box::new(value))), + caller, + ); + Self::create_new(RefCellStorageEntryData::Reference(data), caller) + } + + fn new_reference( + pointer: GenerationalPointer, + ) -> BorrowResult> { + // Chase the reference to get the final location + let (pointer, value) = Self::get_split_ref(pointer)?; + if let RefCellStorageEntryData::Rc(data) = &value.data { + data.add_ref(); + } else { + unreachable!() } - borrow_mut.data = Some(Box::new(value)); + Ok(Self::create_new( + RefCellStorageEntryData::Reference(pointer), + pointer + .location + .created_at() + .unwrap_or(std::panic::Location::caller()), + )) + } + + fn change_reference( + location: GenerationalPointer, + other: GenerationalPointer, + ) -> BorrowResult { + if location == other { + return Ok(()); + } + + let (other_final, other_write) = Self::get_split_ref(other)?; + + let mut write = location.storage.data.borrow_mut(); + // First check if the generation is still valid + if !write.valid(&location.location) { + return Err(BorrowError::Dropped(ValueDroppedError::new_for_location( + location.location, + ))); + } + + if let (RefCellStorageEntryData::Reference(reference), RefCellStorageEntryData::Rc(data)) = + (&mut write.data, &other_write.data) + { + if reference == &other_final { + return Ok(()); + } + drop_ref(*reference); + *reference = other_final; + data.add_ref(); + } else { + tracing::trace!( + "References should always point to a data entry directly found {:?} instead", + other_write.data + ); + return Err(BorrowError::Dropped(ValueDroppedError::new_for_location( + other_final.location, + ))); + } + + Ok(()) } } diff --git a/packages/generational-box/tests/reference_counting.rs b/packages/generational-box/tests/reference_counting.rs new file mode 100644 index 000000000..34de83349 --- /dev/null +++ b/packages/generational-box/tests/reference_counting.rs @@ -0,0 +1,61 @@ +use generational_box::{Storage, SyncStorage, UnsyncStorage}; + +#[test] +fn reference_counting() { + fn reference_counting>() { + let data = String::from("hello world"); + let reference; + { + let outer_owner = S::owner(); + { + // create an owner + let owner = S::owner(); + // insert data into the store + let original = owner.insert_rc(data); + reference = outer_owner.insert_reference(original).unwrap(); + // The reference should point to the value immediately + assert_eq!(&*reference.read(), "hello world"); + // Original is dropped + } + // The reference should still point to the value + assert_eq!(&*reference.read(), "hello world"); + } + // Now that all references are dropped, the value should be dropped + assert!(reference.try_read().is_err()); + } + + reference_counting::(); + reference_counting::(); +} + +#[test] +fn move_reference_in_place() { + fn move_reference_in_place>() { + let data1 = String::from("hello world"); + let data2 = String::from("hello world 2"); + + // create an owner + let original_owner = S::owner(); + // insert data into the store + let original = original_owner.insert_rc(data1.clone()); + let mut reference = original_owner.insert_reference(original).unwrap(); + // The reference should point to the original value + assert_eq!(&*reference.read(), &data1); + + let new_owner = S::owner(); + // Move the reference in place + let new = new_owner.insert_rc(data2.clone()); + reference.point_to(new).unwrap(); + // The reference should point to the new value + assert_eq!(&*reference.read(), &data2); + + // make sure both got dropped + drop(original_owner); + drop(new_owner); + assert!(original.try_read().is_err()); + assert!(new.try_read().is_err()); + } + + move_reference_in_place::(); + move_reference_in_place::(); +} diff --git a/packages/hooks/src/use_callback.rs b/packages/hooks/src/use_callback.rs index 000e6c177..088a9dc4c 100644 --- a/packages/hooks/src/use_callback.rs +++ b/packages/hooks/src/use_callback.rs @@ -1,6 +1,3 @@ -use std::cell::RefCell; -use std::rc::Rc; - use dioxus_core::prelude::use_hook; use dioxus_core::prelude::Callback; @@ -19,7 +16,7 @@ pub fn use_callback(f: impl FnMut(T) -> O + 'static) -> if let Some(callback) = callback.take() { // Every time this hook is called replace the inner callback with the new callback - inner.__set(Rc::new(RefCell::new(callback))); + inner.replace(Box::new(callback)); } inner diff --git a/packages/html/src/document/mod.rs b/packages/html/src/document/mod.rs index 8131657fb..53eba8a04 100644 --- a/packages/html/src/document/mod.rs +++ b/packages/html/src/document/mod.rs @@ -133,7 +133,8 @@ impl Document for NoOpDocument { } } -struct NoOpEvaluator; +/// The default No-Op evaluator +pub struct NoOpEvaluator; impl Evaluator for NoOpEvaluator { fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> { Err(EvalError::Unsupported) diff --git a/packages/signals/src/copy_value.rs b/packages/signals/src/copy_value.rs index 6aa3f44bf..196ba18a9 100644 --- a/packages/signals/src/copy_value.rs +++ b/packages/signals/src/copy_value.rs @@ -71,6 +71,19 @@ impl> CopyValue { Self::new_with_caller(value, std::panic::Location::caller()) } + /// Create a new CopyValue without an owner. This will leak memory if you don't manually drop it. + pub fn leak_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self { + Self { + value: GenerationalBox::leak(value, caller), + origin_scope: current_scope_id().expect("in a virtual dom"), + } + } + + /// Point to another copy value + pub fn point_to(&mut self, other: Self) -> BorrowResult { + self.value.point_to(other.value) + } + pub(crate) fn new_with_caller( value: T, caller: &'static std::panic::Location<'static>, @@ -78,7 +91,7 @@ impl> CopyValue { let owner = current_owner(); Self { - value: owner.insert_with_caller(value, caller), + value: owner.insert_rc_with_caller(value, caller), origin_scope: current_scope_id().expect("in a virtual dom"), } } @@ -99,13 +112,13 @@ impl> CopyValue { let owner = scope.owner(); Self { - value: owner.insert_with_caller(value, caller), + value: owner.insert_rc_with_caller(value, caller), origin_scope: scope, } } /// Manually drop the value in the CopyValue, invalidating the value in the process. - pub fn manually_drop(&self) -> Option { + pub fn manually_drop(&self) { self.value.manually_drop() } diff --git a/packages/signals/src/read_only_signal.rs b/packages/signals/src/read_only_signal.rs index 4879dffa5..5aed2636e 100644 --- a/packages/signals/src/read_only_signal.rs +++ b/packages/signals/src/read_only_signal.rs @@ -42,25 +42,17 @@ impl>> ReadOnlySignal { self.inner.id() } - #[doc(hidden)] - /// This should only be used by the `rsx!` macro. - pub fn __set(&mut self, value: T) { - use crate::write::Writable; - use warnings::Warning; - // This is only called when converting T -> ReadOnlySignal which will not cause loops - crate::warnings::signal_write_in_component_body::allow(|| { - crate::warnings::signal_read_and_write_in_reactive_scope::allow(|| { - self.inner.set(value); - }); - }); + /// Point to another signal + pub fn point_to(&mut self, other: Self) -> BorrowResult { + self.inner.point_to(other.inner) } #[doc(hidden)] - /// This should only be used by the `rsx!` macro. - pub fn __take(&self) -> T { - self.inner - .manually_drop() - .expect("Signal has already been dropped") + /// This is only used by the `props` macro. + /// Mark any readers of the signal as dirty + pub fn mark_dirty(&mut self) { + use crate::write::Writable; + _ = self.inner.try_write(); } } diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 65f6cd85b..194a46ea2 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -182,6 +182,19 @@ impl>> Signal { } } + /// Create a new Signal without an owner. This will leak memory if you don't manually drop it. + pub fn leak_with_caller(value: T, caller: &'static std::panic::Location<'static>) -> Self { + Self { + inner: CopyValue::leak_with_caller( + SignalData { + subscribers: Default::default(), + value, + }, + 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. #[track_caller] #[tracing::instrument(skip(value))] @@ -208,9 +221,14 @@ impl>> Signal { } } + /// Point to another signal + pub fn point_to(&mut self, other: Self) -> BorrowResult { + self.inner.point_to(other.inner) + } + /// Drop the value out of the signal, invalidating the signal in the process. - pub fn manually_drop(&self) -> Option { - self.inner.manually_drop().map(|i| i.value) + pub fn manually_drop(&self) { + self.inner.manually_drop() } /// Get the scope the signal was created in. diff --git a/packages/web/src/document.rs b/packages/web/src/document.rs index 62127c7c7..2b187ba62 100644 --- a/packages/web/src/document.rs +++ b/packages/web/src/document.rs @@ -1,5 +1,5 @@ use dioxus_core::ScopeId; -use dioxus_html::document::{Document, EvalError, Evaluator}; +use dioxus_html::document::{Document, EvalError, Evaluator, NoOpEvaluator}; use generational_box::{AnyStorage, GenerationalBox, UnsyncStorage}; use js_sys::Function; use serde::Serialize; @@ -95,7 +95,7 @@ impl WebEvaluator { fn create(js: String) -> GenerationalBox> { let owner = UnsyncStorage::owner(); - let generational_box = owner.invalid(); + let generational_box = owner.insert(Box::new(NoOpEvaluator) as Box); // add the drop handler to DioxusChannel so that it gets dropped when the channel is dropped in js let channels = WebDioxusChannel::new(JSOwner::new(owner));