Merge pull request #6953 from dotCooCoo/fix/canvas-still-wait-placed-generation

Canvas: each document keeps its own placed-bitmap value in the frame key
This commit is contained in:
BFlat
2026-11-13 03:27:26 +07:00
committed by GitHub
8 changed files with 322 additions and 60 deletions
+1
View File
@@ -89,6 +89,7 @@ impl VectorcraftApp {
selection_bounds: None,
content_bounds: None,
plugin_keys: Default::default(),
placed_keys: Default::default(),
cursors: Default::default(),
view_moved: None,
hover_cursor: None,
+20 -1
View File
@@ -61,6 +61,8 @@ pub struct CanvasCache {
/// [`CacheKey::plugins`] by document uid, with the plug-in revision
/// ([`vectorcraft_render::effects::plugin_revision`]) it was last checked at.
pub plugin_keys: std::collections::HashMap<u64, (u64, u64)>,
/// [`CacheKey::placed`] by document uid ([`canvas::placed_key`]).
pub placed_keys: std::collections::HashMap<u64, PlacedKey>,
/// The tools' cursors as OS cursor bitmaps.
pub cursors: cursors::Images,
/// The view the canvas last drew and when it last changed (egui's input time): while it moves,
@@ -82,6 +84,20 @@ pub struct CanvasCache {
pub windows: crate::doc_windows::Windows,
}
/// [`CanvasCache::placed_keys`]: a document's [`CacheKey::placed`] and the placed-document bitmap
/// generations ([`vectorcraft_render::placed_document::generation`]) it was checked at.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct PlacedKey {
/// The last generation the document was checked at.
pub checked: u64,
/// The document's [`CacheKey::placed`]: the generation of its first check, or of the last check
/// that found a newly made bitmap the document may show.
pub key: u64,
/// The value before it last changed, and the last generation that value was checked at: no
/// bitmap of a placed document the document shows was made between the two.
pub before: (u64, u64),
}
/// [`CanvasCache::slices`]: the layout of the slices of (document uid, revision).
pub type SliceCache = ((u64, u64), std::sync::Arc<Vec<vectorcraft_doc::SliceArea>>);
@@ -120,7 +136,10 @@ pub struct CacheKey {
pub highlight_alternate_glyphs: bool,
/// Type › Highlight Substituted Fonts.
pub highlight_substituted_fonts: bool,
/// [`vectorcraft_render::placed_document::generation`]: placed documents' bitmaps made since.
/// A [`vectorcraft_render::placed_document::generation`] that changes when the frame may show a
/// newly made bitmap of a placed document ([`canvas::placed_key`]). Bitmaps of files that only
/// other documents hold leave it as it is, unless more bitmaps were made since the last check
/// than are recorded and the document shows a placed document.
pub placed: u64,
/// The plug-in revision ([`vectorcraft_render::effects::plugin_revision`]) of the last plug-in
/// install or removal, or of the last time deferred plug-in effect stages gave results, after
+81 -3
View File
@@ -327,9 +327,10 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
// their pixels as they rasterize, hard-edged.
let smooth_images = pixel.is_none() || app.session.prefs.anti_aliased_bitmaps;
let plugins = plugin_key(&mut app.canvas.plugin_keys, &app.session, st);
let placed = placed_key(&mut app.canvas.placed_keys, &app.session, st);
// The frame renders the view at its zoom in the interface's points, as `xf` maps it.
let drawn = v.on_screen(app.canvas_ui_zoom);
let key = cache_key(app, ui, st, &t, &drawn, (w, h, ppp), (pixel, smooth_images, plugins));
let key = cache_key(app, ui, st, &t, &drawn, (w, h, ppp), (pixel, smooth_images, plugins, placed));
// Placed documents' bitmaps are being made: draw again when they are ready.
if vectorcraft_render::placed_document::busy() {
ui.ctx().request_repaint_after(std::time::Duration::from_millis(30));
@@ -579,7 +580,7 @@ fn cache_key(
t: &Tokens,
v: &View,
(w, h, ppp): (u32, u32, f32),
(pixel, smooth_images, plugins): (Option<[i64; 4]>, bool, u64),
(pixel, smooth_images, plugins, placed): (Option<[i64; 4]>, bool, u64, u64),
) -> CacheKey {
// Paths made into guides are drawn as the ruler guides are (Preferences › Guides & Grid).
let guide_look = LineLook::guides(&app.session.prefs, t);
@@ -603,7 +604,7 @@ fn cache_key(
anti_alias: app.session.prefs.anti_aliased_artwork,
highlight_alternate_glyphs: app.session.prefs.highlight_alternate_glyphs,
highlight_substituted_fonts: app.session.prefs.highlight_substituted_fonts,
placed: vectorcraft_render::placed_document::generation(),
placed,
plugins,
pixel,
smooth_images,
@@ -637,6 +638,83 @@ fn plugin_key(
}
}
/// [`CacheKey::placed`] of document `st`, one of the documents open in `session`. `keys` holds each
/// document's value and the placed-document bitmap generations it was checked at
/// ([`vectorcraft_render::placed_document::generation`]).
fn placed_key(
keys: &mut std::collections::HashMap<u64, crate::PlacedKey>,
session: &vectorcraft_engine::Session,
st: &vectorcraft_engine::DocState,
) -> u64 {
placed_key_at(keys, session, st, vectorcraft_render::placed_document::generation(), vectorcraft_render::placed_document::made_between)
}
/// [`placed_key`] at generation `now`, with `made` giving the files whose bitmaps were made between
/// two generations ([`vectorcraft_render::placed_document::made_between`]). The value becomes `now`
/// when `st` shows a placed document of a file whose bitmap was made since the last check, or when
/// `made` no longer knows which files they were and `st` shows a placed document of a file in its
/// images, as [`vectorcraft_render::placed_area`] decides (every file shows while a pattern is
/// edited). Otherwise it stays: a bitmap made for another document, or for a file `st` keeps only
/// for Undo, leaves this document's frame as it is. A document checked for the first time takes
/// `now`.
fn placed_key_at(
keys: &mut std::collections::HashMap<u64, crate::PlacedKey>,
session: &vectorcraft_engine::Session,
st: &vectorcraft_engine::DocState,
now: u64,
made: impl FnOnce(u64, u64) -> Option<std::collections::HashSet<String>>,
) -> u64 {
let entry = match keys.get(&st.uid).copied() {
Some(p) if p.checked == now => return p.key,
None => crate::PlacedKey { checked: now, key: now, before: (now, now) },
Some(p) => {
let images = &st.doc.images;
// The files made that `st` holds: a placed document of one of them may show.
let files: std::collections::HashSet<String> = if images.is_empty() {
Default::default()
} else {
match made(p.checked, now) {
Some(files) => files.into_iter().filter(|f| images.contains_key(f)).collect(),
None => images.keys().cloned().collect(),
}
};
let stays = matches!(vectorcraft_render::placed_area(&st.doc, &files), vectorcraft_render::Changed::Nothing);
if stays { crate::PlacedKey { checked: now, ..p } } else { crate::PlacedKey { checked: now, key: now, before: (p.key, p.checked) } }
}
};
keys.retain(|uid, _| session.documents().iter().any(|d| d.uid == *uid));
keys.insert(st.uid, entry);
entry.key
}
/// The generation after which the bitmaps made decide what a frame shown at [`CacheKey::placed`]
/// value `shown` draws again at value `key`, with `entry` the document's [`crate::PlacedKey`]. When
/// the value changed once since `shown`, it is the last generation `shown` was checked at: no
/// bitmap of the document's placed documents was made between `shown` and that generation.
/// Otherwise it is `shown`.
fn placed_since(entry: Option<&crate::PlacedKey>, shown: u64, key: u64) -> u64 {
entry.filter(|p| p.key == key && p.before.0 == shown).map_or(shown, |p| p.before.1)
}
/// What placed documents' bitmaps change in `doc` between a frame shown at [`CacheKey::placed`]
/// value `shown` and the frame at value `key`: where `doc` shows the files whose bitmaps were made
/// after [`placed_since`], with `entry` the document's [`crate::PlacedKey`] and `made` giving the
/// files whose bitmaps were made between two generations
/// ([`vectorcraft_render::placed_document::made_between`]). None when those files are no longer
/// known.
fn placed_changes(
entry: Option<&crate::PlacedKey>,
shown: u64,
key: u64,
doc: &vectorcraft_doc::Document,
made: impl FnOnce(u64, u64) -> Option<std::collections::HashSet<String>>,
) -> Option<vectorcraft_render::Changed> {
if shown == key {
return Some(vectorcraft_render::Changed::Nothing);
}
Some(vectorcraft_render::placed_area(doc, &made(placed_since(entry, shown, key), key)?))
}
/// Once the first view of a document has been drawn, the status bar says how many plug-in effect
/// stages it left deferred, if any.
fn report_deferred(app: &mut VectorcraftApp, effects: &vectorcraft_plugins::deferred::Deferrals) {
+4 -3
View File
@@ -12,8 +12,8 @@ use super::outline::{c32, node_outline, stroke_path, too_many_anchors};
use super::view::fit_zoom;
use super::{
Affine, CacheKey, Point, Rect, RulerScale, Sense, Stroke, StrokeKind, Tokens, Ui, VectorcraftApp, View, Xf, artboard_edges, cache_key,
dim_on_canvas, frame_options, frame_view, grid, guides_of, json, paint_paper, pixel_region, plugin_key, pos2, rulers, shows_artboards,
textured_quad, vec2, view_rects,
dim_on_canvas, frame_options, frame_view, grid, guides_of, json, paint_paper, pixel_region, placed_key, plugin_key, pos2, rulers,
shows_artboards, textured_quad, vec2, view_rects,
};
use crate::doc_windows::Win;
use crate::thumb_worker::ThumbWorker;
@@ -305,8 +305,9 @@ pub(crate) fn pane(app: &mut VectorcraftApp, ui: &Ui, w: Win, cell: egui::Rect)
let pixel = (app.ui.view.pixel_preview && xf.zoom * ppp as f64 > 1.0).then(|| pixel_region(&xf)).flatten();
let smooth_images = pixel.is_none() || app.session.prefs.anti_aliased_bitmaps;
let plugins = plugin_key(&mut app.canvas.plugin_keys, &app.session, st);
let placed = placed_key(&mut app.canvas.placed_keys, &app.session, st);
let drawn = v.on_screen(ui_zoom);
let mut key = cache_key(app, ui, st, &t, &drawn, (pw, ph, ppp), (pixel, smooth_images, plugins));
let mut key = cache_key(app, ui, st, &t, &drawn, (pw, ph, ppp), (pixel, smooth_images, plugins, placed));
// The current document's drag and Image Trace hold are not this document's, and this view has
// its own Outline or Preview.
(key.dragging, key.trace_source, key.outline) = (false, None, v.outline);
+16
View File
@@ -323,6 +323,22 @@ fn other_panes_show_pixel_preview() {
assert!(x1 > x0 && y1 > y0);
}
/// A pane's render key takes its document's [`CacheKey::placed`] value ([`placed_key`]), which a
/// bitmap made for a test running beside this one does not move: the document holds no images.
#[test]
fn a_panes_key_takes_its_documents_placed_value() {
let mut app = app_with(2);
let ctx = egui::Context::default();
app.run("window.arrange.tile", json!({})).unwrap();
let uid = app.session.documents()[0].uid;
let generation = vectorcraft_render::placed_document::generation();
let marked = generation.wrapping_add(1000);
app.canvas.placed_keys.insert(uid, crate::PlacedKey { checked: generation, key: marked, before: (marked, generation) });
frame(&mut app, &ctx, vec![]);
let key = app.canvas.panes.shown.get(&(uid, 1)).map(|(k, _)| k.placed);
assert_eq!(key, Some(marked));
}
#[test]
fn a_resized_window_fits_the_tiled_documents_again_unless_one_was_zoomed() {
let mut app = app_with(3);
+2 -5
View File
@@ -57,11 +57,8 @@ pub(super) fn changed_pixels(
return None;
}
// The placed documents' files whose bitmaps were made since the frame shown.
let placed = if shown_key.placed == key.placed {
vectorcraft_render::Changed::Nothing
} else {
vectorcraft_render::placed_area(doc, &vectorcraft_render::placed_document::made_between(shown_key.placed, key.placed)?)
};
let entry = app.session.documents().iter().find(|d| d.uid as usize == key.doc).and_then(|d| app.canvas.placed_keys.get(&d.uid));
let placed = super::placed_changes(entry, shown_key.placed, key.placed, doc, vectorcraft_render::placed_document::made_between)?;
match app.canvas.renderer.changed(shown, doc, opts).and(placed) {
vectorcraft_render::Changed::Nothing => Some(None),
vectorcraft_render::Changed::Area(area) => match vectorcraft_render::view_pixels(area, view, w, h) {
+197 -47
View File
@@ -2856,13 +2856,10 @@ fn cached_frames_keep_their_scale_during_resize_rotation_and_dpi_changes() {
"{old_rot}° -> {new_rot}°, DPI {old_ppp} -> {new_ppp}, pixel={pixel}: {vertex:?}"
);
}
// Deliver the actual replacement and verify that it replaces the cached key. The frame
// reports a slow render: when a parallel test makes a placed document's bitmap, the key's
// process-wide `placed` generation moves, and the next frame goes to the controlled worker
// instead of rendering over the delivered frame on this thread.
// Deliver the actual replacement and verify that it replaces the cached key.
let img = app.canvas.renderer.render(&job.doc, job.w, job.h, job.view, &job.opts);
let expected = job.key.clone();
completed.send(crate::render_worker::Done { key: job.key, doc: job.doc, opts: job.opts, img, ms: 50.0, moved: 0, reused: false }).unwrap();
completed.send(crate::render_worker::Done { key: job.key, doc: job.doc, opts: job.opts, img, ms: 1.0, moved: 0, reused: false }).unwrap();
frame(&mut app, size, new_ppp);
assert_eq!(app.canvas.key.as_ref(), Some(&expected), "the worker's frame arrived");
}
@@ -2942,21 +2939,6 @@ fn a_turned_view_draws_the_last_frame_turned_until_its_own_arrives() {
/// fill is dropped.
#[test]
fn a_pan_fills_the_area_the_last_frame_leaves_until_the_worker_frame_arrives() {
// The canvas key holds the global placed-document bitmap clock, which tests on other threads
// move: a frame whose key changed by more than the view drops the fill. An attempt that fails
// while the clock moved runs again; one that fails while it stood still is a real failure.
for _ in 0..20 {
let clock = vectorcraft_render::placed_document::generation();
match std::panic::catch_unwind(pan_fills_the_area_the_last_frame_leaves) {
Ok(()) => return,
Err(_) if vectorcraft_render::placed_document::generation() != clock => {}
Err(e) => std::panic::resume_unwind(e),
}
}
pan_fills_the_area_the_last_frame_leaves();
}
fn pan_fills_the_area_the_last_frame_leaves() {
let ctx = egui::Context::default();
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
@@ -3185,6 +3167,200 @@ fn the_canvas_waits_only_while_the_view_of_a_slow_frame_changes() {
assert_eq!(still_wait(&mut still, Some(&shown), &shown, true, 15.0), None, "the frame shown");
}
/// A bitmap made in the background for a file that only another document holds leaves this
/// document's canvas key as it is. Without a render worker (the web build's setting), a pan of a
/// heavy canvas keeps drawing the frame shown until the view is still. A light canvas renders the
/// pan at once, and its key differs from the frame shown only in the view: the pan fill and the
/// worker's pixel copy ask for that.
#[test]
fn a_bitmap_made_for_another_documents_file_leaves_a_pan_waiting() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
app.session.execute("shape.rectangle", &json!({"x": 100, "y": 50, "width": 200, "height": 200})).unwrap();
app.canvas.worker_started = true; // no worker, as in the web build
app.canvas.waits_for_still = true;
// Another window's document places a file, which its canvas asks a bitmap of.
let mut other = VectorcraftApp::new(Session::new(), Default::default());
other.canvas.worker_started = true; // render on this thread
let mut src = Session::new();
src.execute("file.new", &json!({"width": 70, "height": 40})).unwrap();
src.execute("shape.ellipse", &json!({"x": 0, "y": 0, "width": 70, "height": 40})).unwrap();
let dir = std::env::temp_dir().join(format!("vectorcraft-placed-pan-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("badge.vectorcraft");
std::fs::write(&path, vectorcraft_format::save_file(&src.active().unwrap().doc)).unwrap();
other.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
other.session.execute("file.place", &json!({"path": path.to_string_lossy(), "at": [100, 100]})).unwrap();
let ctx = egui::Context::default();
let frame = |app: &mut VectorcraftApp| {
let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), ..Default::default() };
let mut out = ctx.run_ui(raw, |ui| show(app, ui));
out.textures_delta.clear();
};
frame(&mut app); // the first frame fits the view to the artboard
frame(&mut app);
app.canvas.last_ms = 1.0;
app.view_mut().unwrap().center.x += 10.0;
frame(&mut app);
let shown = app.canvas.key.clone().unwrap();
// The other canvas asks for the bitmap, and it is made: the generation changes before the
// bitmap stops being pending.
let generation = vectorcraft_render::placed_document::generation();
shapes(&mut other, &egui::Context::default());
while vectorcraft_render::placed_document::busy() {
std::thread::yield_now();
}
assert_ne!(vectorcraft_render::placed_document::generation(), generation, "a bitmap was made");
app.canvas.last_ms = 50.0;
app.view_mut().unwrap().center.x += 10.0;
frame(&mut app);
assert_eq!(app.canvas.key.as_ref(), Some(&shown), "a heavy canvas is not rendered while the view moves");
app.canvas.last_ms = 1.0;
app.view_mut().unwrap().center.x += 10.0;
frame(&mut app);
let next = app.canvas.key.clone().unwrap();
assert_eq!(next.cx, app.view().unwrap().center.x, "a light canvas renders at once");
assert!(super::cursor::only_view_differs(&shown, &next), "only the view differs: {shown:?} {next:?}");
std::fs::remove_dir_all(&dir).ok();
}
/// A document's [`CacheKey::placed`] moves to the current placed-document bitmap generation only
/// when a bitmap of a file it shows in a placed document was made, or when the files made are no
/// longer known and it shows a placed document. A bitmap of another document's file, or of a file
/// it keeps only for Undo, leaves it, so a pan of a heavy
/// document keeps waiting for the view to stop. Each change keeps the value before it and the
/// last generation that value was checked at. A closed document's value is dropped.
#[test]
fn a_documents_placed_key_moves_only_for_bitmaps_of_its_own_files() {
let mut session = Session::new();
session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
session.execute("shape.rectangle", &json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
let plain = session.active().unwrap().uid;
let mut src = Session::new();
src.execute("file.new", &json!({"width": 60, "height": 30})).unwrap();
src.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 60, "height": 30})).unwrap();
let dir = std::env::temp_dir().join(format!("vectorcraft-placed-key-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("card.vectorcraft");
std::fs::write(&path, vectorcraft_format::save_file(&src.active().unwrap().doc)).unwrap();
session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
let r = session.execute("file.place", &json!({"path": path.to_string_lossy(), "at": [60, 50]})).unwrap();
let st = session.active().unwrap();
let placing = st.uid;
let file = match &st.doc.node(vectorcraft_doc::NodeId(r["ids"][0].as_u64().unwrap())).unwrap().kind {
vectorcraft_doc::NodeKind::PlacedDocument(p) => p.key.clone(),
other => panic!("a placed document: {other:?}"),
};
type Made<'a> = &'a dyn Fn(u64, u64) -> Option<std::collections::HashSet<String>>;
fn key(keys: &mut std::collections::HashMap<u64, crate::PlacedKey>, session: &Session, uid: u64, now: u64, made: Made<'_>) -> u64 {
let st = session.documents().iter().find(|d| d.uid == uid).unwrap();
placed_key_at(keys, session, st, now, made)
}
let files = |names: &[&str]| Some(names.iter().map(|n| n.to_string()).collect::<std::collections::HashSet<_>>());
let unasked = |_: u64, _: u64| -> Option<std::collections::HashSet<String>> { panic!("no bitmaps to look up") };
let mut keys = std::collections::HashMap::new();
assert_eq!(key(&mut keys, &session, placing, 5, &unasked), 5, "checked for the first time");
assert_eq!(key(&mut keys, &session, placing, 5, &unasked), 5, "no bitmap made since");
let other = |from: u64, to: u64| {
assert_eq!((from, to), (5, 6), "the bitmaps made since the last check");
files(&["another document's file"])
};
assert_eq!(key(&mut keys, &session, placing, 6, &other), 5, "another document's file");
assert_eq!(key(&mut keys, &session, placing, 7, &|_, _| files(&["another document's file", file.as_str()])), 7, "its own file");
assert_eq!(keys[&placing], crate::PlacedKey { checked: 7, key: 7, before: (5, 6) });
assert_eq!(key(&mut keys, &session, placing, 8, &|_, _| None), 8, "files no longer known");
assert_eq!(keys[&placing].before, (7, 7));
assert_eq!(key(&mut keys, &session, plain, 8, &unasked), 8, "the other document, checked for the first time");
assert_eq!(key(&mut keys, &session, plain, 9, &|_, _| None), 8, "no images: files no longer known");
assert_eq!(key(&mut keys, &session, plain, 10, &|_, _| files(&[file.as_str()])), 8, "no images: a file another document holds");
// The same document holding an image and showing no placed document.
let mut raster = session.documents().iter().find(|d| d.uid == plain).unwrap().clone();
let blob = vectorcraft_doc::ImageBlob { mime: "image/png".into(), bytes: Default::default(), proxy: None };
std::sync::Arc::make_mut(&mut raster.doc).images.insert("an image".into(), blob);
assert_eq!(placed_key_at(&mut keys, &session, &raster, 11, |_, _| None), 8, "an image and no placed document: files no longer known");
assert_eq!(keys.len(), 2);
let index = session.documents().iter().position(|d| d.uid == plain).unwrap();
session.execute("file.close", &json!({"index": index})).unwrap();
assert_eq!(key(&mut keys, &session, placing, 12, &|_, _| files(&[])), 8, "no bitmaps of its files");
assert_eq!(keys.keys().copied().collect::<Vec<_>>(), [placing], "the closed document's value is dropped");
// The placed document deleted: its file stays in the images for Undo, and no longer shows.
assert_eq!(session.active().unwrap().uid, placing);
session.execute("edit.clear", &json!({"ids": [r["ids"][0]]})).unwrap();
assert!(session.active().unwrap().doc.images.contains_key(&file), "kept for Undo");
assert_eq!(key(&mut keys, &session, placing, 13, &|_, _| files(&[file.as_str()])), 8, "a file kept only for Undo");
std::fs::remove_dir_all(&dir).ok();
}
/// The canvas key takes the document's value from [`placed_key`], which a bitmap made for a test
/// running beside this one does not move: this document holds no images.
#[test]
fn the_canvas_key_takes_the_documents_placed_value() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
app.session.execute("shape.rectangle", &json!({"x": 100, "y": 50, "width": 200, "height": 200})).unwrap();
app.canvas.worker_started = true; // render on this thread
let uid = app.session.active().unwrap().uid;
let generation = vectorcraft_render::placed_document::generation();
let marked = generation.wrapping_add(1000);
app.canvas.placed_keys.insert(uid, crate::PlacedKey { checked: generation, key: marked, before: (marked, generation) });
shapes(&mut app, &egui::Context::default());
assert_eq!(app.canvas.key.as_ref().map(|k| k.placed), Some(marked));
}
/// A frame shown at one [`CacheKey::placed`] value and drawn again at the next looks at the
/// bitmaps made since the last check before the change: those made earlier were of other
/// documents' files, and past 256 bitmaps they are no longer known ([`placed_since`]).
#[test]
fn a_frame_drawn_again_after_one_change_of_its_placed_value_looks_at_the_bitmaps_since_the_last_check() {
let entry = crate::PlacedKey { checked: 400, key: 400, before: (5, 399) };
assert_eq!(placed_since(Some(&entry), 5, 400), 399, "the value changed once since the frame shown");
assert_eq!(placed_since(Some(&entry), 3, 400), 3, "it changed more than once");
assert_eq!(placed_since(Some(&entry), 5, 401), 5, "a value the document no longer has");
assert_eq!(placed_since(None, 5, 400), 5, "a document not checked");
}
/// A frame drawn again at a new [`CacheKey::placed`] value draws again where the document shows
/// the files whose bitmaps were made after [`placed_since`]. After one change of the value, the
/// bitmaps made before the last check of the old value are not looked up: past 256 bitmaps they
/// are no longer known, and the whole frame would render. At the same value nothing is looked up.
#[test]
fn a_frame_drawn_again_at_a_new_placed_value_draws_where_the_bitmaps_since_the_last_check_show() {
let mut session = Session::new();
let mut src = Session::new();
src.execute("file.new", &json!({"width": 60, "height": 30})).unwrap();
src.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 60, "height": 30})).unwrap();
let dir = std::env::temp_dir().join(format!("vectorcraft-placed-changes-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let path = dir.join("card.vectorcraft");
std::fs::write(&path, vectorcraft_format::save_file(&src.active().unwrap().doc)).unwrap();
session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
let r = session.execute("file.place", &json!({"path": path.to_string_lossy(), "at": [60, 50]})).unwrap();
session.execute("shape.rectangle", &json!({"x": 250, "y": 200, "width": 40, "height": 30})).unwrap();
let doc = &session.active().unwrap().doc;
let placed = doc.node(vectorcraft_doc::NodeId(r["ids"][0].as_u64().unwrap())).unwrap();
let file = match &placed.kind {
vectorcraft_doc::NodeKind::PlacedDocument(p) => p.key.clone(),
other => panic!("a placed document: {other:?}"),
};
let bounds = placed.geometric_bounds().unwrap();
let entry = crate::PlacedKey { checked: 400, key: 400, before: (5, 399) };
// Only the bitmaps made after generation 399 are known: one of the placed file.
let made = |from: u64, to: u64| (from >= 399 && to == 400).then(|| std::collections::HashSet::from([file.clone()]));
let unasked = |_: u64, _: u64| -> Option<std::collections::HashSet<String>> { panic!("no bitmaps to look up") };
assert_eq!(placed_changes(Some(&entry), 400, 400, doc, unasked), Some(vectorcraft_render::Changed::Nothing), "the same value");
match placed_changes(Some(&entry), 5, 400, doc, made) {
Some(vectorcraft_render::Changed::Area(a)) => {
assert!(a.x0 <= bounds.x0 && a.y0 <= bounds.y0 && a.x1 >= bounds.x1 && a.y1 >= bounds.y1, "{a:?} holds {bounds:?}");
assert!(a.x1 < 250.0, "{a:?}: not the rectangle");
}
other => panic!("the placed document's part: {other:?}"),
}
assert_eq!(placed_changes(Some(&entry), 3, 400, doc, made), None, "the value changed more than once: no longer known");
let none = |_: u64, _: u64| Some(std::collections::HashSet::new());
assert_eq!(placed_changes(Some(&entry), 5, 400, doc, none), Some(vectorcraft_render::Changed::Nothing), "no bitmaps since");
std::fs::remove_dir_all(&dir).ok();
}
/// File Handling › Display Bitmaps as Anti-aliased Images in Pixel Preview (#394): off, Pixel
/// Preview draws images with each pixel taking its nearest image pixel; on, smoothly, as the
/// canvas always does out of Pixel Preview.
@@ -11592,31 +11768,11 @@ fn a_pan_over_art_the_frame_size_decides_renders_whole() {
}
}
/// Runs `test`, whose canvas key holds the global placed-document bitmap clock, which tests on
/// other threads move: a frame whose key changed by more than the view renders whole. An attempt
/// that fails while the clock moved runs again; one that fails while it stood still is a real
/// failure.
fn while_the_placed_clock_stands_still(test: fn()) {
for _ in 0..20 {
let clock = vectorcraft_render::placed_document::generation();
match std::panic::catch_unwind(test) {
Ok(()) => return,
Err(_) if vectorcraft_render::placed_document::generation() != clock => {}
Err(e) => std::panic::resume_unwind(e),
}
}
test();
}
/// The canvas asks the worker for a frame that may copy pixels when the view moves, keeps the whole
/// frame's time, and renders the frame whole once the view has been still for STILL_SECS, not
/// before and only once.
#[test]
fn a_frame_with_copied_pixels_renders_whole_once_the_view_stops() {
while_the_placed_clock_stands_still(frame_with_copied_pixels_renders_whole_once_the_view_stops);
}
fn frame_with_copied_pixels_renders_whole_once_the_view_stops() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
app.canvas.worker_started = true;
@@ -11663,10 +11819,6 @@ fn frame_with_copied_pixels_renders_whole_once_the_view_stops() {
/// first step of the next pan copies from it. The jobs are the ones the canvas asks for.
#[test]
fn a_pan_after_the_settling_frame_copies() {
while_the_placed_clock_stands_still(pan_after_the_settling_frame_copies);
}
fn pan_after_the_settling_frame_copies() {
let mut app = VectorcraftApp::new(Session::new(), Default::default());
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
app.canvas.worker_started = true;
@@ -11778,9 +11930,7 @@ fn a_documents_first_frame_renders_on_the_worker() {
let job = jobs.try_recv().expect("the first frame is queued on the worker");
assert!(app.canvas.texture.is_none(), "not rendered on the UI thread");
frame(&mut app);
// A placed document of a test running beside this one moves the process-wide generation in
// the key: that makes a new frame, not this one again.
assert!(jobs.try_iter().all(|j| j.key.placed != job.key.placed), "not queued again while it renders");
assert!(jobs.try_recv().is_err(), "not queued again while it renders");
assert!(app.canvas.texture.is_none());
done_tx.send(finished(&job)).unwrap();
+1 -1
View File
@@ -116,7 +116,7 @@ mod clock;
mod services;
pub use app::MAX_RECENT_FONTS;
pub use caches::{CacheKey, CanvasCache, LivePaintGapsCache, Perf, PrintTilingCache, SliceCache};
pub use caches::{CacheKey, CanvasCache, LivePaintGapsCache, Perf, PlacedKey, PrintTilingCache, SliceCache};
pub use clock::{now_ms, now_secs};
pub use craft_ui::docking::Monitor as DockMonitor;
#[cfg(test)]