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
@@ -37,7 +37,7 @@ pub type ReadableRef<'a, T: Readable, O = <T as Readable>::Target> =
|
||||
/// ```
|
||||
pub trait Readable {
|
||||
/// The target type of the reference.
|
||||
type Target: ?Sized + 'static;
|
||||
type Target: ?Sized;
|
||||
|
||||
/// The type of the storage this readable uses.
|
||||
type Storage: AnyStorage;
|
||||
@@ -47,7 +47,9 @@ pub trait Readable {
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
fn try_read_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>;
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
|
||||
where
|
||||
Self::Target: 'static;
|
||||
|
||||
/// Try to peek the current value of the signal without subscribing to updates. If the value has
|
||||
/// been dropped, this will return an error.
|
||||
@@ -55,10 +57,14 @@ pub trait Readable {
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
fn try_peek_unchecked(
|
||||
&self,
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>;
|
||||
) -> Result<ReadableRef<'static, Self>, generational_box::BorrowError>
|
||||
where
|
||||
Self::Target: 'static;
|
||||
|
||||
/// Get the underlying subscriber list for this readable. This is used to track when the value changes and notify subscribers.
|
||||
fn subscribers(&self) -> Option<Subscribers>;
|
||||
fn subscribers(&self) -> Subscribers
|
||||
where
|
||||
Self::Target: 'static;
|
||||
}
|
||||
|
||||
/// An extension trait for `Readable` types that provides some convenience methods.
|
||||
@@ -67,13 +73,19 @@ pub trait ReadableExt: Readable {
|
||||
/// If the value has been dropped, this will panic. Calling this on a Signal is the same as
|
||||
/// using the signal() syntax to read and subscribe to its value
|
||||
#[track_caller]
|
||||
fn read(&self) -> ReadableRef<Self> {
|
||||
fn read(&self) -> ReadableRef<Self>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
self.try_read().unwrap()
|
||||
}
|
||||
|
||||
/// Try to get the current value of the state. If this is a signal, this will subscribe the current scope to the signal.
|
||||
#[track_caller]
|
||||
fn try_read(&self) -> Result<ReadableRef<Self>, generational_box::BorrowError> {
|
||||
fn try_read(&self) -> Result<ReadableRef<Self>, generational_box::BorrowError>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
self.try_read_unchecked()
|
||||
.map(Self::Storage::downcast_lifetime_ref)
|
||||
}
|
||||
@@ -82,7 +94,10 @@ pub trait ReadableExt: Readable {
|
||||
///
|
||||
/// NOTE: This method is completely safe because borrow checking is done at runtime.
|
||||
#[track_caller]
|
||||
fn read_unchecked(&self) -> ReadableRef<'static, Self> {
|
||||
fn read_unchecked(&self) -> ReadableRef<'static, Self>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
self.try_read_unchecked().unwrap()
|
||||
}
|
||||
|
||||
@@ -121,14 +136,20 @@ pub trait ReadableExt: Readable {
|
||||
/// }
|
||||
/// ```
|
||||
#[track_caller]
|
||||
fn peek(&self) -> ReadableRef<Self> {
|
||||
fn peek(&self) -> ReadableRef<Self>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
Self::Storage::downcast_lifetime_ref(self.peek_unchecked())
|
||||
}
|
||||
|
||||
/// Try to peek the current value of the signal without subscribing to updates. If the value has
|
||||
/// been dropped, this will return an error.
|
||||
#[track_caller]
|
||||
fn try_peek(&self) -> Result<ReadableRef<Self>, generational_box::BorrowError> {
|
||||
fn try_peek(&self) -> Result<ReadableRef<Self>, generational_box::BorrowError>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
self.try_peek_unchecked()
|
||||
.map(Self::Storage::downcast_lifetime_ref)
|
||||
}
|
||||
@@ -137,13 +158,19 @@ pub trait ReadableExt: Readable {
|
||||
///
|
||||
/// If the signal has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
fn peek_unchecked(&self) -> ReadableRef<'static, Self> {
|
||||
fn peek_unchecked(&self) -> ReadableRef<'static, Self>
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
self.try_peek_unchecked().unwrap()
|
||||
}
|
||||
|
||||
/// 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.
|
||||
/// Map the references of the readable value to a new type. This lets you provide a view
|
||||
/// into the readable value without creating a new signal or cloning the value.
|
||||
///
|
||||
/// 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.
|
||||
/// 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. For fine grained scoped updates, use
|
||||
/// stores instead
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
@@ -167,8 +194,8 @@ pub trait ReadableExt: Readable {
|
||||
/// ```
|
||||
fn map<F, O>(self, f: F) -> MappedSignal<O, Self, F>
|
||||
where
|
||||
Self: Clone + Sized + 'static,
|
||||
F: Fn(&Self::Target) -> &O + 'static,
|
||||
Self: Clone + Sized,
|
||||
F: Fn(&Self::Target) -> &O,
|
||||
{
|
||||
MappedSignal::new(self, f)
|
||||
}
|
||||
@@ -177,20 +204,26 @@ pub trait ReadableExt: Readable {
|
||||
#[track_caller]
|
||||
fn cloned(&self) -> Self::Target
|
||||
where
|
||||
Self::Target: Clone,
|
||||
Self::Target: Clone + 'static,
|
||||
{
|
||||
self.read().clone()
|
||||
}
|
||||
|
||||
/// Run a function with a reference to the value. If the value has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
fn with<O>(&self, f: impl FnOnce(&Self::Target) -> O) -> O {
|
||||
fn with<O>(&self, f: impl FnOnce(&Self::Target) -> O) -> O
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
f(&*self.read())
|
||||
}
|
||||
|
||||
/// Run a function with a reference to the value. If the value has been dropped, this will panic.
|
||||
#[track_caller]
|
||||
fn with_peek<O>(&self, f: impl FnOnce(&Self::Target) -> O) -> O {
|
||||
fn with_peek<O>(&self, f: impl FnOnce(&Self::Target) -> O) -> O
|
||||
where
|
||||
Self::Target: 'static,
|
||||
{
|
||||
f(&*self.peek())
|
||||
}
|
||||
|
||||
@@ -198,7 +231,7 @@ pub trait ReadableExt: Readable {
|
||||
#[track_caller]
|
||||
fn index<I>(&self, index: I) -> ReadableRef<Self, <Self::Target as std::ops::Index<I>>::Output>
|
||||
where
|
||||
Self::Target: std::ops::Index<I>,
|
||||
Self::Target: std::ops::Index<I> + 'static,
|
||||
{
|
||||
<Self::Storage as AnyStorage>::map(self.read(), |v| v.index(index))
|
||||
}
|
||||
@@ -210,7 +243,7 @@ pub trait ReadableExt: Readable {
|
||||
unsafe fn deref_impl<'a>(&self) -> &'a dyn Fn() -> Self::Target
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
Self::Target: Clone,
|
||||
Self::Target: Clone + 'static,
|
||||
{
|
||||
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|
||||
|
||||
@@ -256,36 +289,52 @@ pub trait ReadableBoxExt: Readable<Storage = UnsyncStorage> {
|
||||
ReadSignal::new(self)
|
||||
}
|
||||
}
|
||||
impl<R: Readable<Storage = UnsyncStorage> + ?Sized> ReadableBoxExt for R {}
|
||||
|
||||
/// An extension trait for `Readable<Vec<T>>` that provides some convenience methods.
|
||||
pub trait ReadableVecExt<T: 'static>: Readable<Target = Vec<T>> {
|
||||
pub trait ReadableVecExt<T>: Readable<Target = Vec<T>> {
|
||||
/// Returns the length of the inner vector.
|
||||
#[track_caller]
|
||||
fn len(&self) -> usize {
|
||||
fn len(&self) -> usize
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with(|v| v.len())
|
||||
}
|
||||
|
||||
/// Returns true if the inner vector is empty.
|
||||
#[track_caller]
|
||||
fn is_empty(&self) -> bool {
|
||||
fn is_empty(&self) -> bool
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
self.with(|v| v.is_empty())
|
||||
}
|
||||
|
||||
/// Get the first element of the inner vector.
|
||||
#[track_caller]
|
||||
fn first(&self) -> Option<ReadableRef<Self, T>> {
|
||||
fn first(&self) -> Option<ReadableRef<Self, T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
<Self::Storage as AnyStorage>::try_map(self.read(), |v| v.first())
|
||||
}
|
||||
|
||||
/// Get the last element of the inner vector.
|
||||
#[track_caller]
|
||||
fn last(&self) -> Option<ReadableRef<Self, T>> {
|
||||
fn last(&self) -> Option<ReadableRef<Self, T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
<Self::Storage as AnyStorage>::try_map(self.read(), |v| v.last())
|
||||
}
|
||||
|
||||
/// Get the element at the given index of the inner vector.
|
||||
#[track_caller]
|
||||
fn get(&self, index: usize) -> Option<ReadableRef<Self, T>> {
|
||||
fn get(&self, index: usize) -> Option<ReadableRef<Self, T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
<Self::Storage as AnyStorage>::try_map(self.read(), |v| v.get(index))
|
||||
}
|
||||
|
||||
@@ -318,45 +367,39 @@ impl<'a, T: 'static, R: Readable<Target = Vec<T>>> Iterator for ReadableValueIte
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> ReadableVecExt<T> for R
|
||||
where
|
||||
T: 'static,
|
||||
R: Readable<Target = Vec<T>>,
|
||||
{
|
||||
}
|
||||
impl<T, R> ReadableVecExt<T> for R where R: Readable<Target = Vec<T>> {}
|
||||
|
||||
/// An extension trait for `Readable<Option<T>>` that provides some convenience methods.
|
||||
pub trait ReadableOptionExt<T: 'static>: Readable<Target = Option<T>> {
|
||||
pub trait ReadableOptionExt<T>: Readable<Target = Option<T>> {
|
||||
/// Unwraps the inner value and clones it.
|
||||
#[track_caller]
|
||||
fn unwrap(&self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
T: Clone + 'static,
|
||||
{
|
||||
self.as_ref().unwrap().clone()
|
||||
}
|
||||
|
||||
/// Attempts to read the inner value of the Option.
|
||||
#[track_caller]
|
||||
fn as_ref(&self) -> Option<ReadableRef<Self, T>> {
|
||||
fn as_ref(&self) -> Option<ReadableRef<Self, T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
<Self::Storage as AnyStorage>::try_map(self.read(), |v| v.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R> ReadableOptionExt<T> for R
|
||||
where
|
||||
T: 'static,
|
||||
R: Readable<Target = Option<T>>,
|
||||
{
|
||||
}
|
||||
impl<T, R> ReadableOptionExt<T> for R where R: Readable<Target = Option<T>> {}
|
||||
|
||||
/// An extension trait for `Readable<Option<T>>` that provides some convenience methods.
|
||||
pub trait ReadableResultExt<T: 'static, E: 'static>: Readable<Target = Result<T, E>> {
|
||||
pub trait ReadableResultExt<T, E>: Readable<Target = Result<T, E>> {
|
||||
/// Unwraps the inner value and clones it.
|
||||
#[track_caller]
|
||||
fn unwrap(&self) -> T
|
||||
where
|
||||
T: Clone,
|
||||
T: Clone + 'static,
|
||||
E: 'static,
|
||||
{
|
||||
self.as_ref()
|
||||
.unwrap_or_else(|_| panic!("Tried to unwrap a Result that was an error"))
|
||||
@@ -365,17 +408,15 @@ pub trait ReadableResultExt<T: 'static, E: 'static>: Readable<Target = Result<T,
|
||||
|
||||
/// Attempts to read the inner value of the Option.
|
||||
#[track_caller]
|
||||
fn as_ref(&self) -> Result<ReadableRef<Self, T>, ReadableRef<Self, E>> {
|
||||
fn as_ref(&self) -> Result<ReadableRef<Self, T>, ReadableRef<Self, E>>
|
||||
where
|
||||
T: 'static,
|
||||
E: 'static,
|
||||
{
|
||||
<Self::Storage as AnyStorage>::try_map(self.read(), |v| v.as_ref().ok()).ok_or(
|
||||
<Self::Storage as AnyStorage>::map(self.read(), |v| v.as_ref().err().unwrap()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, E, R> ReadableResultExt<T, E> for R
|
||||
where
|
||||
T: 'static,
|
||||
E: 'static,
|
||||
R: Readable<Target = Result<T, E>>,
|
||||
{
|
||||
}
|
||||
impl<T, E, R> ReadableResultExt<T, E> for R where R: Readable<Target = Result<T, E>> {}
|
||||
Reference in new issue
Block a user