Skip to main content

truce_loader/
lib.rs

1//! Hot-reload mechanics for truce: dylib loading, ABI canary,
2//! vtable probe, and the shells (`HotShell<P, S>`, `StaticShell<P, L, S>`)
3//! that bridge the user-facing `truce_plugin::PluginLogic` /
4//! `truce_plugin::PluginLogic64` leaf traits onto
5//! [`truce_core::PluginRuntime`] for format wrappers.
6//!
7//! Plugin authors don't reach into this crate directly. They write
8//! `impl PluginLogic for MyPlugin` (the leaf trait is sample-pinned
9//! via the prelude re-export) and the `truce::plugin!` macro picks
10//! the static or hot shell based on the `shell` Cargo feature.
11//!
12//! # ABI boundary
13//!
14//! Across the dylib boundary the shell holds a
15//! `Box<dyn truce_plugin::PluginLogicCore<S>>` - the generic
16//! wrapper-facing trait that both leaf traits forward into via
17//! blanket impls in `truce-plugin`. The single trait object
18//! carries DSP and GUI methods through one vtable, with `S` baked
19//! in by the shell's generic parameter (and recorded in
20//! `AbiCanary::sample_precision` so a precision mismatch fails
21//! the canary check before vtable-binding).
22//!
23//! ```ignore
24//! use truce_loader::{AbiCanary, PluginLogic, PluginLogicCore};
25//!
26//! struct MyPlugin { /* ... */ }
27//! impl PluginLogic for MyPlugin { /* DSP + GUI */ }
28//!
29//! // Emitted by `truce::plugin!`; plugin authors don't write these
30//! // by hand. `Sample` resolves through the prelude alias
31//! // (`f32` for `prelude` / `prelude32` / `prelude64m`,
32//! // `f64` for `prelude64`). The macro also emits a
33//! // `truce_vtable_probe` symbol that constructs an internal
34//! // `ProbePlugin`; that type lives in `__macro_deps` and isn't
35//! // intended for direct use.
36//! #[unsafe(no_mangle)]
37//! pub fn truce_create(p: *const ()) -> Box<dyn PluginLogicCore<Sample>> {
38//!     Box::new(MyPlugin::new(/* params from p */))
39//! }
40//!
41//! #[unsafe(no_mangle)]
42//! pub fn truce_abi_canary() -> AbiCanary { AbiCanary::current::<Sample>() }
43//! ```
44
45#[doc(hidden)]
46pub mod __macro_deps {
47    pub use truce_core;
48    // `truce_plugin` carries the `PluginLogicCore` blanket the
49    // `export_plugin!` / `export_static!` macros need to name
50    // (`<L as PluginLogicCore<Sample>>::supports_in_place()` etc.).
51    // Re-exported here so the macro can resolve it via
52    // `$crate::__macro_deps::truce_plugin` regardless of whether the
53    // caller has `truce-plugin` as a direct dep.
54    pub use truce_plugin;
55    // `ProbePlugin` is a vtable-binding shim emitted into every
56    // `export_plugin!` expansion as the `truce_vtable_probe` symbol.
57    // It is not a public type; plugin authors never name it. Kept
58    // reachable here under `$crate::__macro_deps::` so the macro can
59    // resolve it without leaking the type at the crate root.
60    pub use crate::canary::ProbePlugin;
61}
62
63mod canary;
64mod safe_types;
65
66#[cfg(feature = "shell")]
67mod loader;
68#[cfg(feature = "shell")]
69pub mod shell;
70pub mod static_shell;
71
72pub use canary::AbiCanary;
73// `ProbePlugin`, `verify_probe`, and `ProbeError` are loader-internal.
74// `ProbePlugin` lives under `__macro_deps` so the `export_plugin!`
75// macro's `truce_vtable_probe` body can name it without leaking the
76// type at the crate root. `verify_probe` / `ProbeError` are only used
77// by `NativeLoader::build_candidate` (gated on `feature = "shell"`)
78// and are reached via `crate::canary` directly inside `loader.rs`.
79// Format wrappers and plugin authors reach the probe by dlopening
80// the `truce_vtable_probe` symbol the macro emits, not by `use` import.
81pub use safe_types::*;
82// Source the leaf + core traits directly from `truce-plugin` rather
83// than via the optional `truce-gui` re-export, so these names are
84// reachable regardless of whether the `builtin-gui` feature is on.
85pub use truce_plugin::{PluginLogic, PluginLogic64, PluginLogicCore};
86
87#[cfg(feature = "shell")]
88pub use loader::NativeLoader;
89
90/// Export the `#[unsafe(no_mangle)]` functions required by the shell.
91///
92/// `params_ptr` is a raw `Arc<Params>` pointer from the shell.
93/// The plugin receives shared params - one copy, no sync.
94#[macro_export]
95macro_rules! export_plugin {
96    ($logic:ty, $params:ty) => {
97        // `Sample` here is the prelude's `type Sample = ...` alias:
98        // `f32` for `prelude` / `prelude32` / `prelude64m`, `f64` for
99        // `prelude64`. `HotShell<P, S>` is generic over `S: Sample`
100        // so both precisions hot-reload through the same dylib export
101        // shape; the canary's `sample_precision` byte at load time
102        // guards against a shell built for one precision dlopening a
103        // dylib built for the other.
104        #[unsafe(no_mangle)]
105        pub fn truce_create(params_ptr: *const ()) -> Box<dyn $crate::PluginLogicCore<Sample>> {
106            let params: Arc<$params> = unsafe {
107                Arc::increment_strong_count(params_ptr as *const $params);
108                Arc::from_raw(params_ptr as *const $params)
109            };
110            // The plugin impls one of the leaf traits
111            // (`PluginLogic` for f32 or `PluginLogic64` for f64); the
112            // blanket impl inside `truce-plugin` gives it
113            // `PluginLogicCore<Sample>` automatically, so the cast
114            // here just picks the right vtable.
115            Box::new(<$logic>::new(params))
116        }
117
118        #[unsafe(no_mangle)]
119        pub fn truce_abi_canary() -> $crate::AbiCanary {
120            // `Sample` from the prelude - the dylib stamps its
121            // chosen precision into the canary so the shell can
122            // reject a mismatched load before vtable-binding.
123            $crate::AbiCanary::current::<Sample>()
124        }
125
126        #[unsafe(no_mangle)]
127        pub fn truce_vtable_probe() -> Box<dyn $crate::PluginLogicCore<Sample>> {
128            Box::new($crate::__macro_deps::ProbePlugin::default())
129        }
130    };
131}