Fix Window feedback loop between the OS and Bevy (#7517)
# Objective Fix #7377 Fix #7513 ## Solution Record the changes made to the Bevy `Window` from `winit` as 'canon' to avoid Bevy sending those changes back to `winit` again, causing a feedback loop. ## Changelog * Removed `ModifiesWindows` system label. Neither `despawn_window` nor `changed_window` actually modify the `Window` component so all the `.after(ModifiesWindows)` shouldn't be necessary. * Moved `changed_window` and `despawn_window` systems to `CoreStage::Last` to avoid systems making changes to the `Window` between `changed_window` and the end of the frame as they would be ignored. ## Migration Guide The `ModifiesWindows` system label was removed. Co-authored-by: devil-ira <justthecooldude@gmail.com>
This commit is contained in:
parent
6314f50e7b
commit
f69f1329e0
@ -11,7 +11,6 @@ mod render;
|
||||
|
||||
pub use alpha::*;
|
||||
use bevy_transform::TransformSystem;
|
||||
use bevy_window::ModifiesWindows;
|
||||
pub use bundle::*;
|
||||
pub use fog::*;
|
||||
pub use light::*;
|
||||
@ -199,8 +198,7 @@ impl Plugin for PbrPlugin {
|
||||
.in_set(SimulationLightSystems::AssignLightsToClusters)
|
||||
.after(TransformSystem::TransformPropagate)
|
||||
.after(VisibilitySystems::CheckVisibility)
|
||||
.after(CameraUpdateSystem)
|
||||
.after(ModifiesWindows),
|
||||
.after(CameraUpdateSystem),
|
||||
)
|
||||
.add_system(
|
||||
update_directional_light_cascades
|
||||
|
||||
@ -7,7 +7,6 @@ use bevy_reflect::{
|
||||
std_traits::ReflectDefault, FromReflect, GetTypeRegistration, Reflect, ReflectDeserialize,
|
||||
ReflectSerialize,
|
||||
};
|
||||
use bevy_window::ModifiesWindows;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Adds [`Camera`](crate::camera::Camera) driver systems for a given projection type.
|
||||
@ -43,7 +42,6 @@ impl<T: CameraProjection + Component + GetTypeRegistration> Plugin for CameraPro
|
||||
.add_system(
|
||||
crate::camera::camera_system::<T>
|
||||
.in_set(CameraUpdateSystem)
|
||||
.after(ModifiesWindows)
|
||||
// We assume that each camera will only have one projection,
|
||||
// so we can ignore ambiguities with all other monomorphizations.
|
||||
// FIXME: Add an archetype invariant for this https://github.com/bevyengine/bevy/issues/1481.
|
||||
|
||||
@ -28,7 +28,6 @@ use bevy_asset::AddAsset;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_render::{camera::CameraUpdateSystem, ExtractSchedule, RenderApp};
|
||||
use bevy_sprite::SpriteSystem;
|
||||
use bevy_window::ModifiesWindows;
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
#[derive(Default)]
|
||||
@ -83,7 +82,6 @@ impl Plugin for TextPlugin {
|
||||
.add_system(
|
||||
update_text2d_layout
|
||||
.in_base_set(CoreSet::PostUpdate)
|
||||
.after(ModifiesWindows)
|
||||
// Potential conflict: `Assets<Image>`
|
||||
// In practice, they run independently since `bevy_render::camera_update_system`
|
||||
// will only ever observe its own render target, and `update_text2d_layout`
|
||||
|
||||
@ -34,7 +34,6 @@ use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_input::InputSystem;
|
||||
use bevy_transform::TransformSystem;
|
||||
use bevy_window::ModifiesWindows;
|
||||
use stack::ui_stack_system;
|
||||
pub use stack::UiStack;
|
||||
use update::update_clipping_system;
|
||||
@ -110,7 +109,6 @@ impl Plugin for UiPlugin {
|
||||
widget::text_system
|
||||
.in_base_set(CoreSet::PostUpdate)
|
||||
.before(UiSystem::Flex)
|
||||
.after(ModifiesWindows)
|
||||
// Potential conflict: `Assets<Image>`
|
||||
// In practice, they run independently since `bevy_render::camera_update_system`
|
||||
// will only ever observe its own render target, and `widget::text_system`
|
||||
@ -135,8 +133,7 @@ impl Plugin for UiPlugin {
|
||||
.add_system(
|
||||
flex_node_system
|
||||
.in_set(UiSystem::Flex)
|
||||
.before(TransformSystem::TransformPropagate)
|
||||
.after(ModifiesWindows),
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
)
|
||||
.add_system(ui_stack_system.in_set(UiSystem::Stack))
|
||||
.add_system(
|
||||
|
||||
@ -137,9 +137,6 @@ impl Plugin for WindowPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub struct ModifiesWindows;
|
||||
|
||||
/// Defines the specific conditions the application should exit on
|
||||
#[derive(Clone)]
|
||||
pub enum ExitCondition {
|
||||
|
||||
@ -6,7 +6,7 @@ mod winit_config;
|
||||
mod winit_windows;
|
||||
|
||||
use bevy_ecs::system::{SystemParam, SystemState};
|
||||
use system::{changed_window, create_window, despawn_window};
|
||||
use system::{changed_window, create_window, despawn_window, CachedWindow};
|
||||
|
||||
pub use winit_config::*;
|
||||
pub use winit_windows::*;
|
||||
@ -26,7 +26,7 @@ use bevy_utils::{
|
||||
};
|
||||
use bevy_window::{
|
||||
exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime,
|
||||
ModifiesWindows, ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged,
|
||||
ReceivedCharacter, RequestRedraw, Window, WindowBackendScaleFactorChanged,
|
||||
WindowCloseRequested, WindowCreated, WindowFocused, WindowMoved, WindowResized,
|
||||
WindowScaleFactorChanged,
|
||||
};
|
||||
@ -39,7 +39,6 @@ use winit::{
|
||||
event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget},
|
||||
};
|
||||
|
||||
use crate::system::WinitWindowInfo;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin};
|
||||
|
||||
@ -70,7 +69,6 @@ impl Plugin for WinitPlugin {
|
||||
app.init_non_send_resource::<WinitWindows>()
|
||||
.init_resource::<WinitSettings>()
|
||||
.set_runner(winit_runner)
|
||||
.configure_set(ModifiesWindows.in_base_set(CoreSet::PostUpdate))
|
||||
// exit_on_all_closed only uses the query to determine if the query is empty,
|
||||
// and so doesn't care about ordering relative to changed_window
|
||||
.add_systems(
|
||||
@ -79,7 +77,7 @@ impl Plugin for WinitPlugin {
|
||||
// Update the state of the window before attempting to despawn to ensure consistent event ordering
|
||||
despawn_window.after(changed_window),
|
||||
)
|
||||
.in_set(ModifiesWindows),
|
||||
.in_base_set(CoreSet::Last),
|
||||
);
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@ -349,7 +347,7 @@ pub fn winit_runner(mut app: App) {
|
||||
// Fetch and prepare details from the world
|
||||
let mut system_state: SystemState<(
|
||||
NonSend<WinitWindows>,
|
||||
Query<(&mut Window, &mut WinitWindowInfo)>,
|
||||
Query<(&mut Window, &mut CachedWindow)>,
|
||||
WindowEvents,
|
||||
InputEvents,
|
||||
CursorEvents,
|
||||
@ -376,7 +374,7 @@ pub fn winit_runner(mut app: App) {
|
||||
return;
|
||||
};
|
||||
|
||||
let (mut window, mut info) =
|
||||
let (mut window, mut cache) =
|
||||
if let Ok((window, info)) = window_query.get_mut(window_entity) {
|
||||
(window, info)
|
||||
} else {
|
||||
@ -394,7 +392,6 @@ pub fn winit_runner(mut app: App) {
|
||||
window
|
||||
.resolution
|
||||
.set_physical_resolution(size.width, size.height);
|
||||
info.last_winit_size = size;
|
||||
|
||||
window_events.window_resized.send(WindowResized {
|
||||
window: window_entity,
|
||||
@ -421,11 +418,7 @@ pub fn winit_runner(mut app: App) {
|
||||
window.resolution.physical_height() as f64 - position.y,
|
||||
);
|
||||
|
||||
// bypassing change detection to not trigger feedback loop with system `changed_window`
|
||||
// this system change the cursor position in winit
|
||||
window
|
||||
.bypass_change_detection()
|
||||
.set_physical_cursor_position(Some(physical_position));
|
||||
window.set_physical_cursor_position(Some(physical_position));
|
||||
|
||||
cursor_events.cursor_moved.send(CursorMoved {
|
||||
window: window_entity,
|
||||
@ -439,14 +432,7 @@ pub fn winit_runner(mut app: App) {
|
||||
});
|
||||
}
|
||||
WindowEvent::CursorLeft { .. } => {
|
||||
// Component
|
||||
if let Ok((mut window, _)) = window_query.get_mut(window_entity) {
|
||||
// bypassing change detection to not trigger feedback loop with system `changed_window`
|
||||
// this system change the cursor position in winit
|
||||
window
|
||||
.bypass_change_detection()
|
||||
.set_physical_cursor_position(None);
|
||||
}
|
||||
window.set_physical_cursor_position(None);
|
||||
|
||||
cursor_events.cursor_left.send(CursorLeft {
|
||||
window: window_entity,
|
||||
@ -594,6 +580,10 @@ pub fn winit_runner(mut app: App) {
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
||||
if window.is_changed() {
|
||||
cache.window = window.clone();
|
||||
}
|
||||
}
|
||||
event::Event::DeviceEvent {
|
||||
event: DeviceEvent::MouseMotion { delta: (x, y) },
|
||||
|
||||
@ -39,20 +39,19 @@ pub(crate) fn create_window<'a>(
|
||||
mut winit_windows: NonSendMut<WinitWindows>,
|
||||
#[cfg(target_arch = "wasm32")] event_channel: ResMut<CanvasParentResizeEventChannel>,
|
||||
) {
|
||||
for (entity, mut component) in created_windows {
|
||||
for (entity, mut window) in created_windows {
|
||||
if winit_windows.get_window(entity).is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
info!(
|
||||
"Creating new window {:?} ({:?})",
|
||||
component.title.as_str(),
|
||||
window.title.as_str(),
|
||||
entity
|
||||
);
|
||||
|
||||
let winit_window = winit_windows.create_window(event_loop, entity, &component);
|
||||
let current_size = winit_window.inner_size();
|
||||
component
|
||||
let winit_window = winit_windows.create_window(event_loop, entity, &window);
|
||||
window
|
||||
.resolution
|
||||
.set_scale_factor(winit_window.scale_factor());
|
||||
commands
|
||||
@ -61,18 +60,14 @@ pub(crate) fn create_window<'a>(
|
||||
window_handle: winit_window.raw_window_handle(),
|
||||
display_handle: winit_window.raw_display_handle(),
|
||||
})
|
||||
.insert(WinitWindowInfo {
|
||||
previous: component.clone(),
|
||||
last_winit_size: PhysicalSize {
|
||||
width: current_size.width,
|
||||
height: current_size.height,
|
||||
},
|
||||
.insert(CachedWindow {
|
||||
window: window.clone(),
|
||||
});
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
if component.fit_canvas_to_parent {
|
||||
let selector = if let Some(selector) = &component.canvas {
|
||||
if window.fit_canvas_to_parent {
|
||||
let selector = if let Some(selector) = &window.canvas {
|
||||
selector
|
||||
} else {
|
||||
WINIT_CANVAS_SELECTOR
|
||||
@ -106,11 +101,10 @@ pub(crate) fn despawn_window(
|
||||
}
|
||||
}
|
||||
|
||||
/// Previous state of the window so we can check sub-portions of what actually was changed.
|
||||
/// The cached state of the window so we can check which properties were changed from within the app.
|
||||
#[derive(Debug, Clone, Component)]
|
||||
pub struct WinitWindowInfo {
|
||||
pub previous: Window,
|
||||
pub last_winit_size: PhysicalSize<u32>,
|
||||
pub struct CachedWindow {
|
||||
pub window: Window,
|
||||
}
|
||||
|
||||
// Detect changes to the window and update the winit window accordingly.
|
||||
@ -121,18 +115,16 @@ pub struct WinitWindowInfo {
|
||||
// - [`Window::canvas`] currently cannot be updated after startup, not entirely sure if it would work well with the
|
||||
// event channel stuff.
|
||||
pub(crate) fn changed_window(
|
||||
mut changed_windows: Query<(Entity, &mut Window, &mut WinitWindowInfo), Changed<Window>>,
|
||||
mut changed_windows: Query<(Entity, &mut Window, &mut CachedWindow), Changed<Window>>,
|
||||
winit_windows: NonSendMut<WinitWindows>,
|
||||
) {
|
||||
for (entity, mut window, mut info) in &mut changed_windows {
|
||||
let previous = &info.previous;
|
||||
|
||||
for (entity, mut window, mut cache) in &mut changed_windows {
|
||||
if let Some(winit_window) = winit_windows.get_window(entity) {
|
||||
if window.title != previous.title {
|
||||
if window.title != cache.window.title {
|
||||
winit_window.set_title(window.title.as_str());
|
||||
}
|
||||
|
||||
if window.mode != previous.mode {
|
||||
if window.mode != cache.window.mode {
|
||||
let new_mode = match window.mode {
|
||||
bevy_window::WindowMode::BorderlessFullscreen => {
|
||||
Some(winit::window::Fullscreen::Borderless(None))
|
||||
@ -156,19 +148,15 @@ pub(crate) fn changed_window(
|
||||
winit_window.set_fullscreen(new_mode);
|
||||
}
|
||||
}
|
||||
if window.resolution != previous.resolution {
|
||||
if window.resolution != cache.window.resolution {
|
||||
let physical_size = PhysicalSize::new(
|
||||
window.resolution.physical_width(),
|
||||
window.resolution.physical_height(),
|
||||
);
|
||||
// Prevents "window.resolution values set from a winit resize event" from
|
||||
// being set here, creating feedback loops.
|
||||
if physical_size != info.last_winit_size {
|
||||
winit_window.set_inner_size(physical_size);
|
||||
}
|
||||
}
|
||||
|
||||
if window.physical_cursor_position() != previous.physical_cursor_position() {
|
||||
if window.physical_cursor_position() != cache.window.physical_cursor_position() {
|
||||
if let Some(physical_position) = window.physical_cursor_position() {
|
||||
let inner_size = winit_window.inner_size();
|
||||
|
||||
@ -184,21 +172,21 @@ pub(crate) fn changed_window(
|
||||
}
|
||||
}
|
||||
|
||||
if window.cursor.icon != previous.cursor.icon {
|
||||
if window.cursor.icon != cache.window.cursor.icon {
|
||||
winit_window.set_cursor_icon(converters::convert_cursor_icon(window.cursor.icon));
|
||||
}
|
||||
|
||||
if window.cursor.grab_mode != previous.cursor.grab_mode {
|
||||
if window.cursor.grab_mode != cache.window.cursor.grab_mode {
|
||||
crate::winit_windows::attempt_grab(winit_window, window.cursor.grab_mode);
|
||||
}
|
||||
|
||||
if window.cursor.visible != previous.cursor.visible {
|
||||
if window.cursor.visible != cache.window.cursor.visible {
|
||||
winit_window.set_cursor_visible(window.cursor.visible);
|
||||
}
|
||||
|
||||
if window.cursor.hit_test != previous.cursor.hit_test {
|
||||
if window.cursor.hit_test != cache.window.cursor.hit_test {
|
||||
if let Err(err) = winit_window.set_cursor_hittest(window.cursor.hit_test) {
|
||||
window.cursor.hit_test = previous.cursor.hit_test;
|
||||
window.cursor.hit_test = cache.window.cursor.hit_test;
|
||||
warn!(
|
||||
"Could not set cursor hit test for window {:?}: {:?}",
|
||||
window.title, err
|
||||
@ -206,19 +194,19 @@ pub(crate) fn changed_window(
|
||||
}
|
||||
}
|
||||
|
||||
if window.decorations != previous.decorations
|
||||
if window.decorations != cache.window.decorations
|
||||
&& window.decorations != winit_window.is_decorated()
|
||||
{
|
||||
winit_window.set_decorations(window.decorations);
|
||||
}
|
||||
|
||||
if window.resizable != previous.resizable
|
||||
if window.resizable != cache.window.resizable
|
||||
&& window.resizable != winit_window.is_resizable()
|
||||
{
|
||||
winit_window.set_resizable(window.resizable);
|
||||
}
|
||||
|
||||
if window.resize_constraints != previous.resize_constraints {
|
||||
if window.resize_constraints != cache.window.resize_constraints {
|
||||
let constraints = window.resize_constraints.check_constraints();
|
||||
let min_inner_size = LogicalSize {
|
||||
width: constraints.min_width,
|
||||
@ -235,7 +223,7 @@ pub(crate) fn changed_window(
|
||||
}
|
||||
}
|
||||
|
||||
if window.position != previous.position {
|
||||
if window.position != cache.window.position {
|
||||
if let Some(position) = crate::winit_window_position(
|
||||
&window.position,
|
||||
&window.resolution,
|
||||
@ -262,42 +250,42 @@ pub(crate) fn changed_window(
|
||||
winit_window.set_minimized(minimized);
|
||||
}
|
||||
|
||||
if window.focused != previous.focused && window.focused {
|
||||
if window.focused != cache.window.focused && window.focused {
|
||||
winit_window.focus_window();
|
||||
}
|
||||
|
||||
if window.window_level != previous.window_level {
|
||||
if window.window_level != cache.window.window_level {
|
||||
winit_window.set_window_level(convert_window_level(window.window_level));
|
||||
}
|
||||
|
||||
// Currently unsupported changes
|
||||
if window.transparent != previous.transparent {
|
||||
window.transparent = previous.transparent;
|
||||
if window.transparent != cache.window.transparent {
|
||||
window.transparent = cache.window.transparent;
|
||||
warn!(
|
||||
"Winit does not currently support updating transparency after window creation."
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
if window.canvas != previous.canvas {
|
||||
window.canvas = previous.canvas.clone();
|
||||
if window.canvas != cache.window.canvas {
|
||||
window.canvas = cache.window.canvas.clone();
|
||||
warn!(
|
||||
"Bevy currently doesn't support modifying the window canvas after initialization."
|
||||
);
|
||||
}
|
||||
|
||||
if window.ime_enabled != previous.ime_enabled {
|
||||
if window.ime_enabled != cache.window.ime_enabled {
|
||||
winit_window.set_ime_allowed(window.ime_enabled);
|
||||
}
|
||||
|
||||
if window.ime_position != previous.ime_position {
|
||||
if window.ime_position != cache.window.ime_position {
|
||||
winit_window.set_ime_position(LogicalPosition::new(
|
||||
window.ime_position.x,
|
||||
window.ime_position.y,
|
||||
));
|
||||
}
|
||||
|
||||
info.previous = window.clone();
|
||||
cache.window = window.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user