Properly implement wait_for_suspense

Wait_for_suspense now does not call flush_sync, making it
appropriate for deferring effects on the server. Futures will
still run on the server (as needed to progress suspense)
but use_effect / and memo watchers will not run on the server
This commit is contained in:
Jonathan Kelley committed 2024-01-29 14:12:05 -08:00
1 parent 1f88103dca
commit dcdada542b
11 files changed
+156 -86

No files matched your search

+62 -41
View File
@@ -47,53 +47,74 @@ impl EffectStackRef {
}
pub(crate) fn get_effect_ref() -> EffectStackRef {
match try_consume_context() {
Some(rt) => rt,
None => {
let (sender, mut receiver) = futures_channel::mpsc::unbounded();
spawn_forever(async move {
let mut queued_memos = Vec::new();
if let Some(rt) = try_consume_context() {
return rt;
}
loop {
// Wait for a flush
// This gives a chance for effects to be updated in place and memos to compute their values
let flush_await = flush_sync();
pin_mut!(flush_await);
let (sender, receiver) = futures_channel::mpsc::unbounded();
loop {
let res =
futures_util::future::select(&mut flush_await, receiver.next()).await;
spawn_forever(async move { effect_driver(receiver).await });
match res {
Either::Right((_queued, _)) => {
if let Some(task) = _queued {
queued_memos.push(task);
}
continue;
}
Either::Left(_flushed) => break,
}
let stack_ref = EffectStackRef {
rerun_effect: sender,
};
provide_root_context(stack_ref.clone());
stack_ref
}
/// The primary top-level driver of all effects
///
/// In Dioxus, effects are neither react effects nor solidjs effects. They are a hybrid of the two, making our model
/// more complex but also more powerful.
///
/// In react, when a component renders, it can queue up effects to be run after the component is done rendering.
/// This is done *only during render* and determined by the dependency array attached to the effect. In Dioxus,
/// we track effects using signals, so these effects can actually run multiple times after the component has rendered.
///
///
async fn effect_driver(
mut receiver: futures_channel::mpsc::UnboundedReceiver<GenerationalBoxId>,
) -> ! {
let mut queued_memos = Vec::new();
loop {
// Wait for a flush
// This gives a chance for effects to be updated in place and memos to compute their values
let flush_await = flush_sync();
pin_mut!(flush_await);
// Until the flush is ready, wait for a new effect to be queued
// We don't run the effects immediately because we want to batch them on the next call to flush
// todo: the queued memos should be unqueued when components are dropped
loop {
match futures_util::future::select(&mut flush_await, receiver.next()).await {
// VDOM is flushed and we can run the queued effects
Either::Left(_flushed) => break,
// A new effect was queued to be run after the next flush
// Marking components as dirty is handled syncrhonously on write, though we could try
// batching them here too
Either::Right((_queued, _)) => {
if let Some(task) = _queued {
queued_memos.push(task);
}
EFFECT_STACK.with(|stack| {
for id in queued_memos.drain(..) {
let effect_mapping = stack.effect_mapping.read();
if let Some(mut effect) = effect_mapping.get(&id).copied() {
tracing::trace!("Rerunning effect: {:?}", id);
effect.try_run();
} else {
tracing::trace!("Effect not found: {:?}", id);
}
}
});
}
});
let stack_ref = EffectStackRef {
rerun_effect: sender,
};
provide_root_context(stack_ref.clone());
stack_ref
}
}
EFFECT_STACK.with(|stack| {
for id in queued_memos.drain(..) {
let effect_mapping = stack.effect_mapping.read();
if let Some(mut effect) = effect_mapping.get(&id).copied() {
tracing::trace!("Rerunning effect: {:?}", id);
effect.try_run();
} else {
tracing::trace!("Effect not found: {:?}", id);
}
}
});
}
}