#![doc = include_str!("../README.md")] #![warn(missing_docs)] use parking_lot::{ MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, }; use std::{ cell::{Ref, RefCell, RefMut}, fmt::Debug, marker::PhantomData, ops::{Deref, DerefMut}, rc::Rc, sync::{atomic::AtomicU32, Arc, OnceLock}, }; /// # Example /// /// ```compile_fail /// let data = String::from("hello world"); /// let owner = UnsyncStorage::owner(); /// let key = owner.insert(&data); /// drop(data); /// assert_eq!(*key.read(), "hello world"); /// ``` #[allow(unused)] fn compile_fail() {} #[test] fn reused() { let first_ptr; { let owner = UnsyncStorage::owner(); first_ptr = owner.insert(1).raw.data.data_ptr(); drop(owner); } { let owner = UnsyncStorage::owner(); let second_ptr = owner.insert(1234).raw.data.data_ptr(); assert_eq!(first_ptr, second_ptr); drop(owner); } } #[test] fn leaking_is_ok() { let data = String::from("hello world"); let key; { // create an owner let owner = UnsyncStorage::owner(); // insert data into the store key = owner.insert(data); // don't drop the owner std::mem::forget(owner); } assert_eq!(key.try_read().as_deref(), Some(&"hello world".to_string())); } #[test] fn drops() { let data = String::from("hello world"); let key; { // create an owner let owner = UnsyncStorage::owner(); // insert data into the store key = owner.insert(data); // drop the owner } assert!(key.try_read().is_none()); } #[test] fn works() { let owner = UnsyncStorage::owner(); let key = owner.insert(1); assert_eq!(*key.read(), 1); } #[test] fn insert_while_reading() { let owner = UnsyncStorage::owner(); let key; { let data: String = "hello world".to_string(); key = owner.insert(data); } let value = key.read(); owner.insert(&1); assert_eq!(*value, "hello world"); } #[test] #[should_panic] fn panics() { let owner = UnsyncStorage::owner(); let key = owner.insert(1); drop(owner); assert_eq!(*key.read(), 1); } #[test] fn fuzz() { fn maybe_owner_scope( valid_keys: &mut Vec>, invalid_keys: &mut Vec>, path: &mut Vec, ) { let branch_cutoff = 5; let children = if path.len() < branch_cutoff { rand::random::() % 4 } else { rand::random::() % 2 }; for i in 0..children { let owner = UnsyncStorage::owner(); let key = owner.insert(format!("hello world {path:?}")); valid_keys.push(key); path.push(i); // read all keys println!("{:?}", path); for key in valid_keys.iter() { let value = key.read(); println!("{:?}", value); assert!(value.starts_with("hello world")); } #[cfg(any(debug_assertions, feature = "check_generation"))] for key in invalid_keys.iter() { assert!(!key.validate()); } maybe_owner_scope(valid_keys, invalid_keys, path); invalid_keys.push(valid_keys.pop().unwrap()); path.pop(); } } for _ in 0..10 { maybe_owner_scope(&mut Vec::new(), &mut Vec::new(), &mut Vec::new()); } } /// The type erased id of a generational box. #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct GenerationalBoxId { data_ptr: *const (), #[cfg(any(debug_assertions, feature = "check_generation"))] generation: u32, } impl Debug for GenerationalBoxId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!("{:?}@{:?}", self.data_ptr, self.generation))?; #[cfg(not(any(debug_assertions, feature = "check_generation")))] f.write_fmt(format_args!("{:?}", self.data_ptr))?; Ok(()) } } /// The core Copy state type. The generational box will be dropped when the [Owner] is dropped. pub struct GenerationalBox { raw: MemoryLocation, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: u32, _marker: PhantomData, } impl Debug for GenerationalBox { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!( "{:?}@{:?}", self.raw.data.data_ptr(), self.generation ))?; #[cfg(not(any(debug_assertions, feature = "check_generation")))] f.write_fmt(format_args!("{:?}", self.raw.data.as_ptr()))?; Ok(()) } } impl> GenerationalBox { #[inline(always)] fn validate(&self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { self.raw .generation .load(std::sync::atomic::Ordering::Relaxed) == self.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { true } } /// Get the id of the generational box. pub fn id(&self) -> GenerationalBoxId { GenerationalBoxId { data_ptr: self.raw.data.data_ptr(), #[cfg(any(debug_assertions, feature = "check_generation"))] generation: self.generation, } } /// Try to read the value. Returns None if the value is no longer valid. pub fn try_read(&self) -> Option { self.validate().then(|| self.raw.data.try_read()).flatten() } /// Read the value. Panics if the value is no longer valid. pub fn read(&self) -> S::Ref { self.try_read().unwrap() } /// Try to write the value. Returns None if the value is no longer valid. pub fn try_write(&self) -> Option where { self.validate().then(|| self.raw.data.try_write()).flatten() } /// Write the value. Panics if the value is no longer valid. pub fn write(&self) -> S::Mut { self.try_write().unwrap() } /// Set the value. Panics if the value is no longer valid. pub fn set(&self, value: T) { self.validate().then(|| { self.raw.data.set(value); }); } /// Returns true if the pointer is equal to the other pointer. pub fn ptr_eq(&self, other: &Self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { self.raw.data.data_ptr() == other.raw.data.data_ptr() && self.generation == other.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { self.raw.data.as_ptr() == other.raw.data.as_ptr() } } } impl Copy for GenerationalBox {} impl Clone for GenerationalBox { fn clone(&self) -> Self { *self } } #[derive(Clone, Copy)] pub struct UnsyncStorage(&'static RefCell>>); impl Default for UnsyncStorage { fn default() -> Self { Self(Box::leak(Box::new(RefCell::new(None)))) } } #[derive(Clone, Copy)] pub struct SyncStorage(&'static RwLock>>); impl Default for SyncStorage { fn default() -> Self { Self(Box::leak(Box::new(RwLock::new(None)))) } } pub trait Mappable: Deref { type Mapped: Mappable + Deref; fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped; fn try_map( _self: Self, f: impl FnOnce(&T) -> Option<&U>, ) -> Option>; } impl Mappable for Ref<'static, T> { type Mapped = Ref<'static, U>; fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { Ref::map(_self, f) } fn try_map( _self: Self, f: impl FnOnce(&T) -> Option<&U>, ) -> Option> { Ref::try_map(_self, f) } } impl Mappable for MappedRwLockReadGuard<'static, T> { type Mapped = MappedRwLockReadGuard<'static, U>; fn map(_self: Self, f: impl FnOnce(&T) -> &U) -> Self::Mapped { MappedRwLockReadGuard::map(_self, f) } fn try_map( _self: Self, f: impl FnOnce(&T) -> Option<&U>, ) -> Option> { MappedRwLockReadGuard::try_map(_self, f).ok() } } pub trait MappableMut: DerefMut { type Mapped: MappableMut + DerefMut; fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped; fn try_map( _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, ) -> Option>; } impl MappableMut for RefMut<'static, T> { type Mapped = RefMut<'static, U>; fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { RefMut::map(_self, f) } fn try_map( _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, ) -> Option> { RefMut::try_map(_self, f) } } impl MappableMut for MappedRwLockWriteGuard<'static, T> { type Mapped = MappedRwLockWriteGuard<'static, U>; fn map(_self: Self, f: impl FnOnce(&mut T) -> &mut U) -> Self::Mapped { MappedRwLockWriteGuard::map(_self, f) } fn try_map( _self: Self, f: impl FnOnce(&mut T) -> Option<&mut U>, ) -> Option> { MappedRwLockWriteGuard::try_map(_self, f).ok() } } pub trait Storage: Copy + AnyStorage + 'static { type Ref: Mappable + Deref; type Mut: MappableMut + DerefMut; fn try_read(&self) -> Option; fn read(&self) -> Self::Ref { self.try_read() .expect("generational box has been invalidated or the type has changed") } fn try_write(&self) -> Option; fn write(&self) -> Self::Mut { self.try_write() .expect("generational box has been invalidated or the type has changed") } fn set(&self, value: Data); } pub trait AnyStorage: Default { fn data_ptr(&self) -> *const (); fn take(&self) -> bool; fn recycle(location: &MemoryLocation); // { // location.drop(); // self.recycled.lock().push(location); // } fn claim() -> MemoryLocation; // where // S: Default, // { // if let Some(location) = self.recycled.lock().pop() { // location // } else { // MemoryLocation { // data: Default::default(), // #[cfg(any(debug_assertions, feature = "check_generation"))] // generation: Box::leak(Box::new(Default::default())), // } // } // } /// Create a new owner. The owner will be responsible for dropping all of the generational boxes that it creates. fn owner() -> Owner { Owner { owned: Default::default(), phantom: PhantomData, } } } impl Storage for UnsyncStorage { type Ref = Ref<'static, T>; type Mut = RefMut<'static, T>; fn try_read(&self) -> Option { Ref::filter_map(self.0.borrow(), |any| any.as_ref()?.downcast_ref()).ok() } fn try_write(&self) -> Option { RefMut::filter_map(self.0.borrow_mut(), |any| any.as_mut()?.downcast_mut()).ok() } fn set(&self, value: T) { *self.0.borrow_mut() = Some(Box::new(value)); } } thread_local! { static UNSYNC_RUNTIME: RefCell>> = RefCell::new(Vec::new()); } impl AnyStorage for UnsyncStorage { fn data_ptr(&self) -> *const () { self.0.as_ptr() as *const () } fn take(&self) -> bool { self.0.borrow_mut().take().is_some() } fn claim() -> MemoryLocation { UNSYNC_RUNTIME.with(|runtime| { if let Some(location) = runtime.borrow_mut().pop() { location } else { MemoryLocation { data: UnsyncStorage(Box::leak(Box::new(RefCell::new(None)))), #[cfg(any(debug_assertions, feature = "check_generation"))] generation: Box::leak(Box::new(Default::default())), } } }) } fn recycle(location: &MemoryLocation) { location.drop(); UNSYNC_RUNTIME.with(|runtime| runtime.borrow_mut().push(*location)); } } impl Storage for SyncStorage { type Ref = MappedRwLockReadGuard<'static, T>; type Mut = MappedRwLockWriteGuard<'static, T>; fn try_read(&self) -> Option { RwLockReadGuard::try_map(self.0.read(), |any| any.as_ref()?.downcast_ref()).ok() } fn try_write(&self) -> Option { RwLockWriteGuard::try_map(self.0.write(), |any| any.as_mut()?.downcast_mut()).ok() } fn set(&self, value: T) { *self.0.write() = Some(Box::new(value)); } } static SYNC_RUNTIME: OnceLock>>>> = OnceLock::new(); fn sync_runtime() -> &'static Arc>>> { SYNC_RUNTIME.get_or_init(|| Arc::new(Mutex::new(Vec::new()))) } impl AnyStorage for SyncStorage { fn data_ptr(&self) -> *const () { self.0.data_ptr() as *const () } fn take(&self) -> bool { self.0.write().take().is_some() } fn claim() -> MemoryLocation { MemoryLocation { data: SyncStorage(Box::leak(Box::new(RwLock::new(None)))), #[cfg(any(debug_assertions, feature = "check_generation"))] generation: Box::leak(Box::new(Default::default())), } } fn recycle(location: &MemoryLocation) { location.drop(); sync_runtime().lock().push(*location); } } #[derive(Clone, Copy)] struct MemoryLocation { data: S, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: &'static AtomicU32, } impl MemoryLocation { #[allow(unused)] fn drop(&self) where S: AnyStorage, { let old = self.data.take(); #[cfg(any(debug_assertions, feature = "check_generation"))] if old { let new_generation = self.generation.load(std::sync::atomic::Ordering::Relaxed) + 1; self.generation .store(new_generation, std::sync::atomic::Ordering::Relaxed); } } fn replace(&mut self, value: T) -> GenerationalBox where S: Storage + Copy, { self.data.set(value); GenerationalBox { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: self.generation.load(std::sync::atomic::Ordering::Relaxed), _marker: PhantomData, } } } /// Owner: Handles dropping generational boxes. The owner acts like a runtime lifetime guard. Any states that you create with an owner will be dropped when that owner is dropped. pub struct Owner { owned: Arc>>>, phantom: PhantomData, } impl Owner { /// Insert a value into the store. The value will be dropped when the owner is dropped. pub fn insert(&self, value: T) -> GenerationalBox where S: Storage, { let mut location = S::claim(); let key = location.replace(value); self.owned.lock().push(location); key } /// Creates an invalid handle. This is useful for creating a handle that will be filled in later. If you use this before the value is filled in, you will get may get a panic or an out of date value. pub fn invalid(&self) -> GenerationalBox { let location = S::claim(); GenerationalBox { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] generation: location .generation .load(std::sync::atomic::Ordering::Relaxed), _marker: PhantomData, } } } impl Drop for Owner { fn drop(&mut self) { for location in self.owned.lock().iter() { S::recycle(location) } } }