Skip to content

Commit 3d3022a

Browse files
committed
Ensure clean exit
Fixes two issues related to bevyengine#13208. First, we ensure render resources for a window are always dropped first to ensure that the `winit::Window` always drops on the main thread when it is removed from `WinitWindows`. Previously, changes in bevyengine#12978 caused the window to drop in the render world, causing issues. We accomplish this by delaying despawning the window by a frame by inserting a marker component `ClosingWindow` that indicates the window has been requested to close and is in the process of closing. The render world now responds to the equivalent `WindowClosing` event rather than `WindowCloseed` which now fires after the render resources are guarunteed to be cleaned up. Secondly, fixing the above caused (revealed?) that additional events were being delivered to the the event loop handler after exit had already been requested: in my testing `RedrawRequested` and `LoopExiting`. This caused errors to be reported try to send an exit event on the close channel. There are two options here: - Guard the handler so no additional events are delivered once the app is exiting. I considered this but worried it might be confusing or bug prone if in the future someone wants to handle `LoopExiting` or some other event to clean-up while exiting. - Only send an exit signal if we are not already exiting. It doesn't appear to cause any problems to handle the extra events so this seems safer. Fixing this also appears to have fixed bevyengine#13231.
1 parent 4083750 commit 3d3022a

File tree

7 files changed

+53
-12
lines changed

7 files changed

+53
-12
lines changed

