use std::{cell::RefCell, collections::HashMap};
use wasm_bindgen::{JsCast, JsValue, closure::Closure, prelude::wasm_bindgen};
use crate::error::Error;
thread_local! {
static VALUE_HANDLERS: RefCell<HashMap<u64, Option<Box<dyn FnMut(JsValue)>>>> =
RefCell::new(HashMap::new());
static VOID_HANDLERS: RefCell<HashMap<u64, Option<Box<dyn FnMut()>>>> =
RefCell::new(HashMap::new());
static NEXT_SLOT_ID: RefCell<u64> = const { RefCell::new(0) };
static VALUE_DISPATCHER: RefCell<Option<js_sys::Function>> = const { RefCell::new(None) };
static VOID_DISPATCHER: RefCell<Option<js_sys::Function>> = const { RefCell::new(None) };
}
pub(crate) fn allocate_value_handler(handler: Box<dyn FnMut(JsValue)>) -> Result<u64, Error> {
let id = next_slot_id();
VALUE_HANDLERS.with(|h| -> Result<(), Error> {
let mut h = h.borrow_mut();
h.try_reserve(1)
.map_err(|_| Error::from_static("out of memory: dispatch value handler slot"))?;
h.insert(id, Some(handler));
Ok(())
})?;
Ok(id)
}
pub(crate) fn allocate_void_handler(handler: Box<dyn FnMut()>) -> Result<u64, Error> {
let id = next_slot_id();
VOID_HANDLERS.with(|h| -> Result<(), Error> {
let mut h = h.borrow_mut();
h.try_reserve(1)
.map_err(|_| Error::from_static("out of memory: dispatch void handler slot"))?;
h.insert(id, Some(handler));
Ok(())
})?;
Ok(id)
}
pub(crate) fn free_value_handler(slot_id: u64) {
VALUE_HANDLERS.with(|h| {
h.borrow_mut().remove(&slot_id);
});
}
pub(crate) fn free_void_handler(slot_id: u64) {
VOID_HANDLERS.with(|h| {
h.borrow_mut().remove(&slot_id);
});
}
fn next_slot_id() -> u64 {
NEXT_SLOT_ID.with(|n| {
let mut n = n.borrow_mut();
let id = *n;
*n = n
.checked_add(1)
.expect("dispatch slot ID counter exhausted (>2^64 allocations)");
id
})
}
pub(crate) fn make_value_trampoline(slot_id: u64) -> js_sys::Function {
let dispatcher = value_dispatcher();
_mediadecode_value_trampoline(slot_id, &dispatcher)
}
pub(crate) fn make_void_trampoline(slot_id: u64) -> js_sys::Function {
let dispatcher = void_dispatcher();
_mediadecode_void_trampoline(slot_id, &dispatcher)
}
fn value_dispatcher() -> js_sys::Function {
VALUE_DISPATCHER.with(|cell| {
let mut slot = cell.borrow_mut();
if let Some(f) = slot.as_ref() {
return f.clone();
}
let cb = Closure::<dyn FnMut(u64, JsValue)>::new(|slot_id: u64, value: JsValue| {
let taken: Option<Box<dyn FnMut(JsValue)>> =
VALUE_HANDLERS.with(|h| h.borrow_mut().get_mut(&slot_id).and_then(|opt| opt.take()));
let Some(mut handler) = taken else {
close_orphan_value(value);
return;
};
handler(value);
VALUE_HANDLERS.with(|h| {
let mut h = h.borrow_mut();
if let Some(opt) = h.get_mut(&slot_id) {
*opt = Some(handler);
}
});
});
let f: js_sys::Function = cb.as_ref().unchecked_ref::<js_sys::Function>().clone();
cb.forget();
*slot = Some(f.clone());
f
})
}
fn void_dispatcher() -> js_sys::Function {
VOID_DISPATCHER.with(|cell| {
let mut slot = cell.borrow_mut();
if let Some(f) = slot.as_ref() {
return f.clone();
}
let cb = Closure::<dyn FnMut(u64)>::new(|slot_id: u64| {
let taken: Option<Box<dyn FnMut()>> =
VOID_HANDLERS.with(|h| h.borrow_mut().get_mut(&slot_id).and_then(|opt| opt.take()));
let Some(mut handler) = taken else {
return;
};
handler();
VOID_HANDLERS.with(|h| {
let mut h = h.borrow_mut();
if let Some(opt) = h.get_mut(&slot_id) {
*opt = Some(handler);
}
});
});
let f: js_sys::Function = cb.as_ref().unchecked_ref::<js_sys::Function>().clone();
cb.forget();
*slot = Some(f.clone());
f
})
}
fn close_orphan_value(value: JsValue) {
if let Ok(data) = value.clone().dyn_into::<web_sys::AudioData>() {
data.close();
return;
}
if let Ok(frame) = value.dyn_into::<web_sys::VideoFrame>() {
frame.close();
}
}
#[wasm_bindgen(inline_js = "
export function _mediadecode_value_trampoline(slotId, dispatcher) {
return function(value) { dispatcher(slotId, value); };
}
export function _mediadecode_void_trampoline(slotId, dispatcher) {
return function() { dispatcher(slotId); };
}
")]
extern "C" {
fn _mediadecode_value_trampoline(slot_id: u64, dispatcher: &js_sys::Function)
-> js_sys::Function;
fn _mediadecode_void_trampoline(slot_id: u64, dispatcher: &js_sys::Function) -> js_sys::Function;
}