diff --git a/sixtyfps_runtime/rendering_backends/gl/eventloop.rs b/sixtyfps_runtime/rendering_backends/gl/eventloop.rs index 4bee0a65e..94b7fbc0f 100644 --- a/sixtyfps_runtime/rendering_backends/gl/eventloop.rs +++ b/sixtyfps_runtime/rendering_backends/gl/eventloop.rs @@ -75,8 +75,45 @@ thread_local! { scoped_tls_hkt::scoped_thread_local!(static CURRENT_WINDOW_TARGET : for<'a> &'a RunningEventLoop<'a>); #[cfg(not(target_arch = "wasm32"))] -pub static GLOBAL_PROXY: once_cell::sync::OnceCell< - std::sync::Mutex>>, +pub(crate) enum GlobalEventLoopProxyOrEventQueue { + Proxy(winit::event_loop::EventLoopProxy), + Queue(Vec), +} + +#[cfg(not(target_arch = "wasm32"))] +impl GlobalEventLoopProxyOrEventQueue { + pub(crate) fn send_event(&mut self, event: CustomEvent) { + match self { + GlobalEventLoopProxyOrEventQueue::Proxy(proxy) => proxy.send_event(event).ok().unwrap(), + GlobalEventLoopProxyOrEventQueue::Queue(queue) => { + queue.push(event); + } + }; + } + + fn set_proxy(&mut self, proxy: winit::event_loop::EventLoopProxy) { + match self { + GlobalEventLoopProxyOrEventQueue::Proxy(_) => {} + GlobalEventLoopProxyOrEventQueue::Queue(queue) => { + std::mem::take(queue) + .into_iter() + .for_each(|event| proxy.send_event(event).ok().unwrap()); + *self = GlobalEventLoopProxyOrEventQueue::Proxy(proxy); + } + } + } +} + +#[cfg(not(target_arch = "wasm32"))] +impl Default for GlobalEventLoopProxyOrEventQueue { + fn default() -> Self { + Self::Queue(Vec::new()) + } +} + +#[cfg(not(target_arch = "wasm32"))] +pub(crate) static GLOBAL_PROXY: once_cell::sync::OnceCell< + std::sync::Mutex, > = once_cell::sync::OnceCell::new(); pub(crate) fn with_window_target(callback: impl FnOnce(&dyn EventLoopInterface) -> T) -> T { @@ -133,8 +170,11 @@ pub fn run() { let event_loop_proxy = not_running_loop_instance.event_loop_proxy; #[cfg(not(target_arch = "wasm32"))] { - *GLOBAL_PROXY.get_or_init(Default::default).lock().unwrap() = - Some(event_loop_proxy.clone()); + GLOBAL_PROXY + .get_or_init(Default::default) + .lock() + .unwrap() + .set_proxy(event_loop_proxy.clone()); } let mut winit_loop = not_running_loop_instance.instance; diff --git a/sixtyfps_runtime/rendering_backends/gl/lib.rs b/sixtyfps_runtime/rendering_backends/gl/lib.rs index 9291ee835..30915b022 100644 --- a/sixtyfps_runtime/rendering_backends/gl/lib.rs +++ b/sixtyfps_runtime/rendering_backends/gl/lib.rs @@ -1520,12 +1520,7 @@ impl sixtyfps_corelib::backend::Backend for Backend { fn post_event(&'static self, event: Box) { let e = crate::eventloop::CustomEvent::UserEvent(event); #[cfg(not(target_arch = "wasm32"))] - crate::eventloop::GLOBAL_PROXY - .get_or_init(Default::default) - .lock() - .unwrap() - .as_ref() - .map(|proxy| proxy.send_event(e)); + crate::eventloop::GLOBAL_PROXY.get_or_init(Default::default).lock().unwrap().send_event(e); #[cfg(target_arch = "wasm32")] crate::eventloop::with_window_target(|event_loop| { event_loop.event_loop_proxy().send_event(e).ok(); diff --git a/tools/lsp/main.rs b/tools/lsp/main.rs index 93acf707a..5c7b34fde 100644 --- a/tools/lsp/main.rs +++ b/tools/lsp/main.rs @@ -45,20 +45,20 @@ impl<'a> DocumentCache<'a> { } fn main() { - std::thread::spawn(|| { - match run_lsp_server() { - Ok(_) => {} - Err(error) => { - eprintln!("Error running LSP server: {}", error); + // Start the LSP thread with a delay when the gui event loop is up and running, to be able + // to be immediately ready to serve preview requests. + preview::run_in_ui_thread(Box::new(|| { + std::thread::spawn(|| { + match run_lsp_server() { + Ok(_) => {} + Err(error) => { + eprintln!("Error running LSP server: {}", error); + } } - } - preview::quit_ui_event_loop(); - }); - // TODO: Don't terminate the event loop when the window is closed with Qt - // TODO: There's a race condition where theoretically the LSP could receive a preview - // request before the gui event loop has started, which would cause post_event to panic. - // Instead we should start the lsp thread when the gui thread is *ready*, for example through - // a single-shot timer. + preview::quit_ui_event_loop(); + }); + })); + // TODO: Don't terminate the event loop when the window is closed preview::start_ui_event_loop(); } diff --git a/tools/lsp/preview.rs b/tools/lsp/preview.rs index 24071dfad..9dc6245fa 100644 --- a/tools/lsp/preview.rs +++ b/tools/lsp/preview.rs @@ -27,7 +27,7 @@ unsafe impl Sync for FutureRunner {} impl Wake for FutureRunner { fn wake(self: Arc) { - sixtyfps_rendering_backend_default::backend().post_event(Box::new(move || { + run_in_ui_thread(Box::new(move || { let waker = self.clone().into(); let mut cx = std::task::Context::from_waker(&waker); let mut fut_opt = self.fut.lock().unwrap(); @@ -41,10 +41,14 @@ impl Wake for FutureRunner { } } -fn run_in_ui_thread(mut fut: Pin>>) { +fn run_future_in_ui_thread(mut fut: Pin>>) { Arc::new(FutureRunner { fut: Mutex::new(Some(fut)) }).wake() } +pub fn run_in_ui_thread(f: Box) { + sixtyfps_rendering_backend_default::backend().post_event(f); +} + pub fn start_ui_event_loop() { sixtyfps_interpreter::run_event_loop(); } @@ -56,7 +60,7 @@ pub fn quit_ui_event_loop() { } pub fn load_preview(path: std::path::PathBuf) { - run_in_ui_thread(Box::pin(async move { reload_preview(&path).await })); + run_future_in_ui_thread(Box::pin(async move { reload_preview(&path).await })); } async fn reload_preview(path: &std::path::Path) {