1use std::panic::{AssertUnwindSafe, catch_unwind};
9use std::sync::OnceLock;
10
11use lumen_plugin_abi::raw::{fill, panic_message};
12
13use crate::abi::{Buf, ERR, HostVtable, OK, PANICKED};
14use crate::wire::{Call, CallOut, InitCx};
15use crate::{Cx, Host, HostInner, PluginFn, Registrar, RuntimePlugin, codec};
16
17#[doc(hidden)]
19pub use lumen_plugin_abi::raw::free_buf;
20
21static REGISTERED: OnceLock<Registered> = OnceLock::new();
25
26struct Registered {
27 fns: Vec<PluginFn>,
28 host: &'static HostInner,
29}
30
31fn init(plugin: &dyn RuntimePlugin, ctx: &[u8], host: *const HostVtable) -> (i32, Vec<u8>) {
34 if REGISTERED.get().is_some() {
35 return (
36 ERR,
37 b"already initialized; a runtime plugin holds one instance per process, and this \
38 library was loaded more than once"
39 .to_vec(),
40 );
41 }
42 let cx: InitCx = match codec::decode(ctx) {
43 Ok(c) => c,
44 Err(e) => return (ERR, format!("context decode: {e}").into_bytes()),
45 };
46 let host: &'static HostInner = Box::leak(Box::new(unsafe { HostInner::from_vtable(host) }));
51 let mut registrar = Registrar::new(Host::new(host));
52 if let Err(e) = plugin.register(&mut registrar, &cx) {
53 return (ERR, e.message.into_bytes());
54 }
55 let manifest = registrar.manifest();
56 let _ = REGISTERED.set(Registered {
57 fns: registrar.take_fns(),
58 host,
59 });
60 match codec::encode(&manifest) {
61 Ok(bytes) => (OK, bytes),
62 Err(e) => (ERR, format!("manifest encode: {e}").into_bytes()),
63 }
64}
65
66fn call(input: &[u8]) -> (i32, Vec<u8>) {
69 let Some(registered) = REGISTERED.get() else {
70 return (ERR, b"called before init".to_vec());
71 };
72 let call: Call = match codec::decode(input) {
73 Ok(c) => c,
74 Err(e) => return (ERR, format!("call decode: {e}").into_bytes()),
75 };
76 let Some(f) = registered.fns.get(call.index as usize) else {
77 return (
78 ERR,
79 format!(
80 "no function at index {}; this plugin registered {}",
81 call.index,
82 registered.fns.len()
83 )
84 .into_bytes(),
85 );
86 };
87 let mut commands = Vec::new();
88 let ret = {
89 let mut cx = Cx::new(&call.args, &mut commands, Host::new(registered.host));
90 f.invoke(&mut cx)
91 };
92 match codec::encode(&CallOut { ret, commands }) {
93 Ok(bytes) => (OK, bytes),
94 Err(e) => (
98 ERR,
99 format!("{}: result encode: {e}", f.name()).into_bytes(),
100 ),
101 }
102}
103
104#[doc(hidden)]
116pub unsafe fn init_entry(
117 plugin: fn() -> &'static dyn RuntimePlugin,
118 ctx: *const u8,
119 ctx_len: usize,
120 host: *const HostVtable,
121 out: *mut Buf,
122) -> i32 {
123 let ctx = unsafe { std::slice::from_raw_parts(ctx, ctx_len) };
124 let (status, bytes) = match catch_unwind(AssertUnwindSafe(|| init(plugin(), ctx, host))) {
128 Ok(r) => r,
129 Err(payload) => (PANICKED, panic_message(payload.as_ref()).into_bytes()),
130 };
131 if !bytes.is_empty() {
132 fill(unsafe { &mut *out }, bytes);
133 }
134 status
135}
136
137#[doc(hidden)]
144pub unsafe fn call_entry(input: *const u8, input_len: usize, out: *mut Buf) -> i32 {
145 let input = unsafe { std::slice::from_raw_parts(input, input_len) };
146 let (status, bytes) = match catch_unwind(AssertUnwindSafe(|| call(input))) {
147 Ok(r) => r,
148 Err(payload) => (PANICKED, panic_message(payload.as_ref()).into_bytes()),
149 };
150 if !bytes.is_empty() {
151 fill(unsafe { &mut *out }, bytes);
152 }
153 status
154}
155
156#[doc(hidden)]
160pub fn shutdown_entry(plugin: fn() -> &'static dyn RuntimePlugin) {
161 if REGISTERED.get().is_none() {
162 return;
163 }
164 let _ = catch_unwind(AssertUnwindSafe(|| plugin().shutdown()));
165}
166
167#[macro_export]
179macro_rules! lumen_plugin {
180 ($ctor:expr) => {
181 const _: () = {
182 static INSTANCE: ::std::sync::OnceLock<::std::boxed::Box<dyn $crate::RuntimePlugin>> =
183 ::std::sync::OnceLock::new();
184
185 fn instance() -> &'static dyn $crate::RuntimePlugin {
186 INSTANCE
187 .get_or_init(|| ::std::boxed::Box::new(($ctor)()))
188 .as_ref()
189 }
190
191 unsafe extern "C" fn init_thunk(
192 ctx: *const u8,
193 ctx_len: usize,
194 host: *const $crate::abi::HostVtable,
195 out: *mut $crate::abi::Buf,
196 ) -> i32 {
197 unsafe { $crate::export::init_entry(instance, ctx, ctx_len, host, out) }
198 }
199
200 unsafe extern "C" fn call_thunk(
201 input: *const u8,
202 input_len: usize,
203 _ctx: *const u8,
204 _ctx_len: usize,
205 out: *mut $crate::abi::Buf,
206 ) -> i32 {
207 unsafe { $crate::export::call_entry(input, input_len, out) }
208 }
209
210 unsafe extern "C" fn shutdown_thunk() {
211 $crate::export::shutdown_entry(instance);
212 }
213
214 static DESC: $crate::abi::Desc = $crate::abi::Desc {
215 abi_version: $crate::abi::ABI_VERSION,
216 struct_size: ::std::mem::size_of::<$crate::abi::Desc>() as u32,
217 script_wire_version: $crate::SCRIPT_WIRE_VERSION,
218 flags: if ::std::cfg!(panic = "abort") {
219 $crate::abi::FLAG_PANIC_ABORT
220 } else {
221 0
222 },
223 reserved: 0,
224 name: concat!(env!("CARGO_PKG_NAME"), "\0").as_ptr()
225 as *const ::std::os::raw::c_char,
226 version: concat!(env!("CARGO_PKG_VERSION"), "\0").as_ptr()
227 as *const ::std::os::raw::c_char,
228 init: Some(init_thunk),
229 call: Some(call_thunk),
230 shutdown: Some(shutdown_thunk),
231 free: Some($crate::export::free_buf),
232 };
233
234 #[unsafe(no_mangle)]
235 pub extern "C" fn lumen_plugin_v1() -> *const $crate::abi::Desc {
236 &DESC
237 }
238 };
239 };
240}