truce_core/export.rs
1use std::sync::Arc;
2
3use crate::plugin::Plugin;
4use truce_params::{ParamInfo, Params};
5
6/// Unified export trait for all plugin formats.
7///
8/// Implement this once on your plugin type. All format wrappers
9/// (CLAP, VST3, AU, standalone) use this to construct your plugin
10/// and access its parameters.
11///
12/// ```ignore
13/// impl PluginExport for MyPlugin {
14/// type Params = MyParams;
15/// fn create() -> Self { Self::new() }
16/// fn params(&self) -> &MyParams { &self.params }
17/// fn params_arc(&self) -> Arc<MyParams> { self.params.clone() }
18/// }
19/// ```
20///
21/// All parameter mutation goes through the atomic-backed accessors on
22/// `&Params` - no `&mut Params` accessor is required, which keeps the
23/// trait usable while the editor holds an `Arc<Params>` reader.
24pub trait PluginExport: Plugin + Sized {
25 type Params: Params;
26
27 /// Construct a new instance of the plugin.
28 fn create() -> Self;
29
30 /// Immutable access to the parameter struct.
31 fn params(&self) -> &Self::Params;
32
33 /// Get a shared `Arc` reference to the parameter struct.
34 ///
35 /// Used by format wrappers to pass params to GUI closures without
36 /// raw pointers. The Arc is cloned (cheap ref-count bump), not the
37 /// params themselves.
38 fn params_arc(&self) -> Arc<Self::Params>;
39
40 /// Static parameter metadata for registration-time access.
41 ///
42 /// Format wrappers' `register_*` paths (`truce-vst2`, `truce-vst3`,
43 /// `truce-au`, `truce-aax`) call this instead of the historical
44 /// `Self::create().params().param_infos()` walk, which constructed
45 /// a full plugin instance - including any allocation the
46 /// constructor did (DSP buffers, FFT plans, image atlases) - just
47 /// to read static metadata. On platforms where registration runs
48 /// from C++ static initializers (notably AAX `Describe`) those
49 /// allocations sit in a fragile init-order regime; avoiding them
50 /// closes a class of platform-dependent registration bugs.
51 ///
52 /// Default impl prefers
53 /// [`Params::param_infos_static`]
54 /// when it returns a non-empty vec (the `#[derive(Params)]` path
55 /// emits an override built from compile-time metadata) and falls
56 /// back to the runtime construction otherwise - so plugins with
57 /// hand-written `Params` impls that don't override the static
58 /// path keep working unchanged.
59 #[must_use]
60 fn param_infos_static() -> Vec<ParamInfo> {
61 let from_params = <Self::Params as Params>::param_infos_static();
62 if from_params.is_empty() {
63 Self::create().params().param_infos()
64 } else {
65 from_params
66 }
67 }
68
69 /// Static "does this plugin have an editor" predicate. AAX's
70 /// `Describe` path needs to know this at registration time
71 /// (`has_editor` field on the static descriptor). Paired with
72 /// [`Self::param_infos_static`], this is the second reason every
73 /// format's registration walk constructed a plugin.
74 ///
75 /// Default impl falls back to that runtime path so unannotated
76 /// plugins keep working. Plugins that want to avoid the
77 /// static-init plugin construction (notably for AAX hosts that
78 /// run `Describe` very early) override with a `const`-style
79 /// answer:
80 ///
81 /// ```ignore
82 /// impl PluginExport for MyPlugin {
83 /// // ...
84 /// fn has_editor_static() -> bool { true }
85 /// }
86 /// ```
87 ///
88 /// VST2 / VST3 / AU never call this - they don't need the answer
89 /// at registration time.
90 #[must_use]
91 fn has_editor_static() -> bool {
92 Self::create().editor().is_some()
93 }
94}