Links: wake for the first look at a link placed while idle
The link watch wakes the app every two seconds only while the last look
found links (068a934, #967). A file placed less than two seconds after a
look that found none asked for no more frames, so while the window sat
idle nothing stamped it; a change made meanwhile was taken for the file
as first seen and never acted on. The watch now remembers the active
document's uid and revision at each look and, when they differ on a
later frame, asks for one wake when the next look is due. There is
still no per-frame walk of the links, and no two-second wakes for a
document without links.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c6486ea50d
commit
a3316d2383
|
|
@ -788,6 +788,8 @@ impl VectorcraftApp {
|
|||
return;
|
||||
}
|
||||
let last: f64 = ctx.data(|d| d.get_temp(last_key)).unwrap_or(f64::NEG_INFINITY);
|
||||
// The active document as the last look saw it: its uid and revision.
|
||||
let (seen_key, doc) = (egui::Id::new("linkWatch.seen"), self.session.active().map(|d| (d.uid, d.revision)));
|
||||
if now - last >= 2.0 || (focused && !was_focused) {
|
||||
// Walking the document's links costs a pass over it: only when a look is due.
|
||||
// `start_link_scan` starts none for a document without links, which then doesn't
|
||||
|
|
@ -795,12 +797,21 @@ impl VectorcraftApp {
|
|||
ctx.data_mut(|d| d.insert_temp(last_key, now));
|
||||
self.session.start_link_scan();
|
||||
let links = self.session.link_scan.is_some();
|
||||
ctx.data_mut(|d| d.insert_temp(egui::Id::new("linkWatch.links"), links));
|
||||
ctx.data_mut(|d| {
|
||||
d.insert_temp(egui::Id::new("linkWatch.links"), links);
|
||||
d.insert_temp(seen_key, doc);
|
||||
});
|
||||
}
|
||||
if self.session.link_scan.is_some() {
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(100));
|
||||
} else if ctx.data(|d| d.get_temp::<bool>(egui::Id::new("linkWatch.links"))).unwrap_or(false) {
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(2000));
|
||||
} else if ctx.data(|d| d.get_temp::<Option<(u64, u64)>>(seen_key)).is_some_and(|seen| seen != doc) {
|
||||
// The document changed (a file placed into one without links) or another came to the
|
||||
// front since that look: wake for the next one even if the app sits idle until then,
|
||||
// or a file placed and changed meanwhile is first seen after the change, which is then
|
||||
// never acted on.
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs_f64((last + 2.0 - now).clamp(0.0, 2.0)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -546,3 +546,68 @@ fn crop_image_shows_a_box_that_apply_crops_to() {
|
|||
app.run("select.none", json!({})).unwrap();
|
||||
assert!(!crate::menus::enabled(&app, "ui.cropImage") && app.run("ui.cropImage", json!({})).is_err());
|
||||
}
|
||||
|
||||
/// One headless frame of the whole window at `time` → how long the app asked to wait before the
|
||||
/// next one.
|
||||
fn frame_at(app: &mut VectorcraftApp, ctx: &egui::Context, time: f64) -> std::time::Duration {
|
||||
let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), time: Some(time), ..Default::default() };
|
||||
let mut out = ctx.run_ui(raw, |ui| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
out.viewport_output.get(&egui::ViewportId::ROOT).map_or(std::time::Duration::MAX, |v| v.repaint_delay)
|
||||
}
|
||||
|
||||
/// The window sits idle for `secs` from `*time` (a frame now, then only the frames the app asks
|
||||
/// for) or until `done` → whether it was.
|
||||
fn idle(app: &mut VectorcraftApp, ctx: &egui::Context, time: &mut f64, secs: f64, done: &dyn Fn(&VectorcraftApp) -> bool) -> bool {
|
||||
let end = *time + secs;
|
||||
loop {
|
||||
let wait = frame_at(app, ctx, *time);
|
||||
if done(app) {
|
||||
return true;
|
||||
}
|
||||
let next = *time + wait.as_secs_f64().max(1.0 / 60.0);
|
||||
if next > end {
|
||||
*time = end;
|
||||
return false;
|
||||
}
|
||||
if app.session.link_scan.is_some() {
|
||||
// The look runs on a worker thread, in real time.
|
||||
std::thread::sleep(std::time::Duration::from_millis(5));
|
||||
}
|
||||
*time = next;
|
||||
}
|
||||
}
|
||||
|
||||
/// A file placed into a document whose last look found no links, then changed by another app
|
||||
/// while the window sits idle in the background, follows Update Links: Automatically: the app
|
||||
/// wakes itself to see the new link before the change, so the change isn't taken for the file as
|
||||
/// first seen.
|
||||
#[test]
|
||||
fn a_file_placed_while_idle_follows_its_changes() {
|
||||
let mut app = app();
|
||||
app.run("prefs.set", json!({"key": "updateLinks", "value": "automatically"})).unwrap();
|
||||
let ctx = egui::Context::default();
|
||||
let mut time = 0.0;
|
||||
idle(&mut app, &ctx, &mut time, 3.0, &|_| false);
|
||||
// A look finds the new document without links: no wakes of its own for it.
|
||||
assert_eq!(frame_at(&mut app, &ctx, time), std::time::Duration::MAX, "{:?}", ctx.repaint_causes());
|
||||
// Half a second later, before the next look is due, a file is placed. The place itself is a
|
||||
// frame (as any command); then nothing happens for 3 s.
|
||||
time += 0.5;
|
||||
let path = temp_file("idle-watch.png", &png(40, 30, 72.0));
|
||||
app.run("file.place", json!({"path": path})).unwrap();
|
||||
let id = app.session.active().unwrap().selection.objects[0];
|
||||
idle(&mut app, &ctx, &mut time, 3.0, &|_| false);
|
||||
std::thread::sleep(std::time::Duration::from_millis(30));
|
||||
let changed = png(60, 30, 72.0);
|
||||
std::fs::write(&path, &changed).unwrap();
|
||||
let hash = vectorcraft_doc::links::hash_bytes(&changed);
|
||||
let updated = |app: &VectorcraftApp| {
|
||||
let st = app.session.active().unwrap();
|
||||
matches!(&st.doc.node(id).unwrap().kind, NodeKind::Image(im) if im.link.as_ref().and_then(|l| l.hash.as_deref()) == Some(hash.as_str()))
|
||||
};
|
||||
assert!(idle(&mut app, &ctx, &mut time, 10.0, &updated), "the changed file was never read again");
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue