From 1126c1d3297e5c06f9d5b56f1492267a4ce0bec1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 26 Oct 2023 12:47:53 -0500 Subject: [PATCH] make signal sync --- packages/generational-box/Cargo.toml | 6 +++ packages/generational-box/benches/lock.rs | 24 +++++++++ packages/generational-box/src/lib.rs | 65 +++++++++++++---------- packages/signals/Cargo.toml | 2 + packages/signals/src/impls.rs | 34 ++++++------ packages/signals/src/rt.rs | 15 +++--- packages/signals/src/signal.rs | 35 ++++++------ 7 files changed, 113 insertions(+), 68 deletions(-) create mode 100644 packages/generational-box/benches/lock.rs diff --git a/packages/generational-box/Cargo.toml b/packages/generational-box/Cargo.toml index 6c5a5a841..5567e9ccc 100644 --- a/packages/generational-box/Cargo.toml +++ b/packages/generational-box/Cargo.toml @@ -8,10 +8,16 @@ edition = "2018" [dependencies] bumpalo = { version = "3.6" } +parking_lot = "0.12.1" [dev-dependencies] rand = "0.8.5" +criterion = "0.3" [features] default = ["check_generation"] check_generation = [] + +[[bench]] +name = "lock" +harness = false diff --git a/packages/generational-box/benches/lock.rs b/packages/generational-box/benches/lock.rs new file mode 100644 index 000000000..fa3954fbb --- /dev/null +++ b/packages/generational-box/benches/lock.rs @@ -0,0 +1,24 @@ +#![allow(unused)] +use generational_box::{GenerationalBox, Owner, Store}; + +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +fn create(owner: &Owner) -> GenerationalBox { + owner.insert(0) +} + +fn set_read(signal: GenerationalBox) -> u32 { + signal.set(1); + *signal.read() +} + +fn bench_fib(c: &mut Criterion) { + let store = Store::default(); + let owner = store.owner(); + c.bench_function("create", |b| b.iter(|| create(black_box(&owner)))); + let signal = create(&owner); + c.bench_function("set_read", |b| b.iter(|| set_read(black_box(signal)))); +} + +criterion_group!(benches, bench_fib); +criterion_main!(benches); diff --git a/packages/generational-box/src/lib.rs b/packages/generational-box/src/lib.rs index 4be383b4f..ac97937fd 100644 --- a/packages/generational-box/src/lib.rs +++ b/packages/generational-box/src/lib.rs @@ -1,11 +1,15 @@ #![doc = include_str!("../README.md")] #![warn(missing_docs)] +use parking_lot::{ + MappedRwLockReadGuard, MappedRwLockWriteGuard, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, +}; use std::{ - cell::{Cell, Ref, RefCell, RefMut}, + cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc, + sync::{atomic::AtomicU32, Arc}, }; use bumpalo::Bump; @@ -29,12 +33,12 @@ fn reused() { let first_ptr; { let owner = store.owner(); - first_ptr = owner.insert(1).raw.data.as_ptr(); + first_ptr = owner.insert(1).raw.data.data_ptr(); drop(owner); } { let owner = store.owner(); - let second_ptr = owner.insert(1234).raw.data.as_ptr(); + let second_ptr = owner.insert(1234).raw.data.data_ptr(); assert_eq!(first_ptr, second_ptr); drop(owner); } @@ -161,7 +165,7 @@ impl Debug for GenerationalBox { #[cfg(any(debug_assertions, feature = "check_generation"))] f.write_fmt(format_args!( "{:?}@{:?}", - self.raw.data.as_ptr(), + self.raw.data.data_ptr(), self.generation ))?; #[cfg(not(any(debug_assertions, feature = "check_generation")))] @@ -175,7 +179,10 @@ impl GenerationalBox { fn validate(&self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.generation.get() == self.generation + self.raw + .generation + .load(std::sync::atomic::Ordering::Relaxed) + == self.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { @@ -184,10 +191,10 @@ impl GenerationalBox { } /// Try to read the value. Returns None if the value is no longer valid. - pub fn try_read(&self) -> Option> { + pub fn try_read(&self) -> Option> { self.validate() .then(|| { - Ref::filter_map(self.raw.data.borrow(), |any| { + RwLockReadGuard::try_map(self.raw.data.read(), |any| { any.as_ref()?.downcast_ref::() }) .ok() @@ -196,15 +203,15 @@ impl GenerationalBox { } /// Read the value. Panics if the value is no longer valid. - pub fn read(&self) -> Ref<'static, T> { + pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { self.try_read().unwrap() } /// Try to write the value. Returns None if the value is no longer valid. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.validate() .then(|| { - RefMut::filter_map(self.raw.data.borrow_mut(), |any| { + RwLockWriteGuard::try_map(self.raw.data.write(), |any| { any.as_mut()?.downcast_mut::() }) .ok() @@ -213,14 +220,14 @@ impl GenerationalBox { } /// Write the value. Panics if the value is no longer valid. - pub fn write(&self) -> RefMut<'static, T> { + pub fn write(&self) -> MappedRwLockWriteGuard<'static, T> { self.try_write().unwrap() } /// Set the value. Panics if the value is no longer valid. pub fn set(&self, value: T) { self.validate().then(|| { - *self.raw.data.borrow_mut() = Some(Box::new(value)); + *self.raw.data.write() = Some(Box::new(value)); }); } @@ -228,7 +235,8 @@ impl GenerationalBox { pub fn ptr_eq(&self, other: &Self) -> bool { #[cfg(any(debug_assertions, feature = "check_generation"))] { - self.raw.data.as_ptr() == other.raw.data.as_ptr() && self.generation == other.generation + self.raw.data.data_ptr() == other.raw.data.data_ptr() + && self.generation == other.generation } #[cfg(not(any(debug_assertions, feature = "check_generation")))] { @@ -247,25 +255,26 @@ impl Clone for GenerationalBox { #[derive(Clone, Copy)] struct MemoryLocation { - data: &'static RefCell>>, + data: &'static RwLock>>, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: &'static Cell, + generation: &'static AtomicU32, } impl MemoryLocation { #[allow(unused)] fn drop(&self) { - let old = self.data.borrow_mut().take(); + let old = self.data.write().take(); #[cfg(any(debug_assertions, feature = "check_generation"))] if old.is_some() { drop(old); - let new_generation = self.generation.get() + 1; - self.generation.set(new_generation); + let new_generation = self.generation.load(std::sync::atomic::Ordering::Relaxed) + 1; + self.generation + .store(new_generation, std::sync::atomic::Ordering::Relaxed); } } fn replace(&mut self, value: T) -> GenerationalBox { - let mut inner_mut = self.data.borrow_mut(); + let mut inner_mut = self.data.write(); let raw = Box::new(value); let old = inner_mut.replace(raw); @@ -273,7 +282,7 @@ impl MemoryLocation { GenerationalBox { raw: *self, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.generation.get(), + generation: self.generation.load(std::sync::atomic::Ordering::Relaxed), _marker: PhantomData, } } @@ -282,14 +291,12 @@ impl MemoryLocation { /// Handles recycling generational boxes that have been dropped. Your application should have one store or one store per thread. #[derive(Clone)] pub struct Store { - bump: &'static Bump, - recycled: Rc>>, + recycled: Arc>>, } impl Default for Store { fn default() -> Self { Self { - bump: Box::leak(Box::new(Bump::new())), recycled: Default::default(), } } @@ -298,18 +305,18 @@ impl Default for Store { impl Store { fn recycle(&self, location: MemoryLocation) { location.drop(); - self.recycled.borrow_mut().push(location); + self.recycled.lock().push(location); } fn claim(&self) -> MemoryLocation { - if let Some(location) = self.recycled.borrow_mut().pop() { + if let Some(location) = self.recycled.lock().pop() { location } else { - let data: &'static RefCell<_> = self.bump.alloc(RefCell::new(None)); + let data: &'static RwLock<_> = Box::leak(Box::new(RwLock::new(None))); MemoryLocation { data, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: self.bump.alloc(Cell::new(0)), + generation: Box::leak(Box::new(Default::default())), } } } @@ -344,7 +351,9 @@ impl Owner { GenerationalBox { raw: location, #[cfg(any(debug_assertions, feature = "check_generation"))] - generation: location.generation.get(), + generation: location + .generation + .load(std::sync::atomic::Ordering::Relaxed), _marker: PhantomData, } } diff --git a/packages/signals/Cargo.toml b/packages/signals/Cargo.toml index 56e19bbd8..6d22ee0c8 100644 --- a/packages/signals/Cargo.toml +++ b/packages/signals/Cargo.toml @@ -12,6 +12,8 @@ generational-box = { workspace = true } tracing = { workspace = true } simple_logger = "4.2.0" serde = { version = "1", features = ["derive"], optional = true } +parking_lot = "0.12.1" +once_cell = "1.18.0" [dev-dependencies] dioxus = { workspace = true } diff --git a/packages/signals/src/impls.rs b/packages/signals/src/impls.rs index 6427c2e2b..9d6ea3019 100644 --- a/packages/signals/src/impls.rs +++ b/packages/signals/src/impls.rs @@ -1,7 +1,6 @@ use crate::rt::CopyValue; use crate::signal::{ReadOnlySignal, Signal, Write}; - -use std::cell::{Ref, RefMut}; +use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use std::{ fmt::{Debug, Display}, @@ -38,8 +37,8 @@ macro_rules! read_impls { impl $ty> { /// Read a value from the inner vector. - pub fn get(&self, index: usize) -> Option> { - Ref::filter_map(self.read(), |v| v.get(index)).ok() + pub fn get(&self, index: usize) -> Option> { + MappedRwLockReadGuard::try_map(self.read(), |v| v.get(index)).ok() } } @@ -53,8 +52,8 @@ macro_rules! read_impls { } /// Attemps to read the inner value of the Option. - pub fn as_ref(&self) -> Option> { - Ref::filter_map(self.read(), |v| v.as_ref()).ok() + pub fn as_ref(&self) -> Option> { + MappedRwLockReadGuard::try_map(self.read(), |v| v.as_ref()).ok() } } }; @@ -182,19 +181,22 @@ macro_rules! write_impls { } /// Gets the value out of the Option, or inserts the given value if the Option is empty. - pub fn get_or_insert(&self, default: T) -> Ref<'_, T> { + pub fn get_or_insert(&self, default: T) -> MappedRwLockReadGuard<'_, T> { self.get_or_insert_with(|| default) } /// Gets the value out of the Option, or inserts the value returned by the given function if the Option is empty. - pub fn get_or_insert_with(&self, default: impl FnOnce() -> T) -> Ref<'_, T> { + pub fn get_or_insert_with( + &self, + default: impl FnOnce() -> T, + ) -> MappedRwLockReadGuard<'_, T> { let borrow = self.read(); if borrow.is_none() { drop(borrow); self.with_mut(|v| *v = Some(default())); - Ref::map(self.read(), |v| v.as_ref().unwrap()) + MappedRwLockReadGuard::map(self.read(), |v| v.as_ref().unwrap()) } else { - Ref::map(borrow, |v| v.as_ref().unwrap()) + MappedRwLockReadGuard::map(borrow, |v| v.as_ref().unwrap()) } } } @@ -238,15 +240,15 @@ impl IntoIterator for CopyValue> { impl CopyValue> { /// Write to an element in the inner vector. - pub fn get_mut(&self, index: usize) -> Option> { - RefMut::filter_map(self.write(), |v| v.get_mut(index)).ok() + pub fn get_mut(&self, index: usize) -> Option> { + MappedRwLockWriteGuard::try_map(self.write(), |v| v.get_mut(index)).ok() } } impl CopyValue> { /// Deref the inner value mutably. - pub fn as_mut(&self) -> Option> { - RefMut::filter_map(self.write(), |v| v.as_mut()).ok() + pub fn as_mut(&self) -> Option> { + MappedRwLockWriteGuard::try_map(self.write(), |v| v.as_mut()).ok() } } @@ -281,14 +283,14 @@ impl IntoIterator for Signal> { impl Signal> { /// Returns a reference to an element or `None` if out of bounds. - pub fn get_mut(&self, index: usize) -> Option>> { + pub fn get_mut(&self, index: usize) -> Option>> { Write::filter_map(self.write(), |v| v.get_mut(index)) } } impl Signal> { /// Returns a reference to an element or `None` if out of bounds. - pub fn as_mut(&self) -> Option>> { + pub fn as_mut(&self) -> Option>> { Write::filter_map(self.write(), |v| v.as_mut()) } } diff --git a/packages/signals/src/rt.rs b/packages/signals/src/rt.rs index 0f89a3fe6..c1fb2994d 100644 --- a/packages/signals/src/rt.rs +++ b/packages/signals/src/rt.rs @@ -1,5 +1,3 @@ -use std::cell::{Ref, RefMut}; - use std::mem::MaybeUninit; use std::ops::Deref; use std::rc::Rc; @@ -8,9 +6,12 @@ use dioxus_core::prelude::*; use dioxus_core::ScopeId; use generational_box::{GenerationalBox, Owner, Store}; +use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use crate::Effect; +static STORE: once_cell::sync::Lazy = once_cell::sync::Lazy::new(Store::default); + fn current_store() -> Store { match consume_context() { Some(rt) => rt, @@ -117,22 +118,22 @@ impl CopyValue { } /// Try to read the value. If the value has been dropped, this will return None. - pub fn try_read(&self) -> Option> { + pub fn try_read(&self) -> Option> { self.value.try_read() } /// Read the value. If the value has been dropped, this will panic. - pub fn read(&self) -> Ref<'static, T> { + pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { self.value.read() } /// Try to write the value. If the value has been dropped, this will return None. - pub fn try_write(&self) -> Option> { + pub fn try_write(&self) -> Option> { self.value.try_write() } /// Write the value. If the value has been dropped, this will panic. - pub fn write(&self) -> RefMut<'static, T> { + pub fn write(&self) -> MappedRwLockWriteGuard<'static, T> { self.value.write() } @@ -168,7 +169,7 @@ impl PartialEq for CopyValue { } impl Deref for CopyValue { - type Target = dyn Fn() -> Ref<'static, T>; + type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types diff --git a/packages/signals/src/signal.rs b/packages/signals/src/signal.rs index 9213307ff..43aa0c904 100644 --- a/packages/signals/src/signal.rs +++ b/packages/signals/src/signal.rs @@ -1,5 +1,5 @@ use std::{ - cell::{Ref, RefCell, RefMut}, + cell::RefCell, mem::MaybeUninit, ops::{Deref, DerefMut}, rc::Rc, @@ -10,6 +10,7 @@ use dioxus_core::{ prelude::{current_scope_id, has_context, provide_context, schedule_update_any}, ScopeId, ScopeState, }; +use parking_lot::{MappedRwLockReadGuard, MappedRwLockWriteGuard}; use crate::{get_effect_stack, CopyValue, Effect, EffectStack}; @@ -173,7 +174,7 @@ impl Signal { /// Get the current value of the signal. This will subscribe the current scope to the signal. /// If the signal has been dropped, this will panic. - pub fn read(&self) -> Ref { + pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { let inner = self.inner.read(); if let Some(effect) = inner.effect_stack.current() { let mut effect_subscribers = inner.effect_subscribers.borrow_mut(); @@ -197,14 +198,14 @@ impl Signal { } } } - Ref::map(inner, |v| &v.value) + MappedRwLockReadGuard::map(inner, |v| &v.value) } /// Get a mutable reference to the signal's value. /// If the signal has been dropped, this will panic. - pub fn write(&self) -> Write<'_, T> { + pub fn write(&self) -> Write { let inner = self.inner.write(); - let borrow = RefMut::map(inner, |v| &mut v.value); + let borrow = MappedRwLockWriteGuard::map(inner, |v| &mut v.value); Write { write: borrow, signal: SignalSubscriberDrop { signal: *self }, @@ -281,7 +282,7 @@ impl PartialEq for Signal { } impl Deref for Signal { - type Target = dyn Fn() -> Ref<'static, T>; + type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types @@ -324,17 +325,17 @@ impl Drop for SignalSubscriberDrop { } /// A mutable reference to a signal's value. -pub struct Write<'a, T: 'static, I: 'static = T> { - write: RefMut<'a, T>, +pub struct Write { + write: MappedRwLockWriteGuard<'static, T>, signal: SignalSubscriberDrop, } -impl<'a, T: 'static, I: 'static> Write<'a, T, I> { +impl Write { /// Map the mutable reference to the signal's value to a new type. - pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write<'a, O, I> { + pub fn map(myself: Self, f: impl FnOnce(&mut T) -> &mut O) -> Write { let Self { write, signal } = myself; Write { - write: RefMut::map(write, f), + write: MappedRwLockWriteGuard::map(write, f), signal, } } @@ -343,14 +344,14 @@ impl<'a, T: 'static, I: 'static> Write<'a, T, I> { pub fn filter_map( myself: Self, f: impl FnOnce(&mut T) -> Option<&mut O>, - ) -> Option> { + ) -> Option> { let Self { write, signal } = myself; - let write = RefMut::filter_map(write, f).ok(); + let write = MappedRwLockWriteGuard::try_map(write, f).ok(); write.map(|write| Write { write, signal }) } } -impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> { +impl Deref for Write { type Target = T; fn deref(&self) -> &Self::Target { @@ -358,7 +359,7 @@ impl<'a, T: 'static, I: 'static> Deref for Write<'a, T, I> { } } -impl DerefMut for Write<'_, T, I> { +impl DerefMut for Write { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } @@ -381,7 +382,7 @@ impl ReadOnlySignal { } /// Get the current value of the signal. This will subscribe the current scope to the signal. - pub fn read(&self) -> Ref { + pub fn read(&self) -> MappedRwLockReadGuard<'static, T> { self.inner.read() } @@ -405,7 +406,7 @@ impl PartialEq for ReadOnlySignal { } impl Deref for ReadOnlySignal { - type Target = dyn Fn() -> Ref<'static, T>; + type Target = dyn Fn() -> MappedRwLockReadGuard<'static, T>; fn deref(&self) -> &Self::Target { // https://github.com/dtolnay/case-studies/tree/master/callable-types