Stores: Externally tracked state and reactive collections (#4483)
* map signals mutably * boxed signals * remove mutable borrow type from boxed writable * improve map mut example * restore boxed_mut helper * make boxed readable/writable types copy * Move away from the ReadOnlySignal alias in examples * automatically convert Write * Fix some tests * ReadSignal and WriteSignal * move read only signal alias * make maps copy * store prototype * implement derive macro * Handle generics in the derive macro * Make Selector boxable * derive boxed conversion * Get rid of store type * Remove hardcoded type from derive macro * selector -> store * Refactor store implementation * more vec methods * todo mvc store example * Implement Storable for HashMap * fix write * restore sorting logic in todomvc * derive readable and writable * improve todomvc store example * Result store * Option store * collect dead subscribers * fix clippy and formatting * recognize some common foreign types automatically * fix foreign stores in props * slice and array selectors * clean up store example * writable is child owned * box selector * read and write to builtins * re-use the proc macro in the derive macro * Switch to a extension trait based approach * switch to extension traits in the derive macro * transposed method * derive enums * Expose Store::new * remove stores from the prelude * More consistent naming scheme for SelectorScope methods * document hashmap methods * document index extension * document option ext * document result ext * document slice extension * document the vec extension * make more methods borrow the store mutably * Move to impls to have higher priority over the builtin write extension traits * fix deadlock * finish stores documentation * Document the store macro * cleanup docs and organization * finish derive docs * fix typo * fix clippy * fix doc tests * fix derive macro with generics * fix clippy * test and fix empty structs * clean up todomvc store example * default to a boxed store writer to make it easier to type when providing to the context api * fix cargo doc * fix clippy in store example * relax generational box lifetime bounds * relax bounds * relax signal bounds * fix compilation * read store * relax bounds on default store impls * fix enum derive * Convert to readsignal and writesignal automatically * simplify type matching logic * add write store * unsized stores * shrink the path vec type * use more concrete types * remove opaque types * remove copy bounds * use lens as generic name everywhere * more consistent borrow semantics * use a concrete type for the signal metadata and mention references in the map docs * improve todomvc store docs * document store derive macro internals * refactor macro * improve selector node documentation * fix clippy * add btreemap * fix doc tests * more option methods * fix doc * fix clippy * add a bunch more result methods * add a bunch of bounds to the iterators * fix clippy * remove option from subscribers * add store macro to generate extension traits * fix store example * fix deadlock * fix docs * fix doc tests * fix clippy * add stores to public api * add store macro, and then update todomvc_store example --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
5591070e92
commit
58c41f8561
57 files changed
+4958
-436
No files matched your search
+114
-65
@@ -36,34 +36,16 @@ pub type WritableRef<'a, T: Writable, O = <T as Readable>::Target> =
|
||||
/// ```
|
||||
pub trait Writable: Readable {
|
||||
/// Additional data associated with the write reference.
|
||||
type WriteMetadata: 'static;
|
||||
|
||||
/// Get a mutable reference to the value. If the value has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
fn write(&mut self) -> WritableRef<'_, Self> {
|
||||
self.try_write().unwrap()
|
||||
}
|
||||
|
||||
/// Try to get a mutable reference to the value.
|
||||
#[track_caller]
|
||||
fn try_write(&mut self) -> Result<WritableRef<'_, Self>, generational_box::BorrowMutError> {
|
||||
self.try_write_unchecked().map(WriteLock::downcast_lifetime)
|
||||
}
|
||||
type WriteMetadata;
|
||||
|
||||
/// Try to get a mutable reference to the value without checking the lifetime. This will update any subscribers.
|
||||
///
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
fn try_write_unchecked(
|
||||
&self,
|
||||
) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError>;
|
||||
|
||||
/// Get a mutable reference to the value without checking the lifetime. This will update any subscribers.
|
||||
///
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
#[track_caller]
|
||||
fn write_unchecked(&self) -> WritableRef<'static, Self> {
|
||||
self.try_write_unchecked().unwrap()
|
||||
}
|
||||
) -> Result<WritableRef<'static, Self>, generational_box::BorrowMutError>
|
||||
where
|
||||
Self::Target: 'static;
|
||||
}
|
||||
|
||||
/// A mutable reference to a writable value. This reference acts similarly to [`std::cell::RefMut`], but it has extra debug information
|
||||
@@ -176,7 +158,7 @@ pub trait Writable: Readable {
|
||||
/// - T is the current type of the write
|
||||
/// - S is the storage type of the signal. This type determines if the signal is local to the current thread, or it can be shared across threads.
|
||||
/// - D is the additional data associated with the write reference. This is used by signals to track when the write is dropped
|
||||
pub struct WriteLock<'a, T: ?Sized + 'static, S: AnyStorage = UnsyncStorage, D = ()> {
|
||||
pub struct WriteLock<'a, T: ?Sized + 'a, S: AnyStorage = UnsyncStorage, D = ()> {
|
||||
write: S::Mut<'a, T>,
|
||||
data: D,
|
||||
}
|
||||
@@ -256,7 +238,7 @@ impl<'a, T: ?Sized, S: AnyStorage, D> WriteLock<'a, T, S, D> {
|
||||
impl<T, S, D> Deref for WriteLock<'_, T, S, D>
|
||||
where
|
||||
S: AnyStorage,
|
||||
T: ?Sized + 'static,
|
||||
T: ?Sized,
|
||||
{
|
||||
type Target = T;
|
||||
|
||||
@@ -268,7 +250,7 @@ where
|
||||
impl<T, S, D> DerefMut for WriteLock<'_, T, S, D>
|
||||
where
|
||||
S: AnyStorage,
|
||||
T: ?Sized + 'static,
|
||||
T: ?Sized,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.write
|
||||
@@ -277,9 +259,41 @@ where
|
||||
|
||||
/// An extension trait for [`Writable`] that provides some convenience methods.
|
||||
pub trait WritableExt: Writable {
|
||||
/// Map the readable type to a new type. This lets you provide a view into a readable type without needing to clone the inner value.
|
||||
/// Get a mutable reference to the value. If the value has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
fn write(&mut self) -> WritableRef<'_, Self>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
self.try_write().unwrap()
|
||||
}
|
||||
|
||||
/// Try to get a mutable reference to the value.
|
||||
#[track_caller]
|
||||
fn try_write(&mut self) -> Result<WritableRef<'_, Self>, generational_box::BorrowMutError>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
self.try_write_unchecked().map(WriteLock::downcast_lifetime)
|
||||
}
|
||||
|
||||
/// Get a mutable reference to the value without checking the lifetime. This will update any subscribers.
|
||||
///
|
||||
/// Anything that subscribes to the readable value will be rerun whenever the original value changes, even if the view does not change. If you want to memorize the view, you can use a [`crate::Memo`] instead.
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
#[track_caller]
|
||||
fn write_unchecked(&self) -> WritableRef<'static, Self>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
self.try_write_unchecked().unwrap()
|
||||
}
|
||||
|
||||
/// Map the references and mutable references of the writable value to a new type. This lets you provide a view
|
||||
/// into the writable value without creating a new signal or cloning the value.
|
||||
///
|
||||
/// Anything that subscribes to the writable value will be rerun whenever the original value changes or you write to this
|
||||
/// scoped value, even if the view does not change. If you want to memorize the view, you can use a [`crate::Memo`] instead.
|
||||
/// For fine grained scoped updates, use stores instead
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
@@ -306,17 +320,20 @@ pub trait WritableExt: Writable {
|
||||
/// ```
|
||||
fn map_mut<O, F, FMut>(self, f: F, f_mut: FMut) -> MappedMutSignal<O, Self, F, FMut>
|
||||
where
|
||||
Self: Clone + Sized + 'static,
|
||||
O: ?Sized + 'static,
|
||||
F: Fn(&Self::Target) -> &O + 'static,
|
||||
FMut: Fn(&mut Self::Target) -> &mut O + 'static,
|
||||
Self: Sized,
|
||||
O: ?Sized,
|
||||
F: Fn(&Self::Target) -> &O,
|
||||
FMut: Fn(&mut Self::Target) -> &mut O,
|
||||
{
|
||||
MappedMutSignal::new(self, f, f_mut)
|
||||
}
|
||||
|
||||
/// Run a function with a mutable reference to the value. If the value has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
fn with_mut<O>(&mut self, f: impl FnOnce(&mut Self::Target) -> O) -> O {
|
||||
fn with_mut<O>(&mut self, f: impl FnOnce(&mut Self::Target) -> O) -> O
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
f(&mut *self.write())
|
||||
}
|
||||
|
||||
@@ -324,7 +341,7 @@ pub trait WritableExt: Writable {
|
||||
#[track_caller]
|
||||
fn set(&mut self, value: Self::Target)
|
||||
where
|
||||
Self::Target: Sized,
|
||||
Self::Target: Sized + 'static,
|
||||
{
|
||||
*self.write() = value;
|
||||
}
|
||||
@@ -333,7 +350,7 @@ pub trait WritableExt: Writable {
|
||||
#[track_caller]
|
||||
fn toggle(&mut self)
|
||||
where
|
||||
Self::Target: std::ops::Not<Output = Self::Target> + Clone,
|
||||
Self::Target: std::ops::Not<Output = Self::Target> + Clone + 'static,
|
||||
{
|
||||
let inverted = !(*self.peek()).clone();
|
||||
self.set(inverted);
|
||||
@@ -346,7 +363,7 @@ pub trait WritableExt: Writable {
|
||||
index: I,
|
||||
) -> WritableRef<'_, Self, <Self::Target as std::ops::Index<I>>::Output>
|
||||
where
|
||||
Self::Target: std::ops::IndexMut<I>,
|
||||
Self::Target: std::ops::IndexMut<I> + 'static,
|
||||
{
|
||||
WriteLock::map(self.write(), |v| v.index_mut(index))
|
||||
}
|
||||
@@ -355,7 +372,7 @@ pub trait WritableExt: Writable {
|
||||
#[track_caller]
|
||||
fn take(&mut self) -> Self::Target
|
||||
where
|
||||
Self::Target: Default,
|
||||
Self::Target: Default + 'static,
|
||||
{
|
||||
self.with_mut(std::mem::take)
|
||||
}
|
||||
@@ -364,7 +381,7 @@ pub trait WritableExt: Writable {
|
||||
#[track_caller]
|
||||
fn replace(&mut self, value: Self::Target) -> Self::Target
|
||||
where
|
||||
Self::Target: Sized,
|
||||
Self::Target: Sized + 'static,
|
||||
{
|
||||
self.with_mut(|v| std::mem::replace(v, value))
|
||||
}
|
||||
@@ -390,16 +407,22 @@ impl<T: Writable<Storage = UnsyncStorage> + 'static> WritableBoxedExt for T {
|
||||
}
|
||||
|
||||
/// An extension trait for [`Writable<Option<T>>`]` that provides some convenience methods.
|
||||
pub trait WritableOptionExt<T: 'static>: Writable<Target = Option<T>> {
|
||||
pub trait WritableOptionExt<T>: Writable<Target = Option<T>> {
|
||||
/// Gets the value out of the Option, or inserts the given value if the Option is empty.
|
||||
#[track_caller]
|
||||
fn get_or_insert(&mut self, default: T) -> WritableRef<'_, Self, T> {
|
||||
fn get_or_insert(&mut self, default: T) -> WritableRef<'_, Self, T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
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.
|
||||
#[track_caller]
|
||||
fn get_or_insert_with(&mut self, default: impl FnOnce() -> T) -> WritableRef<'_, Self, T> {
|
||||
fn get_or_insert_with(&mut self, default: impl FnOnce() -> T) -> WritableRef<'_, Self, T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
let is_none = self.read().is_none();
|
||||
if is_none {
|
||||
self.with_mut(|v| *v = Some(default()));
|
||||
@@ -411,83 +434,114 @@ pub trait WritableOptionExt<T: 'static>: Writable<Target = Option<T>> {
|
||||
|
||||
/// Attempts to write the inner value of the Option.
|
||||
#[track_caller]
|
||||
fn as_mut(&mut self) -> Option<WritableRef<'_, Self, T>> {
|
||||
fn as_mut(&mut self) -> Option<WritableRef<'_, Self, T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
WriteLock::filter_map(self.write(), |v: &mut Option<T>| v.as_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W> WritableOptionExt<T> for W
|
||||
where
|
||||
T: 'static,
|
||||
W: Writable<Target = Option<T>>,
|
||||
{
|
||||
}
|
||||
impl<T, W> WritableOptionExt<T> for W where W: Writable<Target = Option<T>> {}
|
||||
|
||||
/// An extension trait for [`Writable<Vec<T>>`] that provides some convenience methods.
|
||||
pub trait WritableVecExt<T: 'static>: Writable<Target = Vec<T>> {
|
||||
pub trait WritableVecExt<T>: Writable<Target = Vec<T>> {
|
||||
/// Pushes a new value to the end of the vector.
|
||||
#[track_caller]
|
||||
fn push(&mut self, value: T) {
|
||||
fn push(&mut self, value: T)
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.push(value))
|
||||
}
|
||||
|
||||
/// Pops the last value from the vector.
|
||||
#[track_caller]
|
||||
fn pop(&mut self) -> Option<T> {
|
||||
fn pop(&mut self) -> Option<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.pop())
|
||||
}
|
||||
|
||||
/// Inserts a new value at the given index.
|
||||
#[track_caller]
|
||||
fn insert(&mut self, index: usize, value: T) {
|
||||
fn insert(&mut self, index: usize, value: T)
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.insert(index, value))
|
||||
}
|
||||
|
||||
/// Removes the value at the given index.
|
||||
#[track_caller]
|
||||
fn remove(&mut self, index: usize) -> T {
|
||||
fn remove(&mut self, index: usize) -> T
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.remove(index))
|
||||
}
|
||||
|
||||
/// Clears the vector, removing all values.
|
||||
#[track_caller]
|
||||
fn clear(&mut self) {
|
||||
fn clear(&mut self)
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.clear())
|
||||
}
|
||||
|
||||
/// Extends the vector with the given iterator.
|
||||
#[track_caller]
|
||||
fn extend(&mut self, iter: impl IntoIterator<Item = T>) {
|
||||
fn extend(&mut self, iter: impl IntoIterator<Item = T>)
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.extend(iter))
|
||||
}
|
||||
|
||||
/// Truncates the vector to the given length.
|
||||
#[track_caller]
|
||||
fn truncate(&mut self, len: usize) {
|
||||
fn truncate(&mut self, len: usize)
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.truncate(len))
|
||||
}
|
||||
|
||||
/// Swaps two values in the vector.
|
||||
#[track_caller]
|
||||
fn swap_remove(&mut self, index: usize) -> T {
|
||||
fn swap_remove(&mut self, index: usize) -> T
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.swap_remove(index))
|
||||
}
|
||||
|
||||
/// Retains only the values that match the given predicate.
|
||||
#[track_caller]
|
||||
fn retain(&mut self, f: impl FnMut(&T) -> bool) {
|
||||
fn retain(&mut self, f: impl FnMut(&T) -> bool)
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.retain(f))
|
||||
}
|
||||
|
||||
/// Splits the vector into two at the given index.
|
||||
#[track_caller]
|
||||
fn split_off(&mut self, at: usize) -> Vec<T> {
|
||||
fn split_off(&mut self, at: usize) -> Vec<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with_mut(|v| v.split_off(at))
|
||||
}
|
||||
|
||||
/// Try to mutably get an element from the vector.
|
||||
#[track_caller]
|
||||
fn get_mut(&mut self, index: usize) -> Option<WritableRef<'_, Self, T>> {
|
||||
fn get_mut(&mut self, index: usize) -> Option<WritableRef<'_, Self, T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
WriteLock::filter_map(self.write(), |v: &mut Vec<T>| v.get_mut(index))
|
||||
}
|
||||
|
||||
@@ -524,9 +578,4 @@ impl<'a, T: 'static, R: Writable<Target = Vec<T>>> Iterator for WritableValueIte
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, W> WritableVecExt<T> for W
|
||||
where
|
||||
T: 'static,
|
||||
W: Writable<Target = Vec<T>>,
|
||||
{
|
||||
}
|
||||
impl<T, W> WritableVecExt<T> for W where W: Writable<Target = Vec<T>> {}
|
||||
Reference in new issue
Block a user