Step to a library photograph without flashing frames in between
Benchmarks / Frame budget (on demand) (push) Canceled after 0s
Benchmarks / CPU and I/O (per commit) (push) Canceled after 5s
Traceability / Requirement traces (push) Canceled after 0s
Build and test / Desktop (Linux) (push) Successful in 1h21m7s
Build and test / Layer separation (push) Successful in 46s
🐳 Android image / Build and push (push) Successful in 4s
Build and test / android-image (push) Successful in 5s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / windows-image (push) Successful in 2s
Build and test / Android (aarch64) (push) Successful in 47m42s
Build and test / Windows (x86_64, cross) (push) Successful in 54m42s
Build and test / Publish the release (push) Successful in 52s
Benchmarks / Frame budget (on demand) (push) Canceled after 0s
Benchmarks / CPU and I/O (per commit) (push) Canceled after 5s
Traceability / Requirement traces (push) Canceled after 0s
Build and test / Desktop (Linux) (push) Successful in 1h21m7s
Build and test / Layer separation (push) Successful in 46s
🐳 Android image / Build and push (push) Successful in 4s
Build and test / android-image (push) Successful in 5s
🐳 Windows image / Build and push (push) Successful in 1s
Build and test / windows-image (push) Successful in 2s
Build and test / Android (aarch64) (push) Successful in 47m42s
Build and test / Windows (x86_64, cross) (push) Successful in 54m42s
Build and test / Publish the release (push) Successful in 52s
A step along the roll showed up to four pictures: the grid thumbnail, the previous photograph again (the thumbnail was dropped when the bytes landed, while the canvas still held the last texture through the decode and first render), the new one at its defaults when a cached original beat the sidecar, and then its edit. The thumbnail now stays up until render_now draws the new photograph's first frame, and that first frame waits up to 400 ms for the stored edit before drawing at the defaults. A later arrival still redraws.
This commit is contained in:
+37
-37
File diff suppressed because one or more lines are too long
+58
-20
@@ -794,17 +794,17 @@ pub(crate) fn refresh_export_label(window: &AppWindow) {
|
||||
/// dragged, because each move event destroys the thing that would deliver
|
||||
/// the next one.
|
||||
/// TRACES: FR-CAT-8
|
||||
/// Apply a fetched sidecar to the open session, now or as soon as it arrives.
|
||||
/// Apply a fetched sidecar to the open session, then draw its first frame.
|
||||
///
|
||||
/// The sidecar fetch is started beside the image fetch and is three orders of
|
||||
/// magnitude smaller, so it has almost always landed by the time there is a
|
||||
/// session to apply it to — and this takes it straight from the channel. The
|
||||
/// timer covers the case where it has not, which is why this is not simply a
|
||||
/// blocking receive: a slow or stalled sidecar request must not freeze the
|
||||
/// window with the photograph already decoded and on screen.
|
||||
///
|
||||
/// A late arrival redraws, so the image is correct either way; the only
|
||||
/// difference is whether it was ever briefly shown at its defaults.
|
||||
/// magnitude smaller, so it has usually landed by the time there is a session
|
||||
/// to apply it to — and this takes it straight from the channel. An original
|
||||
/// read from the cache can still beat it, and drawing then showed the
|
||||
/// photograph at its defaults and changed it a moment later. So the first
|
||||
/// frame waits up to `SIDECAR_GRACE` for the edit, with the grid's thumbnail
|
||||
/// still standing in; past that it is drawn at its defaults, and a later
|
||||
/// arrival redraws. Not a blocking receive: a stalled request must not freeze
|
||||
/// the window.
|
||||
///
|
||||
/// `still_current` is false once the view has moved to another photograph,
|
||||
/// whose session this sidecar must not be applied to.
|
||||
@@ -816,11 +816,14 @@ fn apply_when_ready(
|
||||
redraw: &Rc<dyn Fn(&AppWindow)>,
|
||||
still_current: impl Fn() -> bool + 'static,
|
||||
) {
|
||||
// Already here — the overwhelmingly common case.
|
||||
const SIDECAR_GRACE: std::time::Duration = std::time::Duration::from_millis(400);
|
||||
|
||||
// Already here — the common case.
|
||||
if let Ok(got) = rx.try_recv() {
|
||||
if let Some(sidecar) = got {
|
||||
presets::apply_stored_edit(window, &sidecar, session, rows);
|
||||
}
|
||||
redraw(window);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -828,21 +831,37 @@ fn apply_when_ready(
|
||||
let session = session.clone();
|
||||
let rows = rows.clone();
|
||||
let redraw = redraw.clone();
|
||||
let deadline = std::time::Instant::now() + SIDECAR_GRACE;
|
||||
let drawn = Cell::new(false);
|
||||
let timer = Rc::new(slint::Timer::default());
|
||||
let held = timer.clone();
|
||||
timer.start(
|
||||
slint::TimerMode::Repeated,
|
||||
std::time::Duration::from_millis(50),
|
||||
move || {
|
||||
let Some(w) = weak.upgrade() else {
|
||||
held.stop();
|
||||
return;
|
||||
};
|
||||
if !still_current() {
|
||||
held.stop();
|
||||
return;
|
||||
}
|
||||
let Ok(got) = rx.try_recv() else { return };
|
||||
let Ok(got) = rx.try_recv() else {
|
||||
// Waited long enough: draw at the defaults and keep
|
||||
// listening, so a late edit still lands.
|
||||
if !drawn.get() && std::time::Instant::now() >= deadline {
|
||||
drawn.set(true);
|
||||
redraw(&w);
|
||||
}
|
||||
return;
|
||||
};
|
||||
held.stop();
|
||||
let Some(w) = weak.upgrade() else { return };
|
||||
let Some(sidecar) = got else { return };
|
||||
if presets::apply_stored_edit(&w, &sidecar, &session, &rows) {
|
||||
let applied = match got {
|
||||
Some(sidecar) => presets::apply_stored_edit(&w, &sidecar, &session, &rows),
|
||||
None => false,
|
||||
};
|
||||
if applied || !drawn.get() {
|
||||
redraw(&w);
|
||||
}
|
||||
},
|
||||
@@ -2252,6 +2271,13 @@ fn build_render_now(
|
||||
window.set_canvas_draft(draft);
|
||||
window.global::<Levels>().set_provisional(draft);
|
||||
window.set_load_error("".into());
|
||||
// TRACES: FR-NC-6a
|
||||
// The library path keeps the grid's thumbnail up until
|
||||
// here, the first frame of the new photograph, because
|
||||
// the canvas still holds the last photograph's texture
|
||||
// until this line replaces it.
|
||||
window.set_load_pending(false);
|
||||
window.set_has_load_preview(false);
|
||||
// The readout and the "Fit" button follow the session
|
||||
// rather than the gesture, so a clamped zoom shows the
|
||||
// value that was actually applied.
|
||||
@@ -2787,13 +2813,18 @@ fn wire_remote_open(
|
||||
log::debug!("{name}: landed after the view moved on");
|
||||
return;
|
||||
}
|
||||
w.set_load_pending(false);
|
||||
// The bar goes, and the thumbnail goes back to full
|
||||
// strength; the thumbnail itself stays until the first
|
||||
// frame of this photograph replaces it in `render_now`.
|
||||
// Dropping it here showed the last photograph's texture,
|
||||
// still in the canvas, for the length of the decode.
|
||||
w.set_load_waiting("".into());
|
||||
w.set_has_load_preview(false);
|
||||
|
||||
let bytes = match got {
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
w.set_load_pending(false);
|
||||
w.set_has_load_preview(false);
|
||||
job.fail(e.message.clone());
|
||||
log::warn!("{name}: {e}");
|
||||
// Offline needs its own words. "network error:
|
||||
@@ -2862,9 +2893,10 @@ fn wire_remote_open(
|
||||
// TRACES: FR-CAT-8
|
||||
// The stored edit, if it has landed. It
|
||||
// was started before the download of a
|
||||
// file thousands of times its size, so in
|
||||
// practice it has; `apply_when_ready`
|
||||
// covers the case where it has not rather
|
||||
// file thousands of times its size, but
|
||||
// an original read from the cache can
|
||||
// beat it; `apply_when_ready` holds the
|
||||
// first frame back a moment for it rather
|
||||
// than blocking the UI thread on a socket.
|
||||
let still = current.clone();
|
||||
apply_when_ready(
|
||||
@@ -2886,12 +2918,16 @@ fn wire_remote_open(
|
||||
// moves the point rather than losing it.
|
||||
resume_inspection(&session, &viewport, &inspection);
|
||||
sync_rows(&w, &rows, &session);
|
||||
redraw(&w);
|
||||
// No redraw here: `apply_when_ready` makes
|
||||
// the first one, once the edit is applied
|
||||
// or has been waited for long enough.
|
||||
}
|
||||
None => {
|
||||
*session.borrow_mut() = None;
|
||||
rows.set_vec(Vec::<ParamRow>::new());
|
||||
w.global::<Develop>().set_enabled(false);
|
||||
w.set_load_pending(false);
|
||||
w.set_has_load_preview(false);
|
||||
if let Some(image) = l.fallback {
|
||||
w.set_canvas(image);
|
||||
}
|
||||
@@ -2903,6 +2939,8 @@ fn wire_remote_open(
|
||||
log::warn!("{name}: {e}");
|
||||
*session.borrow_mut() = None;
|
||||
w.global::<Develop>().set_enabled(false);
|
||||
w.set_load_pending(false);
|
||||
w.set_has_load_preview(false);
|
||||
w.set_load_error(e.into());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user