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:
1 parent
05eaca320f
commit
5290b658fd
11 files changed
+305
-53
No files matched your search
@@ -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
|
||||
|
||||
Reference in new issue
Block a user