fre-rs 0.4.0

Safe, ergonomic Rust abstraction over the AIR Native Extension (ANE) C API (`fre-sys`) for native-side development.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
#[allow(unused_imports)]
use super::*;


/// Generates and links the required Flash Runtime Extension entry points and lifecycle hooks,
/// bridging the C ABI with safe Rust abstractions.
/// 
/// This macro accepts two external symbols to be exported as part of the public ABI,
/// and four functions: [`Initializer`], [`Finalizer`], [`ContextInitializer`], [`ContextFinalizer`].
/// Some of these arguments are optional.
/// 
/// # Full Examples
/// ```
/// mod lib {
///     use fre_rs::prelude::*;
///     fre_rs::extension! {
///         extern symbol_initializer, symbol_finalizer;
///         move initializer, final finalizer;
///         gen context_initializer, final context_finalizer;
///     }
///     struct NativeData (i32);
///     impl Data for NativeData {}
///     fn initializer() -> Option<Box<dyn Any>> {
///         let extension_data = NativeData(-3).into_boxed();
///         return Some(extension_data);
///     }
///     fn finalizer(ext_data: Option<Box<dyn Any>>) {
///         assert_eq!(NativeData::from_boxed(ext_data.unwrap()).unwrap().0, -3);
///     }
///     fn context_initializer(ctx: &mut CurrentContext) -> (Option<Box<dyn Any>>, FunctionSet) {
///         let context_data = NativeData(-2).into_boxed();
///         let mut funcs = FunctionSet::with_capacity(1);
///         let function_data = NativeData(-1).into_boxed();
///         if ctx.ty().is_some() {
///             funcs.add(None, Some(function_data), method_name);
///         } else {
///             funcs.add(None, Some(function_data), method_name2);
///         }
///         return (Some(context_data), funcs);
///     }
///     fn context_finalizer(ctx: &mut CurrentContext) {
///         let context_data: &NativeData = ctx.data().unwrap().downcast_ref().unwrap();
///         assert_eq!(context_data.0, -2);
///         ctx.set_actionscript_data(as3::null)
///     }
///     fn method_implementation <'a> (ctx: &mut CurrentContext<'a>, data: Option<&mut dyn Any>, args: &[Object<'a>]) -> Object<'a> {as3::null}
///     fre_rs::function! (method_name use method_implementation);
///     fre_rs::function! {
///         method_name2 (ctx, data, args) -> Option<as3::Array> {
///             let function_data: &mut NativeData = data.unwrap().downcast_mut().unwrap();
///             assert_eq!(function_data.0, -1);
///             return None;
///         }
///     }
/// }
/// ```
/// # Minimal Examples
/// ```
/// mod lib {
///     use fre_rs::prelude::*;
///     fre_rs::extension! {
///         extern symbol_initializer;
///         gen context_initializer, final;
///     }
///     fn context_initializer(_: &mut CurrentContext) -> (Option<Box<dyn Any>>, FunctionSet) {
///         let mut funcs = FunctionSet::new();
///         funcs.add(None, None, method_name);
///         (None, funcs)
///     }
///     fre_rs::function! {
///         method_name (ctx, _, args) -> as3::String {
///             trace(args);
///             as3::String::new(ctx, "Hello, Flash runtime!")
///         }
///     }
/// }
/// ```
/// 
#[macro_export]
macro_rules! extension {
    {// #0
        extern $symbol_initializer:ident $(, $symbol_finalizer:ident;
        move $initializer:path, final $($finalizer:path)?)?;
        gen $context_initializer:path, final $($context_finalizer:path)?;
    } => {
        const _: () = {
        mod __flash_runtime_extension {
            use super::*;
            $crate::extension! {@Extern [$symbol_initializer $(, $symbol_finalizer, $initializer $(, $finalizer)?)?]}
            #[allow(unsafe_op_in_unsafe_fn)]
            unsafe extern "C" fn ctx_initializer (
                ext_data: $crate::c::FREData,
                ctx_type: $crate::c::FREStr,
                ctx: $crate::c::FREContext,
                num_funcs_to_set: *mut u32,
                funcs_to_set: *mut *const $crate::c::FRENamedFunction,
            ) {
                let context_initializer: $crate::function::ContextInitializer = $context_initializer;
                $crate::__private::context::with_initializer(ext_data, ctx_type, &ctx, num_funcs_to_set, funcs_to_set, $context_initializer);
            }
            #[allow(unsafe_op_in_unsafe_fn)]
            unsafe extern "C" fn ctx_finalizer (ctx: $crate::c::FREContext) {
                $crate::__private::context::with(&ctx, |ctx| {
                    $(
                        let context_finalizer: $crate::function::ContextFinalizer = $context_finalizer;
                        context_finalizer(ctx);
                    )?
                    let ctx = *(ctx as *mut $crate::context::CurrentContext as *mut $crate::context::ForeignContext);
                    let raw = ctx
                        .get_native_data()
                        .expect("Failed to retrieve native data from FFI.")
                        .expect("`ContextRegistry` is expected in native data but is missing.");
                    assert!(<::std::cell::RefCell<$crate::context::ContextRegistry> as $crate::data::Data>::ref_from(raw).is_ok());
                    $crate::data::drop_from(raw);
                });

            }
        }
        };
    };
    {// #1
        @Extern [$symbol_initializer:ident, $symbol_finalizer:ident, $initializer:path $(, $finalizer:path)?]
    } => {
        #[allow(unsafe_op_in_unsafe_fn, non_snake_case)]
        #[unsafe(no_mangle)]
        pub unsafe extern "C" fn $symbol_initializer (
            ext_data_to_set: *mut $crate::c::FREData,
            ctx_initializer_to_set: *mut $crate::c::FREContextInitializer,
            ctx_finalizer_to_set: *mut $crate::c::FREContextFinalizer,
        ) {
            assert!(!ext_data_to_set.is_null());
            assert!(!ctx_initializer_to_set.is_null());
            assert!(!ctx_finalizer_to_set.is_null());
            let initializer: $crate::function::Initializer = $initializer;
            if let Some(ext_data) = initializer() {
                let ext_data = ::std::sync::Arc::new(::std::sync::Mutex::new(ext_data));
                *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();
            }
            *ctx_initializer_to_set = ctx_initializer;
            *ctx_finalizer_to_set = ctx_finalizer;
        }
        #[allow(unsafe_op_in_unsafe_fn, non_snake_case)]
        #[unsafe(no_mangle)]
        pub unsafe extern "C" fn $symbol_finalizer (ext_data: $crate::c::FREData) {
            let ext_data = $crate::validated::NonNullFREData::new(ext_data)
                .map(|raw| {
                    let arc_mutex_boxed = <::std::sync::Arc<::std::sync::Mutex<Box<dyn ::std::any::Any>>> as $crate::data::Data>::from_raw(raw);
                    let mutex = ::std::sync::Arc::try_unwrap(arc_mutex_boxed).expect("INVARIANT: No context exists.");
                    let boxed = mutex.into_inner().expect("The mutex is poisoned.");
                    boxed
                });
            $(
                let finalizer: $crate::function::Finalizer = $finalizer;
                finalizer(ext_data);
            )?
        }
    };
    {// #2
        @Extern [$symbol_initializer:ident]
    } => {
        #[allow(unsafe_op_in_unsafe_fn, non_snake_case)]
        #[unsafe(no_mangle)]
        pub unsafe extern "C" fn $symbol_initializer (
            ext_data_to_set: *mut $crate::c::FREData,
            ctx_initializer_to_set: *mut $crate::c::FREContextInitializer,
            ctx_finalizer_to_set: *mut $crate::c::FREContextFinalizer,
        ) {
            assert!(!ctx_initializer_to_set.is_null());
            assert!(!ctx_finalizer_to_set.is_null());
            *ctx_initializer_to_set = ctx_initializer;
            *ctx_finalizer_to_set = ctx_finalizer;
        }
    };
}


