Fix effects triggered from async tasks; improve work scheduling docs (#2370)

* outline work scheduling

* move scheduling code from dirty_scope to scheduler

* introduce queue_effect

* Run effects after all tasks are polled if there are no components rerendered

* add a new test for async effects
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-05-04 15:30:13 +01:00
1 parent 05eaca320f
commit 5290b658fd
11 files changed
+305 -53

No files matched your search

+31 -14
View File
@@ -481,6 +481,7 @@ impl VirtualDom {
// The task may be marked dirty at the same time as the scope that owns the task is dropped.
self.mark_task_dirty(id);
}
SchedulerMsg::EffectQueued => {}
};
}
@@ -491,6 +492,7 @@ impl VirtualDom {
match msg {
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
SchedulerMsg::TaskNotified(task) => self.mark_task_dirty(task),
SchedulerMsg::EffectQueued => {}
}
}
}
@@ -513,21 +515,36 @@ impl VirtualDom {
fn poll_tasks(&mut self) {
// Make sure we set the runtime since we're running user code
let _runtime = RuntimeGuard::new(self.runtime.clone());
// Next, run any queued tasks
// We choose not to poll the deadline since we complete pretty quickly anyways
while let Some(task) = self.pop_task() {
// Then poll any tasks that might be pending
let mut tasks = task.tasks_queued.into_inner();
while let Some(task) = tasks.pop_front() {
let _ = self.runtime.handle_task_wakeup(task);
// Running that task, may mark a scope higher up as dirty. If it does, return from the function early
// Keep polling tasks until there are no more effects or tasks to run
// Or until we have no more dirty scopes
while !self.dirty_tasks.is_empty() || !self.runtime.pending_effects.borrow().is_empty() {
// Next, run any queued tasks
// We choose not to poll the deadline since we complete pretty quickly anyways
while let Some(task) = self.pop_task() {
// Then poll any tasks that might be pending
let mut tasks = task.tasks_queued.into_inner();
while let Some(task) = tasks.pop_front() {
let _ = self.runtime.handle_task_wakeup(task);
// Running that task, may mark a scope higher up as dirty. If it does, return from the function early
self.queue_events();
if self.has_dirty_scopes() {
// requeue any remaining tasks
for task in tasks {
self.mark_task_dirty(task);
}
return;
}
}
}
// At this point, we have finished running all tasks that are pending and we haven't found any scopes to rerun. This means it is safe to run our lowest priority work: effects
while let Some(effect) = self.pop_effect() {
effect.run(&self.runtime);
// Check if any new scopes are queued for rerun
self.queue_events();
if self.has_dirty_scopes() {
// requeue any remaining tasks
for task in tasks {
self.mark_task_dirty(task);
}
return;
}
}
@@ -633,7 +650,7 @@ impl VirtualDom {
// Process any events that might be pending in the queue
// Signals marked with .write() need a chance to be handled by the effect driver
// This also processes futures which might progress into immediates
// This also processes futures which might progress into immediately rerunning a scope
self.process_events();
// Next, diff any dirty scopes
@@ -658,7 +675,7 @@ impl VirtualDom {
}
}
self.runtime.render_signal.send();
self.runtime.finish_render();
}
/// [`Self::render_immediate`] to a vector of mutations for testing purposes