-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Description
Hey, I've been tinkering with (primarily) the macOS backend for a while now, and I feel like I've been bit several times by the callback-y nature of AppKit poorly matching the (almost) data-only Event.
So here's my proposal for remedying this.
Background
Most of the underlying system APIs work by letting the user register callbacks on a window, that then get called when an event happens. See the below table for a complete overview:
| Library | Method |
|---|---|
| AppKit | Multiple callbacks per application/window/view class. |
| UIKit | Same as AppKit. |
| Windows | Single callback per window class. |
| Web | Multiple callbacks per DOM element. |
| Wayland | Multiple callbacks on "seats" (don't really understand this). |
| X11 | Uses an internal event queue that you poll from. |
| Android | Same as X11. |
| Orbital | Same as X11. |
winit then turns this into a variant of event::Event, and either calls the event handler directly or buffers it for later (currently buffered events on macOS: WindowEvent except ScaleFactorChanged, DeviceEvent and UserEvent).
The issue
Well, this is honestly a pretty good user-facing API! It's easy to share data between event handlers (since there's only one), and is in general pretty "rusty".
However, it's very much an abstraction, and as with almost any abstraction, we lose some form of control.
One problem is that it's hard for users to know which events are emitted directly / blocking, and hence need to be acted on immediately (like RedrawRequested), and which ones are buffered and can be handled at leisure.
Another is that the user can't directly respond to the events unless we break the assumption that Events are just data, see #1387.
Thirdly, on macOS, the behaviour is different depending on whether a callback is registered or not, see #1759 (comment). So we can't expose this behaviour as an event, because it would negatively affect users that aren't handling the event.
In general, the design forces you to pay for stuff you don't use, which is against the Rust principle of zero-cost abstractions.
The actual proposal
I would like to propose that we change the internals of our backends into having callback-style APIs.
We'd still build the much more user-friendly EventLoop API on top of that, but we'd have the goal of at some point in the future exposing some of these "lower-level" callbacks to user code.
Apart from being a way forward on fixing the above issues, I think it would improve our backend's code quality; one part of the backend would focus on making a safe abstraction (with all the Send and Sync that entails) that matches the OS' callback-based nature, while another could focus on the order events are buffered and how they're dispatched.
Prior art
- Events Loop 2.0 discussion thread created the current API, but at a glance it didn't seem like this proposal came up.
druid-shell, which contains shared functionality withwinit, exposes an entirely callback-based API.
Future possibilities
We might be able to split the lower-level callback part of winit off into a separate crate or something (probably still same repository), which might be able to become a shared base between winit and druid-shell? In any case, we should have a way forward on this.
Finishing up
I know that what I'm proposing is somewhat vauge here, please bear with me. If people are on board with this, I'll try (at some point) to spearhead with the macOS implementation, then the benefits might become more apparent.