/// Defines a function intended for context registration by generating its
/// ABI-compatible wrapper and binding it to a Rust implementation.
///
/// Expands to a `&'static` constant of type [`FunctionImplementation`],
/// intended to be added to a [`FunctionSet`].
/// 
/// This macro supports two forms:
/// one that defines a function inline, and another that binds to an existing
/// [`Function`] using the `use` keyword.
///
/// For larger or more complex implementations, the latter can provide better
/// IDE support. However, it introduces two distinct items with the same
/// semantics, so naming should be chosen carefully to avoid confusion.
/// 
/// # Panics and Debugging
/// 
/// If a panic occurs on the call stack, the process will abort.
/// The runtime may print a backtrace to `stderr`, but it may lack sufficient
/// symbol information (such as function names and source locations)
/// for meaningful debugging.
///
/// For example, when using the MSVC toolchain on Windows, including
/// the generated `.pdb` file alongside the `.dll` (in the `.ane` package)
/// allows debuggers and crash reporting tools to resolve symbols
/// and produce human-readable stack traces.
/// 
/// # Full Examples
/// ```
/// mod lib {
///     use fre_rs::prelude::*;
///     fre_rs::function! {
///         method_name (ctx, data, args) -> Object {
///             return ctx.get_actionscript_data();
///         }
///     }
///     fre_rs::function! (method_name2 use method_implementation);
///     fn method_implementation <'a> (ctx: &mut CurrentContext<'a>, data: Option<&mut dyn Any>, args: &[Object<'a>]) -> Object<'a> {as3::null}
/// }
/// ```
/// # Minimal Examples
/// ```
/// mod lib {
///     fre_rs::function! {
///         method_name (_, _, _) {}
///     }
/// }
/// ```
/// 
#[macro_export]
macro_rules! function {
    {// #0
        $name:ident ($($arguments:tt)+) $(-> $return_type:ty)? $body:block
    } => {
        #[allow(non_upper_case_globals)]
        pub const $name: &'static $crate::function::FunctionImplementation = & $crate::__private::function::implement(
            $crate::ucstringify! ($name), {
            #[allow(unsafe_op_in_unsafe_fn)]
            unsafe extern "C" fn abi(
                ctx: $crate::c::FREContext,
                func_data: $crate::c::FREData,
                argc: u32,
                argv: *const $crate::c::FREObject,
            ) -> $crate::c::FREObject {
                fn func <'a> (
                    ctx: &mut $crate::context::CurrentContext<'a>,
                    func_data: Option<&mut dyn ::std::any::Any>,
                    args: &[$crate::as3::Object<'a>],
                ) -> $crate::as3::Object<'a> {
                    $crate::function! {@Parameters [ctx, func_data, args] $($arguments)+}
                    (|| -> $crate::function!(@Return $($return_type)?) {
                        $body
                    })().into()
                }
                $crate::__private::context::with_method(&ctx, func_data, argc, argv, func)
            }
            abi},
        );
    };
    (// #1
        $name:ident use $func:path
    ) => {
        #[allow(non_upper_case_globals)]
        pub const $name: &'static $crate::function::FunctionImplementation = & $crate::__private::function::implement(
            $crate::ucstringify! ($name), {
            #[allow(unsafe_op_in_unsafe_fn)]
            unsafe extern "C" fn abi(
                ctx: $crate::c::FREContext,
                func_data: $crate::c::FREData,
                argc: u32,
                argv: *const $crate::c::FREObject,
            ) -> $crate::c::FREObject {
                $crate::__private::context::with_method(&ctx, func_data, argc, argv, $func)
            }
            abi},
        );
    };
    (// #2
        @Return $return_type:ty
    ) => ($return_type); 
    (// #3
        @Return
    ) => (());
    {// #4
        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
        $ctx:ident, $data:ident, $args:ident $(,)?
    } => {
        let $ctx: &mut $crate::context::CurrentContext<'a> = $c;
        let $data: Option<&mut dyn ::std::any::Any> = $d;
        let $args: &[$crate::as3::Object<'a>] = $a;
    };
    {// #5
        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
        $ctx:ident, $data:ident, _ $(,)?
    } => {
        $crate::function! {@Parameters [$c, $d, $a]
            $ctx, $data, _args
        }
    };
    {// #6
        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
        $ctx:ident, _, $args:ident $(,)?
    } => {
        $crate::function! {@Parameters [$c, $d, $a]
            $ctx, _data, $args
        }
    };
    {// #7
        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
        _, $data:ident, $args:ident $(,)?
    } => {
        $crate::function! {@Parameters [$c, $d, $a]
            _ctx, $data, $args
        }
    };
    {// #8
        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
        _, _, $args:ident $(,)?
    } => {
        $crate::function! {@Parameters [$c, $d, $a]
            _ctx, _data, $args
        }
    };
    {// #9
        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
        _, $data:ident, _ $(,)?
    } => {
        $crate::function! {@Parameters [$c, $d, $a]
            _ctx, $data, _args
        }
    };
    {// #10
        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
        $ctx:ident, _, _ $(,)?
    } => {
        $crate::function! {@Parameters [$c, $d, $a]
            $ctx, _data, _args
        }
    };
    {// #11
        @Parameters [$c:ident, $d:ident, $a:ident $(,)?]
        _, _, _ $(,)?
    } => {
        $crate::function! {@Parameters [$c, $d, $a]
            _ctx, _data, _args
        }
    };
}


