diff --git a/docs/example_wasm.js b/docs/example_wasm.js index cdfd84c1..7ab54672 100644 --- a/docs/example_wasm.js +++ b/docs/example_wasm.js @@ -207,28 +207,28 @@ function makeMutClosure(arg0, arg1, dtor, f) { real.original = state; return real; } -function __wbg_adapter_26(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); +function __wbg_adapter_26(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h5402719cc6dde927(arg0, arg1); } function __wbg_adapter_29(arg0, arg1, arg2) { wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); } -function __wbg_adapter_32(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h5402719cc6dde927(arg0, arg1); +function __wbg_adapter_32(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); } -function __wbg_adapter_35(arg0, arg1, arg2) { - wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); +function __wbg_adapter_35(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h84da5f062b972f09(arg0, arg1); } function __wbg_adapter_38(arg0, arg1, arg2) { wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); } -function __wbg_adapter_41(arg0, arg1) { - wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h84da5f062b972f09(arg0, arg1); +function __wbg_adapter_41(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2)); } /** @@ -725,30 +725,30 @@ async function init(input) { var ret = wasm.memory; return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper380 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_38); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_closure_wrapper377 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_35); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_closure_wrapper384 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_29); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_closure_wrapper376 = function(arg0, arg1, arg2) { - var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_32); - return addHeapObject(ret); - }; - imports.wbg.__wbindgen_closure_wrapper382 = function(arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper385 = function(arg0, arg1, arg2) { var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_41); return addHeapObject(ret); }; - imports.wbg.__wbindgen_closure_wrapper386 = function(arg0, arg1, arg2) { + imports.wbg.__wbindgen_closure_wrapper376 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_29); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper379 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_38); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper381 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_35); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper375 = function(arg0, arg1, arg2) { var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_26); return addHeapObject(ret); }; + imports.wbg.__wbindgen_closure_wrapper383 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 71, __wbg_adapter_32); + return addHeapObject(ret); + }; if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) { input = fetch(input); diff --git a/docs/example_wasm_bg.wasm b/docs/example_wasm_bg.wasm index 0b6a903e..7bfac57e 100644 Binary files a/docs/example_wasm_bg.wasm and b/docs/example_wasm_bg.wasm differ diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index a65bebeb..7e0c7414 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -39,6 +39,7 @@ pub struct Backend { /// If true, paint at full framerate always. /// If false, only paint on input. run_mode: RunMode, + last_save_time: Option, } impl Backend { @@ -51,6 +52,7 @@ impl Backend { frame_times: egui::MovementTracker::new(1000, 1.0), frame_start: None, run_mode, + last_save_time: None, }) } @@ -72,28 +74,40 @@ impl Backend { self.ctx.begin_frame(raw_input) } - pub fn end_frame(&mut self) -> Result { + pub fn end_frame(&mut self) -> Result<(egui::Output, egui::PaintBatches), JsValue> { let frame_start = self .frame_start .take() .expect("unmatched calls to begin_frame/end_frame"); - let bg_color = egui::color::srgba(0, 0, 0, 0); // Use background css color. let (output, batches) = self.ctx.end_frame(); + self.auto_save(); + let now = now_sec(); self.frame_times.add(now, (now - frame_start) as f32); + Ok((output, batches)) + } + + pub fn paint(&mut self, batches: egui::PaintBatches) -> Result<(), JsValue> { + let bg_color = egui::color::TRANSPARENT; // Use background css color. self.painter.paint_batches( bg_color, batches, self.ctx.texture(), self.ctx.pixels_per_point(), - )?; + ) + } - save_memory(&self.ctx); // TODO: don't save every frame - - Ok(output) + pub fn auto_save(&mut self) { + let now = now_sec(); + let time_since_last_save = now - self.last_save_time.unwrap_or(now); + const AUTO_SAVE_INTERVAL: f64 = 5.0; + if time_since_last_save > AUTO_SAVE_INTERVAL { + self.last_save_time = Some(now); + save_memory(&self.ctx); + } } pub fn painter_debug_info(&self) -> String { @@ -144,8 +158,7 @@ pub struct AppRunner { pub backend: Backend, pub web_input: WebInput, pub app: Box, - /// Used to prevent calling paint() twice in one frame - pub has_requested_animaiton_frame: bool, + pub needs_repaint: bool, // TODO: move } impl AppRunner { @@ -154,7 +167,7 @@ impl AppRunner { backend, web_input: Default::default(), app, - has_requested_animaiton_frame: false, + needs_repaint: true, // TODO: move }) } @@ -162,7 +175,7 @@ impl AppRunner { self.backend.canvas_id() } - pub fn paint(&mut self) -> Result { + pub fn logic(&mut self) -> Result<(egui::Output, egui::PaintBatches), JsValue> { resize_to_screen_size(self.backend.canvas_id()); let raw_input = self.web_input.new_frame(); @@ -173,11 +186,13 @@ impl AppRunner { let mut ui = self.backend.begin_frame(raw_input); self.app.ui(&mut ui, &mut self.backend, &info); - let output = self.backend.end_frame()?; - + let (output, batches) = self.backend.end_frame()?; handle_output(&output); + Ok((output, batches)) + } - Ok(output) + pub fn paint(&mut self, batches: egui::PaintBatches) -> Result<(), JsValue> { + self.backend.paint(batches) } } @@ -395,33 +410,27 @@ pub fn translate_key(key: &str) -> Option { pub struct AppRunnerRef(Arc>); fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> { - let needs_repaint = { + fn paint(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let mut runner_lock = runner_ref.0.lock(); - runner_lock.has_requested_animaiton_frame = false; - let output = runner_lock.paint()?; - let run_mode = runner_lock.backend.run_mode(); - run_mode == RunMode::Continuous || output.needs_repaint - }; - if needs_repaint { - request_animation_frame(runner_ref)?; + if runner_lock.backend.run_mode() == RunMode::Continuous || runner_lock.needs_repaint { + runner_lock.needs_repaint = false; + let (output, batches) = runner_lock.logic()?; + runner_lock.paint(batches)?; + runner_lock.needs_repaint = output.needs_repaint; + } + Ok(()) } - Ok(()) -} - -fn request_animation_frame(runner_ref: AppRunnerRef) -> Result<(), JsValue> { - if !{ runner_ref.0.lock().has_requested_animaiton_frame } { - runner_ref.0.lock().has_requested_animaiton_frame = true; + fn request_animation_frame(runner_ref: AppRunnerRef) -> Result<(), JsValue> { use wasm_bindgen::JsCast; let window = web_sys::window().unwrap(); let closure = Closure::once(move || paint_and_schedule(runner_ref)); window.request_animation_frame(closure.as_ref().unchecked_ref())?; closure.forget(); // We must forget it, or else the callback is canceled on drop + Ok(()) } - Ok(()) -} -fn invalidate(runner_ref: AppRunnerRef) -> Result<(), JsValue> { + paint(&runner_ref)?; request_animation_frame(runner_ref) } @@ -443,8 +452,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { } else { runner_lock.web_input.events.push(egui::Event::Text(key)); } - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; }) as Box); document.add_event_listener_with_callback("keydown", closure.as_ref().unchecked_ref())?; closure.forget(); @@ -461,8 +469,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { key, pressed: false, }); - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; } }) as Box); document.add_event_listener_with_callback("keyup", closure.as_ref().unchecked_ref())?; @@ -472,7 +479,7 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { for event_name in &["load", "pagehide", "pageshow", "resize"] { let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move || { - invalidate(runner_ref.clone()).unwrap(); + runner_ref.0.lock().needs_repaint = true; }) as Box); document.add_event_listener_with_callback(event_name, closure.as_ref().unchecked_ref())?; closure.forget(); @@ -494,8 +501,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { runner_lock.web_input.mouse_pos = Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); runner_lock.web_input.mouse_down = true; - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; event.stop_propagation(); event.prevent_default(); } @@ -512,8 +518,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { if !runner_lock.web_input.is_touch { runner_lock.web_input.mouse_pos = Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; event.stop_propagation(); event.prevent_default(); } @@ -531,8 +536,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { runner_lock.web_input.mouse_pos = Some(pos_from_mouse_event(runner_lock.canvas_id(), &event)); runner_lock.web_input.mouse_down = false; - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; event.stop_propagation(); event.prevent_default(); } @@ -548,8 +552,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let mut runner_lock = runner_ref.0.lock(); if !runner_lock.web_input.is_touch { runner_lock.web_input.mouse_pos = None; - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; event.stop_propagation(); event.prevent_default(); } @@ -566,8 +569,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { runner_lock.web_input.is_touch = true; runner_lock.web_input.mouse_pos = Some(pos_from_touch_event(&event)); runner_lock.web_input.mouse_down = true; - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; event.stop_propagation(); event.prevent_default(); }) as Box); @@ -582,8 +584,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let mut runner_lock = runner_ref.0.lock(); runner_lock.web_input.is_touch = true; runner_lock.web_input.mouse_pos = Some(pos_from_touch_event(&event)); - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; event.stop_propagation(); event.prevent_default(); }) as Box); @@ -595,14 +596,12 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let event_name = "touchend"; let runner_ref = runner_ref.clone(); let closure = Closure::wrap(Box::new(move |event: web_sys::TouchEvent| { - // TODO: this paints twice in one frame, which is not great let mut runner_lock = runner_ref.0.lock(); runner_lock.web_input.is_touch = true; runner_lock.web_input.mouse_down = false; // First release mouse to click... - runner_lock.paint().unwrap(); // ...do the clicking... + runner_lock.logic().unwrap(); // ...do the clicking... runner_lock.web_input.mouse_pos = None; // ...remove hover effect - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; event.stop_propagation(); event.prevent_default(); }) as Box); @@ -617,8 +616,7 @@ fn install_canvas_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { let mut runner_lock = runner_ref.0.lock(); runner_lock.web_input.scroll_delta.x -= event.delta_x() as f32; runner_lock.web_input.scroll_delta.y -= event.delta_y() as f32; - drop(runner_lock); - invalidate(runner_ref.clone()).unwrap(); + runner_lock.needs_repaint = true; event.stop_propagation(); event.prevent_default(); }) as Box);