use std::sync::Mutex; use std::{cell::RefCell, rc::Rc}; 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 = Result; type HandlerClosure = Closure 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 { let x = document() .query_selector("section.watch canvas")? .ok_or("no canvas")?; Ok(x.dyn_into()?) } fn fps() -> JSResult { let x = document() .query_selector("section.watch .fps")? .ok_or("no fps counter")?; Ok(x.dyn_into()?) } fn go() -> JSResult { let x = document() .query_selector("section.watch button")? .ok_or("no go button")?; Ok(x.dyn_into()?) } fn iters() -> JSResult { let x = document() .query_selector("section.bench input")? .ok_or("no iters input")?; Ok(x.dyn_into()?) } fn bench() -> JSResult { let x = document() .query_selector("section.bench button")? .ok_or("no bench button")?; Ok(x.dyn_into()?) } fn results() -> JSResult { let x = document() .query_selector("section.bench .results")? .ok_or("no results element")?; Ok(x.dyn_into()?) } fn dots() -> JSResult { let x = document() .query_selector("#dots")? .ok_or("no dots element")?; Ok(x.dyn_into()?) } fn dots_val() -> JSResult { Ok(dots()?.value().parse().map_err(|_| "dots isn't int")?) } #[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.set_dot_count(dots_val()?); s.render_frame(0.0)?; let shared_s = Rc::new(RefCell::new(s)); let s1 = shared_s.clone(); let s2 = 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 dot_count_handler: HandlerClosure = Closure::new(move |e| { debug!("dot count changed {:?} - {}", e, dots_val()?); let mut s = s2.borrow_mut(); s.set_dot_count(dots_val()?); s.render_frame(0.0)?; Ok(()) }); dots()?.set_onchange(Some(dot_count_handler.as_ref().unchecked_ref())); // otherwise it gets dropped after we're done, invalidating the // handler. dot_count_handler.forget(); 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())); // ibid. go_handler.forget(); let bench_handler: HandlerClosure = Closure::new(move |e| { debug!("bench clicked {:?}", e); let iters = iters()? .value() .parse::() .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.points.sort_by(|a, b| { if a.y > b.y { std::cmp::Ordering::Greater } else if a.y == b.y { std::cmp::Ordering::Equal } else { std::cmp::Ordering::Less } }); s.find_poly(); s.update()?; } let end = perf.now(); let delta = end - start; let iters_per_ms = Into::::into(iters) / delta; results()?.set_text_content(Some( format!( "{} iters in {:0.2} ms ({:0.2} iters per ms)", iters, delta, iters_per_ms ) .as_str(), )); Ok(()) }); bench()?.set_onclick(Some(bench_handler.as_ref().unchecked_ref())); // ibid. bench_handler.forget(); Ok(()) }