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::Plugin`] 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    pub use truce_gui;
49    // `ProbePlugin` is a vtable-binding shim emitted into every
50    // `export_plugin!` expansion as the `truce_vtable_probe` symbol.
51    // It is not a public type; plugin authors never name it. Kept
52    // reachable here under `$crate::__macro_deps::` so the macro can
53    // resolve it without leaking the type at the crate root.
54    pub use crate::canary::ProbePlugin;
55}
56
57mod canary;
58mod safe_types;
59
60#[cfg(feature = "shell")]
61mod loader;
62#[cfg(feature = "shell")]
63pub mod shell;
64pub mod static_shell;
65
66pub use canary::AbiCanary;
67// `ProbePlugin`, `verify_probe`, and `ProbeError` are loader-internal.
68// `ProbePlugin` lives under `__macro_deps` so the `export_plugin!`
69// macro's `truce_vtable_probe` body can name it without leaking the
70// type at the crate root. `verify_probe` / `ProbeError` are only used
71// by `NativeLoader::build_candidate` (gated on `feature = "shell"`)
72// and are reached via `crate::canary` directly inside `loader.rs`.
73// Format wrappers and plugin authors reach the probe by dlopening
74// the `truce_vtable_probe` symbol the macro emits, not by `use` import.
75pub use safe_types::*;
76pub use truce_gui::{PluginLogic, PluginLogic64, PluginLogicCore};
77
78#[cfg(feature = "shell")]
79pub use loader::NativeLoader;
80
81/// Export the `#[unsafe(no_mangle)]` functions required by the shell.
82///
83/// `params_ptr` is a raw `Arc<Params>` pointer from the shell.
84/// The plugin receives shared params - one copy, no sync.
85#[macro_export]
86macro_rules! export_plugin {
87    ($logic:ty, $params:ty) => {
88        // `Sample` here is the prelude's `type Sample = ...` alias:
89        // `f32` for `prelude` / `prelude32` / `prelude64m`, `f64` for
90        // `prelude64`. `HotShell<P, S>` is generic over `S: Sample`
91        // so both precisions hot-reload through the same dylib export
92        // shape; the canary's `sample_precision` byte at load time
93        // guards against a shell built for one precision dlopening a
94        // dylib built for the other.
95        #[unsafe(no_mangle)]
96        pub fn truce_create(params_ptr: *const ()) -> Box<dyn $crate::PluginLogicCore<Sample>> {
97            let params: Arc<$params> = unsafe {
98                Arc::increment_strong_count(params_ptr as *const $params);
99                Arc::from_raw(params_ptr as *const $params)
100            };
101            // The plugin impls one of the leaf traits
102            // (`PluginLogic` for f32 or `PluginLogic64` for f64); the
103            // blanket impl inside `truce-gui` gives it
104            // `PluginLogicCore<Sample>` automatically, so the cast
105            // here just picks the right vtable.
106            Box::new(<$logic>::new(params))
107        }
108
109        #[unsafe(no_mangle)]
110        pub fn truce_abi_canary() -> $crate::AbiCanary {
111            // `Sample` from the prelude - the dylib stamps its
112            // chosen precision into the canary so the shell can
113            // reject a mismatched load before vtable-binding.
114            $crate::AbiCanary::current::<Sample>()
115        }
116
117        #[unsafe(no_mangle)]
118        pub fn truce_vtable_probe() -> Box<dyn $crate::PluginLogicCore<Sample>> {
119            Box::new($crate::__macro_deps::ProbePlugin::default())
120        }
121    };
122}