Implement effects using reactivecontext
This commit is contained in:
21 files changed
+287
-320
No files matched your search
+1
-1
@@ -68,7 +68,7 @@ dioxus-html-internal-macro = { path = "packages/html-internal-macro", version =
|
|||||||
dioxus-hooks = { path = "packages/hooks", version = "0.4.0" }
|
dioxus-hooks = { path = "packages/hooks", version = "0.4.0" }
|
||||||
dioxus-web = { path = "packages/web", version = "0.4.0", default-features = false }
|
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-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-mobile = { path = "packages/mobile", version = "0.4.0" }
|
||||||
dioxus-interpreter-js = { path = "packages/interpreter", version = "0.4.0" }
|
dioxus-interpreter-js = { path = "packages/interpreter", version = "0.4.0" }
|
||||||
dioxus-liveview = { path = "packages/liveview", version = "0.4.0" }
|
dioxus-liveview = { path = "packages/liveview", version = "0.4.0" }
|
||||||
|
|||||||
+1
-1
@@ -22,7 +22,7 @@ fn app() -> Element {
|
|||||||
res
|
res
|
||||||
});
|
});
|
||||||
|
|
||||||
match future.value().read().as_ref() {
|
match future.value().as_ref() {
|
||||||
Some(v) => rsx!( p { "{v}" } ),
|
Some(v) => rsx!( p { "{v}" } ),
|
||||||
_ => rsx!( p { "waiting.." } ),
|
_ => rsx!( p { "waiting.." } ),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,23 @@ fn main() {
|
|||||||
fn app() -> Element {
|
fn app() -> Element {
|
||||||
let mut count = use_signal(|| 0);
|
let mut count = use_signal(|| 0);
|
||||||
|
|
||||||
|
use_effect(move || {
|
||||||
|
println!("The count is now: {}", count);
|
||||||
|
});
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
|
h1 { "High-Five counter: {count}" }
|
||||||
|
Child { sig: count }
|
||||||
button { onclick: move |_| count += 1, "Up high!" }
|
button { onclick: move |_| count += 1, "Up high!" }
|
||||||
button { onclick: move |_| count -= 1, "Down low!" }
|
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}"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -77,6 +77,8 @@ fn app() -> Element {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
fn Child(mut count: ReadOnlySignal<i32>) -> Element {
|
fn Child(mut count: ReadOnlySignal<i32>) -> Element {
|
||||||
|
println!("rendering child with count {count}");
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
h1 { "{count}" }
|
h1 { "{count}" }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -111,6 +111,11 @@ pub fn needs_update() {
|
|||||||
Runtime::with_current_scope(|cx| cx.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
|
/// Schedule an update for the current component
|
||||||
///
|
///
|
||||||
/// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime.
|
/// 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
|
/// 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.
|
/// are run immediately before diffing the DOM, which causes all sorts of out-of-sync weirdness.
|
||||||
pub async fn flush_sync() {
|
pub async fn flush_sync() {
|
||||||
|
// if flushing() {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
_ = Runtime::with(|rt| rt.flush.clone())
|
_ = Runtime::with(|rt| rt.flush.clone())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.recv_async()
|
.recv_async()
|
||||||
|
|||||||
@@ -88,13 +88,13 @@ pub use crate::innerlude::{
|
|||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, flush_sync,
|
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,
|
generation, has_context, needs_update, needs_update_any, parent_scope, provide_context,
|
||||||
remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
|
provide_root_context, remove_future, schedule_update, schedule_update_any, spawn,
|
||||||
try_consume_context, use_after_render, use_before_render, use_drop, use_error_boundary,
|
spawn_forever, suspend, try_consume_context, use_after_render, use_before_render, use_drop,
|
||||||
use_hook, use_hook_with_cleanup, AnyValue, Attribute, Component, ComponentFunction,
|
use_error_boundary, use_hook, use_hook_with_cleanup, AnyValue, Attribute, Component,
|
||||||
Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
|
ComponentFunction, Element, ErrorBoundary, Event, EventHandler, Fragment, HasAttributes,
|
||||||
IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId,
|
IntoAttributeValue, IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard,
|
||||||
ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw,
|
ScopeId, ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode,
|
||||||
VNode, VNodeInner, VirtualDom,
|
Throw, VNode, VNodeInner, VirtualDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -60,8 +60,13 @@ impl Scope {
|
|||||||
|
|
||||||
/// Mark this scope as dirty, and schedule a render for it.
|
/// Mark this scope as dirty, and schedule a render for it.
|
||||||
pub fn needs_update(&self) {
|
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()
|
self.sender()
|
||||||
.unbounded_send(SchedulerMsg::Immediate(self.id))
|
.unbounded_send(SchedulerMsg::Immediate(id))
|
||||||
.expect("Scheduler to exist if scope exists");
|
.expect("Scheduler to exist if scope exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -372,11 +372,15 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// Whenever the Runtime "works", it will re-render this scope
|
/// Whenever the Runtime "works", it will re-render this scope
|
||||||
pub fn mark_dirty(&mut self, id: ScopeId) {
|
pub fn mark_dirty(&mut self, id: ScopeId) {
|
||||||
if let Some(context) = self.runtime.get_state(id) {
|
let Some(scope) = self.runtime.get_state(id) else {
|
||||||
let height = context.height();
|
return;
|
||||||
tracing::trace!("Marking scope {:?} ({}) as dirty", id, context.name);
|
};
|
||||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
|
||||||
}
|
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.**
|
/// 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);
|
/// let dom = VirtualDom::new(app);
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn wait_for_work(&mut self) {
|
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
|
// And then poll the futures
|
||||||
self.poll_tasks().await;
|
self.poll_tasks().await;
|
||||||
}
|
}
|
||||||
@@ -432,6 +433,9 @@ impl VirtualDom {
|
|||||||
/// Poll futures without progressing any futures from the flush table
|
/// Poll futures without progressing any futures from the flush table
|
||||||
async fn poll_tasks(&mut self) {
|
async fn poll_tasks(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
|
// Send the flush signal to the runtime, waking up tasks
|
||||||
|
_ = self.flush_tx.try_send(());
|
||||||
|
|
||||||
// Process all events - Scopes are marked dirty, etc
|
// 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
|
// Sometimes when wakers fire we get a slew of updates at once, so its important that we drain this completely
|
||||||
self.process_events();
|
self.process_events();
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ fn app() -> Element {
|
|||||||
let mut text = use_signal(|| "...".to_string());
|
let mut text = use_signal(|| "...".to_string());
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div {
|
div { "Server state: {state.value().unwrap()}" }
|
||||||
"Server state: {state.unwrap().value().clone()}"
|
|
||||||
}
|
|
||||||
h1 { "High-Five counter: {count}" }
|
h1 { "High-Five counter: {count}" }
|
||||||
button { onclick: move |_| count += 1, "Up high!" }
|
button { onclick: move |_| count += 1, "Up high!" }
|
||||||
button { onclick: move |_| count -= 1, "Down low!" }
|
button { onclick: move |_| count -= 1, "Down low!" }
|
||||||
@@ -51,6 +49,7 @@ async fn get_server_data() -> Result<String, ServerFnError> {
|
|||||||
fn main() {
|
fn main() {
|
||||||
#[cfg(feature = "web")]
|
#[cfg(feature = "web")]
|
||||||
tracing_wasm::set_as_global_default();
|
tracing_wasm::set_as_global_default();
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
|||||||
@@ -62,37 +62,38 @@ pub struct UseServerFuture<T: 'static> {
|
|||||||
value: Signal<Option<Signal<T>>>,
|
value: Signal<Option<Signal<T>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl<T> UseServerFuture<T> {
|
impl<T> UseServerFuture<T> {
|
||||||
// /// Restart the future with new dependencies.
|
// /// Restart the future with new dependencies.
|
||||||
// ///
|
// ///
|
||||||
// /// Will not cancel the previous future, but will ignore any values that it
|
// /// Will not cancel the previous future, but will ignore any values that it
|
||||||
// /// generates.
|
// /// generates.
|
||||||
// pub fn restart(&self) {
|
// pub fn restart(&self) {
|
||||||
// self.needs_regen.set(true);
|
// self.needs_regen.set(true);
|
||||||
// (self.update)();
|
// (self.update)();
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// /// Forcefully cancel a future
|
// /// Forcefully cancel a future
|
||||||
// pub fn cancel(&self) {
|
// pub fn cancel(&self) {
|
||||||
// if let Some(task) = self.task.take() {
|
// if let Some(task) = self.task.take() {
|
||||||
// remove_future(task);
|
// remove_future(task);
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// /// Return any value, even old values if the future has not yet resolved.
|
/// 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`.
|
/// If the future has never completed, the returned value will be `None`.
|
||||||
// pub fn value(&self) -> Ref<'_, T> {
|
pub fn value(&self) -> Option<Signal<T>> {
|
||||||
// Ref::map(self.value.borrow(), |v| v.as_deref().unwrap())
|
todo!()
|
||||||
// }
|
// Ref::map(self.value.read(), |v: &Option<T>| v.as_deref().unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
// /// Get the ID of the future in Dioxus' internal scheduler
|
// /// Get the ID of the future in Dioxus' internal scheduler
|
||||||
// pub fn task(&self) -> Option<Task> {
|
// pub fn task(&self) -> Option<Task> {
|
||||||
// self.task.get()
|
// self.task.get()
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// /// Get the current state of the future.
|
// /// Get the current state of the future.
|
||||||
// pub fn reloading(&self) -> bool {
|
// pub fn reloading(&self) -> bool {
|
||||||
// self.task.get().is_some()
|
// self.task.get().is_some()
|
||||||
// }
|
// }
|
||||||
// }
|
}
|
||||||
@@ -1,24 +1,27 @@
|
|||||||
use crate::use_hook_did_run;
|
use crate::use_hook_did_run;
|
||||||
use dioxus_core::prelude::*;
|
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.
|
/// 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.
|
/// 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.
|
/// 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) {
|
pub fn use_effect(mut callback: impl FnMut() + 'static) {
|
||||||
let mut run_effect = use_hook(|| CopyValue::new(true));
|
// let mut run_effect = use_hook(|| CopyValue::new(true));
|
||||||
|
// use_hook_did_run(move |did_run| run_effect.set(did_run));
|
||||||
use_hook_did_run(move |did_run| match did_run {
|
|
||||||
true => run_effect.set(true),
|
|
||||||
false => run_effect.set(false),
|
|
||||||
});
|
|
||||||
|
|
||||||
use_hook(|| {
|
use_hook(|| {
|
||||||
Effect::new(move || {
|
spawn(async move {
|
||||||
if run_effect() {
|
let rc = ReactiveContext::new(None);
|
||||||
callback();
|
|
||||||
|
loop {
|
||||||
|
// Run the effect
|
||||||
|
rc.run_in(|| callback());
|
||||||
|
|
||||||
|
// Wait for context to change
|
||||||
|
rc.changed().await;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -21,11 +21,6 @@ where
|
|||||||
let mut callback = use_callback(move || {
|
let mut callback = use_callback(move || {
|
||||||
let fut = future();
|
let fut = future();
|
||||||
spawn(async move {
|
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);
|
state.set(UseFutureState::Pending);
|
||||||
fut.await;
|
fut.await;
|
||||||
state.set(UseFutureState::Complete);
|
state.set(UseFutureState::Complete);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use crate::dependency::Dependency;
|
use crate::dependency::Dependency;
|
||||||
use crate::use_signal;
|
use crate::use_signal;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_signals::{ReadOnlySignal, Readable, Signal, SignalData};
|
use dioxus_signals::{ReactiveContext, ReadOnlySignal, Readable, Signal, SignalData};
|
||||||
use dioxus_signals::{Storage, Writable};
|
use dioxus_signals::{Storage, Writable};
|
||||||
|
|
||||||
/// Creates a new unsync Selector. The selector will be run immediately and whenever any signal it reads changes.
|
/// 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]
|
#[track_caller]
|
||||||
pub fn use_maybe_sync_memo<R: PartialEq, S: Storage<SignalData<R>>>(
|
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> {
|
) -> 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
|
/// 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
|
||||||
|
|||||||
@@ -27,20 +27,20 @@ where
|
|||||||
|
|
||||||
// Spawn a wrapper task that polls the innner future and watch its dependencies
|
// Spawn a wrapper task that polls the innner future and watch its dependencies
|
||||||
let task = spawn(async move {
|
let task = spawn(async move {
|
||||||
// move the future here and pin it so we can poll it
|
// // move the future here and pin it so we can poll it
|
||||||
let fut = fut;
|
// let fut = fut;
|
||||||
pin_mut!(fut);
|
// pin_mut!(fut);
|
||||||
|
|
||||||
let res = future::poll_fn(|cx| {
|
// let res = future::poll_fn(|cx| {
|
||||||
// Set the effect stack properly
|
// // Set the effect stack properly
|
||||||
// add any dependencies to the effect stack that we need to watch when restarting the future
|
// // add any dependencies to the effect stack that we need to watch when restarting the future
|
||||||
// Poll the inner future
|
// // Poll the inner future
|
||||||
fut.poll_unpin(cx)
|
// fut.poll_unpin(cx)
|
||||||
})
|
// })
|
||||||
.await;
|
// .await;
|
||||||
|
|
||||||
// Set the value
|
// // Set the value
|
||||||
value.set(Some(Signal::new(res)));
|
// value.set(Some(Signal::new(res)));
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(task)
|
Some(task)
|
||||||
|
|||||||
@@ -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
|
// 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
|
// 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
|
signal
|
||||||
}
|
}
|
||||||
@@ -91,10 +91,15 @@ impl IntoFuture for UseEval {
|
|||||||
/// Represents an error when evaluating JavaScript
|
/// Represents an error when evaluating JavaScript
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum EvalError {
|
pub enum EvalError {
|
||||||
|
/// The platform does not support evaluating JavaScript.
|
||||||
|
Unspported,
|
||||||
|
|
||||||
/// The provided JavaScript has already been ran.
|
/// The provided JavaScript has already been ran.
|
||||||
Finished,
|
Finished,
|
||||||
|
|
||||||
/// The provided JavaScript is not valid and can't be ran.
|
/// The provided JavaScript is not valid and can't be ran.
|
||||||
InvalidJs(String),
|
InvalidJs(String),
|
||||||
|
|
||||||
/// Represents an error communicating between JavaScript and Rust.
|
/// Represents an error communicating between JavaScript and Rust.
|
||||||
Communication(String),
|
Communication(String),
|
||||||
}
|
}
|
||||||
@@ -36,3 +36,6 @@ pub use write::*;
|
|||||||
|
|
||||||
mod props;
|
mod props;
|
||||||
pub use props::*;
|
pub use props::*;
|
||||||
|
|
||||||
|
mod rc;
|
||||||
|
pub use rc::*;
|
||||||
+93
-24
@@ -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 generational_box::{GenerationalBoxId, SyncStorage};
|
||||||
use rustc_hash::FxHashSet;
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use std::{cell::RefCell, hash::Hash};
|
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
|
/// 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.
|
/// 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
|
/// 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 {
|
pub struct ReactiveContext {
|
||||||
// todo: we dont need to use syncstorage per say
|
pub inner: CopyValue<Inner, SyncStorage>,
|
||||||
inner: CopyValue<Inner, SyncStorage>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_local! {
|
thread_local! {
|
||||||
@@ -22,27 +25,94 @@ thread_local! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ReactiveContext {
|
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
|
/// Get the current reactive context
|
||||||
///
|
///
|
||||||
/// If this was set manually, then that value will be returned.
|
/// 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.
|
/// If there's no current reactive context, then a new one will be created at the current scope and returned.
|
||||||
pub fn current() -> Self {
|
pub fn current() -> Option<Self> {
|
||||||
todo!()
|
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
|
/// 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.
|
/// 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.
|
/// You can then get information about the current subscriptions.
|
||||||
pub fn run_in(&self, f: impl FnOnce()) {
|
pub fn run_in<O>(&self, f: impl FnOnce() -> O) -> O {
|
||||||
todo!()
|
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
|
/// Marks this reactive context as dirty
|
||||||
///
|
///
|
||||||
/// If there's a scope associated with this context, then it will be marked as dirty too
|
/// 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
|
/// Clear all subscribers from this reactive context
|
||||||
pub fn clear_subscribers(&self) {
|
pub fn clear_subscribers(&self) {
|
||||||
@@ -51,9 +121,8 @@ impl ReactiveContext {
|
|||||||
|
|
||||||
/// Wait for this reactive context to change
|
/// Wait for this reactive context to change
|
||||||
pub async fn changed(&self) {
|
pub async fn changed(&self) {
|
||||||
let waiter = self.inner.read().waiters.1.clone();
|
let rx = self.inner.read().receiver.clone();
|
||||||
|
_ = rx.recv_async().await;
|
||||||
_ = waiter.recv_async().await;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,22 +132,22 @@ impl Hash for ReactiveContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Inner {
|
pub struct Inner {
|
||||||
// Set of signals bound to this context
|
// Set of signals bound to this context
|
||||||
subscribers: FxHashSet<GenerationalBoxId>,
|
pub signal_subscribers: FxHashMap<GenerationalBoxId, RcList>,
|
||||||
|
scope_subscribers: FxHashSet<ScopeId>,
|
||||||
// The scope that this context is associated with
|
_self: Option<ReactiveContext>,
|
||||||
// This is only relevant when RC is being used to call update on a signal
|
|
||||||
scope: Option<ScopeId>,
|
|
||||||
|
|
||||||
// Futures will call .changed().await
|
// Futures will call .changed().await
|
||||||
waiters: (flume::Sender<()>, flume::Receiver<()>),
|
sender: flume::Sender<()>,
|
||||||
|
receiver: flume::Receiver<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Inner {}
|
|
||||||
|
|
||||||
impl Drop for Inner {
|
impl Drop for Inner {
|
||||||
fn drop(&mut self) {
|
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());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,6 +284,7 @@ impl<T: 'static, S: Storage<T>> PartialEq for CopyValue<T, S> {
|
|||||||
self.value.ptr_eq(&other.value)
|
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> {
|
impl<T: Copy, S: Storage<T>> Deref for CopyValue<T, S> {
|
||||||
type Target = dyn Fn() -> T;
|
type Target = dyn Fn() -> T;
|
||||||
|
|||||||
+44
-153
@@ -1,11 +1,12 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
get_effect_ref, read::Readable, write::Writable, CopyValue, Effect, EffectInner,
|
get_effect_ref, read::Readable, write::Writable, CopyValue, Effect, EffectInner,
|
||||||
EffectStackRef, GlobalMemo, GlobalSignal, MappedSignal, ReactiveContext,
|
EffectStackRef, GlobalMemo, GlobalSignal, MappedSignal, ReactiveContext, ReadOnlySignal,
|
||||||
ReactiveContextProvider, ReadSignal, EFFECT_STACK,
|
EFFECT_STACK,
|
||||||
};
|
};
|
||||||
use dioxus_core::{
|
use dioxus_core::{
|
||||||
prelude::{
|
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,
|
ScopeId,
|
||||||
};
|
};
|
||||||
@@ -59,21 +60,16 @@ pub struct Signal<T: 'static, S: Storage<SignalData<T>> = UnsyncStorage> {
|
|||||||
/// A signal that can safely shared between threads.
|
/// A signal that can safely shared between threads.
|
||||||
pub type SyncSignal<T> = Signal<T, SyncStorage>;
|
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.
|
/// The data stored for tracking in a signal.
|
||||||
pub struct SignalData<T> {
|
pub struct SignalData<T> {
|
||||||
pub(crate) subscribers: Arc<RwLock<HashSet<ReactiveContext>>>,
|
pub(crate) subscribers: RcList,
|
||||||
pub(crate) update_any: Arc<dyn Fn(ScopeId) + Sync + Send>,
|
|
||||||
pub(crate) effect_ref: EffectStackRef,
|
|
||||||
pub(crate) value: T,
|
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> {
|
impl<T: 'static> Signal<T> {
|
||||||
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
|
/// Creates a new Signal. Signals are a Copy state management solution with automatic dependency tracking.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
@@ -97,10 +93,7 @@ impl<T: 'static> Signal<T> {
|
|||||||
impl<T: PartialEq + 'static> Signal<T> {
|
impl<T: PartialEq + 'static> Signal<T> {
|
||||||
/// Creates a new global Signal that can be used in a global static.
|
/// Creates a new global Signal that can be used in a global static.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T>
|
pub const fn global_memo(constructor: fn() -> T) -> GlobalMemo<T> {
|
||||||
where
|
|
||||||
T: PartialEq,
|
|
||||||
{
|
|
||||||
GlobalMemo::new(constructor)
|
GlobalMemo::new(constructor)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,38 +112,26 @@ impl<T: PartialEq + 'static> Signal<T> {
|
|||||||
pub fn maybe_sync_memo<S: Storage<SignalData<T>>>(
|
pub fn maybe_sync_memo<S: Storage<SignalData<T>>>(
|
||||||
mut f: impl FnMut() -> T + 'static,
|
mut f: impl FnMut() -> T + 'static,
|
||||||
) -> ReadOnlySignal<T, S> {
|
) -> ReadOnlySignal<T, S> {
|
||||||
let effect = Effect {
|
// Get the current reactive context
|
||||||
source: current_scope_id().expect("in a virtual dom"),
|
let rc = ReactiveContext::current()
|
||||||
inner: CopyValue::invalid(),
|
.expect("Cannot use a selector outside of a reactive context");
|
||||||
};
|
|
||||||
|
|
||||||
{
|
// Create a new signal in that context, wiring up its dependencies and subscribers
|
||||||
EFFECT_STACK.with(|stack| stack.effects.write().push(effect));
|
let mut state: Signal<T, S> = rc.run_in(|| Signal::new_maybe_sync(f()));
|
||||||
}
|
|
||||||
let mut state: Signal<T, S> = Signal::new_maybe_sync(f());
|
|
||||||
{
|
|
||||||
EFFECT_STACK.with(|stack| stack.effects.write().pop());
|
|
||||||
}
|
|
||||||
|
|
||||||
let invalid_id = effect.id();
|
spawn(async move {
|
||||||
tracing::trace!("Creating effect: {:?}", invalid_id);
|
loop {
|
||||||
effect.inner.value.set(EffectInner {
|
rc.changed().await;
|
||||||
callback: Box::new(move || {
|
println!("changed");
|
||||||
let value = f();
|
|
||||||
let changed = {
|
let new = f();
|
||||||
let old = state.inner.read();
|
if new != *state.peek() {
|
||||||
value != old.value
|
*state.write() = new;
|
||||||
};
|
|
||||||
if changed {
|
|
||||||
state.set(value)
|
|
||||||
}
|
}
|
||||||
}),
|
}
|
||||||
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)
|
ReadOnlySignal::new_maybe_sync(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -163,9 +144,7 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
|||||||
Self {
|
Self {
|
||||||
inner: CopyValue::<SignalData<T>, S>::new_maybe_sync(SignalData {
|
inner: CopyValue::<SignalData<T>, S>::new_maybe_sync(SignalData {
|
||||||
subscribers: Default::default(),
|
subscribers: Default::default(),
|
||||||
update_any: schedule_update_any(),
|
|
||||||
value,
|
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(
|
inner: CopyValue::new_with_caller(
|
||||||
SignalData {
|
SignalData {
|
||||||
subscribers: Default::default(),
|
subscribers: Default::default(),
|
||||||
update_any: schedule_update_any(),
|
|
||||||
value,
|
value,
|
||||||
effect_ref: get_effect_ref(),
|
|
||||||
},
|
},
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
caller,
|
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(
|
inner: CopyValue::<SignalData<T>, S>::new_maybe_sync_in_scope(
|
||||||
SignalData {
|
SignalData {
|
||||||
subscribers: Default::default(),
|
subscribers: Default::default(),
|
||||||
update_any: schedule_update_any(),
|
|
||||||
value,
|
value,
|
||||||
effect_ref: get_effect_ref(),
|
|
||||||
},
|
},
|
||||||
owner,
|
owner,
|
||||||
),
|
),
|
||||||
@@ -217,44 +192,13 @@ impl<T: 'static, S: Storage<SignalData<T>>> Signal<T, S> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn update_subscribers(&self) {
|
fn update_subscribers(&self) {
|
||||||
todo!()
|
{
|
||||||
// {
|
let inner = self.inner.read();
|
||||||
// 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 self_read = &self.inner.read();
|
for cx in inner.subscribers.read().iter() {
|
||||||
// let subscribers = {
|
cx.mark_dirty();
|
||||||
// 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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Map the signal to a new type.
|
/// 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> {
|
fn read(&self) -> S::Ref<T> {
|
||||||
let inner = self.inner.read();
|
let inner = self.inner.read();
|
||||||
|
|
||||||
todo!();
|
if let Some(mut cx) = ReactiveContext::current() {
|
||||||
|
cx.link(self.id(), inner.subscribers.clone());
|
||||||
// // 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(¤t_scope_id) {
|
|
||||||
// subscribers.subscribers.insert(current_scope_id);
|
|
||||||
// subscribers
|
|
||||||
// .subscribers
|
|
||||||
// .insert(current_unsubscriber().borrow().scope);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
S::map(inner, |v| &v.value)
|
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.
|
/// A mutable reference to a signal's value.
|
||||||
///
|
///
|
||||||
/// T is the current type of the write
|
/// 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
|
&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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user