use super::{super::ScaleChangeArgs, backend, state::State}; use crate::event::{Event, StartCause}; use crate::event_loop::ControlFlow; use crate::window::WindowId; use std::{ cell::RefCell, clone::Clone, collections::{HashSet, VecDeque}, iter, ops::Deref, rc::{Rc, Weak}, }; use web_time::{Duration, Instant}; pub struct Shared(Rc>); pub(super) type EventHandler = dyn FnMut(Event<'_, T>, &mut ControlFlow); impl Clone for Shared { fn clone(&self) -> Self { Shared(self.0.clone()) } } pub struct Execution { runner: RefCell>, events: RefCell>>, id: RefCell, window: web_sys::Window, all_canvases: RefCell>)>>, redraw_pending: RefCell>, destroy_pending: RefCell>, scale_change_detector: RefCell>, unload_event_handle: RefCell>, } enum RunnerEnum { /// The `EventLoop` is created but not being run. Pending, /// The `EventLoop` is being run. Running(Runner), /// The `EventLoop` is exited after being started with `EventLoop::run`. Since /// `EventLoop::run` takes ownership of the `EventLoop`, we can be certain /// that this event loop will never be run again. Destroyed, } impl RunnerEnum { fn maybe_runner(&self) -> Option<&Runner> { match self { RunnerEnum::Running(runner) => Some(runner), _ => None, } } } struct Runner { state: State, event_handler: Box>, } impl Runner { pub fn new(event_handler: Box>) -> Self { Runner { state: State::Init, event_handler, } } /// Returns the corresponding `StartCause` for the current `state`, or `None` /// when in `Exit` state. fn maybe_start_cause(&self) -> Option { Some(match self.state { State::Init => StartCause::Init, State::Poll { .. } => StartCause::Poll, State::Wait { start } => StartCause::WaitCancelled { start, requested_resume: None, }, State::WaitUntil { start, end, .. } => StartCause::WaitCancelled { start, requested_resume: Some(end), }, State::Exit => return None, }) } fn handle_single_event(&mut self, event: Event<'_, T>, control: &mut ControlFlow) { let is_closed = matches!(*control, ControlFlow::ExitWithCode(_)); (self.event_handler)(event, control); // Maintain closed state, even if the callback changes it if is_closed { *control = ControlFlow::Exit; } } } impl Shared { pub fn new() -> Self { Shared(Rc::new(Execution { runner: RefCell::new(RunnerEnum::Pending), events: RefCell::new(VecDeque::new()), #[allow(clippy::disallowed_methods)] window: web_sys::window().expect("only callable from inside the `Window`"), id: RefCell::new(0), all_canvases: RefCell::new(Vec::new()), redraw_pending: RefCell::new(HashSet::new()), destroy_pending: RefCell::new(VecDeque::new()), scale_change_detector: RefCell::new(None), unload_event_handle: RefCell::new(None), })) } pub fn window(&self) -> &web_sys::Window { &self.0.window } pub fn add_canvas(&self, id: WindowId, canvas: &Rc>) { self.0 .all_canvases .borrow_mut() .push((id, Rc::downgrade(canvas))); } pub fn notify_destroy_window(&self, id: WindowId) { self.0.destroy_pending.borrow_mut().push_back(id); } // Set the event callback to use for the event loop runner // This the event callback is a fairly thin layer over the user-provided callback that closes // over a RootEventLoopWindowTarget reference pub fn set_listener(&self, event_handler: Box>) { { let mut runner = self.0.runner.borrow_mut(); assert!(matches!(*runner, RunnerEnum::Pending)); *runner = RunnerEnum::Running(Runner::new(event_handler)); } self.init(); let close_instance = self.clone(); *self.0.unload_event_handle.borrow_mut() = Some(backend::on_unload(self.window(), move || { close_instance.handle_unload() })); } pub(crate) fn set_on_scale_change(&self, handler: F) where F: 'static + FnMut(ScaleChangeArgs), { *self.0.scale_change_detector.borrow_mut() = Some(backend::ScaleChangeDetector::new( self.window().clone(), handler, )); } // Generate a strictly increasing ID // This is used to differentiate windows when handling events pub fn generate_id(&self) -> u32 { let mut id = self.0.id.borrow_mut(); *id += 1; *id } pub fn request_redraw(&self, id: WindowId) { self.0.redraw_pending.borrow_mut().insert(id); self.send_events(iter::empty()); } pub fn init(&self) { // NB: For consistency all platforms must emit a 'resumed' event even though web // applications don't themselves have a formal suspend/resume lifecycle. self.run_until_cleared([Event::NewEvents(StartCause::Init), Event::Resumed].into_iter()); } // Run the polling logic for the Poll ControlFlow, which involves clearing the queue pub fn poll(&self) { let start_cause = Event::NewEvents(StartCause::Poll); self.run_until_cleared(iter::once(start_cause)); } // Run the logic for waking from a WaitUntil, which involves clearing the queue // Generally there shouldn't be events built up when this is called pub fn resume_time_reached(&self, start: Instant, requested_resume: Instant) { let start_cause = Event::NewEvents(StartCause::ResumeTimeReached { start, requested_resume, }); self.run_until_cleared(iter::once(start_cause)); } // Add an event to the event loop runner, from the user or an event handler // // It will determine if the event should be immediately sent to the user or buffered for later pub fn send_event(&self, event: Event<'static, T>) { self.send_events(iter::once(event)); } // Add a series of events to the event loop runner // // It will determine if the event should be immediately sent to the user or buffered for later pub fn send_events(&self, events: impl IntoIterator>) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; } // If we can run the event processing right now, or need to queue this and wait for later let mut process_immediately = true; match self.0.runner.try_borrow().as_ref().map(Deref::deref) { Ok(RunnerEnum::Running(ref runner)) => { // If we're currently polling, queue this and wait for the poll() method to be called if let State::Poll { .. } = runner.state { process_immediately = false; } } Ok(RunnerEnum::Pending) => { // The runner still hasn't been attached: queue this event and wait for it to be process_immediately = false; } // Some other code is mutating the runner, which most likely means // the event loop is running and busy. So we queue this event for // it to be processed later. Err(_) => { process_immediately = false; } // This is unreachable since `self.is_closed() == true`. Ok(RunnerEnum::Destroyed) => unreachable!(), } if !process_immediately { // Queue these events to look at later self.0.events.borrow_mut().extend(events); return; } // At this point, we know this is a fresh set of events // Now we determine why new events are incoming, and handle the events let start_cause = match (self.0.runner.borrow().maybe_runner()) .unwrap_or_else(|| { unreachable!("The runner cannot process events when it is not attached") }) .maybe_start_cause() { Some(c) => c, // If we're in the exit state, don't do event processing None => return, }; // Take the start event, then the events provided to this function, and run an iteration of // the event loop let start_event = Event::NewEvents(start_cause); let events = iter::once(start_event).chain(events); self.run_until_cleared(events); } // Process the destroy-pending windows. This should only be called from // `run_until_cleared` and `handle_scale_changed`, somewhere between emitting // `NewEvents` and `MainEventsCleared`. fn process_destroy_pending_windows(&self, control: &mut ControlFlow) { while let Some(id) = self.0.destroy_pending.borrow_mut().pop_front() { self.0 .all_canvases .borrow_mut() .retain(|&(item_id, _)| item_id != id); self.handle_event( Event::WindowEvent { window_id: id, event: crate::event::WindowEvent::Destroyed, }, control, ); self.0.redraw_pending.borrow_mut().remove(&id); } } // Given the set of new events, run the event loop until the main events and redraw events are // cleared // // This will also process any events that have been queued or that are queued during processing fn run_until_cleared(&self, events: impl Iterator>) { let mut control = self.current_control_flow(); for event in events { self.handle_event(event, &mut control); } self.process_destroy_pending_windows(&mut control); self.handle_event(Event::MainEventsCleared, &mut control); // Collect all of the redraw events to avoid double-locking the RefCell let redraw_events: Vec = self.0.redraw_pending.borrow_mut().drain().collect(); for window_id in redraw_events { self.handle_event(Event::RedrawRequested(window_id), &mut control); } self.handle_event(Event::RedrawEventsCleared, &mut control); self.apply_control_flow(control); // If the event loop is closed, it has been closed this iteration and now the closing // event should be emitted if self.is_closed() { self.handle_loop_destroyed(&mut control); } } pub fn handle_scale_changed(&self, old_scale: f64, new_scale: f64) { // If there aren't any windows, then there is nothing to do here. if self.0.all_canvases.borrow().is_empty() { return; } let start_cause = match (self.0.runner.borrow().maybe_runner()) .unwrap_or_else(|| unreachable!("`scale_changed` should not happen without a runner")) .maybe_start_cause() { Some(c) => c, // If we're in the exit state, don't do event processing None => return, }; let mut control = self.current_control_flow(); // Handle the start event and all other events in the queue. self.handle_event(Event::NewEvents(start_cause), &mut control); // It is possible for windows to be dropped before this point. We don't // want to send `ScaleFactorChanged` for destroyed windows, so we process // the destroy-pending windows here. self.process_destroy_pending_windows(&mut control); // Now handle the `ScaleFactorChanged` events. for &(id, ref canvas) in &*self.0.all_canvases.borrow() { let rc = match canvas.upgrade() { Some(rc) => rc, // This shouldn't happen, but just in case... None => continue, }; let canvas = rc.borrow(); // First, we send the `ScaleFactorChanged` event: let current_size = canvas.size().get(); let logical_size = current_size.to_logical::(old_scale); let mut new_size = logical_size.to_physical(new_scale); self.handle_single_event_sync( Event::WindowEvent { window_id: id, event: crate::event::WindowEvent::ScaleFactorChanged { scale_factor: new_scale, new_inner_size: &mut new_size, }, }, &mut control, ); // Then we resize the canvas to the new size and send a `Resized` event: if current_size != new_size { backend::set_canvas_size(&canvas, crate::dpi::Size::Physical(new_size)); self.handle_single_event_sync( Event::WindowEvent { window_id: id, event: crate::event::WindowEvent::Resized(new_size), }, &mut control, ); } } // Process the destroy-pending windows again. self.process_destroy_pending_windows(&mut control); self.handle_event(Event::MainEventsCleared, &mut control); // Discard all the pending redraw as we shall just redraw all windows. self.0.redraw_pending.borrow_mut().clear(); for &(window_id, _) in &*self.0.all_canvases.borrow() { self.handle_event(Event::RedrawRequested(window_id), &mut control); } self.handle_event(Event::RedrawEventsCleared, &mut control); self.apply_control_flow(control); // If the event loop is closed, it has been closed this iteration and now the closing // event should be emitted if self.is_closed() { self.handle_loop_destroyed(&mut control); } } fn handle_unload(&self) { self.apply_control_flow(ControlFlow::Exit); let mut control = self.current_control_flow(); // We don't call `handle_loop_destroyed` here because we don't need to // perform cleanup when the web browser is going to destroy the page. self.handle_event(Event::LoopDestroyed, &mut control); } // handle_single_event_sync takes in an event and handles it synchronously. // // It should only ever be called from `scale_changed`. fn handle_single_event_sync(&self, event: Event<'_, T>, control: &mut ControlFlow) { if self.is_closed() { *control = ControlFlow::Exit; } match *self.0.runner.borrow_mut() { RunnerEnum::Running(ref mut runner) => { runner.handle_single_event(event, control); } _ => panic!("Cannot handle event synchronously without a runner"), } } // handle_event takes in events and either queues them or applies a callback // // It should only ever be called from `run_until_cleared` and `scale_changed`. fn handle_event(&self, event: Event<'static, T>, control: &mut ControlFlow) { if self.is_closed() { *control = ControlFlow::Exit; } match *self.0.runner.borrow_mut() { RunnerEnum::Running(ref mut runner) => { runner.handle_single_event(event, control); } // If an event is being handled without a runner somehow, add it to the event queue so // it will eventually be processed RunnerEnum::Pending => self.0.events.borrow_mut().push_back(event), // If the Runner has been destroyed, there is nothing to do. RunnerEnum::Destroyed => return, } let is_closed = matches!(*control, ControlFlow::ExitWithCode(_)); // Don't take events out of the queue if the loop is closed or the runner doesn't exist // If the runner doesn't exist and this method recurses, it will recurse infinitely if !is_closed && self.0.runner.borrow().maybe_runner().is_some() { // Take an event out of the queue and handle it // Make sure not to let the borrow_mut live during the next handle_event let event = { self.0.events.borrow_mut().pop_front() }; if let Some(event) = event { self.handle_event(event, control); } } } // Apply the new ControlFlow that has been selected by the user // Start any necessary timeouts etc fn apply_control_flow(&self, control_flow: ControlFlow) { let new_state = match control_flow { ControlFlow::Poll => { let cloned = self.clone(); State::Poll { request: backend::AnimationFrameRequest::new( self.window().clone(), move || cloned.poll(), ), } } ControlFlow::Wait => State::Wait { start: Instant::now(), }, ControlFlow::WaitUntil(end) => { let start = Instant::now(); let delay = if end <= start { Duration::from_millis(0) } else { end - start }; let cloned = self.clone(); State::WaitUntil { start, end, timeout: backend::Timeout::new( self.window().clone(), move || cloned.resume_time_reached(start, end), delay, ), } } ControlFlow::ExitWithCode(_) => State::Exit, }; if let RunnerEnum::Running(ref mut runner) = *self.0.runner.borrow_mut() { runner.state = new_state; } } fn handle_loop_destroyed(&self, control: &mut ControlFlow) { self.handle_event(Event::LoopDestroyed, control); let all_canvases = std::mem::take(&mut *self.0.all_canvases.borrow_mut()); *self.0.scale_change_detector.borrow_mut() = None; *self.0.unload_event_handle.borrow_mut() = None; // Dropping the `Runner` drops the event handler closure, which will in // turn drop all `Window`s moved into the closure. *self.0.runner.borrow_mut() = RunnerEnum::Destroyed; for (_, canvas) in all_canvases { // In case any remaining `Window`s are still not dropped, we will need // to explicitly remove the event handlers associated with their canvases. if let Some(canvas) = canvas.upgrade() { let mut canvas = canvas.borrow_mut(); canvas.remove_listeners(); } } // At this point, the `self.0` `Rc` should only be strongly referenced // by the following: // * `self`, i.e. the item which triggered this event loop wakeup, which // is usually a `wasm-bindgen` `Closure`, which will be dropped after // returning to the JS glue code. // * The `EventLoopWindowTarget` leaked inside `EventLoop::run` due to the // JS exception thrown at the end. // * For each undropped `Window`: // * The `register_redraw_request` closure. // * The `destroy_fn` closure. } // Check if the event loop is currently closed fn is_closed(&self) -> bool { match self.0.runner.try_borrow().as_ref().map(Deref::deref) { Ok(RunnerEnum::Running(runner)) => runner.state.is_exit(), // The event loop is not closed since it is not initialized. Ok(RunnerEnum::Pending) => false, // The event loop is closed since it has been destroyed. Ok(RunnerEnum::Destroyed) => true, // Some other code is mutating the runner, which most likely means // the event loop is running and busy. Err(_) => false, } } // Get the current control flow state fn current_control_flow(&self) -> ControlFlow { match *self.0.runner.borrow() { RunnerEnum::Running(ref runner) => runner.state.control_flow(), RunnerEnum::Pending => ControlFlow::Poll, RunnerEnum::Destroyed => ControlFlow::Exit, } } }