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
//!
//! Core implementation of the extension entry point abstraction.
//!
use super::*;
/// The primary entry point of this crate, providing access to its public APIs.
#[derive(Debug)]
#[repr(transparent)]
pub struct FlashRuntime<'a> (FREContext, PhantomData<&'a()>);
impl<'a> FlashRuntime<'a> {
pub fn current_context(&self) -> Context<'a> {Context(self.0, PhantomData)}
pub fn event_dispatcher(&self) -> EventDispatcher {self.current_context().event_dispatcher()}
/// A wrapper used by [`FREContextInitializer`], [`FREContextFinalizer`], and
/// [`FREFunction`] that provides a safe stack-level execution environment.
///
/// **In typical usage of this crate, this function should not be called directly.**
///
/// # Safety
/// While all operations performed within this function are safe at the stack level,
/// calling this function itself is unsafe and requires that all input arguments
/// are valid. In particular, this function assumes it is invoked directly with
/// arguments provided by the Flash runtime.
///
/// Violating these assumptions may lead to undefined behavior.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn with <F, R> (ctx: &'a FREContext, f: F) -> R
where
F: FnOnce (&FlashRuntime<'a>) -> R,
R: 'a,
{
assert!(!ctx.is_null());
assert!(!IS_FRT_BORROWED.get());
IS_FRT_BORROWED.set(true);
let frt: FlashRuntime<'a> = Self(*ctx, PhantomData);
let r = f(&frt);
IS_FRT_BORROWED.set(false);
r
}
/// A wrapper around [`FREContextInitializer`] that provides a safe stack-level
/// execution environment for context initialization.
///
/// **In typical usage of this crate, this function should not be called directly.**
///
/// # Safety
/// While all operations performed within this function are safe at the stack level,
/// calling this function itself is unsafe and requires the following conditions:
///
/// - The native data associated with [`Context`] must not be accessed or managed
/// by external code.
/// - This function will construct a [`ContextRegistry`] and assign it as the native data.
/// The constructed [`ContextRegistry`] must be properly disposed in
/// [`FREContextFinalizer`] to ensure its lifecycle is correctly terminated.
/// - This function assumes it is invoked directly with arguments provided by the
/// Flash runtime, meaning all arguments must be valid and consistent.
///
/// Violating these assumptions may lead to undefined behavior.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn with_context_initializer <F> (
ext_data: FREData,// &'extension mut
ctx_type: FREStr,// &'function
ctx: &'a FREContext,// &'function
num_funcs_to_set: *mut u32,// return
funcs_to_set: *mut *const FRENamedFunction,// return &'context mut
f: F
)
where F: FnOnce (&FlashRuntime<'a>) -> FunctionSet
{
assert!(!num_funcs_to_set.is_null());
assert!(!funcs_to_set.is_null());
Self::with(ctx, |frt|{
let ctx_type = if ctx_type.is_null() {None} else {
let ctx_type = CStr::from_ptr(ctx_type as *const c_char);
let ctx_type = UCStr::try_from(ctx_type).expect("Input string is not valid UTF-8.");
Some(ctx_type)
};
let ctx_data = ContextRegistry::new(ext_data, ctx_type).into_raw();
let r= frt.current_context().set_native_data(ctx_data);// <'context> move
assert!(r.is_ok());
let r = f(frt);
let methods = MethodSet::from(r);
let r = methods.as_ref();
assert!(r.len() <= u32::MAX as usize);
*num_funcs_to_set = r.len() as u32;
*funcs_to_set = r.as_ptr();
let ctx_data = ContextRegistry::mut_from(ctx_data).unwrap();
ctx_data.methods = Some(methods);
})
}
/// A wrapper around [`FREFunction`] that provides a safe stack-level execution
/// environment for the given closure.
///
/// **In typical usage of this crate, this function should not be called directly.**
///
/// # Safety
/// While all operations performed within this function are safe at the stack level,
/// calling this function itself is unsafe and requires the following conditions:
///
/// - `func_data` must either be constructed via [`Data::into_raw`] before
/// [`ContextInitializer`] returns, or be a null pointer.
/// - This function assumes it is invoked directly with arguments provided by the
/// Flash runtime, meaning all arguments must be valid and consistent.
///
/// Violating these assumptions may lead to undefined behavior.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn with_method <F> (
ctx: &'a FREContext,// &'function
func_data: FREData,// &'function mut
argc: u32,
argv: *const FREObject,// &'function
f: F
) -> FREObject
where F: FnOnce (&FlashRuntime<'a>, Option<&mut dyn Any>, &[Object<'a>]) -> Object<'a>
{
assert!(!argv.is_null() || argc==0);
Self::with(ctx, |frt| {
let func_data = NonNullFREData::new(func_data)
.map(|raw| crate::data::mut_from(raw));
let args = std::slice::from_raw_parts(argv as *const Object, argc as usize);
f(frt, func_data, args).as_ptr()
})
}
pub fn trace (&self, msg: impl Into<UCStr>) {_ = self.current_context().trace(msg);}
}
impl Drop for FlashRuntime<'_> {
fn drop(&mut self) {IS_FRT_BORROWED.set(false);}
}
thread_local! {static IS_FRT_BORROWED: Cell<bool> = Cell::new(false)}
/// A handle to a context that may become invalid under specific conditions.
///
/// Invalidity only occurs after the associated `ExtensionContext` AS3 object
/// has been disposed. Therefore, callers should be prepared for operations
/// on [`Context`] to fail at appropriate points.
///
/// This crate leverages [`FREGetFREContextFromExtensionContext`] to enable
/// more advanced use cases, but doing so also increases overall complexity.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct Context <'a> (FREContext, PhantomData<&'a()>);
impl<'a> Context<'a> {
pub fn is_valid(self) -> bool {self.get_actionscript_data().is_ok()}
pub fn event_dispatcher(self) -> EventDispatcher {EventDispatcher(self.0)}
/// Provides exclusive access to the [`ContextRegistry`] in a constrained manner.
///
/// # Safety
/// This method assumes that the native data associated with the [`Context`]
/// was created and is exclusively managed by [`crate::extension!`] as a [`ContextRegistry`].
///
/// To call this method safely, you must guarantee that the native data
/// attached to the [`Context`] has NOT been manually managed, replaced,
/// or altered by external code. Violating this assumption may lead to
/// undefined behavior.
///
/// The use of a closure and the [`Sync`] bound is intentional: it constrains
/// the ordering of FFI interactions, while helping prevent data races,
/// preserving memory safety, and avoiding uncontrolled complexity growth
/// in cross-boundary usage.
///
/// [`Err`]=> [`ContextError::InvalidContext`], [`ContextError::NullData`], [`ContextError::UnexpectedData`], [`ContextError::FfiCallFailed`];
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn with <F, R> (self, f: F) -> Result<R, ContextError>
where F: FnOnce (&mut ContextRegistry) -> R + Sync {
if !self.is_valid() {return Err(ContextError::InvalidContext)}
let raw = NonNullFREData::new(self.get_native_data()?)
.ok_or(ContextError::NullData)?;
let cr = ContextRegistry::mut_from(raw)
.map_err(|_| ContextError::UnexpectedData)?;
let r = f(cr);
Ok(r)
}
/// **In typical usage of this crate, this function should not be called directly.**
///
/// This is because the native data is reserved for [`ContextRegistry`] and is
/// automatically managed by [`crate::extension!`].
pub fn get_native_data (self) -> Result<FREData, FfiError> {
let mut data = FREData::default();
let r = unsafe {FREGetContextNativeData(self.0, &mut data)};
if let Ok(e) = FfiError::try_from(r) {Err(e)} else {Ok(data)}
}
/// **In typical usage of this crate, this function should not be called directly.**
///
/// This is because the native data is reserved for [`ContextRegistry`] and is
/// automatically managed by [`crate::extension!`].
///
/// # Safety
///
/// This function sets the native data pointer associated with the underlying [`FREContext`].
/// Calling this function is unsafe because the caller must uphold the following invariants:
///
/// 1. **The native data must not have been previously set**
/// - The context must currently be in an uninitialized state with respect to native data.
/// - In other words, [`Self::get_native_data`] must return null pointer.
/// - Violating this will overwrite an existing pointer, causing a memory leak or double-free.
///
/// 2. **The ownership and lifetime of `data` must be correctly managed**
/// - If `data` represents a moved (owned) allocation, the caller is responsible for ensuring
/// that it is eventually freed.
/// - The memory must remain valid for the entire lifetime of the [`FREContext`].
/// - The allocation must be released no later than in the [`FREContextFinalizer`] callback,
/// where the native data is expected to be cleaned up manually.
///
/// Failure to uphold these guarantees may result in undefined behavior, including memory leaks,
/// use-after-free, or double-free errors.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn set_native_data (self, data: NonNullFREData) -> Result<(), FfiError> {
debug_assert!(self.get_native_data()?.is_null());
let r = FRESetContextNativeData(self.0, data.as_ptr());
if let Ok(e) = FfiError::try_from(r) {Err(e)} else {Ok(())}
}
/// `flash.external.ExtensionContext.actionScriptData`
pub fn get_actionscript_data (self) -> Result<Object<'a>, FfiError> {
let mut obj = FREObject::default();
let r = unsafe {FREGetContextActionScriptData(self.0, &mut obj)};
if let Ok(e) = FfiError::try_from(r) {Err(e)} else {Ok(unsafe {transmute(obj)})}
}
/// `flash.external.ExtensionContext.actionScriptData`
pub fn set_actionscript_data (self, object: Object<'_>) -> Result<(), FfiError> {
let r = unsafe {FRESetContextActionScriptData(self.0, object.as_ptr())};
if let Ok(e) = FfiError::try_from(r) {Err(e)} else {Ok(())}
}
/// Calls a registered function by name through this crate's internal API.
///
/// # Safety
/// To call this method safely, all safety requirements of [`Self::with`]
/// must be upheld. In particular, the native data associated with the
/// [`Context`] must be a valid [`ContextRegistry`] created and exclusively
/// managed by [`crate::extension!`], and must not be manually modified or
/// replaced by external code.
///
/// Violating these conditions may lead to undefined behavior.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn call_method (self, name: &str, args: &[Object<'a>] ) -> Result<Object<'a>, ContextError> {
self.with(|registry| {
registry.methods.as_mut()
.ok_or(ContextError::MethodsNotRegistered)
.map(|ms| ms.get(name))
})??
.ok_or(ContextError::MethodNotFound)
.map(|(func, data)| {
let r = func(self.0, data, args.len() as u32, args.as_ptr() as *mut FREObject);
transmute(r)
})
}
pub fn trace (self, msg: impl Into<UCStr>) -> Result<(), FfiError> {
let r = unsafe {FRETrace(self.0, msg.into().as_ptr())};
if let Ok(e) = FfiError::try_from(r) {Err(e)} else {Ok(())}
}
/// Return [`Err`] if `stage` is non-null but not a `Stage` object
///
/// This is a minimal safety wrapper around the underlying FFI. Its current placement,
/// shape, and usage are not ideal, and it is hoped that it can be refactored
/// if a more flexible C API becomes available in the AIR SDK.
pub fn get_render_mode (self, stage: Option<Object<'a>>) -> Result<crate::misc::RenderMode, FfiError> {
let stage = stage.unwrap_or_default();
let mut rm = u8::default();
let r = unsafe {FREGetRenderMode(self.0, stage.as_ptr(), &mut rm)};
if let Ok(e) = FfiError::try_from(r) {
Err(e)
} else {
let rm: FRERenderMode = FRERenderMode(rm as i32);
Ok(crate::misc::RenderMode::from(rm))
}
}
/// `air.media.MediaBuffer` (AIR SDK 51)
///
/// `AIR-5963: Add ANE capabilities to render a Sprite using a MediaBuffer - initial support via BitmapData`
///
/// This is a minimal safety wrapper around the underlying FFI. Its current placement,
/// shape, and usage are not ideal, and it is hoped that it can be refactored
/// if a more flexible C API becomes available in the AIR SDK.
pub fn set_render_source (self, media_buffer: Object<'a>, sprite: Object<'a>) -> Result<(), FfiError> {
let r = unsafe {FRESetRenderSource(self.0, media_buffer.as_ptr(), sprite.as_ptr())};
if let Ok(e) = FfiError::try_from(r) {Err(e)} else {Ok(())}
}
/// This is a minimal safety wrapper around the underlying FFI. Its current placement,
/// shape, and usage are not ideal, and it is hoped that it can be refactored
/// if a more flexible C API becomes available in the AIR SDK.
pub fn with_media_buffer <F, R> (self, media_buffer: Object<'a>, f: F) -> Result<R, FfiError>
where F: FnOnce (MediaBufferDataAdapter) -> R {
let mut bytes = FREBytes::default();
let mut width = u32::default();
let mut height = u32::default();
let mut stride = u32::default();
let mut format = u32::default();
let result = unsafe {FREMediaBufferLock(self.0, media_buffer.as_ptr(), &mut bytes, &mut width, &mut height, &mut stride, &mut format)};
if let Ok(e) = FfiError::try_from(result) {return Err(e);}
let adapter = unsafe {MediaBufferDataAdapter::new(bytes, width, height, stride, format)};
let r = f(adapter);
let result = unsafe {FREMediaBufferUnlock(self.0, media_buffer.as_ptr(), u32::default())};
debug_assert!(result.is_ok());
Ok(r)
}
}
/// The extension-side concrete representation of a [`Context`].
///
/// This can be considered the actual context instance, while [`Context`]
/// serves as an abstract handle or outer wrapper around it.
///
/// This struct is only constructed via [`FlashRuntime::with_context_initializer`].
#[derive(Debug)]
pub struct ContextRegistry {
ctx_type: Option<UCStr>,
ctx_data: Option<Box<dyn Any>>,
ext_data: Option<NonNullFREData>,// &'extension mut
methods: Option<MethodSet>,
}
impl ContextRegistry {
pub fn context_type(&self) -> Option<UCStr> {(self.ctx_type).clone()}
pub fn context_data(&self) -> Option<&dyn Any> {self.ctx_data.as_ref().map(|b| b.as_ref())}
pub fn context_data_mut(&mut self) -> &mut Option<Box<dyn Any>> {&mut self.ctx_data}
/// Returns a reference to the extension data.
///
/// # Safety
/// This method assumes that `ext_data` was either constructed via
/// [`Data::into_raw`] or is a null pointer.
///
/// Violating these assumptions may lead to undefined behavior.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn extension_data(&self) -> Option<&dyn Any> {
self.ext_data
.as_ref()
.map(|raw| crate::data::ref_from(*raw))
}
/// Returns a mutable reference to the extension data.
///
/// # Safety
/// This method assumes that `ext_data` was either constructed via
/// [`Data::into_raw`] or is a null pointer.
///
/// Violating these assumptions may lead to undefined behavior.
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn extension_data_mut(&mut self) -> Option<&mut dyn Any> {
self.ext_data
.as_ref()
.map(|raw| crate::data::mut_from(*raw))
}
fn new (ext_data: FREData, ctx_type: Option<UCStr>) -> Self {
let ext_data = NonNullFREData::new(ext_data);
Self {
ctx_type,
ctx_data: None,
ext_data,
methods: None,
}
}
}
impl Data for ContextRegistry {}