aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib.rs
blob: 60fdc0f8a5694724ecb5e80b35609a65992712b6 (plain)
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
use std::{cell::RefCell, rc::Rc};
use std::sync::Mutex;

use log::{Level, debug, info};
use wasm_bindgen::prelude::*;

use render_loop::RenderLoop;
use state::State;
use web_sys::Event;

mod line;
mod point;
mod render_loop;
mod state;

type JSResult<T> = Result<T, JsValue>;
type HandlerClosure = Closure<dyn FnMut(Event) -> JSResult<()>>;

fn window() -> web_sys::Window {
    web_sys::window().expect("no window")
}

fn document() -> web_sys::Document {
    window().document().expect("no document")
}

fn canvas() -> JSResult<web_sys::HtmlCanvasElement> {
    let x = document().query_selector("section.watch canvas")?.ok_or("no canvas")?;
    Ok(x.dyn_into()?)
}

fn fps() -> JSResult<web_sys::HtmlElement> {
    let x = document().query_selector("section.watch .fps")?.ok_or("no fps counter")?;
    Ok(x.dyn_into()?)
}

fn go() -> JSResult<web_sys::HtmlElement> {
    let x = document().query_selector("section.watch button")?.ok_or("no go button")?;
    Ok(x.dyn_into()?)
}

fn iters() -> JSResult<web_sys::HtmlInputElement> {
    let x = document().query_selector("section.bench input")?.ok_or("no go button")?;
    Ok(x.dyn_into()?)
}

fn bench() -> JSResult<web_sys::HtmlElement> {
    let x = document().query_selector("section.bench button")?.ok_or("no go button")?;
    Ok(x.dyn_into()?)
}

#[wasm_bindgen(start)]
pub fn init() -> JSResult<()> {
    console_log::init_with_level(Level::Debug)
        .map_err(|e| format!("couldn't init console: {e}"))?;
    info!("wasm init");

    let paused = Rc::new(Mutex::new(true));
    let p1 = paused.clone();

    let mut s = State::new(canvas()?, fps()?)?;
    s.render_frame(0.0)?;

    let shared_s = Rc::new(RefCell::new(s));
    let s1 = shared_s.clone();

    let render_loop = RenderLoop::new(move |t| {
        let mut s = shared_s.borrow_mut();
        s.update()?;
        s.render_frame(t)?;
        Ok(!*paused.lock().unwrap())
    });

    let go_handler: HandlerClosure = Closure::new(move |e| {
        debug!("go clicked: {:?}", e);
        let mut p = p1.lock().unwrap();
        *p = !*p;
        let text = if *p {
            "go"
        } else {
            render_loop.start()?;
            "pause"
        };
        go()?.set_text_content(Some(text));
        Ok(())
    });
    go()?.set_onclick(Some(go_handler.as_ref().unchecked_ref()));
    // otherwise it gets dropped after we're done, invalidating the
    // handler.
    go_handler.forget();

    let bench_handler: HandlerClosure = Closure::new(move |e| {
        debug!("bench clicked {:?}", e);
        let iters = iters()?.value().parse::<u32>().map_err(|_| "iters isn't int")?;

        let mut s = s1.borrow_mut();
        let perf = window().performance().unwrap();
        let start = perf.now();
        for _ in 0..iters {
            s.update()?;
        }
        let end = perf.now();
        let delta = end - start;
        let iters_per_ms = Into::<f64>::into(iters) / delta;

        info!("bench done. {iters} iters in {delta} ms ({iters_per_ms} iters per ms)");
        Ok(())
    });
    bench()?.set_onclick(Some(bench_handler.as_ref().unchecked_ref()));
    // ibid.
    bench_handler.forget();

    Ok(())
}