-
Notifications
You must be signed in to change notification settings - Fork 919
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
Add initialization closure and drop on exit #3895
base: master
Are you sure you want to change the base?
Conversation
9827aff
to
d713d52
Compare
f6a520e
to
478b4df
Compare
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as resolved.
This comment was marked as resolved.
This comment was marked as off-topic.
This comment was marked as off-topic.
478b4df
to
1d1d9b2
Compare
EventLoop::run
Thanks for the input @MarijnS95! You're right that I was mistaken in my original solution, and I've changed the approach in the PR (only add initialization closure, don't touch @mickvangelderen: I've linked to your comment in #2903 (comment), but I think it's out of scope for this PR, so I've hidden the comment. |
In general I'd prefer exploring type state patterns during runtime then just having a closure, since it won't really solve e.g. android stuff or anything that has an option to remove windows and keep running. |
1d1d9b2
to
fbc6fdc
Compare
What do you mean by this? Something like #3710?
Indeed, this won't solve Android stuff. And for a lot of "real world" applications, you'll want multiple windows, and then the point is a bit moot anyways, true. But a lot of uses of Winit are not full-fledged applications, and there it does make sense to allow more easily creating windows. |
impl ApplicationHandler for Inactive {}
impl ApplicationHandler for Active {} and a way to switch between them during a runtime with something on the event loop. |
Interesting, could you give more details on how this would work? How do you imagine you would switch between these? And how does it differ from the ideas in #2903? In any case, I feel like that perhaps relates more to the Android surface stuff, not so much this specific PR? |
I'd assume that you store all of them in the big struct and then borrow a field, then you'd have a way to get back to your big wrapper. The problem is that it's self referencial, so requires a bit of unsafe. struct Foo {
state1: Option<State1>,
state2: Option<State2>,
}
EventLoop {
active: &mut dyn AppHandler, // either state1 or state2.
original_state: Foo // You just store it for reference.
} |
/// | ||
/// With OpenGL it could be EGLDisplay. | ||
#[cfg(not(android_platform))] | ||
context: Option<Context<DisplayHandle<'static>>>, | ||
context: Context<DisplayHandle<'static>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this is a softbuffer context (which shouldn't be using OpenGL at all, so the doc-comment is weird unless it was using glutin
).
I really wish we could make progress on rust-windowing/softbuffer#130, so that this weird cfg
can be removed.
/// Applications that need to run on Android should assume their [`NativeWindow`] has been | ||
/// destroyed, which indirectly invalidates any existing render surfaces that may have been | ||
/// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). | ||
/// Applications that need to run on Android must be able to handle their underlying | ||
/// [`SurfaceView`] being destroyed, which in turn indirectly invalidates any existing | ||
/// render surfaces that may have been created outside of Winit (such as an `EGLSurface`, | ||
/// [`VkSurfaceKHR`] or [`wgpu::Surface`]). | ||
/// | ||
/// When receiving [`destroy_surfaces()`] Android applications should drop all render surfaces | ||
/// before the event callback completes, which may be re-created when the application next | ||
/// receives [`can_create_surfaces()`]. | ||
/// This means that in this method, you must drop all render surfaces before the event callback | ||
/// completes, and only re-create them in or after [`can_create_surfaces()`] is next recieved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, you're right!
Summary
Allow windows to be created before the user's application state, avoiding
Option
around these in user code.This is paired with using the
Drop
impl ofApplicationHandler
instead ofexited
to communicate that the event loop is done (composes much better).See this example for how things now look:
Motivation
One of the most common problems that users encounter in
v0.30
is that their window (and surface) needs to be wrapped in anOption
, because they can no longer initialize windows at the start ofmain
, and the method they're expected to initialize it in (resumed
inv0.30
,can_create_surfaces
onmaster
) isn't called before the application state is already created.See #3447 for the original change, it tackles many real bugs, such as:
EventLoop::run
on macOS and iOS.create_window
being called in a non-sync fashion on Wayland.We were aware that the solution was overly restrictive (as also noted in #3626 and the changelog), and provided the deprecated
EventLoop::create_window
to ease the transition. This API has since been removed in #3826 to prepare forv0.30
, but users are still left with no real solution to the ergonomic issue!Furthermore, the current design is also pushing the user towards re-creating
Window
, but that is incorrect, it really is only the surface that needs to be re-created on Android - @MarijnS95 stated it well:Proposed solution
We add a closure to the initial
EventLoop::run
call (keepingEventLoop::run_app
as deprecated fallback), which allows access toActiveEventLoop
before creating the application. On most platforms, this closure will simply be called before the event loop starts, but on macOS/iOS, it will be called at key initialization steps in the application's lifecycle, namelyNSApplicationDidFinishLaunchingNotification
/UIApplicationDidFinishLaunchingNotification
.This will require some boxing to be made object-safe, see this playground, but that's the cost of going the trait route (which we've discussed at length, so I won't belabour the point ;) ).
This PR was my original motivation for doing #3721, and resolves my primary concerns from #2903.
Prior art
The SDL uses basically the old
poll_events
API that we moved away from a long time ago. To tackle iOS, it spawns a new thread and handles the user's call back in that thread (which is questionably thread safe). On macOS, to allow e.g. handling events while resizing, it provides the extraSDL_SetEventFilter
which handles events immediately in a callback.SDL3 provides the same API for backwards compat, but also allows a new design with
SDL_AppInit
/SDL_AppQuit
, see this documentation. This is literally just a poor mans static/global closure, i.e. effectively the same as what we're doing in this PR.Possible future work
Make using
can_create_surfaces
andsurface_destroyed
easier to use correctly.ApplicationHandler
between different type-states, similar to that described in Encode event lifecycle in the type-system #2903.FnMut
instead ofFnOnce
for the initialization closure. This depends on at what point in the lifecycle the surface creation callbacks are called, which I'm not really sure of.can_create_surfaces
, see the comment history for details. There might still be a space to explore there.TODO
EventLoopExtRunOnDemand::run_app_on_demand
. I think it is?EventLoopExtWeb::spawn_app
?EventLoopExtPumpEvents::pump_app_events
. Should we just ignore the issue there?StartCause::Init
]?