feat(plugin-api): entity-class display declarations on the manifest (API v10)

Plugins can now declare the entity classes they identify via XDATA, and the
host resolves them to display-only type labels while the declaring plugin is
loaded.

- Append `BuiltinPlugin::entity_classes()` (a guarded trailing vtable slot,
  the same additive pattern as `on_load` in v5) returning a &'static
  `EntityClasses { app_id, class_prefix, labels }`. The static
  `PluginManifest` struct is deliberately untouched: older cdylibs share it
  by pointer and its layout is frozen ABI since v1, so adding a field would
  read past their allocation.
- `OwnedEntityClasses` travels on the registered manifest as
  `OwnedPluginManifest.entity_classes` with `#[serde(default)]`, so manifests
  serialized before the field keep decoding (older runners tolerate it
  both ways).
- The runner folds the declaration into the GetManifest registration only
  when the loaded cdylib's exported `ocs_plugin_api_version` symbol reports
  >= 10 — an older binary's vtable predates the slot, so it is never called;
  its entities simply keep their native type names.
- `PluginManager::entity_class_declarations` exposes the live set, filtered
  to alive, non-disabled plugins in load order. The registered manifests are
  the only source and nothing is cached: not installed, disabled,
  cap-excluded (`OCS_PLUGIN_MAX_API_VERSION`), failed at load, removed, or a
  dead process all contribute nothing.
- Embedded version info `api_version` follows the bump to 10;
  `API_VERSION_MIN_SUPPORTED` stays 3.

Tests: runner fold (v10 folds, pre-v10 skipped, `entity_classes()` panic is
contained and reported), manager filtering (disabled / dead / undeclaring
skipped; declarations disappear once the process dies — verified with a real
killed child), bincode registration round-trip, and older-manifest serde
default.
This commit is contained in:
Sebastian committed 2026-10-15 22:39:09 +02:00
1 parent 859070403c
commit 60eb7b086e
9 files changed
+502 -13

No files matched your search

+19 -1
View File
@@ -19,7 +19,7 @@ use serde::{Deserialize, Serialize};
use crate::host::{CommandSource, CommandStep, HostSettingValue};
use crate::manifest::ApiVersion;
use crate::ribbon::owned::{OwnedPluginManifest, OwnedRibbonGroup};
use crate::ribbon::owned::{OwnedEntityClasses, OwnedPluginManifest, OwnedRibbonGroup};
pub use crate::host::PreviewWire;
pub use codec::xdata::{ExtendedDataRecord, XDataValue};
@@ -307,6 +307,24 @@ impl From<&'static crate::manifest::PluginManifest> for OwnedPluginManifest {
ribbon_order: m.ribbon_order,
xdata_apps: m.xdata_apps.iter().map(|s| s.to_string()).collect(),
command_prefixes: m.command_prefixes.iter().map(|s| s.to_string()).collect(),
// The declaration does not live on the static manifest struct (its
// layout is frozen ABI, shared by pointer with older cdylibs); the
// runner folds the API-v10 trait accessor in after conversion.
entity_classes: None,
}
}
}
impl From<&'static crate::manifest::EntityClasses> for OwnedEntityClasses {
fn from(c: &'static crate::manifest::EntityClasses) -> Self {
Self {
app_id: c.app_id.to_string(),
class_prefix: c.class_prefix.to_string(),
labels: c
.labels
.iter()
.map(|(key, label)| (key.to_string(), label.to_string()))
.collect(),
}
}
}