use crate::*;
fn get_effect_subscriber_registry() -> &'static mut HashMap<usize, Vec<usize>> {
let addr: usize = unsafe {
match EFFECT_SUBSCRIBERS {
Some(addr) => addr,
None => {
let registry: Box<HashMap<usize, Vec<usize>>> = Box::default();
let leaked: &mut HashMap<usize, Vec<usize>> = Box::leak(registry);
let addr: usize = leaked as *mut HashMap<usize, Vec<usize>> as usize;
EFFECT_SUBSCRIBERS = Some(addr);
addr
}
}
};
unsafe { &mut *(addr as *mut HashMap<usize, Vec<usize>>) }
}
fn get_pending_effects() -> &'static mut Vec<usize> {
let addr: usize = unsafe {
match PENDING_EFFECTS {
Some(addr) => addr,
None => {
let queue: Box<Vec<usize>> = Box::default();
let leaked: &mut Vec<usize> = Box::leak(queue);
let addr: usize = leaked as *mut Vec<usize> as usize;
PENDING_EFFECTS = Some(addr);
addr
}
}
};
unsafe { &mut *(addr as *mut Vec<usize>) }
}
pub fn create_render_effect<F>(effect_fn: F) -> RenderEffect
where
F: FnMut() + 'static,
{
RenderEffect::new(effect_fn)
}
pub(crate) fn track_signal<T>(signal: &Signal<T>, effect_addr: usize)
where
T: Clone + PartialEq + 'static,
{
let signal_addr: usize = signal.get_inner() as usize;
let registry: &mut HashMap<usize, Vec<usize>> = get_effect_subscriber_registry();
let subscribers: &mut Vec<usize> = registry.entry(signal_addr).or_default();
if !subscribers.contains(&effect_addr) {
subscribers.push(effect_addr);
}
let effect: RenderEffect = RenderEffect { inner: effect_addr };
let inner: &mut RenderEffectInner = effect.leak_mut();
if !inner.get_dependencies().contains(&signal_addr) {
inner.get_mut_dependencies().push(signal_addr);
}
}
pub(crate) fn cleanup_effect_dependencies(effect_addr: usize, dependencies: &mut Vec<usize>) {
let registry: &mut HashMap<usize, Vec<usize>> = get_effect_subscriber_registry();
for signal_addr in dependencies.iter() {
if let Some(subscribers) = registry.get_mut(signal_addr) {
subscribers.retain(|addr| *addr != effect_addr);
if subscribers.is_empty() {
registry.remove(signal_addr);
}
}
}
dependencies.clear();
}
fn is_effect_notifications_paused() -> bool {
unsafe { EFFECT_NOTIFICATIONS_PAUSED }
}
fn set_effect_notifications_paused(paused: bool) {
unsafe {
EFFECT_NOTIFICATIONS_PAUSED = paused;
}
}
fn is_effect_flush_scheduled() -> bool {
unsafe { EFFECT_FLUSH_SCHEDULED }
}
fn set_effect_flush_scheduled(scheduled: bool) {
unsafe {
EFFECT_FLUSH_SCHEDULED = scheduled;
}
}
pub(crate) fn notify_effect_subscribers(signal_addr: usize) {
if is_effect_notifications_paused() {
let paused: &mut Vec<usize> = get_paused_effect_signals();
if !paused.contains(&signal_addr) {
paused.push(signal_addr);
}
return;
}
let effect_addrs: Vec<usize> = {
let registry: &mut HashMap<usize, Vec<usize>> = get_effect_subscriber_registry();
match registry.get(&signal_addr) {
Some(subscribers) => subscribers.clone(),
None => return,
}
};
let pending: &mut Vec<usize> = get_pending_effects();
for effect_addr in effect_addrs {
if !pending.contains(&effect_addr) {
pending.push(effect_addr);
}
}
schedule_effect_flush();
}
fn schedule_effect_flush() {
if is_effect_flush_scheduled() {
return;
}
set_effect_flush_scheduled(true);
#[cfg(target_arch = "wasm32")]
{
let callback: &'static js_sys::Function = get_or_create_flush_closure();
let _ = js_sys::Reflect::get(&js_sys::global(), &JsValue::from_str("queueMicrotask"))
.and_then(|qmt| {
let qmt_fn: js_sys::Function = qmt.into();
qmt_fn.call1(&JsValue::NULL, callback)
});
}
#[cfg(not(target_arch = "wasm32"))]
{
flush_pending_effects();
}
}
#[cfg(target_arch = "wasm32")]
fn get_flush_closure_addr() -> Option<usize> {
unsafe { FLUSH_CLOSURE_FN }
}
#[cfg(target_arch = "wasm32")]
fn set_flush_closure_addr(addr: Option<usize>) {
unsafe {
FLUSH_CLOSURE_FN = addr;
}
}
#[cfg(target_arch = "wasm32")]
fn get_or_create_flush_closure() -> &'static js_sys::Function {
let addr: usize = match get_flush_closure_addr() {
Some(addr) => addr,
None => {
let closure: wasm_bindgen::closure::Closure<dyn FnMut()> =
wasm_bindgen::closure::Closure::wrap(Box::new(|| {
flush_pending_effects();
}));
let func: js_sys::Function =
closure.as_ref().unchecked_ref::<js_sys::Function>().clone();
closure.forget();
let leaked: &js_sys::Function = Box::leak(Box::new(func));
let addr: usize = leaked as *const js_sys::Function as usize;
set_flush_closure_addr(Some(addr));
addr
}
};
unsafe { &*(addr as *const js_sys::Function) }
}
fn flush_pending_effects() {
set_effect_flush_scheduled(false);
for _ in 0..MAX_FLUSH_ITERATIONS {
let batch: Vec<usize> = {
let pending: &mut Vec<usize> = get_pending_effects();
take(pending)
};
if batch.is_empty() {
return;
}
for effect_addr in batch {
let effect: RenderEffect = RenderEffect { inner: effect_addr };
if effect.leak_mut().get_disposed() {
continue;
}
effect.run_once();
}
}
}
fn get_paused_effect_signals() -> &'static mut Vec<usize> {
let addr: usize = unsafe {
match PAUSED_EFFECT_SIGNALS {
Some(addr) => addr,
None => {
let queue: Box<Vec<usize>> = Box::default();
let leaked: &mut Vec<usize> = Box::leak(queue);
let addr: usize = leaked as *mut Vec<usize> as usize;
PAUSED_EFFECT_SIGNALS = Some(addr);
addr
}
}
};
unsafe { &mut *(addr as *mut Vec<usize>) }
}
pub(crate) fn pause_effect_notifications() {
set_effect_notifications_paused(true);
}
pub(crate) fn resume_effect_notifications() {
let suppressed: Vec<usize> = {
let paused: &mut Vec<usize> = get_paused_effect_signals();
take(paused)
};
set_effect_notifications_paused(false);
for signal_addr in suppressed {
notify_effect_subscribers(signal_addr);
}
}
pub(crate) fn current_effect_addr() -> Option<usize> {
unsafe { CURRENT_EFFECT }
}
pub(crate) fn set_current_effect(addr: Option<usize>) {
unsafe {
CURRENT_EFFECT = addr;
}
}
pub(crate) fn swap_current_effect(new_addr: Option<usize>) -> Option<usize> {
unsafe {
let old: Option<usize> = CURRENT_EFFECT;
CURRENT_EFFECT = new_addr;
old
}
}