mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-25 14:51:30 +11:00
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.
This commit is contained in:
parent
8856b6ecb7
commit
fd946feac4
|
@ -8,6 +8,7 @@ use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
clone::Clone,
|
clone::Clone,
|
||||||
collections::{HashSet, VecDeque},
|
collections::{HashSet, VecDeque},
|
||||||
|
iter,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ impl<T: 'static> Shared<T> {
|
||||||
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
|
event_handler: Box<dyn FnMut(Event<'static, T>, &mut root::ControlFlow)>,
|
||||||
) {
|
) {
|
||||||
self.0.runner.replace(Some(Runner::new(event_handler)));
|
self.0.runner.replace(Some(Runner::new(event_handler)));
|
||||||
self.send_event(Event::NewEvents(StartCause::Init));
|
self.init();
|
||||||
|
|
||||||
let close_instance = self.clone();
|
let close_instance = self.clone();
|
||||||
backend::on_unload(move || close_instance.handle_unload());
|
backend::on_unload(move || close_instance.handle_unload());
|
||||||
|
@ -79,22 +80,65 @@ impl<T: 'static> Shared<T> {
|
||||||
self.0.redraw_pending.borrow_mut().insert(id);
|
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
|
// 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>) {
|
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<Item = Event<'static, T>>) {
|
||||||
// If the event loop is closed, it should discard any new events
|
// If the event loop is closed, it should discard any new events
|
||||||
if self.is_closed() {
|
if self.is_closed() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// If we can run the event processing right now, or need to queue this and wait for later
|
||||||
// Determine if event handling is in process, and then release the borrow on the runner
|
let mut process_immediately = true;
|
||||||
let (start_cause, event_is_start) = match *self.0.runner.borrow() {
|
if let Some(ref runner) = &*self.0.runner.borrow() {
|
||||||
Some(ref runner) if !runner.is_busy => {
|
// If we're currently polling, queue this and wait for the poll() method to be called
|
||||||
if let Event::NewEvents(cause) = event {
|
if let State::Poll { .. } = runner.state {
|
||||||
(cause, true)
|
process_immediately = false;
|
||||||
|
}
|
||||||
|
// If the runner is busy, queue this and wait for it to process it later
|
||||||
|
if runner.is_busy {
|
||||||
|
process_immediately = false;
|
||||||
|
}
|
||||||
} else {
|
} 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 {
|
match runner.state {
|
||||||
State::Init => StartCause::Init,
|
State::Init => StartCause::Init,
|
||||||
State::Poll { .. } => StartCause::Poll,
|
State::Poll { .. } => StartCause::Poll,
|
||||||
|
@ -107,27 +151,27 @@ impl<T: 'static> Shared<T> {
|
||||||
requested_resume: Some(end),
|
requested_resume: Some(end),
|
||||||
},
|
},
|
||||||
State::Exit => {
|
State::Exit => {
|
||||||
|
// If we're in the exit state, don't do event processing
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!("The runner cannot process events when it is not attached");
|
||||||
};
|
};
|
||||||
let mut control = self.current_control_flow();
|
// Take the start event, then the events provided to this function, and run an iteration of
|
||||||
// Handle starting a new batch of events
|
// 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
|
||||||
//
|
//
|
||||||
// The user is informed via Event::NewEvents that there is a batch of events to process
|
// This will also process any events that have been queued or that are queued during processing
|
||||||
// However, there is only one of these per batch of events
|
fn run_until_cleared(&self, events: impl Iterator<Item = Event<'static, T>>) {
|
||||||
self.handle_event(Event::NewEvents(start_cause), &mut control);
|
let mut control = self.current_control_flow();
|
||||||
if !event_is_start {
|
for event in events {
|
||||||
self.handle_event(event, &mut control);
|
self.handle_event(event, &mut control);
|
||||||
}
|
}
|
||||||
self.handle_event(Event::MainEventsCleared, &mut control);
|
self.handle_event(Event::MainEventsCleared, &mut control);
|
||||||
|
@ -196,10 +240,7 @@ impl<T: 'static> Shared<T> {
|
||||||
root::ControlFlow::Poll => {
|
root::ControlFlow::Poll => {
|
||||||
let cloned = self.clone();
|
let cloned = self.clone();
|
||||||
State::Poll {
|
State::Poll {
|
||||||
timeout: backend::Timeout::new(
|
timeout: backend::Timeout::new(move || cloned.poll(), Duration::from_millis(0)),
|
||||||
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
|
||||||
Duration::from_millis(0),
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
root::ControlFlow::Wait => State::Wait {
|
root::ControlFlow::Wait => State::Wait {
|
||||||
|
@ -220,7 +261,7 @@ impl<T: 'static> Shared<T> {
|
||||||
start,
|
start,
|
||||||
end,
|
end,
|
||||||
timeout: backend::Timeout::new(
|
timeout: backend::Timeout::new(
|
||||||
move || cloned.send_event(Event::NewEvents(StartCause::Poll)),
|
move || cloned.resume_time_reached(start, end),
|
||||||
delay,
|
delay,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 device;
|
||||||
mod error;
|
mod error;
|
||||||
mod event_loop;
|
mod event_loop;
|
||||||
|
|
Loading…
Reference in a new issue