Skip to content

Commit 61a4645

Browse files
authored
Refactor window creation logic (#22088)
# Objective Fixes #21948 Currently we tell winit to spawn any new `Window`s during the `about_to_wait` lifecycle method, but that's unreliable: if no new events appear while we're already waiting it could take an arbitrarily long time, or no new events could appear at all so we never spawn the window. This is especially bad when going from 0 to 1 `Window`s, as many events are related to the existing window. ## Solution Check for new windows in `resumed` for the initial window (which is also what winit examples do) and in `user_event` for new windows. I also did some other improvements while I was investigating this: * Inlined the `Added<Window>` query filter into `CreateWindowParams`, as the logic didn't make sense with any other filter. * Insert the `EventLoopProxyWrapper` resource when the plugin builds instead of inside the `winit_runner`. This currently makes no practical difference, but in another version of this PR I had a crash because the observer tried to use the resource before the runner started. It's also a more natural way to do it. * Made `WinitPlugin` no longer generic, and removed the `custom_user_event` example. The functionality it provides (being able to send events into bevy from the outside) only works in one direction, and I don't see why it needs to go via winit instead of a dedicated plugin with channels. ~~If this feature is useful for reasons I've missed then I'm happy to re-add it.~~ edit: winit is removing custom user events in 0.31, which made me even more confident it's the right direction for us too. ## Testing Run different examples and see that the window spawns in a timely fashion, edit the `multiple_windows` example as in #21948 and see that the window spawns.
1 parent d88ff08 commit 61a4645

File tree

10 files changed

+97
-172
lines changed

10 files changed

+97
-172
lines changed

Cargo.toml

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3872,17 +3872,6 @@ description = "Demonstrates creating an animated custom cursor from an image"
38723872
category = "Window"
38733873
wasm = true
38743874

3875-
[[example]]
3876-
name = "custom_user_event"
3877-
path = "examples/window/custom_user_event.rs"
3878-
doc-scrape-examples = true
3879-
3880-
[package.metadata.example.custom_user_event]
3881-
name = "Custom User Event"
3882-
description = "Handles custom user events within the event loop"
3883-
category = "Window"
3884-
wasm = true
3885-
38863875
[[example]]
38873876
name = "low_power"
38883877
path = "examples/window/low_power.rs"

crates/bevy_internal/src/default_plugins.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ plugin_group! {
2929
bevy_asset:::AssetPlugin,
3030
#[cfg(feature = "bevy_scene")]
3131
bevy_scene:::ScenePlugin,
32+
// NOTE: WinitPlugin needs to be after AssetPlugin because of custom cursors.
3233
#[cfg(feature = "bevy_winit")]
3334
bevy_winit:::WinitPlugin,
3435
#[custom(cfg(all(feature = "dlss", not(feature = "force_disable_dlss"))))]

crates/bevy_winit/src/cursor/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ pub enum CursorSource {
5656
#[derive(Component, Debug)]
5757
pub struct PendingCursor(pub Option<CursorSource>);
5858

59-
impl<M: Message> WinitAppRunnerState<M> {
59+
impl WinitAppRunnerState {
6060
pub(crate) fn update_cursors(
6161
&mut self,
6262
#[cfg(feature = "custom_cursor")] event_loop: &ActiveEventLoop,

crates/bevy_winit/src/lib.rs

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
extern crate alloc;
1616

1717
use bevy_derive::Deref;
18-
use bevy_reflect::prelude::ReflectDefault;
1918
use bevy_reflect::Reflect;
2019
use bevy_window::{RawHandleWrapperHolder, WindowEvent};
2120
use core::cell::RefCell;
22-
use core::marker::PhantomData;
2321
use winit::{event_loop::EventLoop, window::WindowId};
2422

2523
use bevy_a11y::AccessibilityRequested;
@@ -71,7 +69,7 @@ thread_local! {
7169
/// When using eg. `MinimalPlugins` you can add this using `WinitPlugin::<WakeUp>::default()`, where
7270
/// `WakeUp` is the default event that bevy uses.
7371
#[derive(Default)]
74-
pub struct WinitPlugin<M: Message = WakeUp> {
72+
pub struct WinitPlugin {
7573
/// Allows the window (and the event loop) to be created on any thread
7674
/// instead of only the main thread.
7775
///
@@ -82,16 +80,15 @@ pub struct WinitPlugin<M: Message = WakeUp> {
8280
/// Only works on Linux (X11/Wayland) and Windows.
8381
/// This field is ignored on other platforms.
8482
pub run_on_any_thread: bool,
85-
marker: PhantomData<M>,
8683
}
8784

88-
impl<T: Message> Plugin for WinitPlugin<T> {
85+
impl Plugin for WinitPlugin {
8986
fn name(&self) -> &str {
9087
"bevy_winit::WinitPlugin"
9188
}
9289

9390
fn build(&self, app: &mut App) {
94-
let mut event_loop_builder = EventLoop::<T>::with_user_event();
91+
let mut event_loop_builder = EventLoop::<WinitUserEvent>::with_user_event();
9592

9693
// linux check is needed because x11 might be enabled on other platforms.
9794
#[cfg(all(target_os = "linux", feature = "x11"))]
@@ -133,6 +130,7 @@ impl<T: Message> Plugin for WinitPlugin<T> {
133130
app.init_resource::<WinitMonitors>()
134131
.init_resource::<WinitSettings>()
135132
.insert_resource(DisplayHandleWrapper(event_loop.owned_display_handle()))
133+
.insert_resource(EventLoopProxyWrapper(event_loop.create_proxy()))
136134
.add_message::<RawWinitWindowEvent>()
137135
.set_runner(|app| winit_runner(app, event_loop))
138136
.add_systems(
@@ -150,14 +148,40 @@ impl<T: Message> Plugin for WinitPlugin<T> {
150148

151149
app.add_plugins(AccessKitPlugin);
152150
app.add_plugins(cursor::WinitCursorPlugin);
151+
152+
app.add_observer(
153+
|_window: On<Add, Window>, event_loop_proxy: Res<EventLoopProxyWrapper>| -> Result {
154+
event_loop_proxy.send_event(WinitUserEvent::WindowAdded)?;
155+
156+
Ok(())
157+
},
158+
);
153159
}
154160
}
155161

156-
/// The default event that can be used to wake the window loop
157-
/// Wakes up the loop if in wait state
158-
#[derive(Debug, Default, Clone, Copy, Message, Reflect)]
159-
#[reflect(Debug, Default, Clone)]
160-
pub struct WakeUp;
162+
/// Events that can be sent to perform actions inside the winit event loop.
163+
///
164+
/// Sent via the [`EventLoopProxyWrapper`] resource.
165+
///
166+
/// # Example
167+
///
168+
/// ```
169+
/// # use bevy_ecs::prelude::*;
170+
/// # use bevy_winit::{EventLoopProxyWrapper, WinitUserEvent};
171+
/// fn wakeup_system(event_loop_proxy: Res<EventLoopProxyWrapper>) -> Result {
172+
/// event_loop_proxy.send_event(WinitUserEvent::WakeUp)?;
173+
///
174+
/// Ok(())
175+
/// }
176+
/// ```
177+
#[derive(Debug, Clone, Copy, Reflect)]
178+
#[reflect(Debug, Clone)]
179+
pub enum WinitUserEvent {
180+
/// Dummy event that just wakes up the winit event loop
181+
WakeUp,
182+
/// Tell winit that a window needs to be created
183+
WindowAdded,
184+
}
161185

162186
/// The original window event as produced by Winit. This is meant as an escape
163187
/// hatch for power users that wish to add custom Winit integrations.
@@ -179,9 +203,9 @@ pub struct RawWinitWindowEvent {
179203
///
180204
/// The `EventLoopProxy` can be used to request a redraw from outside bevy.
181205
///
182-
/// Use `Res<EventLoopProxy>` to receive this resource.
206+
/// Use `Res<EventLoopProxyWrapper>` to retrieve this resource.
183207
#[derive(Resource, Deref)]
184-
pub struct EventLoopProxyWrapper<T: 'static>(EventLoopProxy<T>);
208+
pub struct EventLoopProxyWrapper(EventLoopProxy<WinitUserEvent>);
185209

186210
/// A wrapper around [`winit::event_loop::OwnedDisplayHandle`]
187211
///
@@ -203,7 +227,7 @@ impl AppSendEvent for Vec<WindowEvent> {
203227
}
204228

205229
/// The parameters of the [`create_windows`] system.
206-
pub type CreateWindowParams<'w, 's, F = ()> = (
230+
pub type CreateWindowParams<'w, 's> = (
207231
Commands<'w, 's>,
208232
Query<
209233
'w,
@@ -214,7 +238,7 @@ pub type CreateWindowParams<'w, 's, F = ()> = (
214238
&'static CursorOptions,
215239
Option<&'static RawHandleWrapperHolder>,
216240
),
217-
F,
241+
Added<Window>,
218242
>,
219243
MessageWriter<'w, WindowCreated>,
220244
ResMut<'w, WinitActionRequestHandlers>,

crates/bevy_winit/src/state.rs

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ use bevy_math::{ivec2, DVec2, Vec2};
1717
use bevy_platform::time::Instant;
1818
#[cfg(not(target_arch = "wasm32"))]
1919
use bevy_tasks::tick_global_task_pools_on_main_thread;
20-
use core::marker::PhantomData;
2120
#[cfg(target_arch = "wasm32")]
2221
use winit::platform::web::EventLoopExtWebSys;
2322
use winit::{
@@ -42,13 +41,13 @@ use crate::{
4241
accessibility::ACCESS_KIT_ADAPTERS,
4342
converters, create_windows,
4443
system::{create_monitors, CachedWindow, WinitWindowPressedKeys},
45-
AppSendEvent, CreateMonitorParams, CreateWindowParams, EventLoopProxyWrapper,
46-
RawWinitWindowEvent, UpdateMode, WinitSettings, WINIT_WINDOWS,
44+
AppSendEvent, CreateMonitorParams, CreateWindowParams, RawWinitWindowEvent, UpdateMode,
45+
WinitSettings, WinitUserEvent, WINIT_WINDOWS,
4746
};
4847

4948
/// Persistent state that is used to run the [`App`] according to the current
5049
/// [`UpdateMode`].
51-
pub(crate) struct WinitAppRunnerState<T: Message> {
50+
pub(crate) struct WinitAppRunnerState {
5251
/// The running app.
5352
app: App,
5453
/// Exit value once the loop is finished.
@@ -78,7 +77,6 @@ pub(crate) struct WinitAppRunnerState<T: Message> {
7877
bevy_window_events: Vec<bevy_window::WindowEvent>,
7978
/// Raw Winit window events to send
8079
raw_winit_events: Vec<RawWinitWindowEvent>,
81-
_marker: PhantomData<T>,
8280

8381
message_writer_system_state: SystemState<(
8482
MessageWriter<'static, WindowResized>,
@@ -98,10 +96,8 @@ pub(crate) struct WinitAppRunnerState<T: Message> {
9896
scheduled_tick_start: Option<Instant>,
9997
}
10098

101-
impl<M: Message> WinitAppRunnerState<M> {
99+
impl WinitAppRunnerState {
102100
fn new(mut app: App) -> Self {
103-
app.add_message::<M>();
104-
105101
let message_writer_system_state: SystemState<(
106102
MessageWriter<WindowResized>,
107103
MessageWriter<WindowBackendScaleFactorChanged>,
@@ -125,7 +121,6 @@ impl<M: Message> WinitAppRunnerState<M> {
125121
startup_forced_updates: 5,
126122
bevy_window_events: Vec::new(),
127123
raw_winit_events: Vec::new(),
128-
_marker: PhantomData,
129124
message_writer_system_state,
130125
scheduled_tick_start: None,
131126
}
@@ -146,7 +141,7 @@ impl<M: Message> WinitAppRunnerState<M> {
146141
}
147142
}
148143

149-
impl<M: Message> ApplicationHandler<M> for WinitAppRunnerState<M> {
144+
impl ApplicationHandler<WinitUserEvent> for WinitAppRunnerState {
150145
fn new_events(&mut self, event_loop: &ActiveEventLoop, cause: StartCause) {
151146
if event_loop.exiting() {
152147
return;
@@ -179,17 +174,31 @@ impl<M: Message> ApplicationHandler<M> for WinitAppRunnerState<M> {
179174
};
180175
}
181176

182-
fn resumed(&mut self, _event_loop: &ActiveEventLoop) {
177+
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
183178
// Mark the state as `WillResume`. This will let the schedule run one extra time
184179
// when actually resuming the app
185180
self.lifecycle = AppLifecycle::WillResume;
181+
182+
// Create the initial window if needed
183+
let mut create_window = SystemState::<CreateWindowParams>::from_world(self.world_mut());
184+
create_windows(event_loop, create_window.get_mut(self.world_mut()));
185+
create_window.apply(self.world_mut());
186186
}
187187

188-
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: M) {
188+
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: WinitUserEvent) {
189189
self.user_event_received = true;
190190

191-
self.world_mut().write_message(event);
192-
self.redraw_requested = true;
191+
match event {
192+
WinitUserEvent::WakeUp => {
193+
self.redraw_requested = true;
194+
}
195+
WinitUserEvent::WindowAdded => {
196+
let mut create_window =
197+
SystemState::<CreateWindowParams>::from_world(self.world_mut());
198+
create_windows(event_loop, create_window.get_mut(self.world_mut()));
199+
create_window.apply(self.world_mut());
200+
}
201+
}
193202
}
194203

195204
fn window_event(
@@ -450,14 +459,8 @@ impl<M: Message> ApplicationHandler<M> for WinitAppRunnerState<M> {
450459

451460
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
452461
let mut create_monitor = SystemState::<CreateMonitorParams>::from_world(self.world_mut());
453-
// create any new windows
454-
// (even if app did not update, some may have been created by plugin setup)
455-
let mut create_window =
456-
SystemState::<CreateWindowParams<Added<Window>>>::from_world(self.world_mut());
457462
create_monitors(event_loop, create_monitor.get_mut(self.world_mut()));
458463
create_monitor.apply(self.world_mut());
459-
create_windows(event_loop, create_window.get_mut(self.world_mut()));
460-
create_window.apply(self.world_mut());
461464

462465
// TODO: This is a workaround for https://github.com/bevyengine/bevy/issues/17488
463466
// while preserving the iOS fix in https://github.com/bevyengine/bevy/pull/11245
@@ -505,7 +508,7 @@ impl<M: Message> ApplicationHandler<M> for WinitAppRunnerState<M> {
505508
}
506509
}
507510

508-
impl<M: Message> WinitAppRunnerState<M> {
511+
impl WinitAppRunnerState {
509512
fn redraw_requested(&mut self, event_loop: &ActiveEventLoop) {
510513
let mut redraw_message_cursor = MessageCursor::<RequestRedraw>::default();
511514
let mut close_message_cursor = MessageCursor::<WindowCloseRequested>::default();
@@ -877,15 +880,12 @@ impl<M: Message> WinitAppRunnerState<M> {
877880
///
878881
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
879882
/// `EventLoop`.
880-
pub fn winit_runner<M: Message>(mut app: App, event_loop: EventLoop<M>) -> AppExit {
883+
pub fn winit_runner(mut app: App, event_loop: EventLoop<WinitUserEvent>) -> AppExit {
881884
if app.plugins_state() == PluginsState::Ready {
882885
app.finish();
883886
app.cleanup();
884887
}
885888

886-
app.world_mut()
887-
.insert_resource(EventLoopProxyWrapper(event_loop.create_proxy()));
888-
889889
let runner_state = WinitAppRunnerState::new(app);
890890

891891
trace!("starting winit event loop");

crates/bevy_winit/src/system.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use bevy_ecs::{
77
lifecycle::RemovedComponents,
88
message::MessageWriter,
99
prelude::{Changed, Component},
10-
query::QueryFilter,
1110
system::{Local, NonSendMarker, Query, SystemParamItem},
1211
};
1312
use bevy_input::keyboard::{Key, KeyCode, KeyboardFocusLost, KeyboardInput};
@@ -48,7 +47,7 @@ use crate::{
4847
///
4948
/// If any of these entities are missing required components, those will be added with their
5049
/// default values.
51-
pub fn create_windows<F: QueryFilter + 'static>(
50+
pub fn create_windows(
5251
event_loop: &ActiveEventLoop,
5352
(
5453
mut commands,
@@ -57,7 +56,7 @@ pub fn create_windows<F: QueryFilter + 'static>(
5756
mut handlers,
5857
accessibility_requested,
5958
monitors,
60-
): SystemParamItem<CreateWindowParams<F>>,
59+
): SystemParamItem<CreateWindowParams>,
6160
) {
6261
WINIT_WINDOWS.with_borrow_mut(|winit_windows| {
6362
ACCESS_KIT_ADAPTERS.with_borrow_mut(|adapters| {

examples/README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,6 @@ Example | Description
623623
--- | ---
624624
[Clear Color](../examples/window/clear_color.rs) | Creates a solid color window
625625
[Custom Cursor Image](../examples/window/custom_cursor_image.rs) | Demonstrates creating an animated custom cursor from an image
626-
[Custom User Event](../examples/window/custom_user_event.rs) | Handles custom user events within the event loop
627626
[Low Power](../examples/window/low_power.rs) | Demonstrates settings to reduce power use for bevy applications
628627
[Monitor info](../examples/window/monitor_info.rs) | Displays information about available monitors (displays).
629628
[Multiple Windows](../examples/window/multiple_windows.rs) | Demonstrates creating multiple windows, and rendering to them

0 commit comments

Comments
 (0)