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:
parent
1e7a216d55
commit
04a8ee222b
6 changed files with 57 additions and 82 deletions
|
@ -39,18 +39,20 @@ use std::{
|
|||
};
|
||||
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 {
|
||||
app: RefCell<AppContext>,
|
||||
}
|
||||
|
||||
impl AppCell {
|
||||
pub fn borrow(&self) -> AppRef {
|
||||
AppRef(self.app.borrow())
|
||||
}
|
||||
|
||||
pub fn borrow_mut(&self) -> AppRefMut {
|
||||
let thread_id = std::thread::current().id();
|
||||
|
||||
eprintln!(">>> borrowing {thread_id:?}");
|
||||
// let thread_id = std::thread::current().id();
|
||||
// dbg!("borrowed {thread_id:?}");
|
||||
AppRefMut(self.app.borrow_mut())
|
||||
}
|
||||
}
|
||||
|
@ -84,7 +86,6 @@ impl App {
|
|||
let this = self.0.clone();
|
||||
let platform = self.0.borrow().platform.clone();
|
||||
platform.run(Box::new(move || {
|
||||
dbg!("run callback");
|
||||
let cx = &mut *this.borrow_mut();
|
||||
on_finish_launching(cx);
|
||||
}));
|
||||
|
@ -110,14 +111,11 @@ impl App {
|
|||
F: 'static + FnMut(&mut AppContext),
|
||||
{
|
||||
let this = Rc::downgrade(&self.0);
|
||||
self.0
|
||||
.borrow_mut()
|
||||
.platform
|
||||
.on_reopen(Box::new(move || {
|
||||
if let Some(app) = this.upgrade() {
|
||||
callback(&mut app.borrow_mut());
|
||||
}
|
||||
}));
|
||||
self.0.borrow_mut().platform.on_reopen(Box::new(move || {
|
||||
if let Some(app) = this.upgrade() {
|
||||
callback(&mut app.borrow_mut());
|
||||
}
|
||||
}));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -139,7 +137,7 @@ impl App {
|
|||
}
|
||||
|
||||
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 Listener = Box<dyn FnMut(&dyn Any, &mut AppContext) -> bool + 'static>;
|
||||
type QuitHandler = Box<dyn FnOnce(&mut AppContext) -> LocalBoxFuture<'static, ()> + 'static>;
|
||||
|
|
|
@ -28,7 +28,6 @@ impl Context for AsyncAppContext {
|
|||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
dbg!("BUILD MODEL A");
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.build_model(build_model))
|
||||
}
|
||||
|
@ -42,7 +41,6 @@ impl Context for AsyncAppContext {
|
|||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
dbg!("UPDATE MODEL B");
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.update_model(handle, update))
|
||||
}
|
||||
|
@ -52,7 +50,6 @@ impl Context for AsyncAppContext {
|
|||
F: FnOnce(AnyView, &mut WindowContext<'_>) -> T,
|
||||
{
|
||||
let app = self.app.upgrade().context("app was released")?;
|
||||
dbg!("UPDATE WINDOW C");
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.update_window(window, f)
|
||||
}
|
||||
|
@ -64,7 +61,6 @@ impl AsyncAppContext {
|
|||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
dbg!("REFRESH");
|
||||
let mut lock = app.borrow_mut();
|
||||
lock.refresh();
|
||||
Ok(())
|
||||
|
@ -125,7 +121,6 @@ impl AsyncAppContext {
|
|||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
dbg!("read global");
|
||||
let app = app.borrow_mut();
|
||||
Ok(read(app.global(), &app))
|
||||
}
|
||||
|
@ -135,7 +130,6 @@ impl AsyncAppContext {
|
|||
read: impl FnOnce(&G, &AppContext) -> R,
|
||||
) -> Option<R> {
|
||||
let app = self.app.upgrade()?;
|
||||
dbg!("try read global");
|
||||
let app = app.borrow_mut();
|
||||
Some(read(app.try_global()?, &app))
|
||||
}
|
||||
|
@ -148,7 +142,6 @@ impl AsyncAppContext {
|
|||
.app
|
||||
.upgrade()
|
||||
.ok_or_else(|| anyhow!("app was released"))?;
|
||||
dbg!("update global");
|
||||
let mut app = app.borrow_mut();
|
||||
Ok(app.update_global(update))
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
AnyView, AnyWindowHandle, AppContext, AsyncAppContext, BackgroundExecutor, Context,
|
||||
AnyView, AnyWindowHandle, AppCell, AppContext, AsyncAppContext, BackgroundExecutor, Context,
|
||||
EventEmitter, ForegroundExecutor, Model, ModelContext, Result, Task, TestDispatcher,
|
||||
TestPlatform, WindowContext, AppCell,
|
||||
TestPlatform, WindowContext,
|
||||
};
|
||||
use anyhow::{anyhow, bail};
|
||||
use futures::{Stream, StreamExt};
|
||||
|
|
|
@ -125,9 +125,7 @@ where
|
|||
} else {
|
||||
cx.spawn(|_, mut cx| async move {
|
||||
if image_future.await.log_err().is_some() {
|
||||
eprintln!(">>> on_next_frame");
|
||||
cx.on_next_frame(|cx| cx.notify());
|
||||
eprintln!("<<< on_next_frame")
|
||||
}
|
||||
})
|
||||
.detach()
|
||||
|
|
|
@ -42,7 +42,6 @@ impl PlatformDispatcher for MacDispatcher {
|
|||
}
|
||||
|
||||
fn dispatch(&self, runnable: Runnable) {
|
||||
println!("DISPATCH");
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
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) {
|
||||
println!("DISPATCH ON MAIN THREAD");
|
||||
unsafe {
|
||||
dispatch_async_f(
|
||||
dispatch_get_main_queue(),
|
||||
|
|
|
@ -410,67 +410,55 @@ impl<'a> WindowContext<'a> {
|
|||
}
|
||||
|
||||
/// 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) {
|
||||
let f = Box::new(f);
|
||||
pub fn on_next_frame(&mut self, callback: impl FnOnce(&mut WindowContext) + 'static) {
|
||||
let handle = self.window.handle;
|
||||
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
|
||||
.entry(display_id)
|
||||
.or_default()
|
||||
.push(f);
|
||||
|
||||
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);
|
||||
.push(Box::new(move |cx: &mut AppContext| {
|
||||
cx.update_window(handle, |_root_view, cx| callback(cx)).ok();
|
||||
}));
|
||||
}
|
||||
|
||||
/// Spawn the future returned by the given closure on the application thread pool.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue