Enforce a Send bound on next frame callbacks

This required using mpsc channels to invoke frame callbacks on the
main thread and send the receiver to the platform display link.

Co-Authored-By: Julia Risley <julia@zed.dev>
This commit is contained in:
Nathan Sobo 2023-11-02 12:01:22 -06:00
parent 1e7a216d55
commit 04a8ee222b
6 changed files with 57 additions and 82 deletions

View file

@ -39,18 +39,20 @@ use std::{
}; };
use util::http::{self, HttpClient}; use util::http::{self, HttpClient};
/// Temporary(?) wrapper around RefCell<AppContext> to help us debug any double borrows.
/// Strongly consider removing after stabilization.
pub struct AppCell { pub struct AppCell {
app: RefCell<AppContext>, app: RefCell<AppContext>,
} }
impl AppCell { impl AppCell {
pub fn borrow(&self) -> AppRef { pub fn borrow(&self) -> AppRef {
AppRef(self.app.borrow()) AppRef(self.app.borrow())
} }
pub fn borrow_mut(&self) -> AppRefMut { pub fn borrow_mut(&self) -> AppRefMut {
let thread_id = std::thread::current().id(); // let thread_id = std::thread::current().id();
// dbg!("borrowed {thread_id:?}");
eprintln!(">>> borrowing {thread_id:?}");
AppRefMut(self.app.borrow_mut()) AppRefMut(self.app.borrow_mut())
} }
} }
@ -84,7 +86,6 @@ impl App {
let this = self.0.clone(); let this = self.0.clone();
let platform = self.0.borrow().platform.clone(); let platform = self.0.borrow().platform.clone();
platform.run(Box::new(move || { platform.run(Box::new(move || {
dbg!("run callback");
let cx = &mut *this.borrow_mut(); let cx = &mut *this.borrow_mut();
on_finish_launching(cx); on_finish_launching(cx);
})); }));
@ -110,14 +111,11 @@ impl App {
F: 'static + FnMut(&mut AppContext), F: 'static + FnMut(&mut AppContext),
{ {
let this = Rc::downgrade(&self.0); let this = Rc::downgrade(&self.0);
self.0 self.0.borrow_mut().platform.on_reopen(Box::new(move || {
.borrow_mut() if let Some(app) = this.upgrade() {
.platform callback(&mut app.borrow_mut());
.on_reopen(Box::new(move || { }
if let Some(app) = this.upgrade() { }));
callback(&mut app.borrow_mut());
}
}));
self self
} }
@ -139,7 +137,7 @@ impl App {
} }
type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>; type ActionBuilder = fn(json: Option<serde_json::Value>) -> anyhow::Result<Box<dyn Action>>;
type FrameCallback = Box<dyn FnOnce(&mut AppContext)>; pub(crate) type FrameCallback = Box<dyn FnOnce(&mut AppContext)>;
type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>; type Handler = Box<dyn FnMut(&mut AppContext) -> bool + 'static>;
type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>; type Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>; type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;

View file

@ -28,7 +28,6 @@ impl Context for AsyncAppContext {
.app .app
.upgrade() .upgrade()
.ok_or_else(|| anyhow!("app was released"))?; .ok_or_else(|| anyhow!("app was released"))?;
dbg!("BUILD MODEL A");
let mut app = app.borrow_mut(); let mut app = app.borrow_mut();
Ok(app.build_model(build_model)) Ok(app.build_model(build_model))
} }
@ -42,7 +41,6 @@ impl Context for AsyncAppContext {
.app .app
.upgrade() .upgrade()
.ok_or_else(|| anyhow!("app was released"))?; .ok_or_else(|| anyhow!("app was released"))?;
dbg!("UPDATE MODEL B");
let mut app = app.borrow_mut(); let mut app = app.borrow_mut();
Ok(app.update_model(handle, update)) Ok(app.update_model(handle, update))
} }
@ -52,7 +50,6 @@ impl Context for AsyncAppContext {
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T, F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
{ {
let app = self.app.upgrade().context("app was released")?; let app = self.app.upgrade().context("app was released")?;
dbg!("UPDATE WINDOW C");
let mut lock = app.borrow_mut(); let mut lock = app.borrow_mut();
lock.update_window(window, f) lock.update_window(window, f)
} }
@ -64,7 +61,6 @@ impl AsyncAppContext {
.app .app
.upgrade() .upgrade()
.ok_or_else(|| anyhow!("app was released"))?; .ok_or_else(|| anyhow!("app was released"))?;
dbg!("REFRESH");
let mut lock = app.borrow_mut(); let mut lock = app.borrow_mut();
lock.refresh(); lock.refresh();
Ok(()) Ok(())
@ -125,7 +121,6 @@ impl AsyncAppContext {
.app .app
.upgrade() .upgrade()
.ok_or_else(|| anyhow!("app was released"))?; .ok_or_else(|| anyhow!("app was released"))?;
dbg!("read global");
let app = app.borrow_mut(); let app = app.borrow_mut();
Ok(read(app.global(), &app)) Ok(read(app.global(), &app))
} }
@ -135,7 +130,6 @@ impl AsyncAppContext {
read: impl FnOnce(&G, &AppContext) -> R, read: impl FnOnce(&G, &AppContext) -> R,
) -> Option<R> { ) -> Option<R> {
let app = self.app.upgrade()?; let app = self.app.upgrade()?;
dbg!("try read global");
let app = app.borrow_mut(); let app = app.borrow_mut();
Some(read(app.try_global()?, &app)) Some(read(app.try_global()?, &app))
} }
@ -148,7 +142,6 @@ impl AsyncAppContext {
.app .app
.upgrade() .upgrade()
.ok_or_else(|| anyhow!("app was released"))?; .ok_or_else(|| anyhow!("app was released"))?;
dbg!("update global");
let mut app = app.borrow_mut(); let mut app = app.borrow_mut();
Ok(app.update_global(update)) Ok(app.update_global(update))
} }

