forked from 1b-agent/opencadstudio-upstream
feat: draw a notice for one frame before rebuilding a layout
Switching to a Layout stops the main thread long enough that the compositor decides the application has stopped responding and offers to kill it. The switch itself is not the problem — `layout-switch` reports about 10 ms — it is the frame after it, which rebuilds the whole wire set. That frame now has a cheap one in front of it. `layout_settling` is set when the switch takes, and while it holds, the viewport is replaced by a centered "Preparing layout…" on the canvas background. No shader widget means no scene primitive, so `prepare` has nothing to do and the frame costs nothing. A `window::frames` subscription clears the flag, which is the guarantee this needs: that callback runs once a frame has actually been presented, so the notice is on screen and the compositor has been answered before the expensive frame starts. The deferral is of the *frame*, not the switch. Two callers depend on the switch having completed when it returns — one calls `fit_all` immediately after, another compares `current_layout` — so deferring the switch itself would have broken both. This restarts the compositor's timer immediately before the slow frame and replaces a stale window with something that says what is happening. It does not make the slow frame fast: if the rebuild still exceeds the compositor's timeout the dialog can return, and the durable fix is the 2 s `memo_miss` behind it, which is a separate change. Only `en-US` is validated against at build time; Greek is filled in too since it is in use here, and the rest fall back until translated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d9a1d55f2e
commit
09838be9ec
6 files changed
+62
-1
No files matched your search
@@ -167,6 +167,7 @@ clipboard =
|
|||||||
.paste-to-original-coordinates = Επικόλληση στις αρχικές συντεταγμένες
|
.paste-to-original-coordinates = Επικόλληση στις αρχικές συντεταγμένες
|
||||||
|
|
||||||
viewport =
|
viewport =
|
||||||
|
.preparing-layout = Προετοιμασία διάταξης…
|
||||||
.split-vertical = Κατακόρυφος διαχωρισμός θύρας προβολής
|
.split-vertical = Κατακόρυφος διαχωρισμός θύρας προβολής
|
||||||
.split-horizontal = Οριζόντιος διαχωρισμός θύρας προβολής
|
.split-horizontal = Οριζόντιος διαχωρισμός θύρας προβολής
|
||||||
.move = Μετακίνηση θύρας προβολής
|
.move = Μετακίνηση θύρας προβολής
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ clipboard =
|
|||||||
.paste-to-original-coordinates = Paste to Original Coordinates
|
.paste-to-original-coordinates = Paste to Original Coordinates
|
||||||
|
|
||||||
viewport =
|
viewport =
|
||||||
|
.preparing-layout = Preparing layout…
|
||||||
.split-vertical = Split Viewport Vertically
|
.split-vertical = Split Viewport Vertically
|
||||||
.split-horizontal = Split Viewport Horizontally
|
.split-horizontal = Split Viewport Horizontally
|
||||||
.move = Move Viewport
|
.move = Move Viewport
|
||||||
|
|||||||
@@ -1055,6 +1055,15 @@ pub(super) struct OpenCADStudio {
|
|||||||
/// `Some` while a CAD file is loading — drives the modal overlay.
|
/// `Some` while a CAD file is loading — drives the modal overlay.
|
||||||
/// Cleared when the load finishes, errors, or the user cancels.
|
/// Cleared when the load finishes, errors, or the user cancels.
|
||||||
pub(super) opening: Option<OpenProgress>,
|
pub(super) opening: Option<OpenProgress>,
|
||||||
|
/// Set the moment a layout switch changes `current_layout`, cleared on the
|
||||||
|
/// next presented frame.
|
||||||
|
///
|
||||||
|
/// The switch itself costs about 10 ms; the frame after it rebuilds the
|
||||||
|
/// whole wire set and can block the main thread for seconds. While this is
|
||||||
|
/// set the viewport is replaced by a notice, so that frame is cheap — which
|
||||||
|
/// gives the compositor an answer, and the user something other than a
|
||||||
|
/// stale window, immediately before the expensive one.
|
||||||
|
pub(super) layout_settling: bool,
|
||||||
open_job_serial: u64,
|
open_job_serial: u64,
|
||||||
/// Last repair or failed-open report shown in the recovery modal.
|
/// Last repair or failed-open report shown in the recovery modal.
|
||||||
recovery_report: Option<crate::io::recovery::RecoveryReport>,
|
recovery_report: Option<crate::io::recovery::RecoveryReport>,
|
||||||
@@ -2283,6 +2292,9 @@ pub enum Message {
|
|||||||
/// Timer pulse while the cursor is dwelling on a grip; drives the
|
/// Timer pulse while the cursor is dwelling on a grip; drives the
|
||||||
/// dwell-to-popup transition without requiring further mouse motion.
|
/// dwell-to-popup transition without requiring further mouse motion.
|
||||||
GripDwellTick,
|
GripDwellTick,
|
||||||
|
/// One frame has been presented since a layout switch; the next may do the
|
||||||
|
/// expensive rebuild.
|
||||||
|
LayoutSettled,
|
||||||
/// Timer pulse while a rollover hit-test is queued; fires when the
|
/// Timer pulse while a rollover hit-test is queued; fires when the
|
||||||
/// cursor has been still long enough to safely run the pick.
|
/// cursor has been still long enough to safely run the pick.
|
||||||
HoverDwellTick,
|
HoverDwellTick,
|
||||||
@@ -3484,6 +3496,7 @@ impl OpenCADStudio {
|
|||||||
print_all_plot_window_prev: None,
|
print_all_plot_window_prev: None,
|
||||||
print_all_plot_setup_prev: None,
|
print_all_plot_setup_prev: None,
|
||||||
opening: None,
|
opening: None,
|
||||||
|
layout_settling: false,
|
||||||
open_job_serial: 0,
|
open_job_serial: 0,
|
||||||
recovery_report: None,
|
recovery_report: None,
|
||||||
pending_opens: std::collections::VecDeque::new(),
|
pending_opens: std::collections::VecDeque::new(),
|
||||||
|
|||||||
@@ -3045,6 +3045,12 @@ impl OpenCADStudio {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::LayoutSettled => {
|
||||||
|
// The notice frame has been presented, so the compositor has
|
||||||
|
// had its answer. Let the next frame build the real thing.
|
||||||
|
self.layout_settling = false;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
Message::GripDwellTick => {
|
Message::GripDwellTick => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
// Reuse the move-time logic — `p` is the last cursor
|
// Reuse the move-time logic — `p` is the last cursor
|
||||||
|
|||||||
@@ -5534,6 +5534,10 @@ impl OpenCADStudio {
|
|||||||
.layers
|
.layers
|
||||||
.sync_with_viewports(&doc_layers, vp_info);
|
.sync_with_viewports(&doc_layers, vp_info);
|
||||||
let layers_ms = perf_phase.elapsed().as_secs_f64() * 1000.0;
|
let layers_ms = perf_phase.elapsed().as_secs_f64() * 1000.0;
|
||||||
|
// The switch is cheap; the frame that follows it is not. Draw a notice
|
||||||
|
// for one frame first, so the compositor gets an answer before the main
|
||||||
|
// thread disappears into rebuilding the wire set.
|
||||||
|
self.layout_settling = true;
|
||||||
if perf {
|
if perf {
|
||||||
crate::perf_record!(
|
crate::perf_record!(
|
||||||
"[perf] layout-switch from={} to={} total={:.2}ms sync={:.2}ms switch={:.2}ms restore={:.2}ms layers={:.2}ms epoch={}",
|
"[perf] layout-switch from={} to={} total={:.2}ms sync={:.2}ms switch={:.2}ms restore={:.2}ms layers={:.2}ms epoch={}",
|
||||||
|
|||||||
+37
-1
@@ -272,7 +272,33 @@ impl OpenCADStudio {
|
|||||||
// viewport draws the layout's own geometry (white sheet + entities +
|
// viewport draws the layout's own geometry (white sheet + entities +
|
||||||
// borders) and the floating content viewports blit on top.
|
// borders) and the floating content viewports blit on top.
|
||||||
mark("viewport_3d");
|
mark("viewport_3d");
|
||||||
let viewport_3d: Element<'_, Message> = if tab.is_start {
|
let viewport_3d: Element<'_, Message> = if self.layout_settling && !tab.is_start {
|
||||||
|
// One cheap frame between the switch and the rebuild. No shader
|
||||||
|
// widget means no scene primitive, so `prepare` has nothing to do
|
||||||
|
// and the event loop turns — which is what stops the compositor
|
||||||
|
// deciding the application has stopped responding.
|
||||||
|
let bg = crosshair_background(tab, is_paper);
|
||||||
|
let lum = 0.299 * bg[0] + 0.587 * bg[1] + 0.114 * bg[2];
|
||||||
|
let ink = if lum > 0.5 {
|
||||||
|
Color::from_rgba(0.0, 0.0, 0.0, 0.55)
|
||||||
|
} else {
|
||||||
|
Color::from_rgba(1.0, 1.0, 1.0, 0.55)
|
||||||
|
};
|
||||||
|
container(
|
||||||
|
text(crate::tr!("viewport", "preparing-layout"))
|
||||||
|
.size(15)
|
||||||
|
.color(ink),
|
||||||
|
)
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
|
.center_x(Fill)
|
||||||
|
.center_y(Fill)
|
||||||
|
.style(move |_| container::Style {
|
||||||
|
background: Some(Color::from_rgba(bg[0], bg[1], bg[2], bg[3]).into()),
|
||||||
|
..container::Style::default()
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
} else if tab.is_start {
|
||||||
start_page_view(
|
start_page_view(
|
||||||
&self.patrons,
|
&self.patrons,
|
||||||
&self.videos,
|
&self.videos,
|
||||||
@@ -2253,6 +2279,15 @@ impl OpenCADStudio {
|
|||||||
// the mouse perfectly still — `ViewportMove` alone would never
|
// the mouse perfectly still — `ViewportMove` alone would never
|
||||||
// fire again. Auto-stops once the hover clears or the popup is
|
// fire again. Auto-stops once the hover clears or the popup is
|
||||||
// already open.
|
// already open.
|
||||||
|
// One frame after a layout switch, then stop. `window::frames` fires
|
||||||
|
// once a frame has actually been presented, which is the guarantee this
|
||||||
|
// needs: the notice is on screen and the compositor has been answered
|
||||||
|
// before the expensive frame starts.
|
||||||
|
let layout_settle = if self.layout_settling {
|
||||||
|
window::frames().map(|_| Message::LayoutSettled)
|
||||||
|
} else {
|
||||||
|
Subscription::none()
|
||||||
|
};
|
||||||
let grip_dwell = if self.grip_hover.is_some() && self.grip_popup.is_none() {
|
let grip_dwell = if self.grip_hover.is_some() && self.grip_popup.is_none() {
|
||||||
window::frames().map(|_| Message::GripDwellTick)
|
window::frames().map(|_| Message::GripDwellTick)
|
||||||
} else {
|
} else {
|
||||||
@@ -2478,6 +2513,7 @@ impl OpenCADStudio {
|
|||||||
control,
|
control,
|
||||||
frames,
|
frames,
|
||||||
history_tick,
|
history_tick,
|
||||||
|
layout_settle,
|
||||||
grip_dwell,
|
grip_dwell,
|
||||||
hover_dwell,
|
hover_dwell,
|
||||||
nav_settle,
|
nav_settle,
|
||||||
|
|||||||
Reference in new issue
Block a user