Boxed and mapped signals preparing for stores (#4413)
This PR contains just the breaking changes required for the upcoming store implementation. It adds two new primitives: Mutable mapped signals (without a different subscriber list). This lets you scope a writable value and pass it to a child component. It requires two closures to map the immutable and mutable references to the inner signal. With stores, these closures will be derived Boxed readable and writable values (ReadSignal, and WriteSignal). Any readable or writable types can be converted to a read signal or write signal with the boxed method or built in types are automatically converted into the boxed variants when passing into props * 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 * remove stores * writable is child owned * fix typo * fix clippy * fix doc tests
This commit is contained in:
1 parent
6919f72abc
commit
d8d11db403
64 files changed
+1307
-726
No files matched your search
@@ -730,7 +730,7 @@ impl<
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Counter(multiplier: ReadOnlySignal<String>) -> Element {
|
||||
/// fn Counter(multiplier: ReadSignal<String>) -> Element {
|
||||
/// let multiplier_parsed = multiplier().parse::<usize>()?;
|
||||
/// let mut count = use_signal(|| multiplier_parsed);
|
||||
/// rsx! {
|
||||
@@ -783,7 +783,7 @@ impl<
|
||||
/// }
|
||||
///
|
||||
/// #[component]
|
||||
/// fn Counter(multiplier: ReadOnlySignal<String>) -> Element {
|
||||
/// fn Counter(multiplier: ReadSignal<String>) -> Element {
|
||||
/// let multiplier_parsed = multiplier().parse::<usize>()?;
|
||||
/// let mut count = use_signal(|| multiplier_parsed);
|
||||
/// rsx! {
|
||||
|
||||
@@ -87,10 +87,10 @@ pub use crate::innerlude::{
|
||||
Element, ElementId, ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes,
|
||||
IntoAttributeValue, IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation,
|
||||
Mutations, NoOpMutations, Ok, OptionStringFromMarker, Properties, ReactiveContext, RenderError,
|
||||
Result, Runtime, RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SuperFrom, SuperInto,
|
||||
SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps, SuspenseContext, SuspenseExtension,
|
||||
Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner, VPlaceholder,
|
||||
VText, VirtualDom, WriteMutations,
|
||||
Result, Runtime, RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers,
|
||||
SuperFrom, SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps,
|
||||
SuspenseContext, SuspenseExtension, Task, Template, TemplateAttribute, TemplateNode,
|
||||
VComponent, VNode, VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
|
||||
};
|
||||
|
||||
pub use const_format;
|
||||
@@ -141,7 +141,7 @@ impl ReactiveContext {
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let old_subscribers = std::mem::take(&mut self.inner.write().subscribers);
|
||||
for subscriber in old_subscribers {
|
||||
subscriber.0.lock().unwrap().remove(self);
|
||||
subscriber.0.remove(self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ impl ReactiveContext {
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let subscribers = &self.inner.read().subscribers;
|
||||
for subscriber in subscribers.iter() {
|
||||
subscriber.0.lock().unwrap().insert(*self);
|
||||
subscriber.0.add(*self);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -238,11 +238,14 @@ impl ReactiveContext {
|
||||
}
|
||||
|
||||
/// Subscribe to this context. The reactive context will automatically remove itself from the subscriptions when it is reset.
|
||||
pub fn subscribe(&self, subscriptions: Arc<Mutex<HashSet<ReactiveContext>>>) {
|
||||
pub fn subscribe(&self, subscriptions: impl Into<Subscribers>) {
|
||||
match self.inner.try_write() {
|
||||
Ok(mut inner) => {
|
||||
subscriptions.lock().unwrap().insert(*self);
|
||||
inner.subscribers.insert(PointerHash(subscriptions));
|
||||
let subscriptions = subscriptions.into();
|
||||
subscriptions.add(*self);
|
||||
inner
|
||||
.subscribers
|
||||
.insert(PointerHash(subscriptions.inner.clone()));
|
||||
}
|
||||
// If the context was dropped, we don't need to subscribe to it anymore
|
||||
Err(BorrowMutError::Dropped(_)) => {}
|
||||
@@ -266,30 +269,28 @@ impl Hash for ReactiveContext {
|
||||
}
|
||||
}
|
||||
|
||||
struct PointerHash<T>(Arc<T>);
|
||||
struct PointerHash<T: ?Sized>(Arc<T>);
|
||||
|
||||
impl<T> Hash for PointerHash<T> {
|
||||
impl<T: ?Sized> Hash for PointerHash<T> {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
std::sync::Arc::<T>::as_ptr(&self.0).hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for PointerHash<T> {
|
||||
impl<T: ?Sized> PartialEq for PointerHash<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::sync::Arc::<T>::as_ptr(&self.0) == std::sync::Arc::<T>::as_ptr(&other.0)
|
||||
std::sync::Arc::ptr_eq(&self.0, &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Eq for PointerHash<T> {}
|
||||
impl<T: ?Sized> Eq for PointerHash<T> {}
|
||||
|
||||
impl<T> Clone for PointerHash<T> {
|
||||
impl<T: ?Sized> Clone for PointerHash<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
type SubscriberMap = Mutex<HashSet<ReactiveContext>>;
|
||||
|
||||
struct Inner {
|
||||
self_: Option<ReactiveContext>,
|
||||
|
||||
@@ -297,7 +298,7 @@ struct Inner {
|
||||
update: Box<dyn FnMut() + Send + Sync>,
|
||||
|
||||
// Subscribers to this context
|
||||
subscribers: HashSet<PointerHash<SubscriberMap>>,
|
||||
subscribers: HashSet<PointerHash<dyn SubscriberList + Send + Sync>>,
|
||||
|
||||
// Debug information for signal subscriptions
|
||||
#[cfg(debug_assertions)]
|
||||
@@ -315,9 +316,88 @@ impl Drop for Inner {
|
||||
};
|
||||
|
||||
for subscriber in std::mem::take(&mut self.subscribers) {
|
||||
if let Ok(mut subscriber) = subscriber.0.lock() {
|
||||
subscriber.remove(&self_);
|
||||
}
|
||||
subscriber.0.remove(&self_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of [ReactiveContext]s that are subscribed. This is used to notify subscribers when the value changes.
|
||||
#[derive(Clone)]
|
||||
pub struct Subscribers {
|
||||
/// The list of subscribers.
|
||||
pub(crate) inner: Arc<dyn SubscriberList + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Default for Subscribers {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Subscribers {
|
||||
/// Create a new list of subscribers.
|
||||
pub fn new() -> Self {
|
||||
Subscribers {
|
||||
inner: Arc::new(Mutex::new(HashSet::new())),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a subscriber to the list.
|
||||
pub fn add(&self, subscriber: ReactiveContext) {
|
||||
self.inner.add(subscriber);
|
||||
}
|
||||
|
||||
/// Remove a subscriber from the list.
|
||||
pub fn remove(&self, subscriber: &ReactiveContext) {
|
||||
self.inner.remove(subscriber);
|
||||
}
|
||||
|
||||
/// Visit all subscribers in the list.
|
||||
pub fn visit(&self, mut f: impl FnMut(&ReactiveContext)) {
|
||||
self.inner.visit(&mut f);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: SubscriberList + Send + Sync + 'static> From<Arc<S>> for Subscribers {
|
||||
fn from(inner: Arc<S>) -> Self {
|
||||
Subscribers { inner }
|
||||
}
|
||||
}
|
||||
|
||||
/// A list of subscribers that can be notified when the value changes. This is used to track when the value changes and notify subscribers.
|
||||
pub trait SubscriberList: Send + Sync {
|
||||
/// Add a subscriber to the list.
|
||||
fn add(&self, subscriber: ReactiveContext);
|
||||
|
||||
/// Remove a subscriber from the list.
|
||||
fn remove(&self, subscriber: &ReactiveContext);
|
||||
|
||||
/// Visit all subscribers in the list.
|
||||
fn visit(&self, f: &mut dyn FnMut(&ReactiveContext));
|
||||
}
|
||||
|
||||
impl SubscriberList for Mutex<HashSet<ReactiveContext>> {
|
||||
fn add(&self, subscriber: ReactiveContext) {
|
||||
if let Ok(mut lock) = self.lock() {
|
||||
lock.insert(subscriber);
|
||||
} else {
|
||||
tracing::warn!("Failed to lock subscriber list to add subscriber: {subscriber}");
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&self, subscriber: &ReactiveContext) {
|
||||
if let Ok(mut lock) = self.lock() {
|
||||
lock.remove(subscriber);
|
||||
} else {
|
||||
tracing::warn!("Failed to lock subscriber list to remove subscriber: {subscriber}");
|
||||
}
|
||||
}
|
||||
|
||||
fn visit(&self, f: &mut dyn FnMut(&ReactiveContext)) {
|
||||
if let Ok(lock) = self.lock() {
|
||||
lock.iter().for_each(f);
|
||||
} else {
|
||||
tracing::warn!("Failed to lock subscriber list to visit subscribers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
//! }
|
||||
//!
|
||||
//! #[component]
|
||||
//! fn ChildComponent(signal: Signal<Option<i32>>) -> Element {
|
||||
//! fn ChildComponent(signal: WriteSignal<Option<i32>>) -> Element {
|
||||
//! // It feels safe to assume that signal is some because the parent component checked that it was some
|
||||
//! rsx! { "{signal.read().unwrap()}" }
|
||||
//! }
|
||||
|
||||
Reference in new issue
Block a user