Fix desktop effect race condition (#2313)

* don't poll desktop before the render has been applied

* fix desktop headless tests

* move edit queued code into the edit channel and add more comments
This commit is contained in:
Evan Almloff authored and GitHub committed 2024-04-26 12:52:33 -04:00
1 parent a0e06271dd
commit 74352f2f61
6 files changed
+124 -48

No files matched your search

+39 -1
View File
@@ -1,4 +1,5 @@
use std::{cell::RefCell, collections::VecDeque, rc::Rc};
use std::cell::Cell;
use std::{cell::RefCell, collections::VecDeque, rc::Rc, task::Waker};
/// This handles communication between the requests that the webview makes and the interpreter. The interpreter constantly makes long running requests to the webview to get any edits that should be made to the DOM almost like server side events.
/// It will hold onto the requests until the interpreter is ready to handle them and hold onto any pending edits until a new request is made.
@@ -6,6 +7,11 @@ use std::{cell::RefCell, collections::VecDeque, rc::Rc};
pub(crate) struct EditQueue {
queue: Rc<RefCell<VecDeque<Vec<u8>>>>,
responder: Rc<RefCell<Option<wry::RequestAsyncResponder>>>,
// Stores any futures waiting for edits to be applied to the webview
// NOTE: We don't use a Notify here because we need polling the notify to be cancel safe
waiting_for_edits_flushed: Rc<RefCell<Vec<Waker>>>,
// If this webview is currently waiting for an edit to be flushed. We don't run the virtual dom while this is true to avoid running effects before the dom has been updated
edits_in_progress: Rc<Cell<bool>>,
}
impl EditQueue {
@@ -14,16 +20,48 @@ impl EditQueue {
if let Some(bytes) = queue.pop_back() {
responder.respond(wry::http::Response::new(bytes));
} else {
// There are now no edits that need to be applied to the webview
self.edits_finished();
*self.responder.borrow_mut() = Some(responder);
}
}
pub fn add_edits(&self, edits: Vec<u8>) {
let mut responder = self.responder.borrow_mut();
// There are pending edits that need to be applied to the webview before we run futures
self.start_edits();
if let Some(responder) = responder.take() {
responder.respond(wry::http::Response::new(edits));
} else {
self.queue.borrow_mut().push_front(edits);
}
}
fn start_edits(&self) {
self.edits_in_progress.set(true);
}
fn edits_finished(&self) {
for waker in self.waiting_for_edits_flushed.borrow_mut().drain(..) {
waker.wake();
}
self.edits_in_progress.set(false);
}
fn edits_in_progress(&self) -> bool {
self.edits_in_progress.get()
}
/// Wait until all pending edits have been rendered in the webview
pub fn poll_edits_flushed(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll<()> {
if self.edits_in_progress() {
let waker = cx.waker();
self.waiting_for_edits_flushed
.borrow_mut()
.push(waker.clone());
std::task::Poll::Pending
} else {
std::task::Poll::Ready(())
}
}
}
+7 -1
View File
@@ -229,10 +229,16 @@ impl WebviewInstance {
pub fn poll_vdom(&mut self) {
let mut cx = std::task::Context::from_waker(&self.waker);
// Continously poll the virtualdom until it's pending
// Continuously poll the virtualdom until it's pending
// Wait for work will return Ready when it has edits to be sent to the webview
// It will return Pending when it needs to be polled again - nothing is ready
loop {
// If we're waiting for a render, wait for it to finish before we continue
let edits_flushed_poll = self.desktop_context.edit_queue.poll_edits_flushed(&mut cx);
if edits_flushed_poll.is_pending() {
return;
}
{
let fut = self.dom.wait_for_work();
pin_mut!(fut);