-
Notifications
You must be signed in to change notification settings - Fork 920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The case for callbacks #2010
Comments
Interesting proposal, I'm not totally sure where this is heading API-wise but I'm not opposed to it in general
There is a similar situation on Android where we have to mark certain events as 'handled' or not for the OS (e.g volume buttons) which would require additional return values from users. |
It would be good to understand a bit more specifically what problem the proposal is trying to solve, to be able to judge the trade offs more clearly. To some extent the current winit event loop 2.0 design is callback based; it's just that you have a single uber callback. For comparison a more pure data-oriented event system (i.e. non-callback-based design) in rust would probably be a stream of events, which isn't really the case currently. Considering the Android backend; the fact that the current design of Winit backends is callback based is actually quite important because it means we can transparently handle synchronization with Java in places where we need to - which wouldn't be possible to support if Winit wasn't already callback based. Put another way; I think it's maybe not so much about being callback based or not that's being raised here - it's about proposing a more fine-grained callback system; more comparable to how delegates are registered on macOS / iOS. Each window system has its own quirks though and although modelling the design more on macOS / iOS would probably make things neater for their respective backends, it might also complicate things for other backends - so it could be good to consider the details a bit more to understand the specific problems. One issue with the current design seems to be that it's not obvious how backends can extend the events that may be emitted to be able to expose certain platform-specific details in situations where it's not really appropriate for Winit to abstract those things (re: #2120) A general concern I'd have with fragmenting the callbacks though would be with defining clear locking and re-entrancy guarantees. If you have ad-hoc callbacks then what guarantees (if any) would you get about what thread they run on for all OSs, and what actions can safely be performed in each callback that won't either trigger re-entrancy or invoke other callbacks that might potentially need to access the same shared state (and lead to dead locks if not careful). Having a single point of entry for events / callbacks can help remove some of those concerns / complexities. From what I recall of working with macOS in the past though, then I do recall that delegates were expected to handle their responsibilities immediately which can sometimes preclude any kind of buffering (you can't just translate all delegate callbacks into an event that is handled asynchronously the next time the event loop runs). That has some similarity with a few things on Android (e.g. handling saving state or SurfaceView termination needs to be handled synchronously in Rust once it has been notified of them) but luckily on Android those cases can still be handled fine with the current Winit design because those things are effectively just buffered before they are delivered to Rust. Maybe the existing uber callback design is still mostly ok for Winit but what might be good evolve is the protocol around how each even loop iteration needs to start with |
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:
winit
then turns this into a variant ofevent::Event
, and either calls the event handler directly or buffers it for later (currently buffered events on macOS:WindowEvent
exceptScaleFactorChanged
,DeviceEvent
andUserEvent
).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
Event
s 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
andSync
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
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 betweenwinit
anddruid-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.
The text was updated successfully, but these errors were encountered: