Skip to main content

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}