#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:
parent
a763144622
commit
f9f7138370
7 changed files with 295 additions and 4 deletions
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -151,6 +151,46 @@ impl HostApi for PluginHostApi {
|
|||
}
|
||||
}
|
||||
|
||||
fn update_entity(&mut self, entity: EntityType) -> bool {
|
||||
match self.client.request(PluginRequest::UpdateEntity(entity)) {
|
||||
Ok(PluginResponse::Bool(b)) => {
|
||||
if b {
|
||||
// The cached snapshot is now stale; drop it so a later
|
||||
// document() re-fetches the host's post-edit truth.
|
||||
self.document_cache = OnceCell::new();
|
||||
}
|
||||
b
|
||||
}
|
||||
Ok(other) => {
|
||||
eprintln!("[plugin] unexpected UpdateEntity response: {other:?}");
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[plugin] UpdateEntity failed: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_entity(&mut self, handle: Handle) -> bool {
|
||||
match self.client.request(PluginRequest::RemoveEntity { handle }) {
|
||||
Ok(PluginResponse::Bool(b)) => {
|
||||
if b {
|
||||
self.document_cache = OnceCell::new();
|
||||
}
|
||||
b
|
||||
}
|
||||
Ok(other) => {
|
||||
eprintln!("[plugin] unexpected RemoveEntity response: {other:?}");
|
||||
false
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[plugin] RemoveEntity failed: {e}");
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn bump_geometry(&mut self) {
|
||||
let _ = self.client.request(PluginRequest::BumpGeometry);
|
||||
}
|
||||
|
|
@ -438,4 +478,36 @@ mod tests {
|
|||
peer_handle.join().unwrap();
|
||||
assert_eq!(handle, Handle::new(42));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_entity_awaits_bool_response() {
|
||||
let (mut api, mut peer) = make_client();
|
||||
let peer_handle = thread::spawn(move || {
|
||||
let msg = recv::<PluginToHost>(&mut peer).unwrap();
|
||||
match msg {
|
||||
PluginToHost::Request(PluginRequest::UpdateEntity(_)) => {}
|
||||
other => panic!("unexpected: {other:?}"),
|
||||
}
|
||||
send(&mut peer, &HostToPlugin::Response(PluginResponse::Bool(true))).unwrap();
|
||||
});
|
||||
assert!(api.update_entity(EntityType::Point(Point::new())));
|
||||
peer_handle.join().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_entity_awaits_bool_response() {
|
||||
let (mut api, mut peer) = make_client();
|
||||
let peer_handle = thread::spawn(move || {
|
||||
let msg = recv::<PluginToHost>(&mut peer).unwrap();
|
||||
match msg {
|
||||
PluginToHost::Request(PluginRequest::RemoveEntity { handle }) => {
|
||||
assert_eq!(handle, Handle::new(7));
|
||||
}
|
||||
other => panic!("unexpected: {other:?}"),
|
||||
}
|
||||
send(&mut peer, &HostToPlugin::Response(PluginResponse::Bool(true))).unwrap();
|
||||
});
|
||||
assert!(api.remove_entity(Handle::new(7)));
|
||||
peer_handle.join().unwrap();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,12 @@ pub enum PluginRequest {
|
|||
PushOutput(String),
|
||||
PushError(String),
|
||||
AddEntity(EntityType),
|
||||
/// Replace the existing entity carrying this entity's handle in place.
|
||||
UpdateEntity(EntityType),
|
||||
/// Delete the entity with `handle`.
|
||||
RemoveEntity {
|
||||
handle: Handle,
|
||||
},
|
||||
BumpGeometry,
|
||||
ReadRecord {
|
||||
handle: Handle,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ pub fn handle_plugin_request(
|
|||
PluginResponse::Ok
|
||||
}
|
||||
AddEntity(entity) => PluginResponse::Handle(host.add_entity(entity)),
|
||||
UpdateEntity(entity) => PluginResponse::Bool(host.update_entity(entity)),
|
||||
RemoveEntity { handle } => PluginResponse::Bool(host.remove_entity(handle)),
|
||||
BumpGeometry => {
|
||||
host.bump_geometry();
|
||||
PluginResponse::Ok
|
||||
|
|
|
|||
Loading…
Reference in a new issue