forked from 1b-agent/opencadstudio-upstream
Fix 4: shared-memory
This commit is contained in:
1 parent
a93d425002
commit
2b898d6f22
13 files changed
+894
-92
No files matched your search
@@ -3,6 +3,7 @@
|
||||
use std::any::Any;
|
||||
use std::cell::{Cell, OnceCell, RefCell};
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::rc::Rc;
|
||||
|
||||
use acadrust::xdata::ExtendedDataRecord;
|
||||
@@ -10,11 +11,12 @@ use acadrust::{CadDocument, EntityType, Handle};
|
||||
use interprocess::local_socket::traits::Stream as StreamTrait;
|
||||
use interprocess::local_socket::{GenericNamespaced, Stream, ToNsName};
|
||||
|
||||
use crate::host::{HostApi, InteractiveCommand};
|
||||
use crate::host::{DocumentReader, HostApi, InteractiveCommand, ReaderEntity};
|
||||
use crate::ipc::protocol::{
|
||||
HostResponse, HostToPlugin, PluginRequest, PluginResponse, PluginToHost,
|
||||
};
|
||||
use crate::ipc::transport::{recv, send};
|
||||
use crate::shm::{DocumentViewInfo, SharedDocumentReader};
|
||||
|
||||
/// Shared registry of active interactive commands, keyed by host-assigned id.
|
||||
pub type InteractiveRegistry = Rc<RefCell<HashMap<u64, Box<dyn InteractiveCommand>>>>;
|
||||
@@ -77,6 +79,9 @@ pub struct PluginHostApi {
|
||||
/// stable references without leaking on every call. Each distinct record is
|
||||
/// leaked once per plugin dispatch/interactive session.
|
||||
record_cache: RefCell<HashMap<(Handle, String), &'static ExtendedDataRecord>>,
|
||||
/// Shared-memory document view information, lazily fetched on first
|
||||
/// `document_reader()` access.
|
||||
doc_view: RefCell<Option<DocumentViewInfo>>,
|
||||
}
|
||||
|
||||
impl PluginHostApi {
|
||||
@@ -88,6 +93,7 @@ impl PluginHostApi {
|
||||
interactive,
|
||||
next_command_id: Cell::new(1),
|
||||
record_cache: RefCell::new(HashMap::new()),
|
||||
doc_view: RefCell::new(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,9 +185,7 @@ impl HostApi for PluginHostApi {
|
||||
{
|
||||
Ok(PluginResponse::Bool(b)) => {
|
||||
if b {
|
||||
self.record_cache
|
||||
.borrow_mut()
|
||||
.remove(&(handle, app));
|
||||
self.record_cache.borrow_mut().remove(&(handle, app));
|
||||
}
|
||||
b
|
||||
}
|
||||
@@ -221,10 +225,9 @@ impl HostApi for PluginHostApi {
|
||||
}
|
||||
|
||||
fn push_undo(&mut self, label: &str) {
|
||||
if let Err(e) = self
|
||||
.client
|
||||
.request(PluginRequest::PushUndo { label: label.to_string() })
|
||||
{
|
||||
if let Err(e) = self.client.request(PluginRequest::PushUndo {
|
||||
label: label.to_string(),
|
||||
}) {
|
||||
eprintln!("[plugin] push_undo failed: {e}");
|
||||
}
|
||||
}
|
||||
@@ -236,19 +239,28 @@ impl HostApi for PluginHostApi {
|
||||
}
|
||||
|
||||
fn push_info(&mut self, msg: &str) {
|
||||
if let Err(e) = self.client.request(PluginRequest::PushInfo(msg.to_string())) {
|
||||
if let Err(e) = self
|
||||
.client
|
||||
.request(PluginRequest::PushInfo(msg.to_string()))
|
||||
{
|
||||
eprintln!("[plugin] push_info failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
fn push_output(&mut self, msg: &str) {
|
||||
if let Err(e) = self.client.request(PluginRequest::PushOutput(msg.to_string())) {
|
||||
if let Err(e) = self
|
||||
.client
|
||||
.request(PluginRequest::PushOutput(msg.to_string()))
|
||||
{
|
||||
eprintln!("[plugin] push_output failed: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
fn push_error(&mut self, msg: &str) {
|
||||
if let Err(e) = self.client.request(PluginRequest::PushError(msg.to_string())) {
|
||||
if let Err(e) = self
|
||||
.client
|
||||
.request(PluginRequest::PushError(msg.to_string()))
|
||||
{
|
||||
eprintln!("[plugin] push_error failed: {e}");
|
||||
}
|
||||
}
|
||||
@@ -285,6 +297,54 @@ impl HostApi for PluginHostApi {
|
||||
// state contract to work across processes.
|
||||
panic!("ensure_plugin_state is not supported for out-of-process plugins; keep state in the plugin crate")
|
||||
}
|
||||
|
||||
fn document_reader(&self) -> Box<dyn DocumentReader + '_> {
|
||||
{
|
||||
let mut view = self.doc_view.borrow_mut();
|
||||
if view.is_none() {
|
||||
match self.client.request(PluginRequest::OpenDocumentView) {
|
||||
Ok(PluginResponse::DocumentView { path, version }) => {
|
||||
*view = Some(DocumentViewInfo { path, version });
|
||||
}
|
||||
Ok(other) => {
|
||||
eprintln!("[plugin] unexpected OpenDocumentView response: {other:?}");
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("[plugin] OpenDocumentView request failed: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
match self.doc_view.borrow().as_ref() {
|
||||
Some(info) => match SharedDocumentReader::open(Path::new(&info.path)) {
|
||||
Ok(reader) => Box::new(reader),
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
"[plugin] failed to open document view at {}: {e}",
|
||||
info.path
|
||||
);
|
||||
Box::new(EmptyDocumentReader)
|
||||
}
|
||||
},
|
||||
None => Box::new(EmptyDocumentReader),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sentinel reader used when the shared-memory view could not be initialized.
|
||||
struct EmptyDocumentReader;
|
||||
|
||||
impl DocumentReader for EmptyDocumentReader {
|
||||
fn entity_count(&self) -> usize {
|
||||
0
|
||||
}
|
||||
fn for_each_entity(&self, _f: &mut dyn FnMut(ReaderEntity<'_>)) {}
|
||||
fn layer_name(&self, _handle: Handle) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
fn app_id_name(&self, _handle: Handle) -> Option<&str> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "host"))]
|
||||
@@ -292,8 +352,8 @@ mod tests {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::thread;
|
||||
|
||||
use acadrust::{EntityType, Handle};
|
||||
use acadrust::entities::Point;
|
||||
use acadrust::{EntityType, Handle};
|
||||
use interprocess::local_socket::{
|
||||
traits::{Listener, Stream as StreamTrait},
|
||||
GenericNamespaced, ListenerOptions, Stream, ToNsName,
|
||||
|
||||
@@ -42,8 +42,7 @@ mod tests {
|
||||
.create_sync()
|
||||
.expect("create listener");
|
||||
let client = thread::spawn(move || {
|
||||
StreamTrait::connect(name.to_ns_name::<GenericNamespaced>().unwrap())
|
||||
.expect("connect")
|
||||
StreamTrait::connect(name.to_ns_name::<GenericNamespaced>().unwrap()).expect("connect")
|
||||
});
|
||||
let server = listener.accept().expect("accept");
|
||||
let client = client.join().expect("client thread");
|
||||
|
||||
@@ -11,8 +11,8 @@ use crate::host::CommandStep;
|
||||
use crate::manifest::ApiVersion;
|
||||
use crate::ribbon::owned::{OwnedPluginManifest, OwnedRibbonGroup};
|
||||
|
||||
pub use acadrust::{CadDocument, EntityType, Handle};
|
||||
pub use acadrust::xdata::ExtendedDataRecord;
|
||||
pub use acadrust::{CadDocument, EntityType, Handle};
|
||||
|
||||
/// Events the host forwards to an active plugin `InteractiveCommand`.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -27,10 +27,19 @@ pub enum InteractiveEvent {
|
||||
pub enum HostRequest {
|
||||
GetManifest,
|
||||
GetRibbon,
|
||||
Dispatch { cmd: String },
|
||||
InteractiveEvent { command_id: u64, event: InteractiveEvent },
|
||||
GetPrompt { command_id: u64 },
|
||||
NeedsEntityPick { command_id: u64 },
|
||||
Dispatch {
|
||||
cmd: String,
|
||||
},
|
||||
InteractiveEvent {
|
||||
command_id: u64,
|
||||
event: InteractiveEvent,
|
||||
},
|
||||
GetPrompt {
|
||||
command_id: u64,
|
||||
},
|
||||
NeedsEntityPick {
|
||||
command_id: u64,
|
||||
},
|
||||
Shutdown,
|
||||
}
|
||||
|
||||
@@ -53,13 +62,29 @@ pub enum PluginRequest {
|
||||
PushError(String),
|
||||
AddEntity(EntityType),
|
||||
BumpGeometry,
|
||||
ReadRecord { handle: Handle, app_name: String },
|
||||
WriteRecord { handle: Handle, record: ExtendedDataRecord },
|
||||
RemoveRecord { handle: Handle, app_name: String },
|
||||
PushUndo { label: String },
|
||||
ReadRecord {
|
||||
handle: Handle,
|
||||
app_name: String,
|
||||
},
|
||||
WriteRecord {
|
||||
handle: Handle,
|
||||
record: ExtendedDataRecord,
|
||||
},
|
||||
RemoveRecord {
|
||||
handle: Handle,
|
||||
app_name: String,
|
||||
},
|
||||
PushUndo {
|
||||
label: String,
|
||||
},
|
||||
SetDirty,
|
||||
StartInteractive { command_id: u64 },
|
||||
StartInteractive {
|
||||
command_id: u64,
|
||||
},
|
||||
DocumentSnapshot,
|
||||
/// Ask the host to create/refresh a shared-memory document view and return
|
||||
/// the file path + current version.
|
||||
OpenDocumentView,
|
||||
}
|
||||
|
||||
/// Responses the host sends back for `PluginRequest`.
|
||||
@@ -71,6 +96,11 @@ pub enum PluginResponse {
|
||||
Record(Option<ExtendedDataRecord>),
|
||||
Document(CadDocument),
|
||||
Error(String),
|
||||
/// Path to the memory-mapped file and the current snapshot version.
|
||||
DocumentView {
|
||||
path: String,
|
||||
version: u64,
|
||||
},
|
||||
}
|
||||
|
||||
/// Messages sent from the host to the plugin runner.
|
||||
|
||||
@@ -35,9 +35,7 @@ pub fn handle_plugin_request(
|
||||
ReadRecord { handle, app_name } => {
|
||||
PluginResponse::Record(host.read_record(handle, &app_name).cloned())
|
||||
}
|
||||
WriteRecord { handle, record } => {
|
||||
PluginResponse::Bool(host.write_record(handle, record))
|
||||
}
|
||||
WriteRecord { handle, record } => PluginResponse::Bool(host.write_record(handle, record)),
|
||||
RemoveRecord { handle, app_name } => {
|
||||
PluginResponse::Bool(host.remove_record(handle, &app_name))
|
||||
}
|
||||
@@ -54,5 +52,12 @@ pub fn handle_plugin_request(
|
||||
PluginResponse::Ok
|
||||
}
|
||||
DocumentSnapshot => PluginResponse::Document(host.document().clone()),
|
||||
OpenDocumentView => match host.document_view() {
|
||||
Some(info) => PluginResponse::DocumentView {
|
||||
path: info.path,
|
||||
version: info.version,
|
||||
},
|
||||
None => PluginResponse::Error("shared document view unavailable".to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user