remove flume, implement lazier memos

This commit is contained in:
Evan Almloff committed 2024-03-06 11:38:28 -06:00
1 parent 716eb11426
commit 492f0329bf
15 files changed
+267 -177

No files matched your search

+41 -69
View File
@@ -1,9 +1,9 @@
use dioxus_core::prelude::{
current_scope_id, has_context, provide_context, schedule_update_any, ScopeId,
};
use futures_channel::mpsc::UnboundedReceiver;
use generational_box::SyncStorage;
use rustc_hash::FxHashSet;
use std::{cell::RefCell, hash::Hash, sync::Arc};
use std::{cell::RefCell, hash::Hash};
use crate::{CopyValue, Readable, Writable};
@@ -25,66 +25,48 @@ thread_local! {
impl std::fmt::Display for ReactiveContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let read = self.inner.read();
match read.scope_subscriber {
Some(scope) => write!(f, "ReactiveContext for scope {:?}", scope),
None => {
#[cfg(debug_assertions)]
return write!(f, "ReactiveContext created at {}", read.origin);
#[cfg(not(debug_assertions))]
write!(f, "ReactiveContext")
}
}
}
}
impl Default for ReactiveContext {
#[track_caller]
fn default() -> Self {
Self::new_for_scope(None, std::panic::Location::caller())
#[cfg(debug_assertions)]
return write!(f, "ReactiveContext created at {}", read.origin);
#[cfg(not(debug_assertions))]
write!(f, "ReactiveContext")
}
}
impl ReactiveContext {
/// Create a new reactive context
#[track_caller]
pub fn new() -> Self {
Self::default()
pub fn new() -> (Self, UnboundedReceiver<()>) {
Self::new_with_origin(std::panic::Location::caller())
}
/// Create a new reactive context with a location for debugging purposes
/// This is useful for reactive contexts created within closures
pub fn new_with_origin(origin: &'static std::panic::Location<'static>) -> Self {
Self::new_for_scope(None, origin)
pub fn new_with_origin(
origin: &'static std::panic::Location<'static>,
) -> (Self, UnboundedReceiver<()>) {
let (tx, rx) = futures_channel::mpsc::unbounded();
let callback = move || {
let _ = tx.unbounded_send(());
};
let _self = Self::new_with_callback(callback, current_scope_id().unwrap(), origin);
(_self, rx)
}
/// Create a new reactive context that may update a scope
#[allow(unused)]
pub(crate) fn new_for_scope(
scope: Option<ScopeId>,
/// Create a new reactive context that may update a scope. When any signal that this context subscribes to changes, the callback will be run
pub fn new_with_callback(
callback: impl FnMut() + Send + Sync + 'static,
scope: ScopeId,
origin: &'static std::panic::Location<'static>,
) -> Self {
let (tx, rx) = flume::unbounded();
let mut scope_subscribers = FxHashSet::default();
if let Some(scope) = scope {
scope_subscribers.insert(scope);
}
let inner = Inner {
scope_subscriber: scope,
sender: tx,
self_: None,
update_any: schedule_update_any(),
receiver: rx,
update: Box::new(callback),
#[cfg(debug_assertions)]
origin,
};
let mut self_ = Self {
inner: CopyValue::new_maybe_sync_in_scope(
inner,
scope.or_else(current_scope_id).unwrap(),
),
inner: CopyValue::new_maybe_sync_in_scope(inner, scope),
};
self_.inner.write().self_ = Some(self_);
@@ -112,10 +94,17 @@ impl ReactiveContext {
if let Some(cx) = has_context() {
return Some(cx);
}
let update_any = schedule_update_any();
let scope_id = current_scope_id().unwrap();
let update_scope = move || {
tracing::trace!("Marking scope {:?} as dirty", scope_id);
update_any(scope_id)
};
// Otherwise, create a new context at the current scope
Some(provide_context(ReactiveContext::new_for_scope(
current_scope_id(),
Some(provide_context(ReactiveContext::new_with_callback(
update_scope,
scope_id,
std::panic::Location::caller(),
)))
}
@@ -137,25 +126,18 @@ impl ReactiveContext {
///
/// Returns true if the context was marked as dirty, or false if the context has been dropped
pub fn mark_dirty(&self) -> bool {
if let Ok(self_read) = self.inner.try_read() {
let mut copy = self.inner;
if let Ok(mut self_write) = copy.try_write() {
#[cfg(debug_assertions)]
{
if let Some(scope) = self_read.scope_subscriber {
tracing::trace!("Marking reactive context for scope {:?} as dirty", scope);
} else {
tracing::trace!(
"Marking reactive context created at {} as dirty",
self_read.origin
);
}
}
if let Some(scope) = self_read.scope_subscriber {
(self_read.update_any)(scope);
tracing::trace!(
"Marking reactive context created at {} as dirty",
self_write.origin
);
}
// mark the listeners as dirty
// If the channel is full it means that the receivers have already been marked as dirty
_ = self_read.sender.try_send(());
(self_write.update)();
true
} else {
false
@@ -166,12 +148,6 @@ impl ReactiveContext {
pub fn origin_scope(&self) -> ScopeId {
self.inner.origin_scope()
}
/// Wait for this reactive context to change
pub async fn changed(&self) {
let rx = self.inner.read().receiver.clone();
_ = rx.recv_async().await;
}
}
impl Hash for ReactiveContext {
@@ -181,14 +157,10 @@ impl Hash for ReactiveContext {
}
struct Inner {
// A scope we mark as dirty when this context is written to
scope_subscriber: Option<ScopeId>,
self_: Option<ReactiveContext>,
update_any: Arc<dyn Fn(ScopeId) + Send + Sync>,
// Futures will call .changed().await
sender: flume::Sender<()>,
receiver: flume::Receiver<()>,
update: Box<dyn FnMut() + Send + Sync>,
// Debug information for signal subscriptions
#[cfg(debug_assertions)]