/// Defines a new AS3 class.
/// 
/// Accepts a unique class name as an argument.
/// 
/// By default, the generated class implements [`PartialEq<Self>`] and [`Eq`]
/// using pointer equality. This can be disabled by adding the `?PartialEq` modifier.
/// 
/// # Examples
/// 
/// ```
/// use fre_rs::prelude::*;
/// fre_rs::class! (EventDispatcher);
/// impl<'a> EventDispatcher<'a> {
///     pub fn new (ctx: &mut CurrentContext<'a>) -> Self {
///        unsafe {ctx.construct(fre_rs::ucstringify!(flash.events.EventDispatcher), None)
///             .expect("Object construction failed.")
///             .as_unchecked()}
///     }
/// }
/// fre_rs::class! (XML: ?PartialEq);
/// impl PartialEq for XML<'_> {
///     fn eq(&self, other: &Self) -> bool {todo!()}
/// }
/// impl Eq for XML<'_> {}
/// ```
/// 
#[macro_export]
macro_rules! class {

    // #0
    {
        $(#[$meta:meta])*
        $name:ident $($modifier:tt)*
    } => {
        $crate::class! {@Define
            $(#[$meta])*
            $name $($modifier)*
        }
        unsafe impl<'a> $crate::types::object::AsObject<'a> for $name<'a> {
            const TYPE: $crate::types::Type = $crate::types::Type::Named(stringify!($name));
        }
    };

    // #1
    {@Typeof
        $(#[$meta:meta])*
        $name:ident $($modifier:tt)*
    } => {                                                          const _: () = $crate::__private::SEALED;
        $crate::class! {@Define
            $(#[$meta])*
            $name $($modifier)*
        }
        unsafe impl<'a> $crate::types::object::AsObject<'a> for $name<'a> {
            const TYPE: $crate::types::Type = $crate::types::Type::$name;
        }
        impl<'a> TryFrom<$crate::as3::Object<'a>> for $name<'a> {
            type Error = $crate::types::Type;
            fn try_from (object: $crate::as3::Object<'a>) -> Result<Self, Self::Error> {
                let ty = <$crate::as3::Object as $crate::types::object::AsObject>::get_type(object);
                if ty == <Self as $crate::types::object::AsObject>::TYPE {
                    Ok(unsafe {<$crate::as3::Object as $crate::types::object::AsObject>::as_unchecked(object)})
                }else{Err(ty)}
            }
        }
        impl<'a> TryFrom<$crate::types::object::NonNullObject<'a>> for $name<'a> {
            type Error = $crate::types::Type;
            fn try_from (object: $crate::types::object::NonNullObject<'a>) -> Result<Self, Self::Error> {
                <$crate::types::object::NonNullObject as $crate::types::object::AsObject>::as_object(object).try_into()
            }
        }
    };

    // #2
    {@Define
        $(#[$meta:meta])*
        $name:ident
    } => {
        $(#[$meta])*
        #[derive(::std::fmt::Debug, Clone, Copy, PartialEq, Eq)]
        #[repr(transparent)]
        pub struct $name <'a> (::std::ptr::NonNull<::std::ffi::c_void>, ::std::marker::PhantomData<&'a ()>);
        $crate::class!(@Implement $name);
    };

    // #3
    {@Define
        $(#[$meta:meta])*
        $name:ident: ?PartialEq
    } => {
        $(#[$meta])*
        #[derive(::std::fmt::Debug, Clone, Copy)]
        #[repr(transparent)]
        pub struct $name <'a> (::std::ptr::NonNull<::std::ffi::c_void>, ::std::marker::PhantomData<&'a ()>);
        $crate::class!(@Implement $name);
    };

    // #4
    {@Implement
        $name:ident
    } => {
        #[cfg(debug_assertions)]
        const _: () = {
            #[used]
            #[unsafe(export_name = concat!("__class_", stringify!($name)))]
            pub static CLASS_NAME_MUST_UNIQUE: u8 = 0;
        };

        unsafe impl $crate::__private::Sealed for $name<'_> {}

        impl<'a> $crate::types::object::AsNonNullObject<'a> for $name<'a> {}
        impl<'a> From<$name<'a>> for $crate::types::object::NonNullObject<'a> {fn from(object: $name<'a>) -> Self {<$name as $crate::types::object::AsNonNullObject>::as_non_null_object(object)}}
        
        impl From<$name<'_>> for $crate::c::FREObject {fn from(object: $name) -> Self {<$name as $crate::types::object::AsObject>::as_ptr(object)}}
        
        impl ::std::fmt::Display for $name<'_> {fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {::std::fmt::Display::fmt(&(<Self as $crate::types::object::AsNonNullObject>::as_non_null_object(*self)), f)}}
        
        impl $crate::validated::ToUcstrLossy for $name<'_> {fn to_ucstr_lossy(&self) -> $crate::validated::UCStr {
            let object = <$name as $crate::types::object::AsObject>::as_object(*self);
            <$crate::as3::Object as $crate::validated::ToUcstrLossy>::to_ucstr_lossy(&object)
        }}
    };
}


/// Interprets the input tokens as a string literal and yields a const [`UCStr`].
///
/// Whitespace between tokens is normalized in the same way as [`stringify!`].
/// 
/// Note that the expanded results of the input tokens may change in the future.
/// You should be careful if you rely on the output.
/// 
/// # Examples
/// 
/// ```
/// use fre_rs::prelude::*;
/// const METHOD_NAME: UCStr = fre_rs::ucstringify! (addChild);
/// const ERROR_MESSAGE: UCStr = fre_rs::ucstringify! {Invalid argument    type.    };
/// assert_eq! (METHOD_NAME.as_str(), "addChild");
/// assert_eq! (ERROR_MESSAGE.as_str(), "Invalid argument type.");
/// ```
/// 
#[macro_export]
macro_rules! ucstringify {

    // #0
    {$($tokens:tt)*} => {
        {
            const STR: &'static str = concat!(stringify!($($tokens)*), "\0");
            const CSTR: &'static ::std::ffi::CStr = unsafe {::std::ffi::CStr::from_bytes_with_nul_unchecked(STR.as_bytes())};
            const UCSTR: $crate::validated::UCStr = unsafe {$crate::validated::UCStr::from_literal_unchecked(CSTR)};
            UCSTR
        }
    };
}