mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-08 20:01:37 +00:00
Merge pull request #5418 from storytold/fix/sec-ui-carry-automation-scope
UI: opening, file picks and the render worker keep the automation roots they started under
This commit is contained in:
@@ -654,7 +654,15 @@ fn draw_frame(app: &mut VectorcraftApp, ui: &Ui, frame: Frame) {
|
||||
app.canvas.shown = Some((doc.clone(), opts.clone()));
|
||||
app.perf.render_ms = now_ms() - t0;
|
||||
}
|
||||
(Some(worker), true, None) => worker.submit(crate::render_worker::Job { key: key.clone(), doc: doc.clone(), w, h, view, opts }),
|
||||
(Some(worker), true, None) => worker.submit(crate::render_worker::Job {
|
||||
key: key.clone(),
|
||||
doc: doc.clone(),
|
||||
w,
|
||||
h,
|
||||
view,
|
||||
opts,
|
||||
scope: vectorcraft_engine::file_access::ScopeHandle::capture(),
|
||||
}),
|
||||
_ => {
|
||||
let t0 = now_ms();
|
||||
let img = app.canvas.renderer.render(doc, w, h, view, &opts);
|
||||
|
||||
@@ -98,7 +98,15 @@ pub(super) fn fill_uncovered(
|
||||
let Some(worker) = app.canvas.fill_worker.as_mut().filter(|_| !gaps.is_empty()) else { return };
|
||||
if !app.canvas.fill.as_ref().is_some_and(|f| covers(&f.key)) && !worker.pending.as_ref().is_some_and(covers) {
|
||||
let (w, h, view) = fill_view(key);
|
||||
worker.submit(crate::render_worker::Job { key: key.clone(), doc: doc.clone(), w, h, view, opts });
|
||||
worker.submit(crate::render_worker::Job {
|
||||
key: key.clone(),
|
||||
doc: doc.clone(),
|
||||
w,
|
||||
h,
|
||||
view,
|
||||
opts,
|
||||
scope: vectorcraft_engine::file_access::ScopeHandle::capture(),
|
||||
});
|
||||
}
|
||||
let Some(f) = app.canvas.fill.as_ref().filter(|f| usable(&f.key)) else { return };
|
||||
let to_uv = Affine::scale_non_uniform(1.0 / f64::from(f.size.0.max(1)), 1.0 / f64::from(f.size.1.max(1))) * f.view * screen_to_doc;
|
||||
|
||||
@@ -20,6 +20,9 @@ pub struct Pending {
|
||||
/// What the status bar shows ("Opening Poster.vectorcraft").
|
||||
pub label: String,
|
||||
done: Receiver<Result<ReadFile, String>>,
|
||||
/// The automation roots in force where the open started: the document is installed under
|
||||
/// them too (its links are read as it opens).
|
||||
scope: ScopeHandle,
|
||||
}
|
||||
|
||||
/// The files being read.
|
||||
@@ -57,6 +60,7 @@ pub fn start(app: &mut VectorcraftApp, path: &str, bytes: Vec<u8>) -> Result<Val
|
||||
let low_res_proxy_eps = app.session.prefs.low_res_proxy_eps;
|
||||
// The automation roots in force here are in force there.
|
||||
let scope = ScopeHandle::capture();
|
||||
let opener = scope.clone();
|
||||
let spawned = std::thread::Builder::new().name("vectorcraft-open".into()).spawn(move || {
|
||||
let r = scope
|
||||
.run(|| {
|
||||
@@ -74,7 +78,7 @@ pub fn start(app: &mut VectorcraftApp, path: &str, bytes: Vec<u8>) -> Result<Val
|
||||
return Err(Arc::try_unwrap(bytes).unwrap_or_else(|b| b.to_vec()));
|
||||
}
|
||||
let label = format!("Opening {}", fileio::file_name(path));
|
||||
app.opening.pending.push(Pending { path: path.to_string(), label: label.clone(), done });
|
||||
app.opening.pending.push(Pending { path: path.to_string(), label: label.clone(), done, scope: opener });
|
||||
let status = format!("{label}…");
|
||||
app.status(status.clone());
|
||||
Ok(json!({ "background": true, "status": status }))
|
||||
@@ -109,7 +113,7 @@ pub fn poll(app: &mut VectorcraftApp) {
|
||||
if app.ui.status == format!("{}…", p.label) {
|
||||
app.ui.status.clear();
|
||||
}
|
||||
finish(app, &p.path, r);
|
||||
p.scope.run(|| finish(app, &p.path, r));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -310,3 +310,25 @@ fn a_file_read_off_the_ui_thread_has_its_links_and_type_as_one_read_at_once() {
|
||||
}
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A file opened under automation roots is installed under them too: the open keeps the scope it
|
||||
/// started in, not the desktop's access of the frame it finishes in.
|
||||
#[test]
|
||||
fn a_file_opened_under_automation_roots_finishes_under_them() {
|
||||
use vectorcraft_engine::file_access;
|
||||
let dir = scope_dir();
|
||||
let roots = file_access::AutomationRoots::new(&[dir.as_path()], &[]).unwrap();
|
||||
let mut app = VectorcraftApp::new(Session::new(), Services::default());
|
||||
file_access::confine(roots.as_ref(), || {
|
||||
start(&mut app, "/art/Scoped.vectorcraft", vectorcraft_format::save(&vectorcraft_doc::Document::new(10.0, 10.0), false))
|
||||
})
|
||||
.unwrap();
|
||||
let scope = app.opening.pending[0].scope.clone();
|
||||
assert!(scope.run(file_access::current).is_some(), "the roots go with the open");
|
||||
}
|
||||
|
||||
fn scope_dir() -> std::path::PathBuf {
|
||||
let dir = std::env::temp_dir().join(format!("vectorcraft-opening-scope-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
dir
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use std::rc::Rc;
|
||||
use std::sync::mpsc::{Receiver, TryRecvError};
|
||||
|
||||
use serde_json::Value;
|
||||
use vectorcraft_engine::file_access::ScopeHandle;
|
||||
|
||||
use crate::state::Dialog;
|
||||
use crate::{FilePick, VectorcraftApp};
|
||||
@@ -91,6 +92,9 @@ struct Waiting {
|
||||
entry: Entry,
|
||||
/// The active document when it was asked for ([`vectorcraft_engine::DocState::uid`]).
|
||||
doc: Option<u64>,
|
||||
/// The automation roots in force when it was asked for: the entry runs again under them, not
|
||||
/// with the desktop's access of the frame the answer comes in.
|
||||
scope: ScopeHandle,
|
||||
}
|
||||
|
||||
/// The app's file dialogs ([`VectorcraftApp`]'s `picks`).
|
||||
@@ -286,7 +290,7 @@ fn ask(app: &mut VectorcraftApp, request: PickRequest) -> Option<PickAnswer> {
|
||||
(Some(entry), Some(start)) => {
|
||||
if let Some(answer) = start(request.clone()) {
|
||||
let doc = entry.document(app);
|
||||
app.picks.waiting = Some(Waiting { request, answer, entry, doc });
|
||||
app.picks.waiting = Some(Waiting { request, answer, entry, doc, scope: ScopeHandle::capture() });
|
||||
return None;
|
||||
}
|
||||
}
|
||||
@@ -333,20 +337,22 @@ pub(crate) fn poll(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
// The dialog's thread is gone: as cancelled.
|
||||
Err(TryRecvError::Disconnected) => vec![],
|
||||
};
|
||||
let Some(Waiting { request, entry, doc, .. }) = app.picks.waiting.take() else { return };
|
||||
let Some(Waiting { request, entry, doc, scope, .. }) = app.picks.waiting.take() else { return };
|
||||
app.picks.refused = false;
|
||||
// Once more: what waited for the dialog (files from the Finder, the menu bar) goes on.
|
||||
ctx.request_repaint();
|
||||
if answer.is_empty() {
|
||||
return;
|
||||
}
|
||||
if !on_document(app, doc) {
|
||||
return;
|
||||
}
|
||||
app.picks.answer = Some((request, answer));
|
||||
rerun(app, entry);
|
||||
// An entry that took another way this time leaves the answer unused.
|
||||
app.picks.answer = None;
|
||||
scope.run(|| {
|
||||
if !on_document(app, doc) {
|
||||
return;
|
||||
}
|
||||
app.picks.answer = Some((request, answer));
|
||||
rerun(app, entry);
|
||||
// An entry that took another way this time leaves the answer unused.
|
||||
app.picks.answer = None;
|
||||
});
|
||||
}
|
||||
|
||||
/// Make the document an entry asked for (`doc`, by its uid) active again before it runs again:
|
||||
|
||||
@@ -7,6 +7,7 @@ use std::sync::Arc;
|
||||
use std::sync::mpsc::{Receiver, Sender, channel};
|
||||
|
||||
use vectorcraft_doc::Document;
|
||||
use vectorcraft_engine::file_access::ScopeHandle;
|
||||
use vectorcraft_geom::Affine;
|
||||
use vectorcraft_render::{RenderOptions, Rendered, Renderer};
|
||||
|
||||
@@ -19,6 +20,9 @@ pub struct Job {
|
||||
pub h: u32,
|
||||
pub view: Affine,
|
||||
pub opts: RenderOptions,
|
||||
/// The automation roots in force where the job was made: the render reads linked files under
|
||||
/// them, as it would on that thread.
|
||||
pub scope: ScopeHandle,
|
||||
}
|
||||
|
||||
pub struct Done {
|
||||
@@ -56,7 +60,7 @@ impl Worker {
|
||||
job = newer;
|
||||
}
|
||||
let t0 = crate::now_ms();
|
||||
let img = r.render(&job.doc, job.w, job.h, job.view, &job.opts);
|
||||
let img = job.scope.clone().run(|| r.render(&job.doc, job.w, job.h, job.view, &job.opts));
|
||||
if done_tx.send(Done { key: job.key, doc: job.doc, opts: job.opts, img, ms: crate::now_ms() - t0, moved: r.stats.moved }).is_err()
|
||||
{
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user