Implement effects using reactivecontext

This commit is contained in:
Jonathan Kelley 2024-02-01 01:05:28 -08:00
parent 968f24a7b3
commit 7c2947a131
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
21 changed files with 287 additions and 320 deletions

View file

@ -68,7 +68,7 @@ dioxus-html-internal-macro = { path = "packages/html-internal-macro", version =
dioxus-hooks = { path = "packages/hooks", version = "0.4.0" }
dioxus-web = { path = "packages/web", version = "0.4.0", default-features = false }
dioxus-ssr = { path = "packages/ssr", version = "0.4.0", default-features = false }
dioxus-desktop = { path = "packages/desktop", version = "0.4.0", default-features = false }
dioxus-desktop = { path = "packages/desktop", version = "0.4.0" }
dioxus-mobile = { path = "packages/mobile", version = "0.4.0" }
dioxus-interpreter-js = { path = "packages/interpreter", version = "0.4.0" }
dioxus-liveview = { path = "packages/liveview", version = "0.4.0" }

View file

@ -22,7 +22,7 @@ fn app() -> Element {
res
});
match future.value().read().as_ref() {
match future.value().as_ref() {
Some(v) => rsx!( p { "{v}" } ),
_ => rsx!( p { "waiting.." } ),
}

View file

@ -7,8 +7,23 @@ fn main() {
fn app() -> Element {
let mut count = use_signal(|| 0);
use_effect(move || {
println!("The count is now: {}", count);
});
rsx! {
h1 { "High-Five counter: {count}" }
Child { sig: count }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
}
}
#[component]
fn Child(sig: Signal<i32>) -> Element {
let doubled = use_memo(move || sig() * 2);
rsx! {
"The count is: {sig}, doubled: {doubled}"
}
}

View file

@ -1,59 +0,0 @@
use dioxus::prelude::*;
fn main() {
launch(app);
}
fn app() -> Element {
let val = use_server_future(fetch_users).suspend()?;
rsx! {
h1 { "Users" }
}
}
#[component]
fn ClientComponent(name: Signal<i32>, id: i64) -> Element {
rsx! {
div { "Name: {name}, ID: {id}" }
button {
onclick: move |_| async move {
// Optimistically change the name on the client
name.set("new name".to_string());
// Change the name on the server
change_name(id, "new name".to_string()).await;
// And then re-fetch the user list
revalidate(user_list);
},
"Change name"
}
}
}
#[derive(Table)]
struct Users {
name: String,
age: i32,
}
#[server]
async fn fetch_users() -> Result<Element> {
let users = get_users().await?;
Ok(rsx! {
for user in users {
ClientComponent {
name: user.name,
id: user.id,
}
}
})
}
#[server]
async fn change_name(id: i64, new_name: String) -> Result<()> {
// Send a request to the server to change the name
}

View file

@ -77,6 +77,8 @@ fn app() -> Element {
#[component]
fn Child(mut count: ReadOnlySignal<i32>) -> Element {
println!("rendering child with count {count}");
rsx! {
h1 { "{count}" }
}

View file

@ -111,6 +111,11 @@ pub fn needs_update() {
Runtime::with_current_scope(|cx| cx.needs_update());
}
/// Mark the current scope as dirty, causing it to re-render
pub fn needs_update_any(id: ScopeId) {
Runtime::with_current_scope(|cx| cx.needs_update_any(id));
}
/// Schedule an update for the current component
///
/// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime.
@ -252,6 +257,10 @@ pub fn after_render(f: impl FnMut() + 'static) {
/// Effects rely on this to ensure that they only run effects after the DOM has been updated. Without flush_sync effects
/// are run immediately before diffing the DOM, which causes all sorts of out-of-sync weirdness.
pub async fn flush_sync() {
// if flushing() {
// return;
// }
_ = Runtime::with(|rt| rt.flush.clone())
.unwrap()
.recv_async()

View file

@ -88,13 +88,13 @@ pub use crate::innerlude::{
pub mod prelude {
pub use crate::innerlude::{
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, flush_sync,
generation, has_context, needs_update, parent_scope, provide_context, provide_root_context,
remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
try_consume_context, use_after_render, use_before_render, use_drop, use_error_boundary,
use_hook, use_hook_with_cleanup, AnyValue, Attribute, Component, ComponentFunction,
Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId,
ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw,
VNode, VNodeInner, VirtualDom,
generation, has_context, needs_update, needs_update_any, parent_scope, provide_context,
provide_root_context, remove_future, schedule_update, schedule_update_any, spawn,
spawn_forever, suspend, try_consume_context, use_after_render, use_before_render, use_drop,
use_error_boundary, use_hook, use_hook_with_cleanup, AnyValue, Attribute, Component,
ComponentFunction, Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes,
IntoAttributeValue, IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard,
ScopeId, ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode,
Throw, VNode, VNodeInner, VirtualDom,
};
}

View file

@ -60,8 +60,13 @@ impl Scope {
/// Mark this scope as dirty, and schedule a render for it.
pub fn needs_update(&self) {
self.needs_update_any(self.id)
}
/// Mark this scope as dirty, and schedule a render for it.
pub fn needs_update_any(&self, id: ScopeId) {
self.sender()
.unbounded_send(SchedulerMsg::Immediate(self.id))
.unbounded_send(SchedulerMsg::Immediate(id))
.expect("Scheduler to exist if scope exists");
}

View file

@ -372,11 +372,15 @@ impl VirtualDom {
///
/// Whenever the Runtime "works", it will re-render this scope
pub fn mark_dirty(&mut self, id: ScopeId) {
if let Some(context) = self.runtime.get_state(id) {
let height = context.height();
tracing::trace!("Marking scope {:?} ({}) as dirty", id, context.name);
self.dirty_scopes.insert(DirtyScope { height, id });
}
let Some(scope) = self.runtime.get_state(id) else {
return;
};
tracing::trace!("Marking scope {:?} ({}) as dirty", id, scope.name);
self.dirty_scopes.insert(DirtyScope {
height: scope.height(),
id,
});
}
/// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an element with a listener, not a static node or a text node.**
@ -422,9 +426,6 @@ impl VirtualDom {
/// let dom = VirtualDom::new(app);
/// ```
pub async fn wait_for_work(&mut self) {
// Send the flush signal to the runtime
_ = self.flush_tx.try_send(());
// And then poll the futures
self.poll_tasks().await;
}
@ -432,6 +433,9 @@ impl VirtualDom {
/// Poll futures without progressing any futures from the flush table
async fn poll_tasks(&mut self) {
loop {
// Send the flush signal to the runtime, waking up tasks
_ = self.flush_tx.try_send(());
// Process all events - Scopes are marked dirty, etc
// Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
self.process_events();

View file

@ -16,9 +16,7 @@ fn app() -> Element {
let mut text = use_signal(|| "...".to_string());
rsx! {
div {
"Server state: {state.unwrap().value().clone()}"
}
div { "Server state: {state.value().unwrap()}" }
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
@ -51,6 +49,7 @@ async fn get_server_data() -> Result<String, ServerFnError> {
fn main() {
#[cfg(feature = "web")]
tracing_wasm::set_as_global_default();
#[cfg(feature = "ssr")]
tracing_subscriber::fmt::init();

View file

@ -62,37 +62,38 @@ pub struct UseServerFuture<T: 'static> {
value: Signal<Option<Signal<T>>>,
}
// impl<T> UseServerFuture<T> {
// /// Restart the future with new dependencies.
// ///
// /// Will not cancel the previous future, but will ignore any values that it
// /// generates.
// pub fn restart(&self) {
// self.needs_regen.set(true);
// (self.update)();
// }
impl<T> UseServerFuture<T> {
// /// Restart the future with new dependencies.
// ///
// /// Will not cancel the previous future, but will ignore any values that it
// /// generates.
// pub fn restart(&self) {
// self.needs_regen.set(true);
// (self.update)();
// }
// /// Forcefully cancel a future
// pub fn cancel(&self) {
// if let Some(task) = self.task.take() {
// remove_future(task);
// }
// }
// /// Forcefully cancel a future
// pub fn cancel(&self) {
// if let Some(task) = self.task.take() {
// remove_future(task);
// }
// }
// /// Return any value, even old values if the future has not yet resolved.
// ///
// /// If the future has never completed, the returned value will be `None`.
// pub fn value(&self) -> Ref<'_, T> {
// Ref::map(self.value.borrow(), |v| v.as_deref().unwrap())
// }
/// Return any value, even old values if the future has not yet resolved.
///
/// If the future has never completed, the returned value will be `None`.
pub fn value(&self) -> Option<Signal<T>> {
todo!()
// Ref::map(self.value.read(), |v: &Option<T>| v.as_deref().unwrap())
}
// /// Get the ID of the future in Dioxus' internal scheduler
// pub fn task(&self) -> Option<Task> {
// self.task.get()
// }
// /// Get the ID of the future in Dioxus' internal scheduler
// pub fn task(&self) -> Option<Task> {
// self.task.get()
// }
// /// Get the current state of the future.
// pub fn reloading(&self) -> bool {
// self.task.get().is_some()
// }
// }
// /// Get the current state of the future.
// pub fn reloading(&self) -> bool {
// self.task.get().is_some()
// }
}

View file

@ -1,24 +1,27 @@
use crate::use_hook_did_run;
use dioxus_core::prelude::*;
use dioxus_signals::{CopyValue, Effect, Writable};
use dioxus_signals::{CopyValue, Effect, ReactiveContext, Writable};
use futures_util::select;
/// Create a new effect. The effect will be run immediately and whenever any signal it reads changes.
/// The signal will be owned by the current component and will be dropped when the component is dropped.
///
/// If the use_effect call was skipped due to an early return, the effect will no longer activate.
pub fn use_effect(mut callback: impl FnMut() + 'static) {
let mut run_effect = use_hook(|| CopyValue::new(true));
use_hook_did_run(move |did_run| match did_run {
true => run_effect.set(true),
false => run_effect.set(false),
});
// let mut run_effect = use_hook(|| CopyValue::new(true));
// use_hook_did_run(move |did_run| run_effect.set(did_run));
use_hook(|| {
Effect::new(move || {
if run_effect() {
callback();
spawn(async move {
let rc = ReactiveContext::new(None);
loop {
// Run the effect
rc.run_in(|| callback());
// Wait for context to change
rc.changed().await;
}
})
});
});
}

View file

@ -21,11 +21,6 @@ where
let mut callback = use_callback(move || {
let fut = future();
spawn(async move {
// todo: not sure if we should flush_sync
// It's fine for most cases but means that the future will always be started in the next frame
// The point here is to not run use_future on the server... which like, shouldn't we?
flush_sync().await;
state.set(UseFutureState::Pending);
fut.await;
state.set(UseFutureState::Complete);

View file

@ -1,7 +1,7 @@
use crate::dependency::Dependency;
use crate::use_signal;
use dioxus_core::prelude::*;
use dioxus_signals::{ReadOnlySignal, Readable, Signal, SignalData};
use dioxus_signals::{ReactiveContext, ReadOnlySignal, Readable, Signal, SignalData};
use dioxus_signals::{Storage, Writable};
/// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes.
@ -45,9 +45,33 @@ pub fn use_memo<R: PartialEq>(f: impl FnMut() -> R + 'static) -> ReadOnlySignal<
/// ```
#[track_caller]
pub fn use_maybe_sync_memo<R: PartialEq, S: Storage<SignalData<R>>>(
f: impl FnMut() -> R + 'static,
mut f: impl FnMut() -> R + 'static,
) -> ReadOnlySignal<R, S> {
use_hook(|| Signal::maybe_sync_memo(f))
use_hook(|| {
// Get the current reactive context
let rc = ReactiveContext::new(None);
// Create a new signal in that context, wiring up its dependencies and subscribers
let mut state: Signal<R, S> = rc.run_in(|| Signal::new_maybe_sync(f()));
spawn(async move {
loop {
rc.changed().await;
println!("change triggered in memo");
let new = rc.run_in(|| f());
if new != *state.peek() {
println!("change sett in memo");
*state.write() = new;
}
}
});
// And just return the readonly variant of that signal
ReadOnlySignal::new_maybe_sync(state)
})
}
/// Creates a new unsync Selector with some local dependencies. The selector will be run immediately and whenever any signal it reads or any dependencies it tracks changes

View file

@ -27,20 +27,20 @@ where
// Spawn a wrapper task that polls the innner future and watch its dependencies
let task = spawn(async move {
// move the future here and pin it so we can poll it
let fut = fut;
pin_mut!(fut);
// // move the future here and pin it so we can poll it
// let fut = fut;
// pin_mut!(fut);
let res = future::poll_fn(|cx| {
// Set the effect stack properly
// add any dependencies to the effect stack that we need to watch when restarting the future
// Poll the inner future
fut.poll_unpin(cx)
})
.await;
// let res = future::poll_fn(|cx| {
// // Set the effect stack properly
// // add any dependencies to the effect stack that we need to watch when restarting the future
// // Poll the inner future
// fut.poll_unpin(cx)
// })
// .await;
// Set the value
value.set(Some(Signal::new(res)));
// // Set the value
// value.set(Some(Signal::new(res)));
});
Some(task)

View file

@ -96,7 +96,7 @@ fn use_maybe_signal_sync<T: 'static, U: Storage<SignalData<T>>>(
// By default, we want to unsubscribe the current component from the signal on every render
// any calls to .read() in the body will re-subscribe the component to the signal
use_before_render(move || signal.unsubscribe(current_scope_id().unwrap()));
// use_before_render(move || signal.unsubscribe(current_scope_id().unwrap()));
signal
}

View file

@ -91,10 +91,15 @@ impl IntoFuture for UseEval {
/// Represents an error when evaluating JavaScript
#[derive(Debug)]
pub enum EvalError {
/// The platform does not support evaluating JavaScript.
Unspported,
/// The provided JavaScript has already been ran.
Finished,
/// The provided JavaScript is not valid and can't be ran.
InvalidJs(String),
/// Represents an error communicating between JavaScript and Rust.
Communication(String),
}

View file

@ -36,3 +36,6 @@ pub use write::*;
mod props;
pub use props::*;
mod rc;
pub use rc::*;

View file

@ -1,9 +1,13 @@
use dioxus_core::prelude::ScopeId;
use dioxus_core::prelude::{
consume_context, consume_context_from_scope, current_scope_id, has_context, needs_update_any,
provide_context, schedule_update, schedule_update_any, try_consume_context, ScopeId,
};
use generational_box::{GenerationalBoxId, SyncStorage};
use rustc_hash::FxHashSet;
use rustc_hash::{FxHashMap, FxHashSet};
use std::{cell::RefCell, hash::Hash};
use tokio::signal;
use crate::{CopyValue, Readable};
use crate::{CopyValue, RcList, Readable, Writable};
/// A context for signal reads and writes to be directed to
///
@ -11,10 +15,9 @@ use crate::{CopyValue, Readable};
/// If it doesn't find it, then it will try and insert a context into the nearest component scope via context api.
///
/// When the ReactiveContext drops, it will remove itself from the the associated contexts attached to signal
#[derive(Clone, Copy, PartialEq)]
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct ReactiveContext {
// todo: we dont need to use syncstorage per say
inner: CopyValue<Inner, SyncStorage>,
pub inner: CopyValue<Inner, SyncStorage>,
}
thread_local! {
@ -22,27 +25,94 @@ thread_local! {
}
impl ReactiveContext {
pub fn new(scope: Option<ScopeId>) -> Self {
let (tx, rx) = flume::unbounded();
let mut scope_subscribers = FxHashSet::default();
if let Some(scope) = scope {
scope_subscribers.insert(scope);
}
let inner = Inner {
signal_subscribers: FxHashMap::default(),
scope_subscribers,
sender: tx,
_self: None,
receiver: rx,
};
let mut _self = Self {
inner: CopyValue::new_maybe_sync(inner),
};
_self.inner.write()._self = Some(_self);
_self
}
/// Get the current reactive context
///
/// If this was set manually, then that value will be returned.
///
/// If there's no current reactive context, then a new one will be created at the current scope and returned.
pub fn current() -> Self {
todo!()
pub fn current() -> Option<Self> {
let cur = CURRENT.with(|current| current.borrow().last().cloned());
// If we're already inside a reactive context, then return that
if let Some(cur) = cur {
println!("Already found context!");
return Some(cur);
}
// Try and get the context out of the current scope
let scope = current_scope_id().unwrap();
// If we're rendering, then try and use the reactive context attached to this component
if let Some(cx) = has_context() {
println!("found context at {scope:?}");
return Some(cx);
}
println!("creating new context at {scope:?}");
// Otherwise, create a new context at the current scope
Some(provide_context(ReactiveContext::new(Some(scope))))
}
/// Run this function in the context of this reactive context
///
/// This will set the current reactive context to this context for the duration of the function.
/// You can then get information about the current subscriptions.
pub fn run_in(&self, f: impl FnOnce()) {
todo!()
pub fn run_in<O>(&self, f: impl FnOnce() -> O) -> O {
CURRENT.with(|current| current.borrow_mut().push(*self));
let out = f();
CURRENT.with(|current| current.borrow_mut().pop());
out
}
/// Marks this reactive context as dirty
///
/// If there's a scope associated with this context, then it will be marked as dirty too
pub fn mark_dirty(&self) {}
pub fn mark_dirty(&self) {
for scope in self.inner.read().scope_subscribers.iter() {
println!("marking dirty {scope:?}");
needs_update_any(*scope);
}
// mark the listeners as dirty
// If the channel is full it means that the receivers have already been marked as dirty
_ = self.inner.read().sender.try_send(());
}
// Create a two-way binding between this reactive context and a signal
pub fn link(&self, signal: GenerationalBoxId, rc_list: RcList) {
rc_list.write().insert(*self);
self.inner
.write()
.signal_subscribers
.insert(signal, rc_list);
}
/// Clear all subscribers from this reactive context
pub fn clear_subscribers(&self) {
@ -51,9 +121,8 @@ impl ReactiveContext {
/// Wait for this reactive context to change
pub async fn changed(&self) {
let waiter = self.inner.read().waiters.1.clone();
_ = waiter.recv_async().await;
let rx = self.inner.read().receiver.clone();
_ = rx.recv_async().await;
}
}
@ -63,22 +132,22 @@ impl Hash for ReactiveContext {
}
}
struct Inner {
pub struct Inner {
// Set of signals bound to this context
subscribers: FxHashSet<GenerationalBoxId>,
// The scope that this context is associated with
// This is only relevant when RC is being used to call update on a signal
scope: Option<ScopeId>,
pub signal_subscribers: FxHashMap<GenerationalBoxId, RcList>,
scope_subscribers: FxHashSet<ScopeId>,
_self: Option<ReactiveContext>,
// Futures will call .changed().await
waiters: (flume::Sender<()>, flume::Receiver<()>),
sender: flume::Sender<()>,
receiver: flume::Receiver<()>,
}
impl Inner {}
impl Drop for Inner {
fn drop(&mut self) {
todo!()
// Remove this context from all the subscribers
self.signal_subscribers.values().for_each(|sub_list| {
sub_list.write().remove(&self._self.unwrap());
});
}
}

View file

@ -284,6 +284,7 @@ impl<T: 'static, S: Storage<T>> PartialEq for CopyValue<T, S> {
self.value.ptr_eq(&other.value)
}
}
impl<T: 'static, S: Storage<T>> Eq for CopyValue<T, S> {}
impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
type Target = dyn Fn() -> T;

View file

@ -1,11 +1,12 @@
use crate::{
get_effect_ref, read::Readable, write::Writable, CopyValue, Effect, EffectInner,
EffectStackRef, GlobalMemo, GlobalSignal, MappedSignal, ReactiveContext,
ReactiveContextProvider, ReadSignal, EFFECT_STACK,
EffectStackRef, GlobalMemo, GlobalSignal, MappedSignal, ReactiveContext, ReadOnlySignal,
EFFECT_STACK,
};
use dioxus_core::{
prelude::{
current_scope_id, has_context, provide_context, schedule_update_any, IntoAttributeValue,
current_scope_id, has_context, provide_context, schedule_update_any, spawn,
IntoAttributeValue,
},
ScopeId,
};
@ -59,21 +60,16 @@ pub struct Signal<T: 'static, S: Storage<SignalData<T>> = UnsyncStorage> {
/// A signal that can safely shared between threads.
pub type SyncSignal<T> = Signal<T, SyncStorage>;
/// The list of reactive contexts this signal is assocaited with
/// Whenever we call .write() on the signal, these contexts will be notified of the change
pub type RcList = Arc<RwLock<HashSet<ReactiveContext>>>;
/// The data stored for tracking in a signal.
pub struct SignalData<T> {
pub(crate) subscribers: Arc<RwLock<HashSet<ReactiveContext>>>,
pub(crate) update_any: Arc<dyn Fn(ScopeId) + Sync + Send>,
pub(crate) effect_ref: EffectStackRef,
pub(crate) subscribers: RcList,
pub(crate) value: T,
}
// #[derive(Default)]
// pub(crate) struct SignalSubscribers {
// // todo: use a linear map here for faster scans
// pub(crate) subscribers: FxHashSet<ScopeId>,
// pub(crate) effect_subscribers: FxHashSet<GenerationalBoxId>,
// }
impl<T: 'static> Signal<T> {
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
#[track_caller]
@ -97,10 +93,7 @@ impl<T: 'static> Signal<T> {
impl<T: PartialEq + 'static> Signal<T> {
/// Creates a new global Signal that can be used in a global static.
#[track_caller]
pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T>
where
T: PartialEq,
{
pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T> {
GlobalMemo::new(constructor)
}
@ -119,38 +112,26 @@ impl<T: PartialEq + 'static> Signal<T> {
pub fn maybe_sync_memo<S: Storage<SignalData<T>>>(
mut f: impl FnMut() -> T + 'static,
) -> ReadOnlySignal<T, S> {
let effect = Effect {
source: current_scope_id().expect("in a virtual dom"),
inner: CopyValue::invalid(),
};
// Get the current reactive context
let rc = ReactiveContext::current()
.expect("Cannot use a selector outside of a reactive context");
{
EFFECT_STACK.with(|stack| stack.effects.write().push(effect));
}
let mut state: Signal<T, S> = Signal::new_maybe_sync(f());
{
EFFECT_STACK.with(|stack| stack.effects.write().pop());
}
// Create a new signal in that context, wiring up its dependencies and subscribers
let mut state: Signal<T, S> = rc.run_in(|| Signal::new_maybe_sync(f()));
let invalid_id = effect.id();
tracing::trace!("Creating effect: {:?}", invalid_id);
effect.inner.value.set(EffectInner {
callback: Box::new(move || {
let value = f();
let changed = {
let old = state.inner.read();
value != old.value
};
if changed {
state.set(value)
spawn(async move {
loop {
rc.changed().await;
println!("changed");
let new = f();
if new != *state.peek() {
*state.write() = new;
}
}),
id: invalid_id,
}
});
{
EFFECT_STACK.with(|stack| stack.effect_mapping.write().insert(invalid_id, effect));
}
// And just return the readonly variant of that signal
ReadOnlySignal::new_maybe_sync(state)
}
}
@ -163,9 +144,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
Self {
inner: CopyValue::<SignalData<T>, S>::new_maybe_sync(SignalData {
subscribers: Default::default(),
update_any: schedule_update_any(),
value,
effect_ref: get_effect_ref(),
}),
}
}
@ -179,9 +158,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
inner: CopyValue::new_with_caller(
SignalData {
subscribers: Default::default(),
update_any: schedule_update_any(),
value,
effect_ref: get_effect_ref(),
},
#[cfg(debug_assertions)]
caller,
@ -197,9 +174,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
inner: CopyValue::<SignalData<T>, S>::new_maybe_sync_in_scope(
SignalData {
subscribers: Default::default(),
update_any: schedule_update_any(),
value,
effect_ref: get_effect_ref(),
},
owner,
),
@ -217,44 +192,13 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
}
fn update_subscribers(&self) {
todo!()
// {
// let inner = self.inner.read();
// for &scope_id in inner.subscribers.read().subscribers.iter() {
// tracing::trace!(
// "Write on {:?} triggered update on {:?}",
// self.inner.value,
// scope_id
// );
// (inner.update_any)(scope_id);
// }
// }
{
let inner = self.inner.read();
// let self_read = &self.inner.read();
// let subscribers = {
// let effects = &mut self_read.subscribers.write().effect_subscribers;
// std::mem::take(&mut *effects)
// };
// let effect_ref = &self_read.effect_ref;
// for effect in subscribers {
// tracing::trace!(
// "Write on {:?} triggered effect {:?}",
// self.inner.value,
// effect
// );
// effect_ref.queue_effect(effect);
// }
}
/// Unsubscribe this scope from the signal's effect list
pub fn unsubscribe(&self, scope: ScopeId) {
todo!()
// self.inner
// .read()
// .subscribers
// .write()
// .subscribers
// .retain(|s| *s != scope);
for cx in inner.subscribers.read().iter() {
cx.mark_dirty();
}
}
}
/// Map the signal to a new type.
@ -290,34 +234,9 @@ impl<T, S: Storage<SignalData<T>>> Readable<T> for Signal<T, S> {
fn read(&self) -> S::Ref<T> {
let inner = self.inner.read();
todo!();
// // if we're in a reactive context, attach the context to the signal via the mapping
// if let Some(effect) = ReactiveContextProvider::current() {
// inner
// .subscribers
// .write()
// .effect_subscribers
// .insert(effect.inner.id());
// } else if let Some(current_scope_id) = current_scope_id() {
// // only subscribe if the vdom is rendering
// if dioxus_core::vdom_is_rendering() {
// tracing::trace!(
// "{:?} subscribed to {:?}",
// self.inner.value,
// current_scope_id
// );
// let mut subscribers = inner.subscribers.write();
// if !subscribers.subscribers.contains(&current_scope_id) {
// subscribers.subscribers.insert(current_scope_id);
// subscribers
// .subscribers
// .insert(current_unsubscriber().borrow().scope);
// }
// }
// }
if let Some(mut cx) = ReactiveContext::current() {
cx.link(self.id(), inner.subscribers.clone());
}
S::map(inner, |v| &v.value)
}
@ -404,44 +323,6 @@ impl<'de, T: serde::Deserialize<'de> + 'static, Store: Storage<SignalData<T>>>
}
}
struct Unsubscriber {
scope: ScopeId,
subscribers: UnsubscriberArray,
}
type UnsubscriberArray = Vec<Rc<RefCell<Vec<ScopeId>>>>;
impl Drop for Unsubscriber {
fn drop(&mut self) {
for subscribers in &self.subscribers {
subscribers.borrow_mut().retain(|s| *s != self.scope);
}
}
}
fn current_unsubscriber() -> Rc<RefCell<Unsubscriber>> {
match has_context() {
Some(rt) => rt,
None => {
let owner = Unsubscriber {
scope: current_scope_id().expect("in a virtual dom"),
subscribers: Default::default(),
};
provide_context(Rc::new(RefCell::new(owner)))
}
}
}
struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
signal: Signal<T, S>,
}
impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
fn drop(&mut self) {
self.signal.update_subscribers();
}
}
/// A mutable reference to a signal's value.
///
/// T is the current type of the write
@ -489,3 +370,13 @@ impl<T: ?Sized, S: AnyStorage> DerefMut for Write<T, S> {
&mut self.write
}
}
struct SignalSubscriberDrop<T: 'static, S: Storage<SignalData<T>>> {
signal: Signal<T, S>,
}
impl<T: 'static, S: Storage<SignalData<T>>> Drop for SignalSubscriberDrop<T, S> {
fn drop(&mut self) {
self.signal.update_subscribers();
}
}