crates/bevy_render/src/view/window/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use bevy_ecs::{entity::EntityHashMap, prelude::*};
1212
use bevy_utils::warn_once;
1313
use bevy_utils::{default, tracing::debug, HashSet};
1414
use bevy_window::{
15-
CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosed,
15+
CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosing,
1616
};
1717
use std::{
1818
num::NonZeroU32,
@@ -117,7 +117,7 @@ impl DerefMut for ExtractedWindows {
117117
fn extract_windows(
118118
mut extracted_windows: ResMut<ExtractedWindows>,
119119
screenshot_manager: Extract<Res<ScreenshotManager>>,
120-
mut closed: Extract<EventReader<WindowClosed>>,
120+
mut closing: Extract<EventReader<WindowClosing>>,
121121
windows: Extract<Query<(Entity, &Window, &RawHandleWrapper, Option<&PrimaryWindow>)>>,
122122
mut removed: Extract<RemovedComponents<RawHandleWrapper>>,
123123
mut window_surfaces: ResMut<WindowSurfaces>,
@@ -177,9 +177,9 @@ fn extract_windows(
177177
}
178178
}
179179

180-
for closed_window in closed.read() {
181-
extracted_windows.remove(&closed_window.window);
182-
window_surfaces.remove(&closed_window.window);
180+
for closing_window in closing.read() {
181+
extracted_windows.remove(&closing_window.window);
182+
window_surfaces.remove(&closing_window.window);
183183
}
184184
for removed_window in removed.read() {
185185
extracted_windows.remove(&removed_window);

crates/bevy_window/src/event.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ pub struct WindowClosed {
9494
pub window: Entity,
9595
}
9696

97+
/// An event that is sent whenever a window is closing. This will be sent when
98+
/// after a [`WindowCloseRequested`] event is received and the window is in the process of closing.
99+
#[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)]
100+
#[reflect(Debug, PartialEq)]
101+
#[cfg_attr(
102+
feature = "serialize",
103+
derive(serde::Serialize, serde::Deserialize),
104+
reflect(Serialize, Deserialize)
105+
)]
106+
pub struct WindowClosing {
107+
/// Window that has been requested to close and is the process of closing.
108+
pub window: Entity,
109+
}
110+
97111
/// An event that is sent whenever a window is destroyed by the underlying window system.
98112
///
99113
/// Note that if your application only has a single window, this event may be your last chance to

crates/bevy_window/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ impl Plugin for WindowPlugin {
8989
#[allow(deprecated)]
9090
app.add_event::<WindowResized>()
9191
.add_event::<WindowCreated>()
92+
.add_event::<WindowClosing>()
9293
.add_event::<WindowClosed>()
9394
.add_event::<WindowCloseRequested>()
9495
.add_event::<WindowDestroyed>()
@@ -139,6 +140,7 @@ impl Plugin for WindowPlugin {
139140
.register_type::<RequestRedraw>()
140141
.register_type::<WindowCreated>()
141142
.register_type::<WindowCloseRequested>()
143+
.register_type::<WindowClosing>()
142144
.register_type::<WindowClosed>()
143145
.register_type::<CursorMoved>()
144146
.register_type::<CursorEntered>()

crates/bevy_window/src/system.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{PrimaryWindow, Window, WindowCloseRequested};
1+
use crate::{ClosingWindow, PrimaryWindow, Window, WindowCloseRequested};
22

33
use bevy_app::AppExit;
44
use bevy_ecs::prelude::*;
@@ -39,8 +39,15 @@ pub fn exit_on_primary_closed(
3939
/// Ensure that you read the caveats documented on that field if doing so.
4040
///
4141
/// [`WindowPlugin`]: crate::WindowPlugin
42-
pub fn close_when_requested(mut commands: Commands, mut closed: EventReader<WindowCloseRequested>) {
42+
pub fn close_when_requested(
43+
mut commands: Commands,
44+
mut closed: EventReader<WindowCloseRequested>,
45+
closing: Query<Entity, With<ClosingWindow>>,
46+
) {
47+
for window in closing.iter() {
48+
commands.entity(window).despawn();
49+
}
4350
for event in closed.read() {
44-
commands.entity(event.window).despawn();
51+
commands.entity(event.window).insert(ClosingWindow);
4552
}
4653
}

crates/bevy_window/src/window.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,11 @@ impl Default for EnabledButtons {
11711171
}
11721172
}
11731173

1174+
/// Marker component for a [`Window`] that has been requested to close and
1175+
/// is in the process of closing (on the next frame).
1176+
#[derive(Component)]
1177+
pub struct ClosingWindow;
1178+
11741179
#[cfg(test)]
11751180
mod tests {
11761181
use super::*;

crates/bevy_winit/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -776,6 +776,11 @@ fn handle_winit_event(
776776
}
777777

778778
if let Some(app_exit) = app.should_exit() {
779+
// The event loop is in the process of exiting, but we might still be processing events.
780+
if event_loop.exiting() {
781+
return;
782+
}
783+
779784
if let Err(err) = exit_notify.try_send(app_exit) {
780785
error!("Failed to send a app exit notification! This is a bug. Reason: {err}");
781786
};

crates/bevy_winit/src/system.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,16 @@ use bevy_ecs::{
88
};
99
use bevy_utils::tracing::{error, info, warn};
1010
use bevy_window::{
11-
RawHandleWrapper, Window, WindowClosed, WindowCreated, WindowMode, WindowResized,
11+
ClosingWindow, RawHandleWrapper, Window, WindowClosed, WindowClosing, WindowCreated,
12+
WindowMode, WindowResized,
1213
};
1314

1415
use winit::{
1516
dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize},
1617
event_loop::EventLoopWindowTarget,
1718
};
1819

20+
use bevy_ecs::query::With;
1921
#[cfg(target_arch = "wasm32")]
2022
use winit::platform::web::WindowExtWebSys;
2123

@@ -97,18 +99,24 @@ pub fn create_windows<F: QueryFilter + 'static>(
9799
}
98100

99101
pub(crate) fn despawn_windows(
102+
closing: Query<Entity, With<ClosingWindow>>,
100103
mut closed: RemovedComponents<Window>,
101104
window_entities: Query<&Window>,
102-
mut close_events: EventWriter<WindowClosed>,
105+
mut closing_events: EventWriter<WindowClosing>,
106+
mut closed_events: EventWriter<WindowClosed>,
103107
mut winit_windows: NonSendMut<WinitWindows>,
104108
) {
109+
for window in closing.iter() {
110+
closing_events.send(WindowClosing { window });
111+
}
105112
for window in closed.read() {
106113
info!("Closing window {:?}", window);
107114
// Guard to verify that the window is in fact actually gone,
108-
// rather than having the component added and removed in the same frame.
115+
// rather than having the component added
116+
// and removed in the same frame.
109117
if !window_entities.contains(window) {
110118
winit_windows.remove_window(window);
111-
close_events.send(WindowClosed { window });
119+
closed_events.send(WindowClosed { window });
112120
}
113121
}
114122
}

0 commit comments

Comments
 (0)