Merge pull request #1383 from ealmloff/debug-signals

Add debug information to borrows and ownership in signals
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2023-12-06 21:19:38 -08:00
commit ccd850edf6
7 files changed
+516 -90

No files matched your search

+2
View File
@@ -18,3 +18,5 @@ rand = "0.8.5"
[features] [features]
default = ["check_generation"] default = ["check_generation"]
check_generation = [] check_generation = []
debug_borrows = []
debug_ownership = []
+2
View File
@@ -11,6 +11,8 @@ Three main types manage state in Generational Box:
Example: Example:
```rust ```rust
use generational_box::Store;
// Create a store for this thread // Create a store for this thread
let store = Store::default(); let store = Store::default();
+407 -46
View File
@@ -2,9 +2,12 @@
#![warn(missing_docs)] #![warn(missing_docs)]
use std::{ use std::{
any::Any,
cell::{Cell, Ref, RefCell, RefMut}, cell::{Cell, Ref, RefCell, RefMut},
fmt::Debug, error::Error,
fmt::{Debug, Display},
marker::PhantomData, marker::PhantomData,
ops::{Deref, DerefMut},
rc::Rc, rc::Rc,
}; };
@@ -29,12 +32,12 @@ fn reused() {
let first_ptr; let first_ptr;
{ {
let owner = store.owner(); let owner = store.owner();
first_ptr = owner.insert(1).raw.data.as_ptr(); first_ptr = owner.insert(1).raw.0.data.as_ptr();
drop(owner); drop(owner);
} }
{ {
let owner = store.owner(); let owner = store.owner();
let second_ptr = owner.insert(1234).raw.data.as_ptr(); let second_ptr = owner.insert(1234).raw.0.data.as_ptr();
assert_eq!(first_ptr, second_ptr); assert_eq!(first_ptr, second_ptr);
drop(owner); drop(owner);
} }
@@ -53,7 +56,10 @@ fn leaking_is_ok() {
// don't drop the owner // don't drop the owner
std::mem::forget(owner); std::mem::forget(owner);
} }
assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string())); assert_eq!(
key.try_read().as_deref().unwrap(),
&"hello world".to_string()
);
} }
#[test] #[test]
@@ -68,7 +74,7 @@ fn drops() {
key = owner.insert(data); key = owner.insert(data);
// drop the owner // drop the owner
} }
assert!(key.try_read().is_none()); assert!(key.try_read().is_err());
} }
#[test] #[test]
@@ -129,7 +135,7 @@ fn fuzz() {
println!("{:?}", path); println!("{:?}", path);
for key in valid_keys.iter() { for key in valid_keys.iter() {
let value = key.read(); let value = key.read();
println!("{:?}", value); println!("{:?}", &*value);
assert!(value.starts_with("hello world")); assert!(value.starts_with("hello world"));
} }
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
@@ -153,6 +159,8 @@ pub struct GenerationalBox<T> {
raw: MemoryLocation, raw: MemoryLocation,
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
generation: u32, generation: u32,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
_marker: PhantomData<T>, _marker: PhantomData<T>,
} }
@@ -161,7 +169,7 @@ impl<T: 'static> Debug for GenerationalBox<T> {
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
f.write_fmt(format_args!( f.write_fmt(format_args!(
"{:?}@{:?}", "{:?}@{:?}",
self.raw.data.as_ptr(), self.raw.0.data.as_ptr(),
self.generation self.generation
))?; ))?;
#[cfg(not(any(debug_assertions, feature = "check_generation")))] #[cfg(not(any(debug_assertions, feature = "check_generation")))]
@@ -175,7 +183,7 @@ impl<T: 'static> GenerationalBox<T> {
fn validate(&self) -> bool { fn validate(&self) -> bool {
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
{ {
self.raw.generation.get() == self.generation self.raw.0.generation.get() == self.generation
} }
#[cfg(not(any(debug_assertions, feature = "check_generation")))] #[cfg(not(any(debug_assertions, feature = "check_generation")))]
{ {
@@ -184,43 +192,51 @@ impl<T: 'static> GenerationalBox<T> {
} }
/// 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.
pub fn try_read(&self) -> Option<Ref<'static, T>> { #[track_caller]
self.validate() pub fn try_read(&self) -> Result<GenerationalRef<T>, BorrowError> {
.then(|| { if !self.validate() {
Ref::filter_map(self.raw.data.borrow(), |any| { return Err(BorrowError::Dropped(ValueDroppedError {
any.as_ref()?.downcast_ref::<T>() #[cfg(any(debug_assertions, feature = "debug_borrows"))]
}) created_at: self.created_at,
.ok() }));
}) }
.flatten() self.raw.try_borrow(
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
self.created_at,
)
} }
/// Read the value. Panics if the value is no longer valid. /// Read the value. Panics if the value is no longer valid.
pub fn read(&self) -> Ref<'static, T> { #[track_caller]
pub fn read(&self) -> GenerationalRef<T> {
self.try_read().unwrap() self.try_read().unwrap()
} }
/// 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.
pub fn try_write(&self) -> Option<RefMut<'static, T>> { #[track_caller]
self.validate() pub fn try_write(&self) -> Result<GenerationalRefMut<T>, BorrowMutError> {
.then(|| { if !self.validate() {
RefMut::filter_map(self.raw.data.borrow_mut(), |any| { return Err(BorrowMutError::Dropped(ValueDroppedError {
any.as_mut()?.downcast_mut::<T>() #[cfg(any(debug_assertions, feature = "debug_borrows"))]
}) created_at: self.created_at,
.ok() }));
}) }
.flatten() self.raw.try_borrow_mut(
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
self.created_at,
)
} }
/// Write the value. Panics if the value is no longer valid. /// Write the value. Panics if the value is no longer valid.
pub fn write(&self) -> RefMut<'static, T> { #[track_caller]
pub fn write(&self) -> GenerationalRefMut<T> {
self.try_write().unwrap() self.try_write().unwrap()
} }
/// 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) { pub fn set(&self, value: T) {
self.validate().then(|| { self.validate().then(|| {
*self.raw.data.borrow_mut() = Some(Box::new(value)); *self.raw.0.data.borrow_mut() = Some(Box::new(value));
}); });
} }
@@ -228,7 +244,8 @@ impl<T: 'static> GenerationalBox<T> {
pub fn ptr_eq(&self, other: &Self) -> bool { pub fn ptr_eq(&self, other: &Self) -> bool {
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
{ {
self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation self.raw.0.data.as_ptr() == other.raw.0.data.as_ptr()
&& self.generation == other.generation
} }
#[cfg(not(any(debug_assertions, feature = "check_generation")))] #[cfg(not(any(debug_assertions, feature = "check_generation")))]
{ {
@@ -246,26 +263,37 @@ impl<T> Clone for GenerationalBox<T> {
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct MemoryLocation { struct MemoryLocation(&'static MemoryLocationInner);
data: &'static RefCell<Option<Box<dyn std::any::Any>>>,
struct MemoryLocationInner {
data: RefCell<Option<Box<dyn std::any::Any>>>,
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
generation: &'static Cell<u32>, generation: Cell<u32>,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_at: RefCell<Vec<&'static std::panic::Location<'static>>>,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_mut_at: Cell<Option<&'static std::panic::Location<'static>>>,
} }
impl MemoryLocation { impl MemoryLocation {
#[allow(unused)] #[allow(unused)]
fn drop(&self) { fn drop(&self) {
let old = self.data.borrow_mut().take(); let old = self.0.data.borrow_mut().take();
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
if old.is_some() { if old.is_some() {
drop(old); drop(old);
let new_generation = self.generation.get() + 1; let new_generation = self.0.generation.get() + 1;
self.generation.set(new_generation); self.0.generation.set(new_generation);
} }
} }
fn replace<T: 'static>(&mut self, value: T) -> GenerationalBox<T> { fn replace_with_caller<T: 'static>(
let mut inner_mut = self.data.borrow_mut(); &mut self,
value: T,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
caller: &'static std::panic::Location<'static>,
) -> GenerationalBox<T> {
let mut inner_mut = self.0.data.borrow_mut();
let raw = Box::new(value); let raw = Box::new(value);
let old = inner_mut.replace(raw); let old = inner_mut.replace(raw);
@@ -273,10 +301,315 @@ impl MemoryLocation {
GenerationalBox { GenerationalBox {
raw: *self, raw: *self,
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
generation: self.generation.get(), generation: self.0.generation.get(),
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: caller,
_marker: PhantomData, _marker: PhantomData,
} }
} }
#[track_caller]
fn try_borrow<T: Any>(
&self,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
) -> Result<GenerationalRef<T>, BorrowError> {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
self.0
.borrowed_at
.borrow_mut()
.push(std::panic::Location::caller());
match self.0.data.try_borrow() {
Ok(borrow) => match Ref::filter_map(borrow, |any| any.as_ref()?.downcast_ref::<T>()) {
Ok(reference) => Ok(GenerationalRef {
inner: reference,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: GenerationalRefBorrowInfo {
borrowed_at: std::panic::Location::caller(),
borrowed_from: self.0,
},
}),
Err(_) => Err(BorrowError::Dropped(ValueDroppedError {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at,
})),
},
Err(_) => Err(BorrowError::AlreadyBorrowedMut(AlreadyBorrowedMutError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_mut_at: self.0.borrowed_mut_at.get().unwrap(),
})),
}
}
#[track_caller]
fn try_borrow_mut<T: Any>(
&self,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
) -> Result<GenerationalRefMut<T>, BorrowMutError> {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
{
self.0
.borrowed_mut_at
.set(Some(std::panic::Location::caller()));
}
match self.0.data.try_borrow_mut() {
Ok(borrow_mut) => {
match RefMut::filter_map(borrow_mut, |any| any.as_mut()?.downcast_mut::<T>()) {
Ok(reference) => Ok(GenerationalRefMut {
inner: reference,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: GenerationalRefMutBorrowInfo {
borrowed_from: self.0,
},
}),
Err(_) => Err(BorrowMutError::Dropped(ValueDroppedError {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at,
})),
}
}
Err(_) => Err(BorrowMutError::AlreadyBorrowed(AlreadyBorrowedError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_at: self.0.borrowed_at.borrow().clone(),
})),
}
}
}
#[derive(Debug, Clone)]
/// An error that can occur when trying to borrow a value.
pub enum BorrowError {
/// The value was dropped.
Dropped(ValueDroppedError),
/// The value was already borrowed mutably.
AlreadyBorrowedMut(AlreadyBorrowedMutError),
}
impl Display for BorrowError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BorrowError::Dropped(error) => Display::fmt(error, f),
BorrowError::AlreadyBorrowedMut(error) => Display::fmt(error, f),
}
}
}
impl Error for BorrowError {}
#[derive(Debug, Clone)]
/// An error that can occur when trying to borrow a value mutably.
pub enum BorrowMutError {
/// The value was dropped.
Dropped(ValueDroppedError),
/// The value was already borrowed.
AlreadyBorrowed(AlreadyBorrowedError),
/// The value was already borrowed mutably.
AlreadyBorrowedMut(AlreadyBorrowedMutError),
}
impl Display for BorrowMutError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BorrowMutError::Dropped(error) => Display::fmt(error, f),
BorrowMutError::AlreadyBorrowedMut(error) => Display::fmt(error, f),
BorrowMutError::AlreadyBorrowed(error) => Display::fmt(error, f),
}
}
}
impl Error for BorrowMutError {}
/// An error that can occur when trying to use a value that has been dropped.
#[derive(Debug, Copy, Clone)]
pub struct ValueDroppedError {
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: &'static std::panic::Location<'static>,
}
impl Display for ValueDroppedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Failed to borrow because the value was dropped.")?;
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
f.write_fmt(format_args!("created_at: {}", self.created_at))?;
Ok(())
}
}
impl std::error::Error for ValueDroppedError {}
/// An error that can occur when trying to borrow a value that has already been borrowed mutably.
#[derive(Debug, Copy, Clone)]
pub struct AlreadyBorrowedMutError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_mut_at: &'static std::panic::Location<'static>,
}
impl Display for AlreadyBorrowedMutError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Failed to borrow because the value was already borrowed mutably.")?;
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
f.write_fmt(format_args!("borrowed_mut_at: {}", self.borrowed_mut_at))?;
Ok(())
}
}
impl std::error::Error for AlreadyBorrowedMutError {}
/// An error that can occur when trying to borrow a value mutably that has already been borrowed immutably.
#[derive(Debug, Clone)]
pub struct AlreadyBorrowedError {
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_at: Vec<&'static std::panic::Location<'static>>,
}
impl Display for AlreadyBorrowedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Failed to borrow mutably because the value was already borrowed immutably.")?;
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
f.write_str("borrowed_at:")?;
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
for location in self.borrowed_at.iter() {
f.write_fmt(format_args!("\t{}", location))?;
}
Ok(())
}
}
impl std::error::Error for AlreadyBorrowedError {}
/// A reference to a value in a generational box.
pub struct GenerationalRef<T: 'static> {
inner: Ref<'static, T>,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: GenerationalRefBorrowInfo,
}
impl<T: 'static> GenerationalRef<T> {
/// Map one ref type to another.
pub fn map<U, F>(orig: GenerationalRef<T>, f: F) -> GenerationalRef<U>
where
F: FnOnce(&T) -> &U,
{
GenerationalRef {
inner: Ref::map(orig.inner, f),
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: GenerationalRefBorrowInfo {
borrowed_at: orig.borrow.borrowed_at,
borrowed_from: orig.borrow.borrowed_from,
},
}
}
/// Filter one ref type to another.
pub fn filter_map<U, F>(orig: GenerationalRef<T>, f: F) -> Option<GenerationalRef<U>>
where
F: FnOnce(&T) -> Option<&U>,
{
let Self {
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
} = orig;
Ref::filter_map(inner, f).ok().map(|inner| GenerationalRef {
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: GenerationalRefBorrowInfo {
borrowed_at: borrow.borrowed_at,
borrowed_from: borrow.borrowed_from,
},
})
}
}
impl<T: 'static> Deref for GenerationalRef<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.deref()
}
}
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
struct GenerationalRefBorrowInfo {
borrowed_at: &'static std::panic::Location<'static>,
borrowed_from: &'static MemoryLocationInner,
}
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
impl Drop for GenerationalRefBorrowInfo {
fn drop(&mut self) {
self.borrowed_from
.borrowed_at
.borrow_mut()
.retain(|location| std::ptr::eq(*location, self.borrowed_at as *const _));
}
}
/// A mutable reference to a value in a generational box.
pub struct GenerationalRefMut<T: 'static> {
inner: RefMut<'static, T>,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: GenerationalRefMutBorrowInfo,
}
impl<T: 'static> GenerationalRefMut<T> {
/// Map one ref type to another.
pub fn map<U, F>(orig: GenerationalRefMut<T>, f: F) -> GenerationalRefMut<U>
where
F: FnOnce(&mut T) -> &mut U,
{
GenerationalRefMut {
inner: RefMut::map(orig.inner, f),
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow: orig.borrow,
}
}
/// Filter one ref type to another.
pub fn filter_map<U, F>(orig: GenerationalRefMut<T>, f: F) -> Option<GenerationalRefMut<U>>
where
F: FnOnce(&mut T) -> Option<&mut U>,
{
let Self {
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
} = orig;
RefMut::filter_map(inner, f)
.ok()
.map(|inner| GenerationalRefMut {
inner,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrow,
})
}
}
impl<T: 'static> Deref for GenerationalRefMut<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.inner.deref()
}
}
impl<T: 'static> DerefMut for GenerationalRefMut<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.inner.deref_mut()
}
}
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
struct GenerationalRefMutBorrowInfo {
borrowed_from: &'static MemoryLocationInner,
}
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
impl Drop for GenerationalRefMutBorrowInfo {
fn drop(&mut self) {
self.borrowed_from.borrowed_mut_at.take();
}
} }
/// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread. /// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread.
@@ -305,12 +638,16 @@ impl Store {
if let Some(location) = self.recycled.borrow_mut().pop() { if let Some(location) = self.recycled.borrow_mut().pop() {
location location
} else { } else {
let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None)); let data: &'static MemoryLocationInner = self.bump.alloc(MemoryLocationInner {
MemoryLocation { data: RefCell::new(None),
data,
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
generation: self.bump.alloc(Cell::new(0)), generation: Cell::new(0),
} #[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_at: Default::default(),
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
borrowed_mut_at: Default::default(),
});
MemoryLocation(data)
} }
} }
@@ -331,9 +668,31 @@ pub struct Owner {
impl Owner { impl Owner {
/// Insert a value into the store. The value will be dropped when the owner is dropped. /// Insert a value into the store. The value will be dropped when the owner is dropped.
#[track_caller]
pub fn insert<T: 'static>(&self, value: T) -> GenerationalBox<T> { pub fn insert<T: 'static>(&self, value: T) -> GenerationalBox<T> {
let mut location = self.store.claim(); let mut location = self.store.claim();
let key = location.replace(value); let key = location.replace_with_caller(
value,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
std::panic::Location::caller(),
);
self.owned.borrow_mut().push(location);
key
}
/// Insert a value into the store with a specific location blamed for creating the value. The value will be dropped when the owner is dropped.
pub fn insert_with_caller<T: 'static>(
&self,
value: T,
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
caller: &'static std::panic::Location<'static>,
) -> GenerationalBox<T> {
let mut location = self.store.claim();
let key = location.replace_with_caller(
value,
#[cfg(any(debug_assertions, feature = "debug_borrows"))]
caller,
);
self.owned.borrow_mut().push(location); self.owned.borrow_mut().push(location);
key key
} }
@@ -344,7 +703,9 @@ impl Owner {
GenerationalBox { GenerationalBox {
raw: location, raw: location,
#[cfg(any(debug_assertions, feature = "check_generation"))] #[cfg(any(debug_assertions, feature = "check_generation"))]
generation: location.generation.get(), generation: location.0.generation.get(),
#[cfg(any(debug_assertions, feature = "debug_ownership"))]
created_at: std::panic::Location::caller(),
_marker: PhantomData, _marker: PhantomData,
} }
} }
+1 -1
View File
@@ -100,7 +100,7 @@ impl Effect {
/// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead. /// Run the effect callback immediately. Returns `true` if the effect was run. Returns `false` is the effect is dead.
pub fn try_run(&self) { pub fn try_run(&self) {
if let Some(mut callback) = self.callback.try_write() { if let Ok(mut callback) = self.callback.try_write() {
{ {
self.effect_stack.effects.write().push(*self); self.effect_stack.effects.write().push(*self);
} }
+17 -17
View File
@@ -1,7 +1,7 @@
use crate::rt::CopyValue; use crate::rt::CopyValue;
use crate::signal::{ReadOnlySignal, Signal, Write}; use crate::signal::{ReadOnlySignal, Signal, Write};
use generational_box::GenerationalRef;
use std::cell::{Ref, RefMut}; use generational_box::GenerationalRefMut;
use std::{ use std::{
fmt::{Debug, Display}, fmt::{Debug, Display},
@@ -38,8 +38,8 @@ macro_rules! read_impls {
impl<T: 'static> $ty<Vec<T>> { impl<T: 'static> $ty<Vec<T>> {
/// Read a value from the inner vector. /// Read a value from the inner vector.
pub fn get(&self, index: usize) -> Option<Ref<'_, T>> { pub fn get(&self, index: usize) -> Option<GenerationalRef<T>> {
Ref::filter_map(self.read(), |v| v.get(index)).ok() GenerationalRef::filter_map(self.read(), |v| v.get(index))
} }
} }
@@ -52,9 +52,9 @@ macro_rules! read_impls {
self.with(|v| v.clone()).unwrap() self.with(|v| v.clone()).unwrap()
} }
/// Attemps to read the inner value of the Option. /// Attempts to read the inner value of the Option.
pub fn as_ref(&self) -> Option<Ref<'_, T>> { pub fn as_ref(&self) -> Option<GenerationalRef<T>> {
Ref::filter_map(self.read(), |v| v.as_ref()).ok() GenerationalRef::filter_map(self.read(), |v| v.as_ref())
} }
} }
}; };
@@ -182,19 +182,19 @@ macro_rules! write_impls {
} }
/// Gets the value out of the Option, or inserts the given value if the Option is empty. /// Gets the value out of the Option, or inserts the given value if the Option is empty.
pub fn get_or_insert(&self, default: T) -> Ref<'_, T> { pub fn get_or_insert(&self, default: T) -> GenerationalRef<T> {
self.get_or_insert_with(|| default) self.get_or_insert_with(|| default)
} }
/// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty.
pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> { pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> GenerationalRef<T> {
let borrow = self.read(); let borrow = self.read();
if borrow.is_none() { if borrow.is_none() {
drop(borrow); drop(borrow);
self.with_mut(|v| *v = Some(default())); self.with_mut(|v| *v = Some(default()));
Ref::map(self.read(), |v| v.as_ref().unwrap()) GenerationalRef::map(self.read(), |v| v.as_ref().unwrap())
} else { } else {
Ref::map(borrow, |v| v.as_ref().unwrap()) GenerationalRef::map(borrow, |v| v.as_ref().unwrap())
} }
} }
} }
@@ -238,15 +238,15 @@ impl<T: Clone + 'static> IntoIterator for CopyValue<Vec<T>> {
impl<T: 'static> CopyValue<Vec<T>> { impl<T: 'static> CopyValue<Vec<T>> {
/// Write to an element in the inner vector. /// Write to an element in the inner vector.
pub fn get_mut(&self, index: usize) -> Option<RefMut<'_, T>> { pub fn get_mut(&self, index: usize) -> Option<GenerationalRefMut<T>> {
RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok() GenerationalRefMut::filter_map(self.write(), |v| v.get_mut(index))
} }
} }
impl<T: 'static> CopyValue<Option<T>> { impl<T: 'static> CopyValue<Option<T>> {
/// Deref the inner value mutably. /// Deref the inner value mutably.
pub fn as_mut(&self) -> Option<RefMut<'_, T>> { pub fn as_mut(&self) -> Option<GenerationalRefMut<T>> {
RefMut::filter_map(self.write(), |v| v.as_mut()).ok() GenerationalRefMut::filter_map(self.write(), |v| v.as_mut())
} }
} }
@@ -281,14 +281,14 @@ impl<T: Clone + 'static> IntoIterator for Signal<Vec<T>> {
impl<T: 'static> Signal<Vec<T>> { impl<T: 'static> Signal<Vec<T>> {
/// Returns a reference to an element or `None` if out of bounds. /// Returns a reference to an element or `None` if out of bounds.
pub fn get_mut(&self, index: usize) -> Option<Write<'_, T, Vec<T>>> { pub fn get_mut(&self, index: usize) -> Option<Write<T, Vec<T>>> {
Write::filter_map(self.write(), |v| v.get_mut(index)) Write::filter_map(self.write(), |v| v.get_mut(index))
} }
} }
impl<T: 'static> Signal<Option<T>> { impl<T: 'static> Signal<Option<T>> {
/// Returns a reference to an element or `None` if out of bounds. /// Returns a reference to an element or `None` if out of bounds.
pub fn as_mut(&self) -> Option<Write<'_, T, Option<T>>> { pub fn as_mut(&self) -> Option<Write<T, Option<T>>> {
Write::filter_map(self.write(), |v| v.as_mut()) Write::filter_map(self.write(), |v| v.as_mut())
} }
} }
+29 -8
View File
@@ -1,5 +1,3 @@
use std::cell::{Ref, RefMut};
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::ops::Deref; use std::ops::Deref;
use std::rc::Rc; use std::rc::Rc;
@@ -7,7 +5,9 @@ use std::rc::Rc;
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
use dioxus_core::ScopeId; use dioxus_core::ScopeId;
use generational_box::{GenerationalBox, Owner, Store}; use generational_box::{
BorrowError, BorrowMutError, GenerationalBox, GenerationalRef, GenerationalRefMut, Owner, Store,
};
use crate::Effect; use crate::Effect;
@@ -83,6 +83,7 @@ impl<T: 'static> CopyValue<T> {
/// Create a new CopyValue. The value will be stored in the current component. /// Create a new CopyValue. The value will be stored in the current component.
/// ///
/// Once the component this value is created in is dropped, the value will be dropped. /// Once the component this value is created in is dropped, the value will be dropped.
#[track_caller]
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
let owner = current_owner(); let owner = current_owner();
@@ -92,6 +93,22 @@ impl<T: 'static> CopyValue<T> {
} }
} }
pub(crate) fn new_with_caller(
value: T,
#[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>,
) -> Self {
let owner = current_owner();
Self {
value: owner.insert_with_caller(
value,
#[cfg(debug_assertions)]
caller,
),
origin_scope: current_scope_id().expect("in a virtual dom"),
}
}
/// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped. /// Create a new CopyValue. The value will be stored in the given scope. When the specified scope is dropped, the value will be dropped.
pub fn new_in_scope(value: T, scope: ScopeId) -> Self { pub fn new_in_scope(value: T, scope: ScopeId) -> Self {
let owner = owner_in_scope(scope); let owner = owner_in_scope(scope);
@@ -117,22 +134,26 @@ impl<T: 'static> CopyValue<T> {
} }
/// Try to read the value. If the value has been dropped, this will return None. /// Try to read the value. If the value has been dropped, this will return None.
pub fn try_read(&self) -> Option<Ref<'_, T>> { #[track_caller]
pub fn try_read(&self) -> Result<GenerationalRef<T>, BorrowError> {
self.value.try_read() self.value.try_read()
} }
/// Read the value. If the value has been dropped, this will panic. /// Read the value. If the value has been dropped, this will panic.
pub fn read(&self) -> Ref<'static, T> { #[track_caller]
pub fn read(&self) -> GenerationalRef<T> {
self.value.read() self.value.read()
} }
/// Try to write the value. If the value has been dropped, this will return None. /// Try to write the value. If the value has been dropped, this will return None.
pub fn try_write(&self) -> Option<RefMut<'static, T>> { #[track_caller]
pub fn try_write(&self) -> Result<GenerationalRefMut<T>, BorrowMutError> {
self.value.try_write() self.value.try_write()
} }
/// Write the value. If the value has been dropped, this will panic. /// Write the value. If the value has been dropped, this will panic.
pub fn write(&self) -> RefMut<'static, T> { #[track_caller]
pub fn write(&self) -> GenerationalRefMut<T> {
self.value.write() self.value.write()
} }
@@ -168,7 +189,7 @@ impl<T: 'static> PartialEq for CopyValue<T> {
} }
impl<T> Deref for CopyValue<T> { impl<T> Deref for CopyValue<T> {
type Target = dyn Fn() -> Ref<'static, T>; type Target = dyn Fn() -> GenerationalRef<T>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
// https://github.com/dtolnay/case-studies/tree/master/callable-types // https://github.com/dtolnay/case-studies/tree/master/callable-types
+58 -18
View File
@@ -1,5 +1,5 @@
use std::{ use std::{
cell::{Ref, RefCell, RefMut}, cell::RefCell,
mem::MaybeUninit, mem::MaybeUninit,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
rc::Rc, rc::Rc,
@@ -10,6 +10,7 @@ use dioxus_core::{
prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, prelude::{current_scope_id, has_context, provide_context, schedule_update_any},
ScopeId, ScopeState, ScopeId, ScopeState,
}; };
use generational_box::{GenerationalRef, GenerationalRefMut};
use crate::{get_effect_stack, CopyValue, Effect, EffectStack}; use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
@@ -44,9 +45,19 @@ use crate::{get_effect_stack, CopyValue, Effect, EffectStack};
/// } /// }
/// } /// }
/// ``` /// ```
#[track_caller]
#[must_use] #[must_use]
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> { pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
*cx.use_hook(|| Signal::new(f())) #[cfg(debug_assertions)]
let caller = std::panic::Location::caller();
*cx.use_hook(|| {
Signal::new_with_caller(
f(),
#[cfg(debug_assertions)]
caller,
)
})
} }
#[derive(Clone)] #[derive(Clone)]
@@ -138,6 +149,7 @@ impl<'de, T: serde::Deserialize<'de> + 'static> serde::Deserialize<'de> for Sign
impl<T: 'static> Signal<T> { impl<T: 'static> Signal<T> {
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking. /// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
#[track_caller]
pub fn new(value: T) -> Self { pub fn new(value: T) -> Self {
Self { Self {
inner: CopyValue::new(SignalData { inner: CopyValue::new(SignalData {
@@ -150,6 +162,26 @@ impl<T: 'static> Signal<T> {
} }
} }
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
fn new_with_caller(
value: T,
#[cfg(debug_assertions)] caller: &'static std::panic::Location<'static>,
) -> Self {
Self {
inner: CopyValue::new_with_caller(
SignalData {
subscribers: Default::default(),
effect_subscribers: Default::default(),
update_any: schedule_update_any().expect("in a virtual dom"),
value,
effect_stack: get_effect_stack(),
},
#[cfg(debug_assertions)]
caller,
),
}
}
/// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope. /// Create a new signal with a custom owner scope. The signal will be dropped when the owner scope is dropped instead of the current scope.
pub fn new_in_scope(value: T, owner: ScopeId) -> Self { pub fn new_in_scope(value: T, owner: ScopeId) -> Self {
Self { Self {
@@ -173,7 +205,8 @@ impl<T: 'static> Signal<T> {
/// Get the current value of the signal. This will subscribe the current scope to the signal. /// Get the current value of the signal. This will subscribe the current scope to the signal.
/// If the signal has been dropped, this will panic. /// If the signal has been dropped, this will panic.
pub fn read(&self) -> Ref<T> { #[track_caller]
pub fn read(&self) -> GenerationalRef<T> {
let inner = self.inner.read(); let inner = self.inner.read();
if let Some(effect) = inner.effect_stack.current() { if let Some(effect) = inner.effect_stack.current() {
let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); let mut effect_subscribers = inner.effect_subscribers.borrow_mut();
@@ -197,14 +230,15 @@ impl<T: 'static> Signal<T> {
} }
} }
} }
Ref::map(inner, |v| &v.value) GenerationalRef::map(inner, |v| &v.value)
} }
/// Get a mutable reference to the signal's value. /// Get a mutable reference to the signal's value.
/// If the signal has been dropped, this will panic. /// If the signal has been dropped, this will panic.
pub fn write(&self) -> Write<'_, T> { #[track_caller]
pub fn write(&self) -> Write<T> {
let inner = self.inner.write(); let inner = self.inner.write();
let borrow = RefMut::map(inner, |v| &mut v.value); let borrow = GenerationalRefMut::map(inner, |v| &mut v.value);
Write { Write {
write: borrow, write: borrow,
signal: SignalSubscriberDrop { signal: *self }, signal: SignalSubscriberDrop { signal: *self },
@@ -240,12 +274,14 @@ impl<T: 'static> Signal<T> {
} }
/// Set the value of the signal. This will trigger an update on all subscribers. /// Set the value of the signal. This will trigger an update on all subscribers.
#[track_caller]
pub fn set(&self, value: T) { pub fn set(&self, value: T) {
*self.write() = value; *self.write() = value;
} }
/// Run a closure with a reference to the signal's value. /// Run a closure with a reference to the signal's value.
/// If the signal has been dropped, this will panic. /// If the signal has been dropped, this will panic.
#[track_caller]
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O { pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
let write = self.read(); let write = self.read();
f(&*write) f(&*write)
@@ -253,6 +289,7 @@ impl<T: 'static> Signal<T> {
/// Run a closure with a mutable reference to the signal's value. /// Run a closure with a mutable reference to the signal's value.
/// If the signal has been dropped, this will panic. /// If the signal has been dropped, this will panic.
#[track_caller]
pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O { pub fn with_mut<O>(&self, f: impl FnOnce(&mut T) -> O) -> O {
let mut write = self.write(); let mut write = self.write();
f(&mut *write) f(&mut *write)
@@ -262,6 +299,7 @@ impl<T: 'static> Signal<T> {
impl<T: Clone + 'static> Signal<T> { impl<T: Clone + 'static> Signal<T> {
/// Get the current value of the signal. This will subscribe the current scope to the signal. /// Get the current value of the signal. This will subscribe the current scope to the signal.
/// If the signal has been dropped, this will panic. /// If the signal has been dropped, this will panic.
#[track_caller]
pub fn value(&self) -> T { pub fn value(&self) -> T {
self.read().clone() self.read().clone()
} }
@@ -281,7 +319,7 @@ impl<T: 'static> PartialEq for Signal<T> {
} }
impl<T> Deref for Signal<T> { impl<T> Deref for Signal<T> {
type Target = dyn Fn() -> Ref<'static, T>; type Target = dyn Fn() -> GenerationalRef<T>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
// https://github.com/dtolnay/case-studies/tree/master/callable-types // https://github.com/dtolnay/case-studies/tree/master/callable-types
@@ -324,17 +362,17 @@ impl<T: 'static> Drop for SignalSubscriberDrop<T> {
} }
/// A mutable reference to a signal's value. /// A mutable reference to a signal's value.
pub struct Write<'a, T: 'static, I: 'static = T> { pub struct Write<T: 'static, I: 'static = T> {
write: RefMut<'a, T>, write: GenerationalRefMut<T>,
signal: SignalSubscriberDrop<I>, signal: SignalSubscriberDrop<I>,
} }
impl<'a, T: 'static, I: 'static> Write<'a, T, I> { impl<T: 'static, I: 'static> Write<T, I> {
/// Map the mutable reference to the signal's value to a new type. /// Map the mutable reference to the signal's value to a new type.
pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> { pub fn map<O>(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<O, I> {
let Self { write, signal } = myself; let Self { write, signal } = myself;
Write { Write {
write: RefMut::map(write, f), write: GenerationalRefMut::map(write, f),
signal, signal,
} }
} }
@@ -343,14 +381,14 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> {
pub fn filter_map<O>( pub fn filter_map<O>(
myself: Self, myself: Self,
f: impl FnOnce(&mut T) -> Option<&mut O>, f: impl FnOnce(&mut T) -> Option<&mut O>,
) -> Option<Write<'a, O, I>> { ) -> Option<Write<O, I>> {
let Self { write, signal } = myself; let Self { write, signal } = myself;
let write = RefMut::filter_map(write, f).ok(); let write = GenerationalRefMut::filter_map(write, f);
write.map(|write| Write { write, signal }) write.map(|write| Write { write, signal })
} }
} }
impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> { impl<T: 'static, I: 'static> Deref for Write<T, I> {
type Target = T; type Target = T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
@@ -358,7 +396,7 @@ impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> {
} }
} }
impl<T, I> DerefMut for Write<'_, T, I> { impl<T, I> DerefMut for Write<T, I> {
fn deref_mut(&mut self) -> &mut Self::Target { fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.write &mut self.write
} }
@@ -381,11 +419,13 @@ impl<T: 'static> ReadOnlySignal<T> {
} }
/// Get the current value of the signal. This will subscribe the current scope to the signal. /// Get the current value of the signal. This will subscribe the current scope to the signal.
pub fn read(&self) -> Ref<T> { #[track_caller]
pub fn read(&self) -> GenerationalRef<T> {
self.inner.read() self.inner.read()
} }
/// Run a closure with a reference to the signal's value. /// Run a closure with a reference to the signal's value.
#[track_caller]
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O { pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
self.inner.with(f) self.inner.with(f)
} }
@@ -405,7 +445,7 @@ impl<T: 'static> PartialEq for ReadOnlySignal<T> {
} }
impl<T> Deref for ReadOnlySignal<T> { impl<T> Deref for ReadOnlySignal<T> {
type Target = dyn Fn() -> Ref<'static, T>; type Target = dyn Fn() -> GenerationalRef<T>;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
// https://github.com/dtolnay/case-studies/tree/master/callable-types // https://github.com/dtolnay/case-studies/tree/master/callable-types