forked from 1b-agent/opencadstudio-upstream
plugin: announce DocumentOpened once per load, not per tab switch
The guard remembered only the last announced (tab, generation), so switching A -> B -> A announced A's load again. Each tab now carries its own pending flag, set when a document is installed and cleared by the boundary dispatch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
68330ce39e
commit
4d66a58374
7 files changed
+28
-51
No files matched your search
@@ -427,8 +427,8 @@ and `SelectionChanged` (the plain selection set) at the fingerprint-guarded mess
|
||||
the v4 bridge already uses (`notify_plugins_selection_changed` / `notify_plugins_document_changed`,
|
||||
plus the automation post-op boundary), and a new additive `DocumentOpened { tab_id }` variant
|
||||
(discriminant 10, the counterpart of the existing `DocumentTabClosed`) that fires once per whole
|
||||
document install — file open, template new, system open — keyed on a new per-tab document
|
||||
generation. Boundary rule: dispatch happens after the mutation settles and pending history scopes
|
||||
document install — file open, template new, system open — keyed on a per-tab pending flag, so
|
||||
neither an edit nor a tab switch repeats it. Boundary rule: dispatch happens after the mutation settles and pending history scopes
|
||||
closed, never mid-undo/mid-pick, and history (undo/redo) replay itself emits nothing — the
|
||||
boundary is the only channel; plugin-initiated writes publish immediately after settling, like
|
||||
the drawing-changed event, with the boundary fingerprint deduplicating. Handlers may call HostApi
|
||||
|
||||
@@ -729,8 +729,8 @@ impl OpenCADStudio {
|
||||
// The headless session starts on the welcome (Start) tab, which
|
||||
// blocks drawing commands; turn it into a real drawing.
|
||||
self.tabs[i].is_start = false;
|
||||
// A fresh document became resident (API v9): mint a new
|
||||
// generation so the post-op boundary announces `DocumentOpened`.
|
||||
// A fresh document became resident (API v9): the post-op
|
||||
// boundary announces `DocumentOpened`.
|
||||
self.tabs[i].note_document_loaded();
|
||||
self.entity_summary()
|
||||
}
|
||||
@@ -770,9 +770,8 @@ impl OpenCADStudio {
|
||||
self.tabs[i].current_path = Some(path_buf);
|
||||
self.tabs[i].is_start = false;
|
||||
self.tabs[i].scene.rebuild_derived_caches();
|
||||
// The opened document settled (API v9): mint a new
|
||||
// generation so the post-op boundary announces
|
||||
// `DocumentOpened` once for this load.
|
||||
// The opened document settled (API v9): the post-op
|
||||
// boundary announces `DocumentOpened` once for this load.
|
||||
self.tabs[i].note_document_loaded();
|
||||
let mut summary = self.entity_summary();
|
||||
if dropped > 0 {
|
||||
@@ -1855,7 +1854,7 @@ mod tests {
|
||||
"a new document announces exactly one open: {log:?}"
|
||||
);
|
||||
|
||||
// A follow-up op does not repeat it: generation-keyed dispatch.
|
||||
// A follow-up op does not repeat it: the tab's pending flag cleared.
|
||||
app.automation_op(r#"{"op":"query","type":"Line"}"#);
|
||||
let log = plugin_dispatch_log();
|
||||
assert!(
|
||||
@@ -1917,7 +1916,7 @@ mod tests {
|
||||
// The next message boundary does not repeat it.
|
||||
app.notify_plugins_document_opened();
|
||||
let log = plugin_dispatch_log();
|
||||
assert_eq!(log.len(), 0, "generation-keyed dispatch dedupes: {log:?}");
|
||||
assert_eq!(log.len(), 0, "the pending flag dedupes: {log:?}");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
}
|
||||
|
||||
|
||||
@@ -466,8 +466,8 @@ impl OpenCADStudio {
|
||||
self.tabs[i].current_path = None;
|
||||
self.tabs[i].is_start = false;
|
||||
self.tabs[i].dirty = true;
|
||||
// A whole document just became resident in the tab (API v9): mint a
|
||||
// fresh generation so the boundary announces `DocumentOpened` once.
|
||||
// A whole document just became resident in the tab (API v9): the
|
||||
// boundary announces `DocumentOpened` once.
|
||||
self.tabs[i].note_document_loaded();
|
||||
Ok(purged)
|
||||
}
|
||||
|
||||
+11
-23
@@ -18,17 +18,6 @@ use std::sync::Arc;
|
||||
|
||||
static NEXT_DOCUMENT_TAB_ID: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
/// Monotonic source for `DocumentTab::document_generation` (API v9): every
|
||||
/// minted value is process-unique, so any freshly installed document — file
|
||||
/// open, template new, system open — differs from every generation a plugin
|
||||
/// has been told about, and the opened-notification guard fires exactly once
|
||||
/// per load.
|
||||
static NEXT_DOCUMENT_GENERATION: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
fn next_document_generation() -> u64 {
|
||||
NEXT_DOCUMENT_GENERATION.fetch_add(1, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// ── Dynamic input ──────────────────────────────────────────────────────────
|
||||
|
||||
/// One quantity shown in the dynamic-input overlay near the cursor.
|
||||
@@ -158,13 +147,12 @@ pub(super) struct DocumentTab {
|
||||
/// Monotonic committed-edit/undo/redo revision. Background save completion
|
||||
/// uses it with scene epochs so an older snapshot never clears newer work.
|
||||
pub(super) edit_revision: u64,
|
||||
/// Monotonic identity of the document currently resident in this tab: a
|
||||
/// fresh value is minted whenever a whole document replaces the tab's
|
||||
/// content (file open, template new, system open), and edits leave it
|
||||
/// alone. The plugin-opened notification guard
|
||||
/// (`OpenCADStudio::notify_plugins_document_opened`, API v9) keys on it
|
||||
/// so `DocumentOpened` dispatches exactly once per load.
|
||||
pub(super) document_generation: u64,
|
||||
/// A whole document replaced this tab's content (file open, template new,
|
||||
/// system open) and native plugins have not been told yet: the
|
||||
/// message boundary (`OpenCADStudio::notify_plugins_document_opened`,
|
||||
/// API v9) dispatches `DocumentOpened` once and clears it, so edits and
|
||||
/// switching between tabs never announce the same load again.
|
||||
pub(super) document_opened_pending: bool,
|
||||
pub(super) tab_title: String,
|
||||
pub(super) properties: PropertiesPanel,
|
||||
pub(super) layers: LayerPanel,
|
||||
@@ -648,7 +636,7 @@ impl DocumentTab {
|
||||
dirty: false,
|
||||
recovery_save_as_required: false,
|
||||
edit_revision: 0,
|
||||
document_generation: next_document_generation(),
|
||||
document_opened_pending: true,
|
||||
prev_selection: Vec::new(),
|
||||
tab_title: format!("Drawing{}", n),
|
||||
properties: PropertiesPanel::empty(),
|
||||
@@ -723,11 +711,11 @@ impl DocumentTab {
|
||||
}
|
||||
|
||||
/// A whole document just replaced this tab's content (file open, template
|
||||
/// new, system open): mint a fresh generation so the message-boundary
|
||||
/// dispatch announces `DocumentOpened` exactly once for this load (API
|
||||
/// v9). Call after the document and its derived caches have settled.
|
||||
/// new, system open): the next message boundary announces
|
||||
/// `DocumentOpened` once for this load (API v9). Call after the document
|
||||
/// and its derived caches have settled.
|
||||
pub(super) fn note_document_loaded(&mut self) {
|
||||
self.document_generation = next_document_generation();
|
||||
self.document_opened_pending = true;
|
||||
}
|
||||
|
||||
pub(super) fn tab_display_name(&self) -> String {
|
||||
|
||||
@@ -1153,12 +1153,6 @@ pub(super) struct OpenCADStudio {
|
||||
/// boundaries as well as after plugin-initiated writes.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
last_plugin_document: Option<(u64, u64)>,
|
||||
/// `(tab id, document generation)` last dispatched as `DocumentOpened` —
|
||||
/// a tab's generation changes only when a whole document replaced its
|
||||
/// content, so the opened notification fires once per load, never per
|
||||
/// edit (API v9).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
last_plugin_document_opened: Option<(u64, u64)>,
|
||||
/// External add-on packages found in the plugins folder, refreshed when the
|
||||
/// Plugin Manager opens.
|
||||
external_plugins: Vec<crate::plugin::external::ExternalPlugin>,
|
||||
@@ -4838,8 +4832,6 @@ impl OpenCADStudio {
|
||||
last_plugin_selection: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
last_plugin_document: None,
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
last_plugin_document_opened: None,
|
||||
external_plugins: Vec::new(),
|
||||
loaded_plugin_ids: rustc_hash::FxHashSet::default(),
|
||||
plugin_load_errors: rustc_hash::FxHashMap::default(),
|
||||
|
||||
@@ -2276,9 +2276,9 @@ impl OpenCADStudio {
|
||||
.set(crate::app::OPEN_PHASE_FINALIZING, 10000, 1, 1);
|
||||
}
|
||||
self.opening.take();
|
||||
// The document and its derived caches have settled: mark the tab's
|
||||
// generation so the next message boundary announces `DocumentOpened`
|
||||
// to native plugins exactly once for this load (API v9).
|
||||
// The document and its derived caches have settled: the next message
|
||||
// boundary announces `DocumentOpened` to native plugins exactly once
|
||||
// for this load (API v9).
|
||||
self.tabs[i].note_document_loaded();
|
||||
// The tab list just changed (and the active tab with it): remember
|
||||
// it for the next boot's restore flow (web session memory).
|
||||
|
||||
@@ -344,9 +344,9 @@ impl OpenCADStudio {
|
||||
|
||||
/// Announce that a whole document became resident in the active tab —
|
||||
/// opened from file, created from a template, or loaded by a system open
|
||||
/// (API v9). Keyed on `(tab id, document generation)`, which changes only
|
||||
/// when a tab's content is wholesale replaced, so an edit can never fire
|
||||
/// it twice and a reload fires it exactly once. Part of the same
|
||||
/// (API v9). Each tab carries its own pending flag, set only when its
|
||||
/// content is wholesale replaced, so neither an edit nor a tab switch can
|
||||
/// fire it twice and a reload fires it exactly once. Part of the same
|
||||
/// post-message boundary as the selection and document dispatches: runs
|
||||
/// after the mutation settles and pending history scopes closed, never
|
||||
/// inside history replay or interactive picking.
|
||||
@@ -359,11 +359,9 @@ impl OpenCADStudio {
|
||||
if self.tabs[i].is_start {
|
||||
return;
|
||||
}
|
||||
let key = (self.tabs[i].id, self.tabs[i].document_generation);
|
||||
if self.last_plugin_document_opened == Some(key) {
|
||||
if !std::mem::take(&mut self.tabs[i].document_opened_pending) {
|
||||
return;
|
||||
}
|
||||
self.last_plugin_document_opened = Some(key);
|
||||
crate::plugin::v4_support::publish_document_opened(self.tabs[i].id);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user