Skip to main content

fre_rs/
macros.rs

1#[allow(unused_imports)]
2use super::*;
3
4
5/// Generates and links the required Flash Runtime Extension entry points and lifecycle hooks,
6/// bridging the C ABI with safe Rust abstractions.
7/// 
8/// This macro accepts two external symbols as arguments for `extern "C"` functions,
9/// and four functions: [`Initializer`], [`Finalizer`], [`ContextInitializer`], [`ContextFinalizer`].
10/// Some of these arguments are optional.
11/// 
12/// ## Full Example
13/// ```
14/// mod lib {
15///     use fre_rs::prelude::*;
16///     fre_rs::extension! {
17///         extern symbol_initializer, symbol_finalizer;
18///         move initializer, final finalizer;
19///         gen context_initializer, final context_finalizer;
20///     }
21///     struct ExtensionData (i32);
22///     impl Data for ExtensionData {}
23///     fn initializer() -> Option<Box<dyn Any>> {Some(ExtensionData(0).into_boxed())}
24///     fn finalizer(ext_data: Option<Box<dyn Any>>) {ExtensionData::from_boxed(ext_data.unwrap()).unwrap().0 = -1;}
25///     fn context_initializer(ctx: &CurrentContext) -> (Option<Box<dyn Any>>, FunctionSet) {
26///         let mut funcs = FunctionSet::with_capacity(1);
27///         if ctx.ty().is_some() {
28///             funcs.add(None, None, method_name);
29///         } else {
30///             funcs.add(None, None, method_name2);
31///         }
32///         return (None, funcs);
33///     }
34///     fn context_finalizer(ctx: &CurrentContext) {ctx.set_actionscript_data(as3::null)}
35///     fn method_implementation <'a> (ctx: &CurrentContext<'a>, data: Option<&mut dyn Any>, args: &[as3::Object<'a>]) -> as3::Object<'a> {as3::null}
36///     fre_rs::function! (method_name use method_implementation);
37///     fre_rs::function! {
38///         method_name2 (_, _, _) -> Option<as3::Array> {None}
39///     }
40/// }
41/// ```
42/// ## Minimal Example
43/// ```
44/// mod lib {
45///     use fre_rs::prelude::*;
46///     fre_rs::extension! {
47///         extern symbol_initializer;
48///         gen context_initializer, final;
49///     }
50///     fn context_initializer(_: &CurrentContext) -> (Option<Box<dyn Any>>, FunctionSet) {
51///         let mut funcs = FunctionSet::new();
52///         funcs.add(None, None, method_name);
53///         (None, funcs)
54///     }
55///     fre_rs::function! {
56///         method_name (ctx, _, args) -> as3::String {
57///             ctx.trace(args);
58///             as3::String::new(ctx, "Hello! Flash Runtime.")
59///         }
60///     }
61/// }
62/// ```
63/// 
64#[macro_export]
65macro_rules! extension {
66    {// #0
67        extern $symbol_initializer:ident $(, $symbol_finalizer:ident;
68        move $initializer:path, final $($finalizer:path)?)?;
69        gen $context_initializer:path, final $($context_finalizer:path)?;
70    } => {
71        const _: () = {
72        mod _flash_runtime_extension {
73            use super::*;
74            $crate::extension! {@Extern [$symbol_initializer $(, $symbol_finalizer, $initializer $(, $finalizer)?)?]}
75            #[allow(unsafe_op_in_unsafe_fn)]
76            unsafe extern "C" fn ctx_initializer (
77                ext_data: $crate::c::markers::FREData,
78                ctx_type: $crate::c::markers::FREStr,
79                ctx: $crate::c::markers::FREContext,
80                num_funcs_to_set: *mut u32,
81                funcs_to_set: *mut *const $crate::c::ffi::FRENamedFunction,
82            ) {
83                let context_initializer: $crate::function::ContextInitializer = $context_initializer;
84                $crate::context::CurrentContext::with_context_initializer(ext_data, ctx_type, &ctx, num_funcs_to_set, funcs_to_set, $context_initializer);
85            }
86            #[allow(unsafe_op_in_unsafe_fn)]
87            unsafe extern "C" fn ctx_finalizer (ctx: $crate::c::markers::FREContext) {
88                $crate::context::CurrentContext::with(&ctx, |ctx| {
89                    $(
90                        let context_finalizer: $crate::function::ContextFinalizer = $context_finalizer;
91                        context_finalizer(ctx);
92                    )?
93                    let ctx = *(ctx as *const $crate::context::CurrentContext as *const $crate::context::ForeignContext);
94                    let raw = ctx
95                        .get_native_data()
96                        .expect("Failed to retrieve native data from FFI.")
97                        .expect("`ContextRegistry` is expected in native data but is missing.");
98                    assert!(<::std::cell::RefCell<$crate::context::ContextRegistry> as $crate::data::Data>::ref_from(raw).is_ok());
99                    $crate::data::drop_from(raw);
100                });
101
102            }
103        }
104        };
105    };
106    {// #1
107        @Extern [$symbol_initializer:ident, $symbol_finalizer:ident, $initializer:path $(, $finalizer:path)?]
108    } => {
109        #[allow(unsafe_op_in_unsafe_fn, non_snake_case)]
110        #[unsafe(no_mangle)]
111        pub unsafe extern "C" fn $symbol_initializer (
112            ext_data_to_set: *mut $crate::c::markers::FREData,
113            ctx_initializer_to_set: *mut $crate::c::ffi::FREContextInitializer,
114            ctx_finalizer_to_set: *mut $crate::c::ffi::FREContextFinalizer,
115        ) {
116            assert!(!ext_data_to_set.is_null());
117            assert!(!ctx_initializer_to_set.is_null());
118            assert!(!ctx_finalizer_to_set.is_null());
119            let initializer: $crate::function::Initializer = $initializer;
120            if let Some(ext_data) = initializer() {
121                let ext_data = ::std::sync::Arc::new(::std::sync::Mutex::new(ext_data));
122                *ext_data_to_set = <::std::sync::Arc<::std::sync::Mutex<Box<dyn ::std::any::Any>>> as $crate::data::Data>::into_raw(ext_data).as_ptr();
123            }
124            *ctx_initializer_to_set = ctx_initializer;
125            *ctx_finalizer_to_set = Some(ctx_finalizer);
126            $crate::extension! (@Hook);
127        }
128        #[allow(unsafe_op_in_unsafe_fn, non_snake_case)]
129        #[unsafe(no_mangle)]
130        pub unsafe extern "C" fn $symbol_finalizer (ext_data: $crate::c::markers::FREData) {
131            let ext_data = $crate::validated::NonNullFREData::new(ext_data)
132                .map(|raw| {
133                    let arc_mutex_boxed = <::std::sync::Arc<::std::sync::Mutex<Box<dyn ::std::any::Any>>> as $crate::data::Data>::from_raw(raw);
134                    let mutex = ::std::sync::Arc::try_unwrap(arc_mutex_boxed).expect("INVARIANT: No context exists.");
135                    let boxed = mutex.into_inner().expect("Mutex poisoned.");
136                    boxed
137                });
138            $(
139                let finalizer: $crate::function::Finalizer = $finalizer;
140                finalizer(ext_data);
141            )?
142        }
143    };
144    {// #2
145        @Extern [$symbol_initializer:ident]
146    } => {
147        #[allow(unsafe_op_in_unsafe_fn, non_snake_case)]
148        #[unsafe(no_mangle)]
149        pub unsafe extern "C" fn $symbol_initializer (
150            ext_data_to_set: *mut $crate::c::markers::FREData,
151            ctx_initializer_to_set: *mut $crate::c::ffi::FREContextInitializer,
152            ctx_finalizer_to_set: *mut $crate::c::ffi::FREContextFinalizer,
153        ) {
154            assert!(!ext_data_to_set.is_null());
155            assert!(!ctx_initializer_to_set.is_null());
156            assert!(!ctx_finalizer_to_set.is_null());
157            *ctx_initializer_to_set = ctx_initializer;
158            *ctx_finalizer_to_set = Some(ctx_finalizer);
159            $crate::extension! (@Hook);
160        }
161    };
162    (// #3
163        @Hook
164    ) => {
165        static INIT_HOOK: ::std::sync::Once = ::std::sync::Once::new();
166        INIT_HOOK.call_once(|| {
167            let default_hook = ::std::panic::take_hook();
168            ::std::panic::set_hook(Box::new(move |info| {
169                let payload = info.payload_as_str().unwrap_or_default();
170                let location = info.location()
171                    .map(|l| format!("at {}:{}:{}", l.file(), l.line(), l.column()))
172                    .unwrap_or_default();
173                let backtrace = ::std::backtrace::Backtrace::force_capture();
174                let s = format!("{payload}\n{location}\n{backtrace}");
175                $crate::__private::LAST_PANIC_INFO.with(|i| {*i.borrow_mut() = Some(s);});
176                default_hook(info);
177            }));
178        });
179    }
180}
181
182
183/// Defines a function intended for context registration by generating its
184/// ABI-compatible wrapper and binding it to a Rust implementation.
185///
186/// Expands to a `&'static` constant of type [`FunctionImplementation`],
187/// intended to be added to a [`FunctionSet`].
188/// 
189/// This macro supports two forms:
190/// one that defines a function inline, and another that binds to an existing
191/// [`Function`] using the `use` keyword.
192///
193/// For larger or more complex implementations, the latter can provide better
194/// IDE support. However, it introduces two distinct items with the same
195/// semantics, so naming should be chosen carefully to avoid confusion.
196/// 
197/// # Panic Handling
198/// 
199/// Any [`panic`] occurring within the function body is intercepted via
200/// [`std::panic::catch_unwind`]. Instead of unwinding across the FFI boundary,
201/// an [`as3::Error`] containing the captured panic details is constructed and
202/// returned to the Flash Runtime.
203///
204/// This fallback return value is **NOT** constrained by the return type
205/// declared in the macro invocation. On the ActionScript side, the result may
206/// either be expected and handled as an `Error`, or not. In the latter case,
207/// if an [`as3::Error`] is returned, casting it to a non-error type yields
208/// `null` and may lead to runtime exceptions.
209///
210/// When the [`as3::Error`] is properly handled, the Flash Runtime remains
211/// stable. However, care must be taken to avoid leaving the native extension
212/// in an inconsistent state; resources should be managed reliably even in the
213/// presence of panics.
214/// 
215/// ## Full Example
216/// ```
217/// mod lib {
218///     use fre_rs::prelude::*;
219///     fre_rs::function! {
220///         method_name (ctx, data, args) -> as3::Object {
221///             return ctx.get_actionscript_data();
222///         }
223///     }
224///     fre_rs::function! (method_name2 use method_implementation);
225///     fn method_implementation <'a> (ctx: &CurrentContext<'a>, data: Option<&mut dyn Any>, args: &[as3::Object<'a>]) -> as3::Object<'a> {as3::null}
226/// }
227/// ```
228/// ## Minimal Example
229/// ```
230/// mod lib {
231///     fre_rs::function! {
232///         method_name (_, _, _) {}
233///     }
234/// }
235/// ```
236/// 
237#[macro_export]
238macro_rules! function {
239    {// #0
240        $name:ident ($($arguments:tt)+) $(-> $return_type:ty)? $body:block
241    } => {
242        #[allow(non_upper_case_globals)]
243        pub const $name: &'static $crate::function::FunctionImplementation = & $crate::function::FunctionImplementation::new(
244            $crate::function!(@Name $name), {
245            #[allow(unsafe_op_in_unsafe_fn)]
246            unsafe extern "C" fn abi(
247                ctx: $crate::c::markers::FREContext,
248                func_data: $crate::c::markers::FREData,
249                argc: u32,
250                argv: *const $crate::c::markers::FREObject,
251            ) -> $crate::c::markers::FREObject {
252                fn func <'a> (
253                    ctx: &$crate::context::CurrentContext<'a>,
254                    func_data: Option<&mut dyn ::std::any::Any>,
255                    args: &[$crate::as3::Object<'a>],
256                ) -> $crate::as3::Object<'a> {
257                    $crate::function! {@Parameters [ctx, func_data, args] $($arguments)+}
258                    let r = ::std::panic::catch_unwind(|| -> $crate::function!(@Return $($return_type)?) {
259                        $body
260                    });
261                    $crate::function! (@Unwind [ctx, r])
262                }
263                $crate::context::CurrentContext::with_method(&ctx, func_data, argc, argv, func)
264            }
265            abi},
266        );
267    };
268    (// #1
269        $name:ident use $func:path
270    ) => {
271        #[allow(non_upper_case_globals)]
272        pub const $name: &'static $crate::function::FunctionImplementation = & $crate::function::FunctionImplementation::new(
273            $crate::function!(@Name $name), {
274            #[allow(unsafe_op_in_unsafe_fn)]
275            unsafe extern "C" fn abi(
276                ctx: $crate::c::markers::FREContext,
277                func_data: $crate::c::markers::FREData,
278                argc: u32,
279                argv: *const $crate::c::markers::FREObject,
280            ) -> $crate::c::markers::FREObject {
281                let r = ::std::panic::catch_unwind(|| -> $crate::c::markers::FREObject {
282                    $crate::context::CurrentContext::with_method(&ctx, func_data, argc, argv, $func)
283                });
284                let ctx: $crate::context::CurrentContext = ::std::mem::transmute(ctx);
285                $crate::function! (@Unwind [&ctx, r])
286            }
287            abi},
288        );
289    };
290    (// #2
291        @Name $name:ident
292    ) => {
293        unsafe {
294            let s: &'static str = concat!(stringify!($name), "\0");
295            let s: &'static ::std::ffi::CStr = ::std::ffi::CStr::from_bytes_with_nul_unchecked(s.as_bytes());
296            let s = $crate::validated::UCStr::from_literal_unchecked(s);
297            s
298        }
299    };
300    (// #3
301        @Return $return_type:ty
302    ) => ($return_type); 
303    (// #4
304        @Return
305    ) => (());
306    {// #5
307        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
308        $ctx:ident, $data:ident, $args:ident $(,)?
309    } => {
310        let $ctx: &$crate::context::CurrentContext<'a> = $c;
311        let $data: Option<&mut dyn ::std::any::Any> = $d;
312        let $args: &[$crate::as3::Object<'a>] = $a;
313    };
314    {// #6
315        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
316        $ctx:ident, $data:ident, _ $(,)?
317    } => {
318        $crate::function! {@Parameters [$c, $d, $a]
319            $ctx, $data, _args
320        }
321    };
322    {// #7
323        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
324        $ctx:ident, _, $args:ident $(,)?
325    } => {
326        $crate::function! {@Parameters [$c, $d, $a]
327            $ctx, _data, $args
328        }
329    };
330    {// #8
331        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
332        _, $data:ident, $args:ident $(,)?
333    } => {
334        $crate::function! {@Parameters [$c, $d, $a]
335            _ctx, $data, $args
336        }
337    };
338    {// #9
339        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
340        _, _, $args:ident $(,)?
341    } => {
342        $crate::function! {@Parameters [$c, $d, $a]
343            _ctx, _data, $args
344        }
345    };
346    {// #10
347        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
348        _, $data:ident, _ $(,)?
349    } => {
350        $crate::function! {@Parameters [$c, $d, $a]
351            _ctx, $data, _args
352        }
353    };
354    {// #11
355        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
356        $ctx:ident, _, _ $(,)?
357    } => {
358        $crate::function! {@Parameters [$c, $d, $a]
359            $ctx, _data, _args
360        }
361    };
362    {// #12
363        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
364        _, _, _ $(,)?
365    } => {
366        $crate::function! {@Parameters [$c, $d, $a]
367            _ctx, _data, _args
368        }
369    };
370    (// #13
371        @Unwind [$ctx:expr_2021, $catched:expr_2021]
372    ) => {
373        match $catched {
374            Ok(r) => r.into(),
375            Err(_) => {
376                let info = $crate::__private::LAST_PANIC_INFO.with(|i| {i.borrow_mut().take()});
377                let msg = info.as_ref()
378                    .map(|s|s.as_str())
379                    .unwrap_or("Panic occurred but no details were captured.");
380                let err = $crate::as3::Error::new($ctx, Some(msg), i32::MIN);
381                err.set_name(Some($crate::as3::String::new($ctx, "Native Extension Panic Error")));
382                err.into()
383            },
384        }
385    }
386}
387