diff --git a/crates/ui-egui/src/canvas.rs b/crates/ui-egui/src/canvas.rs index 065907b4f..1330cae2b 100644 --- a/crates/ui-egui/src/canvas.rs +++ b/crates/ui-egui/src/canvas.rs @@ -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); diff --git a/crates/ui-egui/src/canvas/fill.rs b/crates/ui-egui/src/canvas/fill.rs index 2f45f23ba..779f6d352 100644 --- a/crates/ui-egui/src/canvas/fill.rs +++ b/crates/ui-egui/src/canvas/fill.rs @@ -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; diff --git a/crates/ui-egui/src/opening.rs b/crates/ui-egui/src/opening.rs index e1949573d..6de2fe0f7 100644 --- a/crates/ui-egui/src/opening.rs +++ b/crates/ui-egui/src/opening.rs @@ -20,6 +20,9 @@ pub struct Pending { /// What the status bar shows ("Opening Poster.vectorcraft"). pub label: String, done: Receiver>, + /// 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) -> Result) -> Result 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 +} diff --git a/crates/ui-egui/src/picks.rs b/crates/ui-egui/src/picks.rs index 362108a46..fd5c4d61c 100644 --- a/crates/ui-egui/src/picks.rs +++ b/crates/ui-egui/src/picks.rs @@ -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, + /// 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 { (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: diff --git a/crates/ui-egui/src/render_worker.rs b/crates/ui-egui/src/render_worker.rs index 5651f3ce2..1b28f7d1f 100644 --- a/crates/ui-egui/src/render_worker.rs +++ b/crates/ui-egui/src/render_worker.rs @@ -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;