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
use crate::{Mailbox, Node, Text};
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::Once;
use web_sys as web;

pub trait App: Sized + 'static {
    type Message;

    fn update(&mut self, _mailbox: &Mailbox<Self::Message>, _message: Self::Message) {}
    fn render(&self) -> Node<Self::Message>;
}

pub struct Instance<A: App> {
    inner: Rc<Inner<A>>,
}

struct Inner<A: App> {
    app: RefCell<A>,
    node: RefCell<web::Node>,
    vnode: RefCell<Node<A::Message>>,
    queue: RefCell<Vec<A::Message>>,
    is_updating: RefCell<bool>,
}

impl<A: App> Instance<A> {
    fn send(&self, message: A::Message) {
        if *self.inner.is_updating.borrow() {
            self.inner.queue.borrow_mut().push(message);
            return;
        }
        self.inner.is_updating.replace(true);
        let mailbox = self.mailbox();
        self.inner.app.borrow_mut().update(&mailbox, message);
        while !self.inner.queue.borrow().is_empty() {
            let message = self.inner.queue.borrow_mut().remove(0);
            self.inner.app.borrow_mut().update(&mailbox, message);
        }
        self.inner.is_updating.replace(false);
        self.render();
    }

    fn render(&self) {
        let mut new_vnode = self.inner.app.borrow().render();
        let new_node = new_vnode.patch(&mut self.inner.vnode.borrow_mut(), self.mailbox());
        self.inner.vnode.replace(new_vnode);
        self.inner.node.replace(new_node);
    }

    fn mailbox(&self) -> Mailbox<A::Message> {
        let cloned = self.clone();
        Mailbox::new(move |message| {
            cloned.send(message);
        })
    }
}

impl<A: App> std::clone::Clone for Instance<A> {
    fn clone(&self) -> Self {
        Instance {
            inner: Rc::clone(&self.inner),
        }
    }
}

impl<A: App> std::fmt::Debug for Instance<A> {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        f.debug_struct("Instance").finish()
    }
}

pub fn start<A: App>(app: A, node: web::Node) -> Mailbox<A::Message> {
    set_panic_hook();
    let mut vnode = Text::new("!");
    let new_node = vnode.create().into();
    node.parent_node()
        .unwrap()
        .replace_child(&new_node, &node)
        .unwrap();
    let instance = Instance {
        inner: Rc::new(Inner {
            app: RefCell::new(app),
            node: RefCell::new(new_node),
            vnode: RefCell::new(vnode.into()),
            is_updating: RefCell::new(false),
            queue: RefCell::new(Vec::new()),
        }),
    };
    instance.render();
    instance.mailbox()
}

fn set_panic_hook() {
    static PANIC_HOOK: Once = Once::new();

    PANIC_HOOK.call_once(|| {
        std::panic::set_hook(Box::new(|panic| {
            crate::console::error(&panic.to_string());
        }));
    });
}