Fix 5: Allow backward-compatibility and add plugin-template-v2
This commit is contained in:
parent
2b898d6f22
commit
d0003534a0
12 changed files with 568 additions and 60 deletions
13
docs/plugin-template-v2/Cargo.toml
Normal file
13
docs/plugin-template-v2/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "plugin-template-v2"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Example OpenCAD Studio plugin demonstrating the shared-memory DocumentReader API."
|
||||
license = "GPL-3.0-only"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ocs_plugin_api = { path = "../../crates/ocs_plugin_api", features = ["host"] }
|
||||
acadrust = "0.3.4"
|
||||
78
docs/plugin-template-v2/DESIGN.md
Normal file
78
docs/plugin-template-v2/DESIGN.md
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
# `plugin-template-v2` — Shared-memory `DocumentReader` example
|
||||
|
||||
This crate is a runnable plugin template that demonstrates the V2 host/plugin
|
||||
contract:
|
||||
|
||||
- **Zero-copy reads** through [`ocs_plugin_api::host::DocumentReader`].
|
||||
- **Validated writes** through the existing `HostApi` RPCs (`add_entity`,
|
||||
`write_record`).
|
||||
- **Read→write round-trips**: read an entity handle from the shared document
|
||||
view and attach XDATA to it.
|
||||
|
||||
It lives under `docs/` (not `crates/`) because it is documentation/example code
|
||||
rather than a runtime dependency of the host.
|
||||
|
||||
---
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---|---|
|
||||
| `COUNT_SURVEY_POINTS` | Count point entities on the `SURVEY` layer via `document_reader()`. |
|
||||
| `ADD_SURVEY_POINT` | Add a new point entity on the `SURVEY` layer via `host.add_entity()`. |
|
||||
| `MARK_FIRST_SURVEY_POINT` | Read the first `SURVEY` point and mark it with an XDATA record. |
|
||||
| `COUNT_MARKED_SURVEY_POINTS` | Count `SURVEY` points that carry the `SURVEYMARK` XDATA record. |
|
||||
|
||||
---
|
||||
|
||||
## Design notes
|
||||
|
||||
### Reads are zero-copy; writes are RPCs
|
||||
|
||||
`document_reader()` returns a read-only view backed by host-owned shared memory
|
||||
(for out-of-process plugins) or by `&CadDocument` (for in-process plugins). The
|
||||
plugin iterates entities without copying the model.
|
||||
|
||||
All mutations still cross the validated RPC boundary. This preserves the
|
||||
host's crash-safety boundary: a buggy plugin cannot corrupt the host document
|
||||
because it never holds a mutable reference to host memory.
|
||||
|
||||
### Entity handles bridge reads and writes
|
||||
|
||||
Each [`ReaderEntity`](ocs_plugin_api::host::ReaderEntity) exposes the entity
|
||||
`handle`. A plugin can therefore:
|
||||
|
||||
1. Find an entity of interest with `document_reader().for_each_entity(...)`.
|
||||
2. Use that handle with `host.write_record(handle, record)` to attach XDATA.
|
||||
3. Read the record back later with `host.read_record(handle, app_name)`.
|
||||
|
||||
This is the primary read→write round-trip pattern the template demonstrates.
|
||||
|
||||
### Ribbon integration
|
||||
|
||||
The plugin registers one ribbon module (`SurveyTools`) with one group
|
||||
(`Survey`). Each tool emits a `ModuleEvent::Command(...)` carrying the command
|
||||
name. The host routes the command to `BuiltinPlugin::dispatch`.
|
||||
|
||||
---
|
||||
|
||||
## Building and running
|
||||
|
||||
From the workspace root:
|
||||
|
||||
```bash
|
||||
cargo build -p plugin-template-v2
|
||||
```
|
||||
|
||||
The resulting cdylib plus `plugin.toml` can be installed into the host's
|
||||
plugins folder (see `docs/plugin-architecture.md`).
|
||||
|
||||
---
|
||||
|
||||
## Compatibility
|
||||
|
||||
The template is built against the current `ocs_plugin_api` and declares
|
||||
`ApiVersion::CURRENT`. Older plugins compiled against API v2 continue to load
|
||||
on an API v3 host because new `HostApi` methods are appended at the end of the
|
||||
trait and the host accepts plugin majors from `API_VERSION_MIN_SUPPORTED` up to
|
||||
`API_VERSION`.
|
||||
175
docs/plugin-template-v2/src/lib.rs
Normal file
175
docs/plugin-template-v2/src/lib.rs
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
//! OpenCAD Studio plugin template v2.
|
||||
//!
|
||||
//! Demonstrates read/write round-trips between the plugin and the host using
|
||||
//! the zero-copy `DocumentReader` API and the validated `HostApi` RPCs.
|
||||
|
||||
use acadrust::entities::Point;
|
||||
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
||||
use acadrust::{EntityType, Handle};
|
||||
use ocs_plugin_api::export_plugin;
|
||||
use ocs_plugin_api::host::{BuiltinPlugin, HostApi, ReaderEntityKind};
|
||||
use ocs_plugin_api::manifest::{ApiVersion, PluginManifest};
|
||||
use ocs_plugin_api::ribbon::{CadModule, IconKind, ModuleEvent, RibbonGroup, RibbonItem, ToolDef};
|
||||
|
||||
static MANIFEST: PluginManifest = PluginManifest {
|
||||
id: "com.example.plugin-template-v2",
|
||||
name: "Plugin Template v2",
|
||||
version: env!("CARGO_PKG_VERSION"),
|
||||
description: "Demonstrates read/write object round-trips with the host.",
|
||||
api_version: ApiVersion::CURRENT,
|
||||
ribbon_order: 100,
|
||||
xdata_apps: &["SURVEYMARK"],
|
||||
command_prefixes: &[
|
||||
"COUNT_SURVEY_POINTS",
|
||||
"ADD_SURVEY_POINT",
|
||||
"MARK_FIRST_SURVEY_POINT",
|
||||
"COUNT_MARKED_SURVEY_POINTS",
|
||||
],
|
||||
};
|
||||
|
||||
struct PluginTemplateV2;
|
||||
|
||||
impl BuiltinPlugin for PluginTemplateV2 {
|
||||
fn manifest(&self) -> &'static PluginManifest {
|
||||
&MANIFEST
|
||||
}
|
||||
|
||||
fn ribbon(&self) -> Box<dyn CadModule> {
|
||||
Box::new(TemplateModule)
|
||||
}
|
||||
|
||||
fn dispatch(&self, host: &mut dyn HostApi, cmd: &str) -> bool {
|
||||
match cmd {
|
||||
"COUNT_SURVEY_POINTS" => {
|
||||
let count = count_survey_points(host);
|
||||
host.push_info(&format!("SURVEY points: {count}"));
|
||||
true
|
||||
}
|
||||
"ADD_SURVEY_POINT" => {
|
||||
let handle = add_survey_point(host);
|
||||
host.push_info(&format!("Added SURVEY point {handle}"));
|
||||
true
|
||||
}
|
||||
"MARK_FIRST_SURVEY_POINT" => {
|
||||
if let Some(handle) = first_survey_point_handle(host) {
|
||||
mark_point(host, handle);
|
||||
} else {
|
||||
host.push_info("No SURVEY point to mark");
|
||||
}
|
||||
true
|
||||
}
|
||||
"COUNT_MARKED_SURVEY_POINTS" => {
|
||||
let count = count_marked_survey_points(host);
|
||||
host.push_info(&format!("Marked SURVEY points: {count}"));
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read: count point entities on the SURVEY layer via the zero-copy reader.
|
||||
fn count_survey_points(host: &mut dyn HostApi) -> usize {
|
||||
let reader = host.document_reader();
|
||||
let mut count = 0usize;
|
||||
reader.for_each_entity(&mut |e| {
|
||||
if e.kind == ReaderEntityKind::Point && e.layer_name.eq_ignore_ascii_case("SURVEY") {
|
||||
count += 1;
|
||||
}
|
||||
});
|
||||
count
|
||||
}
|
||||
|
||||
/// Write: add a new point entity on the SURVEY layer through a validated RPC.
|
||||
fn add_survey_point(host: &mut dyn HostApi) -> Handle {
|
||||
let mut point = Point::from_coords(0.0, 0.0, 0.0);
|
||||
point.common.layer = "SURVEY".to_string();
|
||||
host.add_entity(EntityType::Point(point))
|
||||
}
|
||||
|
||||
/// Read: locate the first SURVEY point and return its handle.
|
||||
fn first_survey_point_handle(host: &mut dyn HostApi) -> Option<Handle> {
|
||||
let reader = host.document_reader();
|
||||
let mut handle = None;
|
||||
reader.for_each_entity(&mut |e| {
|
||||
if handle.is_none()
|
||||
&& e.kind == ReaderEntityKind::Point
|
||||
&& e.layer_name.eq_ignore_ascii_case("SURVEY")
|
||||
{
|
||||
handle = Some(e.handle);
|
||||
}
|
||||
});
|
||||
handle
|
||||
}
|
||||
|
||||
/// Write: attach an XDATA record to the entity, registering the APPID.
|
||||
fn mark_point(host: &mut dyn HostApi, handle: Handle) {
|
||||
let mut record = ExtendedDataRecord::new("SURVEYMARK");
|
||||
record.add_value(XDataValue::Integer32(1));
|
||||
if host.write_record(handle, record) {
|
||||
host.push_info(&format!("Marked SURVEY point {handle}"));
|
||||
} else {
|
||||
host.push_error(&format!("Failed to mark SURVEY point {handle}"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Read+write round-trip: count SURVEY points that have the SURVEYMARK XDATA.
|
||||
fn count_marked_survey_points(host: &mut dyn HostApi) -> usize {
|
||||
let reader = host.document_reader();
|
||||
let mut handles = Vec::new();
|
||||
reader.for_each_entity(&mut |e| {
|
||||
if e.kind == ReaderEntityKind::Point && e.layer_name.eq_ignore_ascii_case("SURVEY") {
|
||||
handles.push(e.handle);
|
||||
}
|
||||
});
|
||||
handles
|
||||
.into_iter()
|
||||
.filter(|h| host.read_record(*h, "SURVEYMARK").is_some())
|
||||
.count()
|
||||
}
|
||||
|
||||
struct TemplateModule;
|
||||
|
||||
impl CadModule for TemplateModule {
|
||||
fn id(&self) -> &'static str {
|
||||
MANIFEST.id
|
||||
}
|
||||
|
||||
fn title(&self) -> &'static str {
|
||||
"Template v2"
|
||||
}
|
||||
|
||||
fn ribbon_groups(&self) -> Vec<RibbonGroup> {
|
||||
vec![RibbonGroup {
|
||||
title: "Survey",
|
||||
tools: vec![
|
||||
RibbonItem::LargeTool(ToolDef {
|
||||
id: "COUNT_SURVEY_POINTS",
|
||||
label: "Count",
|
||||
icon: IconKind::Glyph("C"),
|
||||
event: ModuleEvent::Command("COUNT_SURVEY_POINTS".to_string()),
|
||||
}),
|
||||
RibbonItem::LargeTool(ToolDef {
|
||||
id: "ADD_SURVEY_POINT",
|
||||
label: "Add Point",
|
||||
icon: IconKind::Glyph("+"),
|
||||
event: ModuleEvent::Command("ADD_SURVEY_POINT".to_string()),
|
||||
}),
|
||||
RibbonItem::LargeTool(ToolDef {
|
||||
id: "MARK_FIRST_SURVEY_POINT",
|
||||
label: "Mark First",
|
||||
icon: IconKind::Glyph("M"),
|
||||
event: ModuleEvent::Command("MARK_FIRST_SURVEY_POINT".to_string()),
|
||||
}),
|
||||
RibbonItem::LargeTool(ToolDef {
|
||||
id: "COUNT_MARKED_SURVEY_POINTS",
|
||||
label: "Marked",
|
||||
icon: IconKind::Glyph("*"),
|
||||
event: ModuleEvent::Command("COUNT_MARKED_SURVEY_POINTS".to_string()),
|
||||
}),
|
||||
],
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export_plugin!(PluginTemplateV2);
|
||||
Loading…
Reference in a new issue