From ef301dd4be2b46d6060e139d6a272aa3f11e9f7a Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 18 Feb 2026 01:18:05 +0000 Subject: [PATCH 1/2] Fix panic caused by invalid viewport scale --- editor/src/messages/viewport/viewport_message_handler.rs | 2 +- frontend/src/utility-functions/viewports.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/editor/src/messages/viewport/viewport_message_handler.rs b/editor/src/messages/viewport/viewport_message_handler.rs index c0d64b92ef..a060a14efa 100644 --- a/editor/src/messages/viewport/viewport_message_handler.rs +++ b/editor/src/messages/viewport/viewport_message_handler.rs @@ -26,7 +26,7 @@ impl MessageHandler for ViewportMessageHandler { fn process_message(&mut self, message: ViewportMessage, responses: &mut VecDeque, _: ()) { match message { ViewportMessage::Update { x, y, width, height, scale } => { - assert_ne!(scale, 0., "Viewport scale cannot be zero"); + assert!(scale > 0., "Viewport scale must be greater than zero"); self.scale = scale; self.bounds = Bounds { diff --git a/frontend/src/utility-functions/viewports.ts b/frontend/src/utility-functions/viewports.ts index 8323d4b149..d3f9d4be69 100644 --- a/frontend/src/utility-functions/viewports.ts +++ b/frontend/src/utility-functions/viewports.ts @@ -42,6 +42,10 @@ export function setupViewportResizeObserver(editor: Editor) { // TODO: Consider passing physical sizes as well to eliminate pixel inaccuracies since width and height could be rounded differently const scale = physicalWidth / logicalWidth; + if (!scale || scale <= 0) { + continue; + } + editor.handle.updateViewport(bounds.x, bounds.y, logicalWidth, logicalHeight, scale); } }); From d59ba42e041ac8d5f3dd53c83fc9b0d6dce20c1b Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 18 Feb 2026 02:36:33 +0000 Subject: [PATCH 2/2] Make shutdown more robust --- desktop/src/app.rs | 16 +++++++++++++++- desktop/src/cef/context/multithreaded.rs | 3 ++- desktop/src/cef/context/singlethreaded.rs | 4 ++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/desktop/src/app.rs b/desktop/src/app.rs index a7a5e0bc7d..06435c4147 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -1,6 +1,8 @@ use rand::Rng; use rfd::AsyncFileDialog; use std::fs; +use std::sync::Arc; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Sender, SyncSender}; use std::thread; use std::time::{Duration, Instant}; @@ -44,6 +46,7 @@ pub(crate) struct App { persistent_data: PersistentData, cli: Cli, startup_time: Option, + exiting: Arc, exit_reason: ExitReason, } @@ -67,14 +70,20 @@ impl App { }) .expect("Error setting Ctrl-C handler"); + let exiting = Arc::new(AtomicBool::new(false)); + let rendering_app_event_scheduler = app_event_scheduler.clone(); let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1); + let exiting_clone = exiting.clone(); std::thread::spawn(move || { let runtime = tokio::runtime::Runtime::new().unwrap(); loop { let result = runtime.block_on(DesktopWrapper::execute_node_graph()); rendering_app_event_scheduler.schedule(AppEvent::NodeGraphExecutionResult(result)); let _ = start_render_receiver.recv_timeout(Duration::from_millis(10)); + if exiting_clone.load(Ordering::Relaxed) { + break; + } } }); @@ -106,8 +115,9 @@ impl App { web_communication_startup_buffer: Vec::new(), persistent_data, cli, - exit_reason: ExitReason::Shutdown, startup_time: None, + exiting, + exit_reason: ExitReason::Shutdown, } } @@ -117,6 +127,10 @@ impl App { } fn exit(&mut self, reason: Option) { + if self.exiting.swap(true, Ordering::Relaxed) { + return; + } + let _ = self.start_render_sender.send(()); if let Some(reason) = reason { self.exit_reason = reason; } diff --git a/desktop/src/cef/context/multithreaded.rs b/desktop/src/cef/context/multithreaded.rs index 90c8001fd7..239e734d22 100644 --- a/desktop/src/cef/context/multithreaded.rs +++ b/desktop/src/cef/context/multithreaded.rs @@ -53,7 +53,8 @@ impl CefContext for MultiThreadedCefContextProxy { impl Drop for MultiThreadedCefContextProxy { fn drop(&mut self) { - cef::shutdown(); + // Force dropping underlying context on the UI thread + run_on_ui_thread(move || drop(CONTEXT.take())); } } diff --git a/desktop/src/cef/context/singlethreaded.rs b/desktop/src/cef/context/singlethreaded.rs index 61662c4d44..abeeed9740 100644 --- a/desktop/src/cef/context/singlethreaded.rs +++ b/desktop/src/cef/context/singlethreaded.rs @@ -42,6 +42,10 @@ impl CefContext for SingleThreadedCefContext { impl Drop for SingleThreadedCefContext { fn drop(&mut self) { + tracing::debug!("Shutting down CEF"); + + // CEF wants us to close the browser before shutting down, otherwise it may run longer that necessary. + self.browser.host().unwrap().close_browser(1); cef::shutdown(); // Sometimes some CEF processes still linger at this point and hold file handles to the cache directory.