nautilus_plugin/macros.rs
1// -------------------------------------------------------------------------------------------------
2// Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3// https://nautechsystems.io
4//
5// Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6// You may not use this file except in compliance with the License.
7// You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Declarative macros for the plug-in author-facing surface.
17//!
18//! The top-level [`nautilus_plugin!`](crate::nautilus_plugin) macro emits the
19//! `extern "C" nautilus_plugin_init` symbol, the static
20//! [`PluginManifest`](crate::manifest::PluginManifest), and the per-plug-point
21//! registration arrays for every type listed.
22
23/// Defines a plug-in's static manifest and emits the `nautilus_plugin_init`
24/// entry symbol.
25///
26/// Use this exactly once per plug-in cdylib, at module scope (typically in
27/// `lib.rs`).
28///
29/// # Required fields
30///
31/// - `name`: short machine-readable plug-in name.
32/// - `version`: plug-in version string (usually `env!("CARGO_PKG_VERSION")`).
33///
34/// # Optional fields
35///
36/// - `vendor`: free-form vendor/author string (default `""`).
37/// - `custom_data`: array of types implementing
38/// [`PluginCustomData`](crate::surfaces::custom_data::PluginCustomData).
39/// - `actors`: array of types implementing
40/// [`PluginActor`](crate::surfaces::actor::PluginActor).
41/// - `strategies`: array of types implementing
42/// [`PluginStrategy`](crate::surfaces::strategy::PluginStrategy).
43/// - `controllers`: array of types implementing
44/// [`PluginController`](crate::surfaces::controller::PluginController).
45///
46/// # Example
47///
48/// ```ignore
49/// use nautilus_plugin::prelude::*;
50///
51/// pub struct MyTick { ts_event: u64, ts_init: u64, value: f64 }
52///
53/// impl PluginCustomData for MyTick {
54/// const TYPE_NAME: &'static str = "MyTick";
55/// fn ts_event(&self) -> u64 { self.ts_event }
56/// fn ts_init(&self) -> u64 { self.ts_init }
57/// // ... other methods
58/// }
59///
60/// nautilus_plugin::nautilus_plugin! {
61/// name: "my-plugin",
62/// version: env!("CARGO_PKG_VERSION"),
63/// custom_data: [MyTick],
64/// }
65/// ```
66#[macro_export]
67macro_rules! nautilus_plugin {
68 (
69 $(name: $name:expr,)?
70 $(vendor: $vendor:expr,)?
71 $(version: $version:expr,)?
72 $(custom_data: [$($cd:ty),* $(,)?] ,)?
73 $(actors: [$($act:ty),* $(,)?] ,)?
74 $(strategies: [$($strategy:ty),* $(,)?] ,)?
75 $(controllers: [$($controller:ty),* $(,)?] ,)?
76 ) => {
77 $crate::__nautilus_plugin_impl! {
78 @parse
79 name = ($($name)?),
80 vendor = ($($vendor)?),
81 version = ($($version)?),
82 custom_data = ($($($cd),*)?),
83 actors = ($($($act),*)?),
84 strategies = ($($($strategy),*)?),
85 controllers = ($($($controller),*)?),
86 }
87 };
88}
89
90/// Internal expansion of [`nautilus_plugin!`]. Not part of the public API.
91#[doc(hidden)]
92#[macro_export]
93macro_rules! __nautilus_plugin_impl {
94 (
95 @parse
96 name = ($name:expr),
97 vendor = ($($vendor:expr)?),
98 version = ($version:expr),
99 custom_data = ($($cd:ty),*),
100 actors = ($($act:ty),*),
101 strategies = ($($strategy:ty),*),
102 controllers = ($($controller:ty),*),
103 ) => {
104 const _: () = {
105 // Compile-time guard: every listed type implements the trait. The
106 // bound checks happen at the call sites below; this block keeps
107 // the trait import scoped to the macro expansion.
108
109 #[allow(unused_imports)]
110 use $crate::surfaces::custom_data::PluginCustomData as _PluginCustomData;
111 #[allow(unused_imports)]
112 use $crate::surfaces::actor::PluginActor as _PluginActor;
113 #[allow(unused_imports)]
114 use $crate::surfaces::strategy::PluginStrategy as _PluginStrategy;
115 #[allow(unused_imports)]
116 use $crate::surfaces::controller::PluginController as _PluginController;
117
118 static CUSTOM_DATA: ::std::sync::LazyLock<
119 [$crate::manifest::CustomDataRegistration; $crate::__nautilus_plugin_impl!(@count $($cd),*)]
120 > = ::std::sync::LazyLock::new(|| {
121 [
122 $(
123 $crate::manifest::CustomDataRegistration {
124 type_name: $crate::boundary::BorrowedStr::from_str(
125 <$cd as $crate::surfaces::custom_data::PluginCustomData>::TYPE_NAME,
126 ),
127 vtable: $crate::surfaces::custom_data::custom_data_vtable::<$cd>(),
128 },
129 )*
130 ]
131 });
132
133 static ACTORS: ::std::sync::LazyLock<
134 [$crate::manifest::ActorRegistration; $crate::__nautilus_plugin_impl!(@count $($act),*)]
135 > = ::std::sync::LazyLock::new(|| {
136 [
137 $(
138 $crate::manifest::ActorRegistration {
139 type_name: $crate::boundary::BorrowedStr::from_str(
140 <$act as $crate::surfaces::actor::PluginActor>::TYPE_NAME,
141 ),
142 vtable: $crate::surfaces::actor::actor_vtable::<$act>(),
143 },
144 )*
145 ]
146 });
147
148 static STRATEGIES: ::std::sync::LazyLock<
149 [$crate::manifest::StrategyRegistration; $crate::__nautilus_plugin_impl!(@count $($strategy),*)]
150 > = ::std::sync::LazyLock::new(|| {
151 [
152 $(
153 $crate::manifest::StrategyRegistration {
154 type_name: $crate::boundary::BorrowedStr::from_str(
155 <$strategy as $crate::surfaces::strategy::PluginStrategy>::TYPE_NAME,
156 ),
157 vtable: $crate::surfaces::strategy::strategy_vtable::<$strategy>(),
158 },
159 )*
160 ]
161 });
162
163 static CONTROLLERS: ::std::sync::LazyLock<
164 [$crate::manifest::ControllerRegistration; $crate::__nautilus_plugin_impl!(@count $($controller),*)]
165 > = ::std::sync::LazyLock::new(|| {
166 [
167 $(
168 $crate::manifest::ControllerRegistration {
169 type_name: $crate::boundary::BorrowedStr::from_str(
170 <$controller as $crate::surfaces::controller::PluginController>::TYPE_NAME,
171 ),
172 vtable: $crate::surfaces::controller::controller_vtable::<$controller>(),
173 },
174 )*
175 ]
176 });
177
178 static MANIFEST: ::std::sync::LazyLock<$crate::manifest::PluginManifest> =
179 ::std::sync::LazyLock::new(|| $crate::manifest::PluginManifest {
180 abi_version: $crate::NAUTILUS_PLUGIN_ABI_VERSION,
181 plugin_name: $crate::boundary::BorrowedStr::from_str($name),
182 plugin_vendor: $crate::boundary::BorrowedStr::from_str(
183 $crate::__nautilus_plugin_impl!(@opt $($vendor)?),
184 ),
185 plugin_version: $crate::boundary::BorrowedStr::from_str($version),
186 build_id: $crate::manifest::PluginBuildId::current(),
187 custom_data: $crate::boundary::Slice::from_slice(&*CUSTOM_DATA),
188 actors: $crate::boundary::Slice::from_slice(&*ACTORS),
189 strategies: $crate::boundary::Slice::from_slice(&*STRATEGIES),
190 controllers: $crate::boundary::Slice::from_slice(&*CONTROLLERS),
191 });
192
193 #[unsafe(no_mangle)]
194 pub unsafe extern "C" fn nautilus_plugin_init(
195 host: *const $crate::host::HostVTable,
196 ) -> *const $crate::manifest::PluginManifest {
197 let result = ::std::panic::catch_unwind(|| {
198 if host.is_null() {
199 return ::core::ptr::null::<$crate::manifest::PluginManifest>();
200 }
201 // SAFETY: host pointer is non-null and the host commits
202 // to keeping the vtable live for the process lifetime.
203 let host_ref = unsafe { &*host };
204 if host_ref.abi_version != $crate::NAUTILUS_PLUGIN_ABI_VERSION {
205 return ::core::ptr::null();
206 }
207 &*MANIFEST as *const _
208 });
209
210 match result {
211 Ok(ptr) => ptr,
212 Err(payload) => {
213 $crate::panic::drop_payload(payload);
214 ::core::ptr::null()
215 }
216 }
217 }
218 };
219 };
220
221 // Empty-string default when the optional `vendor` field is omitted
222 (@opt) => { "" };
223 (@opt $vendor:expr) => { $vendor };
224
225 // Counts the listed types so the registration array has a fixed size
226 (@count) => { 0usize };
227 (@count $head:ty $(, $tail:ty)*) => {
228 1usize + $crate::__nautilus_plugin_impl!(@count $($tail),*)
229 };
230}