feat(plugin): persist XDATA to DWG and let plugins modify/delete entities (#249, #250)

#249 — plugin XDATA written via write_record survived only in memory: the
acadrust DWG writer dropped ExtendedData::records on save. Bump acadrust to
e88a9a6 (records now encode to EED and decode back on read) and fix the host
side that fed it:
  - ensure_app_id allocates a real APPID handle; a null handle serializes as
    0 and the EED reference can't resolve, so the XDATA vanished on reopen.
  - write_record / remove_record drop stale raw_dwg_eed for the target app so
    an edit made after a save/reopen wins over the pre-edit bytes.

#250 — out-of-process plugins got a throwaway document_mut() snapshot, so
edits to existing entities were silently discarded and deletion wasn't
expressible at all. Add the missing mutation surface:
  - UpdateEntity / RemoveEntity IPC requests + HostApi::update_entity /
    remove_entity (default in-process impls, RPC overrides on the client that
    invalidate the stale document cache).
  - Scene::update_entity replaces the entity in place, preserving its handle
    and owning block, and reseeds only its derived caches; remove reuses the
    cache-coherent erase_entities (which also honours layer locks).
  - document_mut() is documented as a local-only snapshot out-of-process.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Hakan Seven 2026-07-02 15:39:26 +03:00
commit f9f7138370
7 changed files with 295 additions and 4 deletions

View file

@ -120,10 +120,38 @@ pub trait HostApi {
// ── Document ────────────────────────────────────────────────────────────
fn document(&self) -> &CadDocument;
/// Mutable access to the active document.
///
/// For an **out-of-process** plugin this borrows a *local snapshot*: edits
/// to existing entities made through it are NOT sent back to the host and
/// are silently discarded. To modify or delete entities from any plugin,
/// use [`add_entity`](Self::add_entity), [`update_entity`](Self::update_entity)
/// and [`remove_entity`](Self::remove_entity), which are committed to the
/// host document over IPC.
fn document_mut(&mut self) -> &mut CadDocument;
/// Add an entity to the active document, returning its handle.
fn add_entity(&mut self, entity: EntityType) -> Handle;
/// Replace the existing entity that carries `entity`'s handle, preserving
/// its identity (handle and owning block). Returns `false` when no entity
/// has that handle. This is the sanctioned way to commit in-place edits
/// from an out-of-process plugin — mutating `document_mut()` does not work
/// across the process boundary.
fn update_entity(&mut self, entity: EntityType) -> bool {
let handle = entity.common().handle;
match self.document_mut().get_entity_mut(handle) {
Some(slot) => {
*slot = entity;
true
}
None => false,
}
}
/// Delete the entity with `handle` (and any derived render caches). Returns
/// `true` when an entity was removed.
fn remove_entity(&mut self, handle: Handle) -> bool {
self.document_mut().remove_entity(handle).is_some()
}
/// Mark the scene geometry dirty so it is re-tessellated next frame.
fn bump_geometry(&mut self);