Fix cloned rsx double drop (#2839)
* Implement Rc generational pointers * Clean up error handling a bit * start adding tests * Simplify a few types * Separate Rc and normal generational boxes * Add tests for rc drop and read * Move over the pointer instead of the data in ReadOnlySignal props * Fix panic on ref drop and release mode builds * final rc signal cleanup * use rc generational boxes for event handlers as well * add a regression test for read only signal cloning * merge main --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
20d146d9bd
commit
baf5a95cae
19 files changed
+905
-250
No files matched your search
Generated
+1
@@ -4031,6 +4031,7 @@ dependencies = [
|
|||||||
"criterion",
|
"criterion",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rand 0.8.5",
|
"rand 0.8.5",
|
||||||
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -652,8 +652,11 @@ mod struct_info {
|
|||||||
let new_value: &_ = &*new.#signal_fields.peek();
|
let new_value: &_ = &*new.#signal_fields.peek();
|
||||||
(&old_value).compare(&&new_value)
|
(&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 {
|
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
|
// Move the old value back
|
||||||
self.#signal_fields = #signal_fields;
|
self.#signal_fields = #signal_fields;
|
||||||
@@ -683,13 +686,13 @@ mod struct_info {
|
|||||||
quote! {
|
quote! {
|
||||||
// If the event handler is None, we don't need to update it
|
// 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()) {
|
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 {
|
} else {
|
||||||
quote! {
|
quote! {
|
||||||
// Update the event handlers
|
// Update the event handlers
|
||||||
self.#name.__set(new.#name.__take());
|
self.#name.__point_to(&new.#name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).collect();
|
}).collect();
|
||||||
@@ -1447,7 +1450,7 @@ Finally, call `.build()` to create the instance of `{name}`.
|
|||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
#vis struct #name #generics_with_bounds #where_clause {
|
#vis struct #name #generics_with_bounds #where_clause {
|
||||||
inner: #original_name #ty_generics,
|
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 {
|
impl #original_impl_generics PartialEq for #name #ty_generics #where_clause {
|
||||||
|
|||||||
@@ -161,3 +161,52 @@ fn spreads_memorize_in_place() {
|
|||||||
assert!(props.memoize(&CompProps::builder().build()));
|
assert!(props.memoize(&CompProps::builder().build()));
|
||||||
assert_eq!(props.attributes, vec![]);
|
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>(T);
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn TakesReadOnlySignalNum(sig: ReadOnlySignal<i32>) -> Element {
|
||||||
|
rsx! {}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
fn TakesReadOnlySignalNonClone(sig: ReadOnlySignal<NonCloneable<i32>>) -> 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::<SerializedMouseData>::default())) as Rc<dyn Any>,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
dom.runtime().handle_event("click", event, ElementId(1));
|
||||||
|
dom.render_immediate(&mut dioxus_core::NoOpMutations);
|
||||||
|
}
|
||||||
|
dom.render_immediate(&mut dioxus_core::NoOpMutations);
|
||||||
|
}
|
||||||
+20
-36
@@ -1,6 +1,6 @@
|
|||||||
use crate::{properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId};
|
use crate::{properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId};
|
||||||
use generational_box::GenerationalBox;
|
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
|
/// A wrapper around some generic data that handles the event's state
|
||||||
///
|
///
|
||||||
@@ -431,19 +431,10 @@ impl<Args: 'static, Ret: 'static> PartialEq for Callback<Args, Ret> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(super) struct ExternalListenerCallback<Args, Ret> {
|
pub(super) struct ExternalListenerCallback<Args, Ret> {
|
||||||
callback: Rc<RefCell<dyn FnMut(Args) -> Ret>>,
|
callback: Box<dyn FnMut(Args) -> Ret>,
|
||||||
runtime: std::rc::Weak<Runtime>,
|
runtime: std::rc::Weak<Runtime>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Args, Ret> Clone for ExternalListenerCallback<Args, Ret> {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
Self {
|
|
||||||
callback: self.callback.clone(),
|
|
||||||
runtime: self.runtime.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
|
impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
|
||||||
/// Create a new [`Callback`] from an [`FnMut`]. The callback is owned by the current scope and will be dropped when the scope is dropped.
|
/// 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.
|
/// 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<Args: 'static, Ret: 'static> Callback<Args, Ret> {
|
|||||||
.current_scope_id()
|
.current_scope_id()
|
||||||
.unwrap_or_else(|e| panic!("{}", e));
|
.unwrap_or_else(|e| panic!("{}", e));
|
||||||
let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>();
|
let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>();
|
||||||
let callback = owner.insert(Some(ExternalListenerCallback {
|
let callback = owner.insert_rc(Some(ExternalListenerCallback {
|
||||||
callback: Rc::new(RefCell::new(move |event: Args| f(event).spawn()))
|
callback: Box::new(move |event: Args| f(event).spawn()),
|
||||||
as Rc<RefCell<dyn FnMut(Args) -> Ret>>,
|
|
||||||
runtime: Rc::downgrade(&runtime),
|
runtime: Rc::downgrade(&runtime),
|
||||||
}));
|
}));
|
||||||
Self { callback, origin }
|
Self { callback, origin }
|
||||||
@@ -471,11 +461,13 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
|
|||||||
let origin = runtime
|
let origin = runtime
|
||||||
.current_scope_id()
|
.current_scope_id()
|
||||||
.unwrap_or_else(|e| panic!("{}", e));
|
.unwrap_or_else(|e| panic!("{}", e));
|
||||||
let callback = GenerationalBox::leak(Some(ExternalListenerCallback {
|
let callback = GenerationalBox::leak_rc(
|
||||||
callback: Rc::new(RefCell::new(move |event: Args| f(event).spawn()))
|
Some(ExternalListenerCallback {
|
||||||
as Rc<RefCell<dyn FnMut(Args) -> Ret>>,
|
callback: Box::new(move |event: Args| f(event).spawn()),
|
||||||
runtime: Rc::downgrade(&runtime),
|
runtime: Rc::downgrade(&runtime),
|
||||||
}));
|
}),
|
||||||
|
Location::caller(),
|
||||||
|
);
|
||||||
Self { callback, origin }
|
Self { callback, origin }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,16 +476,13 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
|
|||||||
/// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.
|
/// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn call(&self, arguments: Args) -> Ret {
|
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
|
let runtime = callback
|
||||||
.runtime
|
.runtime
|
||||||
.upgrade()
|
.upgrade()
|
||||||
.expect("Callback was called after the runtime was dropped");
|
.expect("Callback was called after the runtime was dropped");
|
||||||
let _guard = RuntimeGuard::new(runtime.clone());
|
let _guard = RuntimeGuard::new(runtime.clone());
|
||||||
runtime.with_scope_on_stack(self.origin, || {
|
runtime.with_scope_on_stack(self.origin, || (callback.callback)(arguments))
|
||||||
let mut callback = callback.callback.borrow_mut();
|
|
||||||
callback(arguments)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
panic!("Callback was manually dropped")
|
panic!("Callback was manually dropped")
|
||||||
}
|
}
|
||||||
@@ -511,24 +500,19 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
|
|||||||
self.callback.set(None);
|
self.callback.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
/// Replace the function in the callback with a new one
|
||||||
/// This should only be used by the `rsx!` macro.
|
pub fn replace(&mut self, callback: Box<dyn FnMut(Args) -> Ret>) {
|
||||||
pub fn __set(&mut self, value: Rc<RefCell<dyn FnMut(Args) -> Ret>>) {
|
let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e));
|
||||||
self.callback.set(Some(ExternalListenerCallback {
|
self.callback.set(Some(ExternalListenerCallback {
|
||||||
callback: value,
|
callback,
|
||||||
runtime: Rc::downgrade(&Runtime::current().unwrap()),
|
runtime: Rc::downgrade(&runtime),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// This should only be used by the `rsx!` macro.
|
/// This should only be used by the `rsx!` macro.
|
||||||
pub fn __take(&self) -> Rc<RefCell<dyn FnMut(Args) -> Ret>> {
|
pub fn __point_to(&mut self, other: &Self) {
|
||||||
self.callback
|
self.callback.point_to(other.callback).unwrap();
|
||||||
.read()
|
|
||||||
.as_ref()
|
|
||||||
.expect("Callback was manually dropped")
|
|
||||||
.callback
|
|
||||||
.clone()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ pub mod internal {
|
|||||||
HotReloadDynamicAttribute, HotReloadDynamicNode, HotReloadLiteral,
|
HotReloadDynamicAttribute, HotReloadDynamicNode, HotReloadLiteral,
|
||||||
HotReloadTemplateWithLocation, HotReloadedTemplate, HotreloadedLiteral, NamedAttribute,
|
HotReloadTemplateWithLocation, HotReloadedTemplate, HotreloadedLiteral, NamedAttribute,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use generational_box;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod innerlude {
|
pub(crate) mod innerlude {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ where
|
|||||||
}
|
}
|
||||||
fn memoize(&mut self, new: &Self) -> bool {
|
fn memoize(&mut self, new: &Self) -> bool {
|
||||||
let equal = self == new;
|
let equal = self == new;
|
||||||
self.fallback.__set(new.fallback.__take());
|
self.fallback.__point_to(&new.fallback);
|
||||||
if !equal {
|
if !equal {
|
||||||
let new_clone = new.clone();
|
let new_clone = new.clone();
|
||||||
self.children = new_clone.children;
|
self.children = new_clone.children;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ keywords = ["generational", "box", "memory", "allocator"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
|
tracing.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rand = { workspace = true }
|
rand = { workspace = true }
|
||||||
|
|||||||
@@ -2,36 +2,66 @@ use crate::{
|
|||||||
BorrowError, BorrowMutError, GenerationalLocation, GenerationalRefBorrowGuard,
|
BorrowError, BorrowMutError, GenerationalLocation, GenerationalRefBorrowGuard,
|
||||||
GenerationalRefBorrowMutGuard,
|
GenerationalRefBorrowMutGuard,
|
||||||
};
|
};
|
||||||
|
use std::{
|
||||||
|
num::NonZeroU64,
|
||||||
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) struct StorageEntry<T> {
|
pub(crate) struct RcStorageEntry<T> {
|
||||||
generation: u64,
|
ref_count: AtomicU64,
|
||||||
|
pub data: T,
|
||||||
pub data: Option<T>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for StorageEntry<T> {
|
impl<T> RcStorageEntry<T> {
|
||||||
fn default() -> Self {
|
pub const fn new(data: T) -> Self {
|
||||||
Self {
|
Self {
|
||||||
generation: 0,
|
ref_count: AtomicU64::new(0),
|
||||||
data: None,
|
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<T> {
|
||||||
|
generation: NonZeroU64,
|
||||||
|
pub(crate) data: T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> StorageEntry<T> {
|
impl<T> StorageEntry<T> {
|
||||||
|
pub const fn new(data: T) -> Self {
|
||||||
|
Self {
|
||||||
|
generation: NonZeroU64::MIN,
|
||||||
|
data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn valid(&self, location: &GenerationalLocation) -> bool {
|
pub fn valid(&self, location: &GenerationalLocation) -> bool {
|
||||||
self.generation == location.generation
|
self.generation == location.generation
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn increment_generation(&mut self) {
|
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
|
self.generation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T: Default + 'static> Default for StorageEntry<T> {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new(T::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct MemoryLocationBorrowInfo(
|
pub(crate) struct MemoryLocationBorrowInfo(
|
||||||
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ use std::fmt::Display;
|
|||||||
use crate::GenerationalLocation;
|
use crate::GenerationalLocation;
|
||||||
|
|
||||||
/// A result that can be returned from a borrow operation.
|
/// A result that can be returned from a borrow operation.
|
||||||
pub type BorrowResult<T> = std::result::Result<T, BorrowError>;
|
pub type BorrowResult<T = ()> = std::result::Result<T, BorrowError>;
|
||||||
|
|
||||||
|
/// A result that can be returned from a borrow mut operation.
|
||||||
|
pub type BorrowMutResult<T = ()> = std::result::Result<T, BorrowMutError>;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
/// An error that can occur when trying to borrow a value.
|
/// An error that can occur when trying to borrow a value.
|
||||||
@@ -50,6 +53,15 @@ impl Display for BorrowMutError {
|
|||||||
|
|
||||||
impl Error for BorrowMutError {}
|
impl Error for BorrowMutError {}
|
||||||
|
|
||||||
|
impl From<BorrowError> 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.
|
/// An error that can occur when trying to use a value that has been dropped.
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
pub struct ValueDroppedError {
|
pub struct ValueDroppedError {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use parking_lot::Mutex;
|
|||||||
use std::{
|
use std::{
|
||||||
fmt::Debug,
|
fmt::Debug,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
|
num::NonZeroU64,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
@@ -24,7 +25,7 @@ mod unsync;
|
|||||||
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
|
||||||
pub struct GenerationalBoxId {
|
pub struct GenerationalBoxId {
|
||||||
data_ptr: *const (),
|
data_ptr: *const (),
|
||||||
generation: u64,
|
generation: NonZeroU64,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Safety: GenerationalBoxId is Send and Sync because there is no way to access the pointer.
|
// Safety: GenerationalBoxId is Send and Sync because there is no way to access the pointer.
|
||||||
@@ -54,9 +55,19 @@ impl<T, S: Storage<T>> GenerationalBox<T, S> {
|
|||||||
/// Create a new generational box by leaking a value into the storage. This is useful for creating
|
/// 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.
|
/// a box that needs to be manually dropped with no owners.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn leak(value: T) -> Self {
|
pub fn leak(value: T, location: &'static std::panic::Location<'static>) -> Self {
|
||||||
let location = S::claim(std::panic::Location::caller());
|
let location = S::new(value, location);
|
||||||
location.set(value);
|
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 {
|
Self {
|
||||||
raw: location,
|
raw: location,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
@@ -98,8 +109,12 @@ impl<T, S: Storage<T>> GenerationalBox<T, S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Set the value. Panics if the value is no longer valid.
|
/// Set the value. Panics if the value is no longer valid.
|
||||||
pub fn set(&self, value: T) {
|
#[track_caller]
|
||||||
S::set(self.raw, value);
|
pub fn set(&self, value: T)
|
||||||
|
where
|
||||||
|
T: 'static,
|
||||||
|
{
|
||||||
|
*self.write() = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns true if the pointer is equal to the other pointer.
|
/// Returns true if the pointer is equal to the other pointer.
|
||||||
@@ -108,24 +123,30 @@ impl<T, S: Storage<T>> GenerationalBox<T, S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Drop the value out of the generational box and invalidate the generational box.
|
/// 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)
|
||||||
pub fn manually_drop(&self) -> Option<T>
|
|
||||||
where
|
where
|
||||||
T: 'static,
|
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.
|
/// 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>> {
|
pub fn created_at(&self) -> Option<&'static std::panic::Location<'static>> {
|
||||||
#[cfg(debug_assertions)]
|
self.raw.location.created_at()
|
||||||
{
|
}
|
||||||
Some(self.raw.location.created_at)
|
|
||||||
}
|
/// Get a reference to the value
|
||||||
#[cfg(not(debug_assertions))]
|
#[track_caller]
|
||||||
{
|
pub fn leak_reference(&self) -> BorrowResult<GenerationalBox<T, S>> {
|
||||||
None
|
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<T, S>) -> BorrowResult {
|
||||||
|
S::change_reference(self.raw, other.raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,17 +161,35 @@ impl<T, S> Clone for GenerationalBox<T, S> {
|
|||||||
/// A trait for a storage backing type. (RefCell, RwLock, etc.)
|
/// A trait for a storage backing type. (RefCell, RwLock, etc.)
|
||||||
pub trait Storage<Data = ()>: AnyStorage + 'static {
|
pub trait Storage<Data = ()>: AnyStorage + 'static {
|
||||||
/// Try to read the value. Returns None if the value is no longer valid.
|
/// Try to read the value. Returns None if the value is no longer valid.
|
||||||
fn try_read(
|
fn try_read(pointer: GenerationalPointer<Self>) -> BorrowResult<Self::Ref<'static, Data>>;
|
||||||
location: GenerationalPointer<Self>,
|
|
||||||
) -> Result<Self::Ref<'static, Data>, BorrowError>;
|
|
||||||
|
|
||||||
/// Try to write the value. Returns None if the value is no longer valid.
|
/// Try to write the value. Returns None if the value is no longer valid.
|
||||||
fn try_write(
|
fn try_write(pointer: GenerationalPointer<Self>) -> BorrowMutResult<Self::Mut<'static, Data>>;
|
||||||
location: GenerationalPointer<Self>,
|
|
||||||
) -> Result<Self::Mut<'static, Data>, BorrowMutError>;
|
|
||||||
|
|
||||||
/// Set the value if the location is valid
|
/// Create a new memory location. This will either create a new memory location or recycle an old one.
|
||||||
fn set(location: GenerationalPointer<Self>, value: Data);
|
fn new(
|
||||||
|
value: Data,
|
||||||
|
caller: &'static std::panic::Location<'static>,
|
||||||
|
) -> GenerationalPointer<Self>;
|
||||||
|
|
||||||
|
/// 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<Self>;
|
||||||
|
|
||||||
|
/// 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<Self>) -> BorrowResult<GenerationalPointer<Self>>;
|
||||||
|
|
||||||
|
/// 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<Self>,
|
||||||
|
rc_pointer: GenerationalPointer<Self>,
|
||||||
|
) -> BorrowResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A trait for any storage backing type.
|
/// A trait for any storage backing type.
|
||||||
@@ -206,10 +245,7 @@ pub trait AnyStorage: Default + 'static {
|
|||||||
fn data_ptr(&self) -> *const ();
|
fn data_ptr(&self) -> *const ();
|
||||||
|
|
||||||
/// Recycle a memory location. This will drop the memory location and return it to the runtime.
|
/// Recycle a memory location. This will drop the memory location and return it to the runtime.
|
||||||
fn recycle(location: GenerationalPointer<Self>) -> Option<Box<dyn std::any::Any>>;
|
fn recycle(location: GenerationalPointer<Self>);
|
||||||
|
|
||||||
/// 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<Self>;
|
|
||||||
|
|
||||||
/// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates.
|
/// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates.
|
||||||
fn owner() -> Owner<Self> {
|
fn owner() -> Owner<Self> {
|
||||||
@@ -222,11 +258,24 @@ pub trait AnyStorage: Default + 'static {
|
|||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(crate) struct GenerationalLocation {
|
pub(crate) struct GenerationalLocation {
|
||||||
/// The generation this location is associated with. Using the location after this generation is invalidated will return errors.
|
/// 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"))]
|
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
|
||||||
created_at: &'static std::panic::Location<'static>,
|
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.
|
/// A pointer to a specific generational box and generation in that box.
|
||||||
pub struct GenerationalPointer<S: 'static = UnsyncStorage> {
|
pub struct GenerationalPointer<S: 'static = UnsyncStorage> {
|
||||||
/// The storage that is backing this location
|
/// The storage that is backing this location
|
||||||
@@ -261,20 +310,6 @@ impl<S: 'static> Clone for GenerationalPointer<S> {
|
|||||||
impl<S: 'static> Copy for GenerationalPointer<S> {}
|
impl<S: 'static> Copy for GenerationalPointer<S> {}
|
||||||
|
|
||||||
impl<S> GenerationalPointer<S> {
|
impl<S> GenerationalPointer<S> {
|
||||||
fn take<T: 'static>(self) -> Option<T>
|
|
||||||
where
|
|
||||||
S: Storage<T>,
|
|
||||||
{
|
|
||||||
S::recycle(self).map(|value| *(value.downcast().unwrap()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set<T>(self, value: T)
|
|
||||||
where
|
|
||||||
S: Storage<T>,
|
|
||||||
{
|
|
||||||
S::set(self, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn try_read<T>(self) -> Result<S::Ref<'static, T>, BorrowError>
|
fn try_read<T>(self) -> Result<S::Ref<'static, T>, BorrowError>
|
||||||
where
|
where
|
||||||
@@ -346,6 +381,32 @@ impl<S: AnyStorage> Owner<S> {
|
|||||||
self.insert_with_caller(value, std::panic::Location::caller())
|
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<T: 'static>(&self, value: T) -> GenerationalBox<T, S>
|
||||||
|
where
|
||||||
|
S: Storage<T>,
|
||||||
|
{
|
||||||
|
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<T: 'static>(
|
||||||
|
&self,
|
||||||
|
value: T,
|
||||||
|
caller: &'static std::panic::Location<'static>,
|
||||||
|
) -> GenerationalBox<T, S>
|
||||||
|
where
|
||||||
|
S: Storage<T>,
|
||||||
|
{
|
||||||
|
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.
|
/// 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<T: 'static>(
|
pub fn insert_with_caller<T: 'static>(
|
||||||
&self,
|
&self,
|
||||||
@@ -355,8 +416,7 @@ impl<S: AnyStorage> Owner<S> {
|
|||||||
where
|
where
|
||||||
S: Storage<T>,
|
S: Storage<T>,
|
||||||
{
|
{
|
||||||
let location = S::claim(caller);
|
let location = S::new(value, caller);
|
||||||
location.set(value);
|
|
||||||
self.0.lock().owned.push(location);
|
self.0.lock().owned.push(location);
|
||||||
GenerationalBox {
|
GenerationalBox {
|
||||||
raw: location,
|
raw: location,
|
||||||
@@ -364,14 +424,19 @@ impl<S: AnyStorage> Owner<S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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]
|
#[track_caller]
|
||||||
pub fn invalid<T: 'static>(&self) -> GenerationalBox<T, S> {
|
pub fn insert_reference<T: 'static>(
|
||||||
let location = S::claim(std::panic::Location::caller());
|
&self,
|
||||||
self.0.lock().owned.push(location);
|
other: GenerationalBox<T, S>,
|
||||||
GenerationalBox {
|
) -> BorrowResult<GenerationalBox<T, S>>
|
||||||
raw: location,
|
where
|
||||||
_marker: PhantomData,
|
S: Storage<T>,
|
||||||
}
|
{
|
||||||
|
let location = other.leak_reference()?;
|
||||||
|
self.0.lock().owned.push(location.raw);
|
||||||
|
Ok(location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,20 +1,174 @@
|
|||||||
use parking_lot::{
|
use parking_lot::{
|
||||||
MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
|
MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard,
|
||||||
};
|
};
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
fmt::Debug,
|
||||||
|
num::NonZeroU64,
|
||||||
|
sync::{Arc, OnceLock},
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
entry::{MemoryLocationBorrowInfo, StorageEntry},
|
entry::{MemoryLocationBorrowInfo, RcStorageEntry, StorageEntry},
|
||||||
error::{self, ValueDroppedError},
|
error::{self, ValueDroppedError},
|
||||||
references::{GenerationalRef, GenerationalRefMut},
|
references::{GenerationalRef, GenerationalRefMut},
|
||||||
AnyStorage, GenerationalLocation, GenerationalPointer, Storage,
|
AnyStorage, BorrowError, BorrowMutError, BorrowMutResult, BorrowResult, GenerationalLocation,
|
||||||
|
GenerationalPointer, Storage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type RwLockStorageEntryRef = RwLockReadGuard<'static, StorageEntry<RwLockStorageEntryData>>;
|
||||||
|
type RwLockStorageEntryMut = RwLockWriteGuard<'static, StorageEntry<RwLockStorageEntryData>>;
|
||||||
|
|
||||||
|
pub(crate) enum RwLockStorageEntryData {
|
||||||
|
Reference(GenerationalPointer<SyncStorage>),
|
||||||
|
Rc(RcStorageEntry<Box<dyn Any + Send + Sync>>),
|
||||||
|
Data(Box<dyn Any + Send + Sync>),
|
||||||
|
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<dyn Any + Send + Sync>) -> Self {
|
||||||
|
Self::Data(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A thread safe storage. This is slower than the unsync storage, but allows you to share the value between threads.
|
/// A thread safe storage. This is slower than the unsync storage, but allows you to share the value between threads.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SyncStorage {
|
pub struct SyncStorage {
|
||||||
borrow_info: MemoryLocationBorrowInfo,
|
borrow_info: MemoryLocationBorrowInfo,
|
||||||
data: RwLock<StorageEntry<Box<dyn std::any::Any + Send + Sync>>>,
|
data: RwLock<StorageEntry<RwLockStorageEntryData>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SyncStorage {
|
||||||
|
pub(crate) fn read(
|
||||||
|
pointer: GenerationalPointer<Self>,
|
||||||
|
) -> BorrowResult<MappedRwLockReadGuard<'static, Box<dyn Any + Send + Sync + 'static>>> {
|
||||||
|
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<Self>,
|
||||||
|
) -> BorrowResult<(GenerationalPointer<Self>, 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<Self>,
|
||||||
|
) -> BorrowMutResult<MappedRwLockWriteGuard<'static, Box<dyn Any + Send + Sync + 'static>>>
|
||||||
|
{
|
||||||
|
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<Self>,
|
||||||
|
) -> BorrowMutResult<(GenerationalPointer<Self>, 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<Self> {
|
||||||
|
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<Arc<Mutex<Vec<&'static SyncStorage>>>> = OnceLock::new();
|
static SYNC_RUNTIME: OnceLock<Arc<Mutex<Vec<&'static SyncStorage>>>> = OnceLock::new();
|
||||||
@@ -71,42 +225,50 @@ impl AnyStorage for SyncStorage {
|
|||||||
self.data.data_ptr() as *const ()
|
self.data.data_ptr() as *const ()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
fn recycle(pointer: GenerationalPointer<Self>) {
|
||||||
#[allow(unused)]
|
|
||||||
fn claim(caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {
|
|
||||||
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<Self>) -> Option<Box<dyn std::any::Any>> {
|
|
||||||
let mut borrow_mut = pointer.storage.data.write();
|
let mut borrow_mut = pointer.storage.data.write();
|
||||||
|
|
||||||
// First check if the generation is still valid
|
// First check if the generation is still valid
|
||||||
if !borrow_mut.valid(&pointer.location) {
|
if !borrow_mut.valid(&pointer.location) {
|
||||||
return None;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
borrow_mut.increment_generation();
|
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);
|
sync_runtime().lock().push(pointer.storage);
|
||||||
old_data.map(|data| data as Box<dyn std::any::Any>)
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drop_ref(pointer: GenerationalPointer<SyncStorage>) {
|
||||||
|
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<T: Sync + Send + 'static> Storage<T> for SyncStorage {
|
|||||||
fn try_read(
|
fn try_read(
|
||||||
pointer: GenerationalPointer<Self>,
|
pointer: GenerationalPointer<Self>,
|
||||||
) -> Result<Self::Ref<'static, T>, error::BorrowError> {
|
) -> Result<Self::Ref<'static, T>, error::BorrowError> {
|
||||||
let read = pointer.storage.data.read();
|
let read = Self::read(pointer)?;
|
||||||
|
|
||||||
let read = RwLockReadGuard::try_map(read, |any| {
|
let read = MappedRwLockReadGuard::try_map(read, |any| {
|
||||||
// Verify the generation is still correct
|
|
||||||
if !any.valid(&pointer.location) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Then try to downcast
|
// Then try to downcast
|
||||||
any.data.as_ref()?.downcast_ref()
|
any.downcast_ref()
|
||||||
});
|
});
|
||||||
match read {
|
match read {
|
||||||
Ok(guard) => Ok(GenerationalRef::new(
|
Ok(guard) => Ok(GenerationalRef::new(
|
||||||
@@ -140,15 +298,11 @@ impl<T: Sync + Send + 'static> Storage<T> for SyncStorage {
|
|||||||
fn try_write(
|
fn try_write(
|
||||||
pointer: GenerationalPointer<Self>,
|
pointer: GenerationalPointer<Self>,
|
||||||
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
|
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
|
||||||
let write = pointer.storage.data.write();
|
let write = Self::write(pointer)?;
|
||||||
|
|
||||||
let write = RwLockWriteGuard::try_map(write, |any| {
|
let write = MappedRwLockWriteGuard::try_map(write, |any| {
|
||||||
// Verify the generation is still correct
|
|
||||||
if !any.valid(&pointer.location) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Then try to downcast
|
// Then try to downcast
|
||||||
any.data.as_mut()?.downcast_mut()
|
any.downcast_mut()
|
||||||
});
|
});
|
||||||
match write {
|
match write {
|
||||||
Ok(guard) => Ok(GenerationalRefMut::new(
|
Ok(guard) => Ok(GenerationalRefMut::new(
|
||||||
@@ -161,12 +315,78 @@ impl<T: Sync + Send + 'static> Storage<T> for SyncStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(pointer: GenerationalPointer<Self>, value: T) {
|
fn new(value: T, caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {
|
||||||
let mut write = pointer.storage.data.write();
|
Self::create_new(RwLockStorageEntryData::new_full(Box::new(value)), caller)
|
||||||
// First check if the generation is still valid
|
}
|
||||||
if !write.valid(&pointer.location) {
|
|
||||||
return;
|
fn new_rc(
|
||||||
|
value: T,
|
||||||
|
caller: &'static std::panic::Location<'static>,
|
||||||
|
) -> GenerationalPointer<Self> {
|
||||||
|
// 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<Self>,
|
||||||
|
) -> BorrowResult<GenerationalPointer<Self>> {
|
||||||
|
// 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<Self>,
|
||||||
|
other: GenerationalPointer<Self>,
|
||||||
|
) -> 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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,35 +1,175 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
entry::{MemoryLocationBorrowInfo, StorageEntry},
|
entry::{MemoryLocationBorrowInfo, RcStorageEntry, StorageEntry},
|
||||||
error,
|
error,
|
||||||
references::{GenerationalRef, GenerationalRefMut},
|
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<RefCellStorageEntryData>>;
|
||||||
|
|
||||||
|
type RefCellStorageEntryMut = RefMut<'static, StorageEntry<RefCellStorageEntryData>>;
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
static UNSYNC_RUNTIME: RefCell<Vec<&'static UnsyncStorage>> = const { RefCell::new(Vec::new()) };
|
static UNSYNC_RUNTIME: RefCell<Vec<&'static UnsyncStorage>> = const { RefCell::new(Vec::new()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) enum RefCellStorageEntryData {
|
||||||
|
Reference(GenerationalPointer<UnsyncStorage>),
|
||||||
|
Rc(RcStorageEntry<Box<dyn Any>>),
|
||||||
|
Data(Box<dyn Any>),
|
||||||
|
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.
|
/// A unsync storage. This is the default storage type.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct UnsyncStorage {
|
pub struct UnsyncStorage {
|
||||||
borrow_info: MemoryLocationBorrowInfo,
|
borrow_info: MemoryLocationBorrowInfo,
|
||||||
data: RefCell<StorageEntry<Box<dyn std::any::Any>>>,
|
data: RefCell<StorageEntry<RefCellStorageEntryData>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnsyncStorage {
|
impl UnsyncStorage {
|
||||||
fn try_borrow_mut(
|
pub(crate) fn read(
|
||||||
&self,
|
pointer: GenerationalPointer<Self>,
|
||||||
) -> Result<RefMut<'_, StorageEntry<Box<dyn std::any::Any>>>, BorrowMutError> {
|
) -> BorrowResult<Ref<'static, Box<dyn Any>>> {
|
||||||
self.data
|
Self::get_split_ref(pointer).map(|(_, guard)| {
|
||||||
.try_borrow_mut()
|
Ref::map(guard, |data| match &data.data {
|
||||||
.map_err(|_| self.borrow_info.borrow_mut_error())
|
RefCellStorageEntryData::Data(data) => data,
|
||||||
|
RefCellStorageEntryData::Rc(data) => &data.data,
|
||||||
|
_ => unreachable!(),
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_borrow(&self) -> Result<Ref<'_, StorageEntry<Box<dyn std::any::Any>>>, BorrowError> {
|
pub(crate) fn get_split_ref(
|
||||||
self.data
|
mut pointer: GenerationalPointer<Self>,
|
||||||
.try_borrow()
|
) -> BorrowResult<(GenerationalPointer<Self>, RefCellStorageEntryRef)> {
|
||||||
.map_err(|_| self.borrow_info.borrow_error())
|
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<Self>,
|
||||||
|
) -> BorrowMutResult<RefMut<'static, Box<dyn Any>>> {
|
||||||
|
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<Self>,
|
||||||
|
) -> BorrowMutResult<(GenerationalPointer<Self>, 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<Self> {
|
||||||
|
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 ()
|
self.data.as_ptr() as *const ()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(unused)]
|
fn recycle(pointer: GenerationalPointer<Self>) {
|
||||||
fn claim(caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {
|
|
||||||
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<Self>) -> Option<Box<dyn std::any::Any>> {
|
|
||||||
let mut borrow_mut = pointer.storage.data.borrow_mut();
|
let mut borrow_mut = pointer.storage.data.borrow_mut();
|
||||||
|
|
||||||
// First check if the generation is still valid
|
// First check if the generation is still valid
|
||||||
if !borrow_mut.valid(&pointer.location) {
|
if !borrow_mut.valid(&pointer.location) {
|
||||||
return None;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
borrow_mut.increment_generation();
|
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
|
||||||
UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(pointer.storage));
|
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<UnsyncStorage>) {
|
||||||
|
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<T: 'static> Storage<T> for UnsyncStorage {
|
|||||||
fn try_read(
|
fn try_read(
|
||||||
pointer: GenerationalPointer<Self>,
|
pointer: GenerationalPointer<Self>,
|
||||||
) -> Result<Self::Ref<'static, T>, error::BorrowError> {
|
) -> Result<Self::Ref<'static, T>, error::BorrowError> {
|
||||||
let read = pointer.storage.try_borrow()?;
|
let read = Self::read(pointer)?;
|
||||||
|
|
||||||
let ref_ = Ref::filter_map(read, |any| {
|
let ref_ = Ref::filter_map(read, |any| {
|
||||||
// Verify the generation is still correct
|
|
||||||
if !any.valid(&pointer.location) {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// Then try to downcast
|
// Then try to downcast
|
||||||
any.data.as_ref()?.downcast_ref()
|
any.downcast_ref()
|
||||||
});
|
});
|
||||||
match ref_ {
|
match ref_ {
|
||||||
Ok(guard) => Ok(GenerationalRef::new(
|
Ok(guard) => Ok(GenerationalRef::new(
|
||||||
@@ -152,15 +295,11 @@ impl<T: 'static> Storage<T> for UnsyncStorage {
|
|||||||
fn try_write(
|
fn try_write(
|
||||||
pointer: GenerationalPointer<Self>,
|
pointer: GenerationalPointer<Self>,
|
||||||
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
|
) -> Result<Self::Mut<'static, T>, error::BorrowMutError> {
|
||||||
let write = pointer.storage.try_borrow_mut()?;
|
let write = Self::write(pointer)?;
|
||||||
|
|
||||||
let ref_mut = RefMut::filter_map(write, |any| {
|
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
|
// Then try to downcast
|
||||||
any.data.as_mut()?.downcast_mut()
|
any.downcast_mut()
|
||||||
});
|
});
|
||||||
match ref_mut {
|
match ref_mut {
|
||||||
Ok(guard) => Ok(GenerationalRefMut::new(
|
Ok(guard) => Ok(GenerationalRefMut::new(
|
||||||
@@ -173,12 +312,78 @@ impl<T: 'static> Storage<T> for UnsyncStorage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set(pointer: GenerationalPointer<Self>, value: T) {
|
fn new(value: T, caller: &'static std::panic::Location<'static>) -> GenerationalPointer<Self> {
|
||||||
let mut borrow_mut = pointer.storage.data.borrow_mut();
|
Self::create_new(RefCellStorageEntryData::Data(Box::new(value)), caller)
|
||||||
// First check if the generation is still valid
|
}
|
||||||
if !borrow_mut.valid(&pointer.location) {
|
|
||||||
return;
|
fn new_rc(
|
||||||
|
value: T,
|
||||||
|
caller: &'static std::panic::Location<'static>,
|
||||||
|
) -> GenerationalPointer<Self> {
|
||||||
|
// 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<Self>,
|
||||||
|
) -> BorrowResult<GenerationalPointer<Self>> {
|
||||||
|
// 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<Self>,
|
||||||
|
other: GenerationalPointer<Self>,
|
||||||
|
) -> 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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
use generational_box::{Storage, SyncStorage, UnsyncStorage};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reference_counting() {
|
||||||
|
fn reference_counting<S: Storage<String>>() {
|
||||||
|
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::<UnsyncStorage>();
|
||||||
|
reference_counting::<SyncStorage>();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn move_reference_in_place() {
|
||||||
|
fn move_reference_in_place<S: Storage<String>>() {
|
||||||
|
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::<UnsyncStorage>();
|
||||||
|
move_reference_in_place::<SyncStorage>();
|
||||||
|
}
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
use std::cell::RefCell;
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
use dioxus_core::prelude::use_hook;
|
use dioxus_core::prelude::use_hook;
|
||||||
use dioxus_core::prelude::Callback;
|
use dioxus_core::prelude::Callback;
|
||||||
|
|
||||||
@@ -19,7 +16,7 @@ pub fn use_callback<T: 'static, O: 'static>(f: impl FnMut(T) -> O + 'static) ->
|
|||||||
|
|
||||||
if let Some(callback) = callback.take() {
|
if let Some(callback) = callback.take() {
|
||||||
// Every time this hook is called replace the inner callback with the new callback
|
// 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
|
inner
|
||||||
|
|||||||
@@ -133,7 +133,8 @@ impl Document for NoOpDocument {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NoOpEvaluator;
|
/// The default No-Op evaluator
|
||||||
|
pub struct NoOpEvaluator;
|
||||||
impl Evaluator for NoOpEvaluator {
|
impl Evaluator for NoOpEvaluator {
|
||||||
fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {
|
fn send(&self, _data: serde_json::Value) -> Result<(), EvalError> {
|
||||||
Err(EvalError::Unsupported)
|
Err(EvalError::Unsupported)
|
||||||
|
|||||||
@@ -71,6 +71,19 @@ impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
|
|||||||
Self::new_with_caller(value, std::panic::Location::caller())
|
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(
|
pub(crate) fn new_with_caller(
|
||||||
value: T,
|
value: T,
|
||||||
caller: &'static std::panic::Location<'static>,
|
caller: &'static std::panic::Location<'static>,
|
||||||
@@ -78,7 +91,7 @@ impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
|
|||||||
let owner = current_owner();
|
let owner = current_owner();
|
||||||
|
|
||||||
Self {
|
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"),
|
origin_scope: current_scope_id().expect("in a virtual dom"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -99,13 +112,13 @@ impl<T: 'static, S: Storage<T>> CopyValue<T, S> {
|
|||||||
let owner = scope.owner();
|
let owner = scope.owner();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
value: owner.insert_with_caller(value, caller),
|
value: owner.insert_rc_with_caller(value, caller),
|
||||||
origin_scope: scope,
|
origin_scope: scope,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually drop the value in the CopyValue, invalidating the value in the process.
|
/// Manually drop the value in the CopyValue, invalidating the value in the process.
|
||||||
pub fn manually_drop(&self) -> Option<T> {
|
pub fn manually_drop(&self) {
|
||||||
self.value.manually_drop()
|
self.value.manually_drop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,25 +42,17 @@ impl<T: 'static, S: Storage<SignalData<T>>> ReadOnlySignal<T, S> {
|
|||||||
self.inner.id()
|
self.inner.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
/// Point to another signal
|
||||||
/// This should only be used by the `rsx!` macro.
|
pub fn point_to(&mut self, other: Self) -> BorrowResult {
|
||||||
pub fn __set(&mut self, value: T) {
|
self.inner.point_to(other.inner)
|
||||||
use crate::write::Writable;
|
|
||||||
use warnings::Warning;
|
|
||||||
// This is only called when converting T -> ReadOnlySignal<T> 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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
/// This should only be used by the `rsx!` macro.
|
/// This is only used by the `props` macro.
|
||||||
pub fn __take(&self) -> T {
|
/// Mark any readers of the signal as dirty
|
||||||
self.inner
|
pub fn mark_dirty(&mut self) {
|
||||||
.manually_drop()
|
use crate::write::Writable;
|
||||||
.expect("Signal has already been dropped")
|
_ = self.inner.try_write();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -182,6 +182,19 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// 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]
|
#[track_caller]
|
||||||
#[tracing::instrument(skip(value))]
|
#[tracing::instrument(skip(value))]
|
||||||
@@ -208,9 +221,14 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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.
|
/// Drop the value out of the signal, invalidating the signal in the process.
|
||||||
pub fn manually_drop(&self) -> Option<T> {
|
pub fn manually_drop(&self) {
|
||||||
self.inner.manually_drop().map(|i| i.value)
|
self.inner.manually_drop()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the scope the signal was created in.
|
/// Get the scope the signal was created in.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use dioxus_core::ScopeId;
|
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 generational_box::{AnyStorage, GenerationalBox, UnsyncStorage};
|
||||||
use js_sys::Function;
|
use js_sys::Function;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -95,7 +95,7 @@ impl WebEvaluator {
|
|||||||
fn create(js: String) -> GenerationalBox<Box<dyn Evaluator>> {
|
fn create(js: String) -> GenerationalBox<Box<dyn Evaluator>> {
|
||||||
let owner = UnsyncStorage::owner();
|
let owner = UnsyncStorage::owner();
|
||||||
|
|
||||||
let generational_box = owner.invalid();
|
let generational_box = owner.insert(Box::new(NoOpEvaluator) as Box<dyn Evaluator>);
|
||||||
|
|
||||||
// add the drop handler to DioxusChannel so that it gets dropped when the channel is dropped in js
|
// 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));
|
let channels = WebDioxusChannel::new(JSOwner::new(owner));
|
||||||
|
|||||||
Reference in new issue
Block a user