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
//! Internal utilities for Sycamore.
//!
//! # Stability
//! This API is currently unstable and can have breaking changed without a semver release.
//! This might be stabilized in the future but it is use-at-your-own-risk for now.

#[cfg(feature = "experimental-hydrate")]
pub mod hydrate;
pub mod render;

use std::cell::RefCell;
use std::hash::{Hash, Hasher};
use std::ptr;
use std::rc::Rc;

use ahash::AHashSet;

thread_local! {
    static TASKS: RefCell<AHashSet<Task>> = RefCell::new(AHashSet::new());
}

/// A wrapper over a callback. Used with [`loop_raf`].
#[derive(Clone)]
pub struct Task {
    callback: Rc<dyn Fn() -> bool>,
}

impl Task {
    pub fn new(callback: impl Fn() -> bool + 'static) -> Self {
        Self {
            callback: Rc::new(callback),
        }
    }

    pub fn abort(&self) {
        TASKS.with(|tasks| {
            tasks.borrow_mut().remove(self);
        });
    }
}

impl Hash for Task {
    fn hash<H: Hasher>(&self, state: &mut H) {
        Rc::as_ptr(&self.callback).hash(state);
    }
}

impl PartialEq for Task {
    fn eq(&self, other: &Self) -> bool {
        ptr::eq::<()>(
            Rc::as_ptr(&self.callback).cast(),
            Rc::as_ptr(&other.callback).cast(),
        )
    }
}
impl Eq for Task {}

#[cfg(feature = "dom")]
pub(crate) fn run_tasks() {
    use wasm_bindgen::prelude::*;
    use wasm_bindgen::JsCast;

    let f = Rc::new(RefCell::new(None::<Closure<dyn Fn()>>));
    let g = Rc::clone(&f);

    *g.borrow_mut() = Some(Closure::wrap(Box::new(move || {
        TASKS.with(|tasks| {
            let task_list = (*tasks.borrow()).clone();
            for task in task_list {
                if !(task.callback)() {
                    tasks.borrow_mut().remove(&task);
                }
            }

            if tasks.borrow().is_empty() {
                let callback = f.take();
                drop(callback);
            } else {
                web_sys::window()
                    .unwrap_throw()
                    .request_animation_frame(
                        f.borrow().as_ref().unwrap_throw().as_ref().unchecked_ref(),
                    )
                    .unwrap_throw();
            }
        });
    })));

    web_sys::window()
        .unwrap_throw()
        .request_animation_frame(g.borrow().as_ref().unwrap_throw().as_ref().unchecked_ref())
        .unwrap_throw();
}

#[cfg(not(feature = "dom"))]
pub(crate) fn run_tasks() {
    // noop on non web targets
}

/// Runs a callback in a `requestAnimationFrame` loop until the `callback` returns `false`.
pub fn loop_raf(task: Task) {
    TASKS.with(|tasks| {
        if tasks.borrow().is_empty() {
            run_tasks();
        }

        tasks.borrow_mut().insert(task);
    });
}