View file

@ -1,7 +1,7 @@
use crate::{ use crate::{
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context, AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher, EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher,
TestPlatform, WindowContext, AppCell, TestPlatform, WindowContext,
}; };
use anyhow::{anyhow, bail}; use anyhow::{anyhow, bail};
use futures::{Stream, StreamExt}; use futures::{Stream, StreamExt};

View file

@ -125,9 +125,7 @@ where
} else { } else {
cx.spawn(|_, mut cx| async move { cx.spawn(|_, mut cx| async move {
if image_future.await.log_err().is_some() { if image_future.await.log_err().is_some() {
eprintln!(">>> on_next_frame");
cx.on_next_frame(|cx| cx.notify()); cx.on_next_frame(|cx| cx.notify());
eprintln!("<<< on_next_frame")
} }
}) })
.detach() .detach()

View file

@ -42,7 +42,6 @@ impl PlatformDispatcher for MacDispatcher {
} }
fn dispatch(&self, runnable: Runnable) { fn dispatch(&self, runnable: Runnable) {
println!("DISPATCH");
unsafe { unsafe {
dispatch_async_f( dispatch_async_f(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.try_into().unwrap(), 0),
@ -53,7 +52,6 @@ impl PlatformDispatcher for MacDispatcher {
} }
fn dispatch_on_main_thread(&self, runnable: Runnable) { fn dispatch_on_main_thread(&self, runnable: Runnable) {
println!("DISPATCH ON MAIN THREAD");
unsafe { unsafe {
dispatch_async_f( dispatch_async_f(
dispatch_get_main_queue(), dispatch_get_main_queue(),

View file

@ -410,67 +410,55 @@ impl<'a> WindowContext<'a> {
} }
/// Schedule the given closure to be run directly after the current frame is rendered. /// Schedule the given closure to be run directly after the current frame is rendered.
pub fn on_next_frame(&mut self, f: impl FnOnce(&mut WindowContext) + 'static) { pub fn on_next_frame(&mut self, callback: impl FnOnce(&mut WindowContext) + 'static) {
let f = Box::new(f); let handle = self.window.handle;
let display_id = self.window.display_id; let display_id = self.window.display_id;
if !self.frame_consumers.contains_key(&display_id) {
let (tx, mut rx) = mpsc::unbounded::<()>();
self.platform.set_display_link_output_callback(
display_id,
Box::new(move |_current_time, _output_time| _ = tx.unbounded_send(())),
);
let consumer_task = self.app.spawn(|cx| async move {
while rx.next().await.is_some() {
cx.update(|cx| {
for callback in cx
.next_frame_callbacks
.get_mut(&display_id)
.unwrap()
.drain(..)
.collect::<SmallVec<[_; 32]>>()
{
callback(cx);
}
})
.ok();
// Flush effects, then stop the display link if no new next_frame_callbacks have been added.
cx.update(|cx| {
if cx.next_frame_callbacks.is_empty() {
cx.platform.stop_display_link(display_id);
}
})
.ok();
}
});
self.frame_consumers.insert(display_id, consumer_task);
}
if self.next_frame_callbacks.is_empty() {
self.platform.start_display_link(display_id);
}
self.next_frame_callbacks self.next_frame_callbacks
.entry(display_id) .entry(display_id)
.or_default() .or_default()
.push(f); .push(Box::new(move |cx: &mut AppContext| {
cx.update_window(handle, |_root_view, cx| callback(cx)).ok();
self.frame_consumers.entry(display_id).or_insert_with(|| { }));
let (tx, rx) = mpsc::unbounded::<()>();
self.spawn(|cx| async move {
while rx.next().await.is_some() {
let _ = cx.update(|_, cx| {
for callback in cx
.app
.next_frame_callbacks
.get_mut(&display_id)
.unwrap()
.drain(..)
{
callback(cx);
}
});
}
})
});
if let Some(callbacks) = self.next_frame_callbacks.get_mut(&display_id) {
callbacks.push(f);
// If there was already a callback, it means that we already scheduled a frame.
if callbacks.len() > 1 {
return;
}
} else {
let mut async_cx = self.to_async();
self.next_frame_callbacks.insert(display_id, vec![f]);
self.platform.set_display_link_output_callback(
display_id,
Box::new(move |_current_time, _output_time| {
let _ = async_cx.update(|_, cx| {
let callbacks = cx
.next_frame_callbacks
.get_mut(&display_id)
.unwrap()
.drain(..)
.collect::<Vec<_>>();
for callback in callbacks {
callback(cx);
}
if cx.next_frame_callbacks.get(&display_id).unwrap().is_empty() {
cx.platform.stop_display_link(display_id);
}
});
}),
);
}
self.platform.start_display_link(display_id);
} }
/// Spawn the future returned by the given closure on the application thread pool. /// Spawn the future returned by the given closure on the application thread pool.