From fd946feac4c2486c44aca91428c976ba69cc1392 Mon Sep 17 00:00:00 2001 From: Ryan G Date: Sat, 25 Jan 2020 19:04:03 -0500 Subject: [PATCH] Web backend refactor and documentation (#1415) The current implementation of the event loop runner has some significant problems. It can't handle multiple events being emitted at once (for example, when a keyboard event causes a key input, a text input, and a modifier change.) It's also relatively easy to introduce bugs for the different possible control flow states. The new model separates intentionally emitting a NewEvents (poll completed, wait completed, init) and emitting a normal event, as well as providing a method for emitting multiple events in a single call. --- src/platform_impl/web/event_loop/runner.rs | 129 ++++++++++++++------- src/platform_impl/web/mod.rs | 19 +++ 2 files changed, 104 insertions(+), 44 deletions(-) diff --git a/src/platform_impl/web/event_loop/runner.rs b/src/platform_impl/web/event_loop/runner.rs index b480054cb6..398fcaf6cb 100644 --- a/src/platform_impl/web/event_loop/runner.rs +++ b/src/platform_impl/web/event_loop/runner.rs @@ -8,6 +8,7 @@ use std::{ cell::RefCell, clone::Clone, collections::{HashSet, VecDeque}, + iter, rc::Rc, }; @@ -60,7 +61,7 @@ impl Shared { event_handler: Box, &mut root::ControlFlow)>, ) { self.0.runner.replace(Some(Runner::new(event_handler))); - self.send_event(Event::NewEvents(StartCause::Init)); + self.init(); let close_instance = self.clone(); backend::on_unload(move || close_instance.handle_unload()); @@ -79,55 +80,98 @@ impl Shared { self.0.redraw_pending.borrow_mut().insert(id); } - // Add an event to the event loop runner + pub fn init(&self) { + let start_cause = Event::NewEvents(StartCause::Init); + self.run_until_cleared(iter::once(start_cause)); + } + + // 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 Iterator>) { // If the event loop is closed, it should discard any new events if self.is_closed() { return; } - - // Determine if event handling is in process, and then release the borrow on the runner - let (start_cause, event_is_start) = match *self.0.runner.borrow() { - Some(ref runner) if !runner.is_busy => { - if let Event::NewEvents(cause) = event { - (cause, true) - } else { - ( - match runner.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; - } - }, - false, - ) - } + // If we can run the event processing right now, or need to queue this and wait for later + let mut process_immediately = true; + if let Some(ref runner) = &*self.0.runner.borrow() { + // 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; } - _ => { - // Events are currently being handled, so queue this one and don't try to - // double-process the event queue - self.0.events.borrow_mut().push_back(event); - return; + // If the runner is busy, queue this and wait for it to process it later + if runner.is_busy { + process_immediately = false; } + } else { + // The runner still hasn't been attached: queue this event and wait for it to be + process_immediately = false; + } + 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 = if let Some(runner) = &*self.0.runner.borrow() { + match runner.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 => { + // If we're in the exit state, don't do event processing + return; + } + } + } else { + unreachable!("The runner cannot process events when it is not attached"); }; + // 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); + } + + // 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(); - // Handle starting a new batch of events - // - // The user is informed via Event::NewEvents that there is a batch of events to process - // However, there is only one of these per batch of events - self.handle_event(Event::NewEvents(start_cause), &mut control); - if !event_is_start { + for event in events { self.handle_event(event, &mut control); } self.handle_event(Event::MainEventsCleared, &mut control); @@ -196,10 +240,7 @@ impl Shared { root::ControlFlow::Poll => { let cloned = self.clone(); State::Poll { - timeout: backend::Timeout::new( - move || cloned.send_event(Event::NewEvents(StartCause::Poll)), - Duration::from_millis(0), - ), + timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)), } } root::ControlFlow::Wait => State::Wait { @@ -220,7 +261,7 @@ impl Shared { start, end, timeout: backend::Timeout::new( - move || cloned.send_event(Event::NewEvents(StartCause::Poll)), + move || cloned.resume_time_reached(start, end), delay, ), } diff --git a/src/platform_impl/web/mod.rs b/src/platform_impl/web/mod.rs index bbd5760ec9..ba022db6e7 100644 --- a/src/platform_impl/web/mod.rs +++ b/src/platform_impl/web/mod.rs @@ -1,3 +1,22 @@ +// Brief introduction to the internals of the web backend: +// Currently, the web backend supports both wasm-bindgen and stdweb as methods of binding to the +// environment. Because they are both supporting the same underlying APIs, the actual web bindings +// are cordoned off into backend abstractions, which present the thinnest unifying layer possible. +// +// When adding support for new events or interactions with the browser, first consult trusted +// documentation (such as MDN) to ensure it is well-standardised and supported across many browsers. +// Once you have decided on the relevant web APIs, add support to both backends. +// +// The backend is used by the rest of the module to implement Winit's business logic, which forms +// the rest of the code. 'device', 'error', 'monitor', and 'window' define web-specific structures +// for winit's cross-platform structures. They are all relatively simple translations. +// +// The event_loop module handles listening for and processing events. 'Proxy' implements +// EventLoopProxy and 'WindowTarget' implements EventLoopWindowTarget. WindowTarget also handles +// registering the event handlers. The 'Execution' struct in the 'runner' module handles taking +// incoming events (from the registered handlers) and ensuring they are passed to the user in a +// compliant way. + mod device; mod error; mod event_loop;