Suppress the `clippy::type_complexity` lint (#8313)
# Objective
The clippy lint `type_complexity` is known not to play well with bevy.
It frequently triggers when writing complex queries, and taking the
lint's advice of using a type alias almost always just obfuscates the
code with no benefit. Because of this, this lint is currently ignored in
CI, but unfortunately it still shows up when viewing bevy code in an
IDE.
As someone who's made a fair amount of pull requests to this repo, I
will say that this issue has been a consistent thorn in my side. Since
bevy code is filled with spurious, ignorable warnings, it can be very
difficult to spot the *real* warnings that must be fixed -- most of the
time I just ignore all warnings, only to later find out that one of them
was real after I'm done when CI runs.
## Solution
Suppress this lint in all bevy crates. This was previously attempted in
#7050, but the review process ended up making it more complicated than
it needs to be and landed on a subpar solution.
The discussion in https://github.com/rust-lang/rust-clippy/pull/10571
explores some better long-term solutions to this problem. Since there is
no timeline on when these solutions may land, we should resolve this
issue in the meantime by locally suppressing these lints.
### Unresolved issues
Currently, these lints are not suppressed in our examples, since that
would require suppressing the lint in every single source file. They are
still ignored in CI.
2023-04-06 21:27:36 +00:00
|
|
|
#![allow(clippy::type_complexity)]
|
2023-03-21 19:59:30 +00:00
|
|
|
#![warn(missing_docs)]
|
|
|
|
//! `bevy_winit` provides utilities to handle window creation and the eventloop through [`winit`]
|
|
|
|
//!
|
|
|
|
//! Most commonly, the [`WinitPlugin`] is used as part of
|
|
|
|
//! [`DefaultPlugins`](https://docs.rs/bevy/latest/bevy/struct.DefaultPlugins.html).
|
2023-10-06 00:31:10 +00:00
|
|
|
//! The app's [runner](bevy_app::App::runner) is set by `WinitPlugin` and handles the `winit` [`EventLoop`].
|
2023-03-21 19:59:30 +00:00
|
|
|
//! See `winit_runner` for details.
|
|
|
|
|
2023-03-01 22:45:04 +00:00
|
|
|
pub mod accessibility;
|
2020-04-04 21:59:49 +00:00
|
|
|
mod converters;
|
2023-01-19 00:38:28 +00:00
|
|
|
mod system;
|
Optionally resize Window canvas element to fit parent element (#4726)
Currently Bevy's web canvases are "fixed size". They are manually set to specific dimensions. This might be fine for some games and website layouts, but for sites with flexible layouts, or games that want to "fill" the browser window, Bevy doesn't provide the tools needed to make this easy out of the box.
There are third party plugins like [bevy-web-resizer](https://github.com/frewsxcv/bevy-web-resizer/) that listen for window resizes, take the new dimensions, and resize the winit window accordingly. However this only covers a subset of cases and this is common enough functionality that it should be baked into Bevy.
A significant motivating use case here is the [Bevy WASM Examples page](https://bevyengine.org/examples/). This scales the canvas to fit smaller windows (such as mobile). But this approach both breaks winit's mouse events and removes pixel-perfect rendering (which means we might be rendering too many or too few pixels). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/2074. And it builds on the ResizeObserver api, which isn't supported in all browsers yet (and is only supported in very new versions of the popular browsers).
While we wait for a complete winit solution, I've added a `fit_canvas_to_parent` option to WindowDescriptor / Window, which when enabled will listen for window resizes and resize the Bevy canvas/window to fit its parent element. This enables users to scale bevy canvases using arbitrary CSS, by "inheriting" their parents' size. Note that the wrapper element _is_ required because winit overrides the canvas sizing with absolute values on each resize.
There is one limitation worth calling out here: while the majority of canvas resizes will be triggered by window resizes, modifying element layout at runtime (css animations, javascript-driven element changes, dev-tool-injected changes, etc) will not be detected here. I'm not aware of a good / efficient event-driven way to do this outside of the ResizeObserver api. In practice, window-resize-driven canvas resizing should cover the majority of use cases. Users that want to actively poll for element resizes can just do that (or we can build another feature and let people choose based on their specific needs).
I also took the chance to make a couple of minor tweaks:
* Made the `canvas` window setting available on all platforms. Users shouldn't need to deal with cargo feature selection to support web scenarios. We can just ignore the value on non-web platforms. I added documentation that explains this.
* Removed the redundant "initial create windows" handler. With the addition of the code in this pr, the code duplication was untenable.
This enables a number of patterns:
## Easy "fullscreen window" mode for the default canvas
The "parent element" defaults to the `<body>` element.
```rust
app
.insert_resource(WindowDescriptor {
fit_canvas_to_parent: true,
..default()
})
```
And CSS:
```css
html, body {
margin: 0;
height: 100%;
}
```
## Fit custom canvas to "wrapper" parent element
```rust
app
.insert_resource(WindowDescriptor {
fit_canvas_to_parent: true,
canvas: Some("#bevy".to_string()),
..default()
})
```
And the HTML:
```html
<div style="width: 50%; height: 100%">
<canvas id="bevy"></canvas>
</div>
```
2022-05-20 23:13:48 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
mod web_resize;
|
2020-08-21 05:37:19 +00:00
|
|
|
mod winit_config;
|
2020-03-30 21:53:32 +00:00
|
|
|
mod winit_windows;
|
2021-01-01 21:31:22 +00:00
|
|
|
|
2023-03-01 22:45:04 +00:00
|
|
|
use bevy_a11y::AccessibilityRequested;
|
2023-07-31 21:41:59 +00:00
|
|
|
use system::{changed_windows, create_windows, despawn_windows, CachedWindow};
|
2020-08-21 05:37:19 +00:00
|
|
|
pub use winit_config::*;
|
|
|
|
pub use winit_windows::*;
|
2020-03-28 00:43:03 +00:00
|
|
|
|
fix run-once runners (#10195)
# Objective
- After #9826, there are issues on "run once runners"
- example `without_winit` crashes:
```
2023-10-19T22:06:01.810019Z INFO bevy_render::renderer: AdapterInfo { name: "llvmpipe (LLVM 15.0.7, 256 bits)", vendor: 65541, device: 0, device_type: Cpu, driver: "llvmpipe", driver_info: "Mesa 23.2.1 - kisak-mesa PPA (LLVM 15.0.7)", backend: Vulkan }
2023-10-19T22:06:02.860331Z WARN bevy_audio::audio_output: No audio device found.
2023-10-19T22:06:03.215154Z INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux 22.04 Ubuntu", kernel: "6.2.0-1014-azure", cpu: "Intel(R) Xeon(R) CPU E5-2673 v3 @ 2.40GHz", core_count: "2", memory: "6.8 GiB" }
thread 'main' panicked at crates/bevy_render/src/pipelined_rendering.rs:91:14:
Unable to get RenderApp. Another plugin may have removed the RenderApp before PipelinedRenderingPlugin
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
- example `headless` runs the app twice with the `run_once` schedule
## Solution
- Expose a more complex state of an app than just "ready"
- Also block adding plugins to an app after it has finished or cleaned
up its plugins as that wouldn't work anyway
## Migration Guide
* `app.ready()` has been replaced by `app.plugins_state()` which will
return more details on the current state of plugins in the app
2023-10-23 12:25:02 +00:00
|
|
|
use bevy_app::{App, AppExit, Last, Plugin, PluginsState};
|
2023-01-19 00:38:28 +00:00
|
|
|
use bevy_ecs::event::{Events, ManualEventReader};
|
2022-05-05 13:35:43 +00:00
|
|
|
use bevy_ecs::prelude::*;
|
2023-07-31 21:41:59 +00:00
|
|
|
use bevy_ecs::system::{SystemParam, SystemState};
|
2023-01-19 00:38:28 +00:00
|
|
|
use bevy_input::{
|
|
|
|
keyboard::KeyboardInput,
|
|
|
|
mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
|
|
|
|
touch::TouchInput,
|
2023-06-08 20:31:43 +00:00
|
|
|
touchpad::{TouchpadMagnify, TouchpadRotate},
|
2022-03-01 19:33:56 +00:00
|
|
|
};
|
2023-01-19 00:38:28 +00:00
|
|
|
use bevy_math::{ivec2, DVec2, Vec2};
|
2023-07-31 21:41:59 +00:00
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
use bevy_tasks::tick_global_task_pools_on_main_thread;
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
use bevy_utils::{
|
2023-01-19 00:38:28 +00:00
|
|
|
tracing::{trace, warn},
|
2023-07-31 21:41:59 +00:00
|
|
|
Duration, Instant,
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
};
|
2020-04-19 19:13:04 +00:00
|
|
|
use bevy_window::{
|
2023-10-23 20:47:55 +00:00
|
|
|
exit_on_all_closed, ApplicationLifetime, CursorEntered, CursorLeft, CursorMoved,
|
|
|
|
FileDragAndDrop, Ime, ReceivedCharacter, RequestRedraw, Window,
|
|
|
|
WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated, WindowDestroyed,
|
|
|
|
WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged, WindowThemeChanged,
|
2020-04-19 19:13:04 +00:00
|
|
|
};
|
2023-10-02 13:06:13 +00:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
use bevy_window::{PrimaryWindow, RawHandleWrapper};
|
2022-05-05 13:35:43 +00:00
|
|
|
|
2023-02-03 16:41:39 +00:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
pub use winit::platform::android::activity::AndroidApp;
|
|
|
|
|
2020-03-28 00:43:03 +00:00
|
|
|
use winit::{
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
event::{self, DeviceEvent, Event, StartCause, WindowEvent},
|
2023-02-03 16:41:39 +00:00
|
|
|
event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget},
|
2020-03-28 00:43:03 +00:00
|
|
|
};
|
|
|
|
|
2023-03-01 22:45:04 +00:00
|
|
|
use crate::accessibility::{AccessKitAdapters, AccessibilityPlugin, WinitActionHandlers};
|
|
|
|
|
2023-06-05 21:04:22 +00:00
|
|
|
use crate::converters::convert_winit_theme;
|
2023-01-19 00:38:28 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
use crate::web_resize::{CanvasParentResizeEventChannel, CanvasParentResizePlugin};
|
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
/// [`AndroidApp`] provides an interface to query the application state as well as monitor events
|
|
|
|
/// (for example lifecycle and input events).
|
2023-02-03 16:41:39 +00:00
|
|
|
#[cfg(target_os = "android")]
|
2023-06-01 21:55:18 +00:00
|
|
|
pub static ANDROID_APP: std::sync::OnceLock<AndroidApp> = std::sync::OnceLock::new();
|
2023-02-03 16:41:39 +00:00
|
|
|
|
2023-08-31 19:05:49 +00:00
|
|
|
/// A [`Plugin`] that uses `winit` to create and manage windows, and receive window and input
|
2023-07-31 21:41:59 +00:00
|
|
|
/// events.
|
|
|
|
///
|
2023-08-31 19:05:49 +00:00
|
|
|
/// This plugin will add systems and resources that sync with the `winit` backend and also
|
2023-09-25 18:35:46 +00:00
|
|
|
/// replace the existing [`App`] runner with one that constructs an [event loop](EventLoop) to
|
2023-07-31 21:41:59 +00:00
|
|
|
/// receive window and input events from the OS.
|
2020-03-29 07:53:47 +00:00
|
|
|
#[derive(Default)]
|
2023-10-06 13:26:06 +00:00
|
|
|
pub struct WinitPlugin {
|
|
|
|
/// Allows the window (and the event loop) to be created on any thread
|
|
|
|
/// instead of only the main thread.
|
|
|
|
///
|
|
|
|
/// See [`EventLoopBuilder::build`] for more information on this.
|
|
|
|
///
|
|
|
|
/// # Supported platforms
|
|
|
|
///
|
|
|
|
/// Only works on Linux (X11/Wayland) and Windows.
|
|
|
|
/// This field is ignored on other platforms.
|
|
|
|
pub run_on_any_thread: bool,
|
|
|
|
}
|
2020-03-29 07:53:47 +00:00
|
|
|
|
2020-08-08 03:22:17 +00:00
|
|
|
impl Plugin for WinitPlugin {
|
2021-07-27 20:21:06 +00:00
|
|
|
fn build(&self, app: &mut App) {
|
2023-02-03 16:41:39 +00:00
|
|
|
let mut event_loop_builder = EventLoopBuilder::<()>::with_user_event();
|
2023-10-06 13:26:06 +00:00
|
|
|
|
|
|
|
// This is needed because the features checked in the inner
|
|
|
|
// block might be enabled on other platforms than linux.
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
{
|
|
|
|
#[cfg(feature = "x11")]
|
|
|
|
{
|
|
|
|
use winit::platform::x11::EventLoopBuilderExtX11;
|
|
|
|
|
|
|
|
// This allows a Bevy app to be started and ran outside of the main thread.
|
|
|
|
// A use case for this is to allow external applications to spawn a thread
|
|
|
|
// which runs a Bevy app without requiring the Bevy app to need to reside on
|
|
|
|
// the main thread, which can be problematic.
|
|
|
|
event_loop_builder.with_any_thread(self.run_on_any_thread);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "wayland")]
|
|
|
|
{
|
|
|
|
use winit::platform::wayland::EventLoopBuilderExtWayland;
|
|
|
|
event_loop_builder.with_any_thread(self.run_on_any_thread);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(target_os = "windows")]
|
|
|
|
{
|
|
|
|
use winit::platform::windows::EventLoopBuilderExtWindows;
|
|
|
|
event_loop_builder.with_any_thread(self.run_on_any_thread);
|
|
|
|
}
|
|
|
|
|
2023-02-03 16:41:39 +00:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
{
|
|
|
|
use winit::platform::android::EventLoopBuilderExtAndroid;
|
|
|
|
event_loop_builder.with_android_app(
|
|
|
|
ANDROID_APP
|
|
|
|
.get()
|
|
|
|
.expect("Bevy must be setup with the #[bevy_main] macro on Android")
|
|
|
|
.clone(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-02-24 01:40:02 +00:00
|
|
|
app.init_non_send_resource::<WinitWindows>()
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
.init_resource::<WinitSettings>()
|
2020-10-15 18:42:19 +00:00
|
|
|
.set_runner(winit_runner)
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
.add_systems(
|
2023-03-18 01:45:34 +00:00
|
|
|
Last,
|
Migrate engine to Schedule v3 (#7267)
Huge thanks to @maniwani, @devil-ira, @hymm, @cart, @superdump and @jakobhellermann for the help with this PR.
# Objective
- Followup #6587.
- Minimal integration for the Stageless Scheduling RFC: https://github.com/bevyengine/rfcs/pull/45
## Solution
- [x] Remove old scheduling module
- [x] Migrate new methods to no longer use extension methods
- [x] Fix compiler errors
- [x] Fix benchmarks
- [x] Fix examples
- [x] Fix docs
- [x] Fix tests
## Changelog
### Added
- a large number of methods on `App` to work with schedules ergonomically
- the `CoreSchedule` enum
- `App::add_extract_system` via the `RenderingAppExtension` trait extension method
- the private `prepare_view_uniforms` system now has a public system set for scheduling purposes, called `ViewSet::PrepareUniforms`
### Removed
- stages, and all code that mentions stages
- states have been dramatically simplified, and no longer use a stack
- `RunCriteriaLabel`
- `AsSystemLabel` trait
- `on_hierarchy_reports_enabled` run criteria (now just uses an ad hoc resource checking run condition)
- systems in `RenderSet/Stage::Extract` no longer warn when they do not read data from the main world
- `RunCriteriaLabel`
- `transform_propagate_system_set`: this was a nonstandard pattern that didn't actually provide enough control. The systems are already `pub`: the docs have been updated to ensure that the third-party usage is clear.
### Changed
- `System::default_labels` is now `System::default_system_sets`.
- `App::add_default_labels` is now `App::add_default_sets`
- `CoreStage` and `StartupStage` enums are now `CoreSet` and `StartupSet`
- `App::add_system_set` was renamed to `App::add_systems`
- The `StartupSchedule` label is now defined as part of the `CoreSchedules` enum
- `.label(SystemLabel)` is now referred to as `.in_set(SystemSet)`
- `SystemLabel` trait was replaced by `SystemSet`
- `SystemTypeIdLabel<T>` was replaced by `SystemSetType<T>`
- The `ReportHierarchyIssue` resource now has a public constructor (`new`), and implements `PartialEq`
- Fixed time steps now use a schedule (`CoreSchedule::FixedTimeStep`) rather than a run criteria.
- Adding rendering extraction systems now panics rather than silently failing if no subapp with the `RenderApp` label is found.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied.
- `SceneSpawnerSystem` now runs under `CoreSet::Update`, rather than `CoreStage::PreUpdate.at_end()`.
- `bevy_pbr::add_clusters` is no longer an exclusive system
- the top level `bevy_ecs::schedule` module was replaced with `bevy_ecs::scheduling`
- `tick_global_task_pools_on_main_thread` is no longer run as an exclusive system. Instead, it has been replaced by `tick_global_task_pools`, which uses a `NonSend` resource to force running on the main thread.
## Migration Guide
- Calls to `.label(MyLabel)` should be replaced with `.in_set(MySet)`
- Stages have been removed. Replace these with system sets, and then add command flushes using the `apply_system_buffers` exclusive system where needed.
- The `CoreStage`, `StartupStage, `RenderStage` and `AssetStage` enums have been replaced with `CoreSet`, `StartupSet, `RenderSet` and `AssetSet`. The same scheduling guarantees have been preserved.
- Systems are no longer added to `CoreSet::Update` by default. Add systems manually if this behavior is needed, although you should consider adding your game logic systems to `CoreSchedule::FixedTimestep` instead for more reliable framerate-independent behavior.
- Similarly, startup systems are no longer part of `StartupSet::Startup` by default. In most cases, this won't matter to you.
- For example, `add_system_to_stage(CoreStage::PostUpdate, my_system)` should be replaced with
- `add_system(my_system.in_set(CoreSet::PostUpdate)`
- When testing systems or otherwise running them in a headless fashion, simply construct and run a schedule using `Schedule::new()` and `World::run_schedule` rather than constructing stages
- Run criteria have been renamed to run conditions. These can now be combined with each other and with states.
- Looping run criteria and state stacks have been removed. Use an exclusive system that runs a schedule if you need this level of control over system control flow.
- For app-level control flow over which schedules get run when (such as for rollback networking), create your own schedule and insert it under the `CoreSchedule::Outer` label.
- Fixed timesteps are now evaluated in a schedule, rather than controlled via run criteria. The `run_fixed_timestep` system runs this schedule between `CoreSet::First` and `CoreSet::PreUpdate` by default.
- Command flush points introduced by `AssetStage` have been removed. If you were relying on these, add them back manually.
- Adding extract systems is now typically done directly on the main app. Make sure the `RenderingAppExtension` trait is in scope, then call `app.add_extract_system(my_system)`.
- the `calculate_bounds` system, with the `CalculateBounds` label, is now in `CoreSet::Update`, rather than in `CoreSet::PostUpdate` before commands are applied. You may need to order your movement systems to occur before this system in order to avoid system order ambiguities in culling behavior.
- the `RenderLabel` `AppLabel` was renamed to `RenderApp` for clarity
- `App::add_state` now takes 0 arguments: the starting state is set based on the `Default` impl.
- Instead of creating `SystemSet` containers for systems that run in stages, simply use `.on_enter::<State::Variant>()` or its `on_exit` or `on_update` siblings.
- `SystemLabel` derives should be replaced with `SystemSet`. You will also need to add the `Debug`, `PartialEq`, `Eq`, and `Hash` traits to satisfy the new trait bounds.
- `with_run_criteria` has been renamed to `run_if`. Run criteria have been renamed to run conditions for clarity, and should now simply return a bool.
- States have been dramatically simplified: there is no longer a "state stack". To queue a transition to the next state, call `NextState::set`
## TODO
- [x] remove dead methods on App and World
- [x] add `App::add_system_to_schedule` and `App::add_systems_to_schedule`
- [x] avoid adding the default system set at inappropriate times
- [x] remove any accidental cycles in the default plugins schedule
- [x] migrate benchmarks
- [x] expose explicit labels for the built-in command flush points
- [x] migrate engine code
- [x] remove all mentions of stages from the docs
- [x] verify docs for States
- [x] fix uses of exclusive systems that use .end / .at_start / .before_commands
- [x] migrate RenderStage and AssetStage
- [x] migrate examples
- [x] ensure that transform propagation is exported in a sufficiently public way (the systems are already pub)
- [x] ensure that on_enter schedules are run at least once before the main app
- [x] re-enable opt-in to execution order ambiguities
- [x] revert change to `update_bounds` to ensure it runs in `PostUpdate`
- [x] test all examples
- [x] unbreak directional lights
- [x] unbreak shadows (see 3d_scene, 3d_shape, lighting, transparaency_3d examples)
- [x] game menu example shows loading screen and menu simultaneously
- [x] display settings menu is a blank screen
- [x] `without_winit` example panics
- [x] ensure all tests pass
- [x] SubApp doc test fails
- [x] runs_spawn_local tasks fails
- [x] [Fix panic_when_hierachy_cycle test hanging](https://github.com/alice-i-cecile/bevy/pull/120)
## Points of Difficulty and Controversy
**Reviewers, please give feedback on these and look closely**
1. Default sets, from the RFC, have been removed. These added a tremendous amount of implicit complexity and result in hard to debug scheduling errors. They're going to be tackled in the form of "base sets" by @cart in a followup.
2. The outer schedule controls which schedule is run when `App::update` is called.
3. I implemented `Label for `Box<dyn Label>` for our label types. This enables us to store schedule labels in concrete form, and then later run them. I ran into the same set of problems when working with one-shot systems. We've previously investigated this pattern in depth, and it does not appear to lead to extra indirection with nested boxes.
4. `SubApp::update` simply runs the default schedule once. This sucks, but this whole API is incomplete and this was the minimal changeset.
5. `time_system` and `tick_global_task_pools_on_main_thread` no longer use exclusive systems to attempt to force scheduling order
6. Implemetnation strategy for fixed timesteps
7. `AssetStage` was migrated to `AssetSet` without reintroducing command flush points. These did not appear to be used, and it's nice to remove these bottlenecks.
8. Migration of `bevy_render/lib.rs` and pipelined rendering. The logic here is unusually tricky, as we have complex scheduling requirements.
## Future Work (ideally before 0.10)
- Rename schedule_v3 module to schedule or scheduling
- Add a derive macro to states, and likely a `EnumIter` trait of some form
- Figure out what exactly to do with the "systems added should basically work by default" problem
- Improve ergonomics for working with fixed timesteps and states
- Polish FixedTime API to match Time
- Rebase and merge #7415
- Resolve all internal ambiguities (blocked on better tools, especially #7442)
- Add "base sets" to replace the removed default sets.
2023-02-06 02:04:50 +00:00
|
|
|
(
|
2023-07-31 21:41:59 +00:00
|
|
|
// `exit_on_all_closed` only checks if windows exist but doesn't access data,
|
|
|
|
// so we don't need to care about its ordering relative to `changed_windows`
|
|
|
|
changed_windows.ambiguous_with(exit_on_all_closed),
|
|
|
|
despawn_windows,
|
|
|
|
)
|
|
|
|
.chain(),
|
2023-01-19 00:38:28 +00:00
|
|
|
);
|
2022-09-06 14:45:44 +00:00
|
|
|
|
2023-06-21 20:51:03 +00:00
|
|
|
app.add_plugins(AccessibilityPlugin);
|
2023-03-01 22:45:04 +00:00
|
|
|
|
2023-01-19 00:38:28 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
2023-06-21 20:51:03 +00:00
|
|
|
app.add_plugins(CanvasParentResizePlugin);
|
2022-07-04 13:04:14 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
let event_loop = event_loop_builder.build();
|
2022-07-04 13:04:14 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
// iOS, macOS, and Android don't like it if you create windows before the event loop is
|
|
|
|
// initialized.
|
|
|
|
//
|
|
|
|
// See:
|
|
|
|
// - https://github.com/rust-windowing/winit/blob/master/README.md#macos
|
|
|
|
// - https://github.com/rust-windowing/winit/blob/master/README.md#ios
|
2023-01-19 00:38:28 +00:00
|
|
|
#[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
|
|
|
|
{
|
2023-07-31 21:41:59 +00:00
|
|
|
// Otherwise, we want to create a window before `bevy_render` initializes the renderer
|
|
|
|
// so that we have a surface to use as a hint. This improves compatibility with `wgpu`
|
|
|
|
// backends, especially WASM/WebGL2.
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
let mut create_window_system_state: SystemState<(
|
|
|
|
Commands,
|
|
|
|
Query<(Entity, &mut Window)>,
|
|
|
|
EventWriter<WindowCreated>,
|
|
|
|
NonSendMut<WinitWindows>,
|
|
|
|
NonSendMut<AccessKitAdapters>,
|
|
|
|
ResMut<WinitActionHandlers>,
|
|
|
|
ResMut<AccessibilityRequested>,
|
|
|
|
)> = SystemState::from_world(&mut app.world);
|
|
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
let mut create_window_system_state: SystemState<(
|
|
|
|
Commands,
|
|
|
|
Query<(Entity, &mut Window)>,
|
|
|
|
EventWriter<WindowCreated>,
|
|
|
|
NonSendMut<WinitWindows>,
|
|
|
|
NonSendMut<AccessKitAdapters>,
|
|
|
|
ResMut<WinitActionHandlers>,
|
|
|
|
ResMut<AccessibilityRequested>,
|
|
|
|
ResMut<CanvasParentResizeEventChannel>,
|
|
|
|
)> = SystemState::from_world(&mut app.world);
|
|
|
|
|
2023-01-19 00:38:28 +00:00
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
2023-03-01 22:45:04 +00:00
|
|
|
let (
|
|
|
|
commands,
|
2023-07-31 21:41:59 +00:00
|
|
|
mut windows,
|
2023-03-01 22:45:04 +00:00
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
) = create_window_system_state.get_mut(&mut app.world);
|
2023-01-19 00:38:28 +00:00
|
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
2023-03-01 22:45:04 +00:00
|
|
|
let (
|
|
|
|
commands,
|
2023-07-31 21:41:59 +00:00
|
|
|
mut windows,
|
2023-03-01 22:45:04 +00:00
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
event_channel,
|
|
|
|
) = create_window_system_state.get_mut(&mut app.world);
|
2023-01-19 00:38:28 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
create_windows(
|
2023-01-19 00:38:28 +00:00
|
|
|
&event_loop,
|
2023-07-31 21:41:59 +00:00
|
|
|
commands,
|
|
|
|
windows.iter_mut(),
|
2023-01-19 00:38:28 +00:00
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
2023-03-01 22:45:04 +00:00
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
2023-01-19 00:38:28 +00:00
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
event_channel,
|
|
|
|
);
|
2023-07-31 21:41:59 +00:00
|
|
|
|
|
|
|
create_window_system_state.apply(&mut app.world);
|
2022-05-05 13:35:43 +00:00
|
|
|
}
|
2023-01-19 00:38:28 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
// `winit`'s windows are bound to the event loop that created them, so the event loop must
|
|
|
|
// be inserted as a resource here to pass it onto the runner.
|
|
|
|
app.insert_non_send_resource(event_loop);
|
2022-05-05 13:35:43 +00:00
|
|
|
}
|
2020-03-29 07:53:47 +00:00
|
|
|
}
|
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
fn run<F, T>(event_loop: EventLoop<T>, event_handler: F) -> !
|
2020-08-21 05:37:19 +00:00
|
|
|
where
|
2023-07-31 21:41:59 +00:00
|
|
|
F: 'static + FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
2020-08-21 05:37:19 +00:00
|
|
|
{
|
|
|
|
event_loop.run(event_handler)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(any(
|
|
|
|
target_os = "windows",
|
|
|
|
target_os = "macos",
|
|
|
|
target_os = "linux",
|
|
|
|
target_os = "dragonfly",
|
|
|
|
target_os = "freebsd",
|
|
|
|
target_os = "netbsd",
|
|
|
|
target_os = "openbsd"
|
|
|
|
))]
|
2023-07-31 21:41:59 +00:00
|
|
|
fn run_return<F, T>(event_loop: &mut EventLoop<T>, event_handler: F)
|
2020-08-21 05:37:19 +00:00
|
|
|
where
|
2023-07-31 21:41:59 +00:00
|
|
|
F: FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
2020-08-21 05:37:19 +00:00
|
|
|
{
|
2020-12-13 19:27:54 +00:00
|
|
|
use winit::platform::run_return::EventLoopExtRunReturn;
|
2022-02-08 04:14:33 +00:00
|
|
|
event_loop.run_return(event_handler);
|
2020-08-21 05:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(not(any(
|
|
|
|
target_os = "windows",
|
|
|
|
target_os = "macos",
|
|
|
|
target_os = "linux",
|
|
|
|
target_os = "dragonfly",
|
|
|
|
target_os = "freebsd",
|
|
|
|
target_os = "netbsd",
|
|
|
|
target_os = "openbsd"
|
|
|
|
)))]
|
2023-07-31 21:41:59 +00:00
|
|
|
fn run_return<F, T>(_event_loop: &mut EventLoop<T>, _event_handler: F)
|
2020-08-21 05:37:19 +00:00
|
|
|
where
|
2023-07-31 21:41:59 +00:00
|
|
|
F: FnMut(Event<'_, T>, &EventLoopWindowTarget<T>, &mut ControlFlow),
|
2020-08-21 05:37:19 +00:00
|
|
|
{
|
|
|
|
panic!("Run return is not supported on this platform!")
|
|
|
|
}
|
|
|
|
|
2023-01-19 00:38:28 +00:00
|
|
|
#[derive(SystemParam)]
|
2023-07-31 21:41:59 +00:00
|
|
|
struct WindowAndInputEventWriters<'w> {
|
|
|
|
// `winit` `WindowEvent`s
|
2023-01-19 00:38:28 +00:00
|
|
|
window_resized: EventWriter<'w, WindowResized>,
|
|
|
|
window_close_requested: EventWriter<'w, WindowCloseRequested>,
|
|
|
|
window_scale_factor_changed: EventWriter<'w, WindowScaleFactorChanged>,
|
|
|
|
window_backend_scale_factor_changed: EventWriter<'w, WindowBackendScaleFactorChanged>,
|
|
|
|
window_focused: EventWriter<'w, WindowFocused>,
|
|
|
|
window_moved: EventWriter<'w, WindowMoved>,
|
2023-06-05 21:04:22 +00:00
|
|
|
window_theme_changed: EventWriter<'w, WindowThemeChanged>,
|
2023-07-04 21:50:53 +00:00
|
|
|
window_destroyed: EventWriter<'w, WindowDestroyed>,
|
2023-10-23 20:47:55 +00:00
|
|
|
lifetime: EventWriter<'w, ApplicationLifetime>,
|
2023-01-19 00:38:28 +00:00
|
|
|
keyboard_input: EventWriter<'w, KeyboardInput>,
|
|
|
|
character_input: EventWriter<'w, ReceivedCharacter>,
|
|
|
|
mouse_button_input: EventWriter<'w, MouseButtonInput>,
|
2023-06-08 20:31:43 +00:00
|
|
|
touchpad_magnify_input: EventWriter<'w, TouchpadMagnify>,
|
|
|
|
touchpad_rotate_input: EventWriter<'w, TouchpadRotate>,
|
2023-01-19 00:38:28 +00:00
|
|
|
mouse_wheel_input: EventWriter<'w, MouseWheel>,
|
|
|
|
touch_input: EventWriter<'w, TouchInput>,
|
2023-01-29 20:27:29 +00:00
|
|
|
ime_input: EventWriter<'w, Ime>,
|
2023-07-31 21:41:59 +00:00
|
|
|
file_drag_and_drop: EventWriter<'w, FileDragAndDrop>,
|
2023-01-19 00:38:28 +00:00
|
|
|
cursor_moved: EventWriter<'w, CursorMoved>,
|
|
|
|
cursor_entered: EventWriter<'w, CursorEntered>,
|
|
|
|
cursor_left: EventWriter<'w, CursorLeft>,
|
2023-07-31 21:41:59 +00:00
|
|
|
// `winit` `DeviceEvent`s
|
|
|
|
mouse_motion: EventWriter<'w, MouseMotion>,
|
2020-12-24 18:43:30 +00:00
|
|
|
}
|
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
/// Persistent state that is used to run the [`App`] according to the current
|
|
|
|
/// [`UpdateMode`].
|
|
|
|
struct WinitAppRunnerState {
|
2023-10-23 20:47:55 +00:00
|
|
|
/// Current active state of the app.
|
|
|
|
active: ActiveState,
|
2023-07-31 21:41:59 +00:00
|
|
|
/// Is `true` if a new [`WindowEvent`] has been received since the last update.
|
|
|
|
window_event_received: bool,
|
|
|
|
/// Is `true` if the app has requested a redraw since the last update.
|
|
|
|
redraw_requested: bool,
|
|
|
|
/// Is `true` if enough time has elapsed since `last_update` to run another update.
|
|
|
|
wait_elapsed: bool,
|
|
|
|
/// The time the last update started.
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
last_update: Instant,
|
2023-07-31 21:41:59 +00:00
|
|
|
/// The time the next update is scheduled to start.
|
|
|
|
scheduled_update: Option<Instant>,
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
}
|
2023-07-31 21:41:59 +00:00
|
|
|
|
2023-10-23 20:47:55 +00:00
|
|
|
#[derive(PartialEq, Eq)]
|
|
|
|
enum ActiveState {
|
|
|
|
NotYetStarted,
|
|
|
|
Active,
|
|
|
|
Suspended,
|
|
|
|
WillSuspend,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ActiveState {
|
|
|
|
#[inline]
|
|
|
|
fn should_run(&self) -> bool {
|
|
|
|
match self {
|
|
|
|
ActiveState::NotYetStarted | ActiveState::Suspended => false,
|
|
|
|
ActiveState::Active | ActiveState::WillSuspend => true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
impl Default for WinitAppRunnerState {
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
fn default() -> Self {
|
|
|
|
Self {
|
2023-10-23 20:47:55 +00:00
|
|
|
active: ActiveState::NotYetStarted,
|
2023-07-31 21:41:59 +00:00
|
|
|
window_event_received: false,
|
|
|
|
redraw_requested: false,
|
|
|
|
wait_elapsed: false,
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
last_update: Instant::now(),
|
2023-07-31 21:41:59 +00:00
|
|
|
scheduled_update: None,
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-21 19:59:30 +00:00
|
|
|
/// The default [`App::runner`] for the [`WinitPlugin`] plugin.
|
|
|
|
///
|
2023-07-31 21:41:59 +00:00
|
|
|
/// Overriding the app's [runner](bevy_app::App::runner) while using `WinitPlugin` will bypass the
|
|
|
|
/// `EventLoop`.
|
2023-01-19 00:38:28 +00:00
|
|
|
pub fn winit_runner(mut app: App) {
|
2023-11-16 21:50:17 +00:00
|
|
|
if app.plugins_state() == PluginsState::Ready {
|
|
|
|
// If we're already ready, we finish up now and advance one frame.
|
|
|
|
// This prevents black frames during the launch transition on iOS.
|
|
|
|
app.finish();
|
|
|
|
app.cleanup();
|
|
|
|
app.update();
|
|
|
|
}
|
|
|
|
|
2022-02-08 23:04:19 +00:00
|
|
|
let mut event_loop = app
|
|
|
|
.world
|
|
|
|
.remove_non_send_resource::<EventLoop<()>>()
|
|
|
|
.unwrap();
|
2023-01-19 00:38:28 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
let return_from_run = app.world.resource::<WinitSettings>().return_from_run;
|
|
|
|
|
2022-02-08 23:04:19 +00:00
|
|
|
app.world
|
|
|
|
.insert_non_send_resource(event_loop.create_proxy());
|
2020-03-28 00:43:03 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
let mut runner_state = WinitAppRunnerState::default();
|
|
|
|
|
|
|
|
// prepare structures to access data in the world
|
|
|
|
let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
|
|
|
|
let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
|
Optionally resize Window canvas element to fit parent element (#4726)
Currently Bevy's web canvases are "fixed size". They are manually set to specific dimensions. This might be fine for some games and website layouts, but for sites with flexible layouts, or games that want to "fill" the browser window, Bevy doesn't provide the tools needed to make this easy out of the box.
There are third party plugins like [bevy-web-resizer](https://github.com/frewsxcv/bevy-web-resizer/) that listen for window resizes, take the new dimensions, and resize the winit window accordingly. However this only covers a subset of cases and this is common enough functionality that it should be baked into Bevy.
A significant motivating use case here is the [Bevy WASM Examples page](https://bevyengine.org/examples/). This scales the canvas to fit smaller windows (such as mobile). But this approach both breaks winit's mouse events and removes pixel-perfect rendering (which means we might be rendering too many or too few pixels). https://github.com/bevyengine/bevy-website/issues/371
In an ideal world, winit would support this behavior out of the box. But unfortunately that seems blocked for now: https://github.com/rust-windowing/winit/pull/2074. And it builds on the ResizeObserver api, which isn't supported in all browsers yet (and is only supported in very new versions of the popular browsers).
While we wait for a complete winit solution, I've added a `fit_canvas_to_parent` option to WindowDescriptor / Window, which when enabled will listen for window resizes and resize the Bevy canvas/window to fit its parent element. This enables users to scale bevy canvases using arbitrary CSS, by "inheriting" their parents' size. Note that the wrapper element _is_ required because winit overrides the canvas sizing with absolute values on each resize.
There is one limitation worth calling out here: while the majority of canvas resizes will be triggered by window resizes, modifying element layout at runtime (css animations, javascript-driven element changes, dev-tool-injected changes, etc) will not be detected here. I'm not aware of a good / efficient event-driven way to do this outside of the ResizeObserver api. In practice, window-resize-driven canvas resizing should cover the majority of use cases. Users that want to actively poll for element resizes can just do that (or we can build another feature and let people choose based on their specific needs).
I also took the chance to make a couple of minor tweaks:
* Made the `canvas` window setting available on all platforms. Users shouldn't need to deal with cargo feature selection to support web scenarios. We can just ignore the value on non-web platforms. I added documentation that explains this.
* Removed the redundant "initial create windows" handler. With the addition of the code in this pr, the code duplication was untenable.
This enables a number of patterns:
## Easy "fullscreen window" mode for the default canvas
The "parent element" defaults to the `<body>` element.
```rust
app
.insert_resource(WindowDescriptor {
fit_canvas_to_parent: true,
..default()
})
```
And CSS:
```css
html, body {
margin: 0;
height: 100%;
}
```
## Fit custom canvas to "wrapper" parent element
```rust
app
.insert_resource(WindowDescriptor {
fit_canvas_to_parent: true,
canvas: Some("#bevy".to_string()),
..default()
})
```
And the HTML:
```html
<div style="width: 50%; height: 100%">
<canvas id="bevy"></canvas>
</div>
```
2022-05-20 23:13:48 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
let mut focused_windows_state: SystemState<(Res<WinitSettings>, Query<&Window>)> =
|
|
|
|
SystemState::new(&mut app.world);
|
2020-08-21 05:37:19 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
let mut event_writer_system_state: SystemState<(
|
|
|
|
WindowAndInputEventWriters,
|
|
|
|
NonSend<WinitWindows>,
|
|
|
|
Query<(&mut Window, &mut CachedWindow)>,
|
2023-11-03 21:37:25 +00:00
|
|
|
NonSend<AccessKitAdapters>,
|
2023-07-31 21:41:59 +00:00
|
|
|
)> = SystemState::new(&mut app.world);
|
2023-01-19 00:38:28 +00:00
|
|
|
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
let mut create_window_system_state: SystemState<(
|
|
|
|
Commands,
|
|
|
|
Query<(Entity, &mut Window), Added<Window>>,
|
|
|
|
EventWriter<WindowCreated>,
|
|
|
|
NonSendMut<WinitWindows>,
|
2023-03-01 22:45:04 +00:00
|
|
|
NonSendMut<AccessKitAdapters>,
|
|
|
|
ResMut<WinitActionHandlers>,
|
|
|
|
ResMut<AccessibilityRequested>,
|
2023-01-19 00:38:28 +00:00
|
|
|
)> = SystemState::from_world(&mut app.world);
|
|
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
let mut create_window_system_state: SystemState<(
|
|
|
|
Commands,
|
|
|
|
Query<(Entity, &mut Window), Added<Window>>,
|
|
|
|
EventWriter<WindowCreated>,
|
|
|
|
NonSendMut<WinitWindows>,
|
2023-03-01 22:45:04 +00:00
|
|
|
NonSendMut<AccessKitAdapters>,
|
|
|
|
ResMut<WinitActionHandlers>,
|
|
|
|
ResMut<AccessibilityRequested>,
|
2023-01-19 00:38:28 +00:00
|
|
|
ResMut<CanvasParentResizeEventChannel>,
|
|
|
|
)> = SystemState::from_world(&mut app.world);
|
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
// setup up the event loop
|
2020-08-21 05:37:19 +00:00
|
|
|
let event_handler = move |event: Event<()>,
|
|
|
|
event_loop: &EventLoopWindowTarget<()>,
|
|
|
|
control_flow: &mut ControlFlow| {
|
2022-11-14 23:08:31 +00:00
|
|
|
#[cfg(feature = "trace")]
|
|
|
|
let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
|
2023-01-19 00:38:28 +00:00
|
|
|
|
fix run-once runners (#10195)
# Objective
- After #9826, there are issues on "run once runners"
- example `without_winit` crashes:
```
2023-10-19T22:06:01.810019Z INFO bevy_render::renderer: AdapterInfo { name: "llvmpipe (LLVM 15.0.7, 256 bits)", vendor: 65541, device: 0, device_type: Cpu, driver: "llvmpipe", driver_info: "Mesa 23.2.1 - kisak-mesa PPA (LLVM 15.0.7)", backend: Vulkan }
2023-10-19T22:06:02.860331Z WARN bevy_audio::audio_output: No audio device found.
2023-10-19T22:06:03.215154Z INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux 22.04 Ubuntu", kernel: "6.2.0-1014-azure", cpu: "Intel(R) Xeon(R) CPU E5-2673 v3 @ 2.40GHz", core_count: "2", memory: "6.8 GiB" }
thread 'main' panicked at crates/bevy_render/src/pipelined_rendering.rs:91:14:
Unable to get RenderApp. Another plugin may have removed the RenderApp before PipelinedRenderingPlugin
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
- example `headless` runs the app twice with the `run_once` schedule
## Solution
- Expose a more complex state of an app than just "ready"
- Also block adding plugins to an app after it has finished or cleaned
up its plugins as that wouldn't work anyway
## Migration Guide
* `app.ready()` has been replaced by `app.plugins_state()` which will
return more details on the current state of plugins in the app
2023-10-23 12:25:02 +00:00
|
|
|
if app.plugins_state() != PluginsState::Cleaned {
|
|
|
|
if app.plugins_state() != PluginsState::Ready {
|
Webgpu support (#8336)
# Objective
- Support WebGPU
- alternative to #5027 that doesn't need any async / await
- fixes #8315
- Surprise fix #7318
## Solution
### For async renderer initialisation
- Update the plugin lifecycle:
- app builds the plugin
- calls `plugin.build`
- registers the plugin
- app starts the event loop
- event loop waits for `ready` of all registered plugins in the same
order
- returns `true` by default
- then call all `finish` then all `cleanup` in the same order as
registered
- then execute the schedule
In the case of the renderer, to avoid anything async:
- building the renderer plugin creates a detached task that will send
back the initialised renderer through a mutex in a resource
- `ready` will wait for the renderer to be present in the resource
- `finish` will take that renderer and place it in the expected
resources by other plugins
- other plugins (that expect the renderer to be available) `finish` are
called and they are able to set up their pipelines
- `cleanup` is called, only custom one is still for pipeline rendering
### For WebGPU support
- update the `build-wasm-example` script to support passing `--api
webgpu` that will build the example with WebGPU support
- feature for webgl2 was always enabled when building for wasm. it's now
in the default feature list and enabled on all platforms, so check for
this feature must also check that the target_arch is `wasm32`
---
## Migration Guide
- `Plugin::setup` has been renamed `Plugin::cleanup`
- `Plugin::finish` has been added, and plugins adding pipelines should
do it in this function instead of `Plugin::build`
```rust
// Before
impl Plugin for MyPlugin {
fn build(&self, app: &mut App) {
app.insert_resource::<MyResource>
.add_systems(Update, my_system);
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};
render_app
.init_resource::<RenderResourceNeedingDevice>()
.init_resource::<OtherRenderResource>();
}
}
// After
impl Plugin for MyPlugin {
fn build(&self, app: &mut App) {
app.insert_resource::<MyResource>
.add_systems(Update, my_system);
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};
render_app
.init_resource::<OtherRenderResource>();
}
fn finish(&self, app: &mut App) {
let render_app = match app.get_sub_app_mut(RenderApp) {
Ok(render_app) => render_app,
Err(_) => return,
};
render_app
.init_resource::<RenderResourceNeedingDevice>();
}
}
```
2023-05-04 22:07:57 +00:00
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
tick_global_task_pools_on_main_thread();
|
|
|
|
} else {
|
|
|
|
app.finish();
|
|
|
|
app.cleanup();
|
|
|
|
}
|
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
|
2023-08-30 14:20:03 +00:00
|
|
|
if app_exit_event_reader.read(app_exit_events).last().is_some() {
|
2023-07-31 21:41:59 +00:00
|
|
|
*control_flow = ControlFlow::Exit;
|
|
|
|
return;
|
|
|
|
}
|
2023-01-19 00:38:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-30 18:52:33 +00:00
|
|
|
match event {
|
2023-07-31 21:41:59 +00:00
|
|
|
event::Event::NewEvents(start_cause) => match start_cause {
|
|
|
|
StartCause::Init => {
|
2023-09-23 06:28:49 +00:00
|
|
|
#[cfg(any(target_os = "ios", target_os = "macos"))]
|
2023-07-31 21:41:59 +00:00
|
|
|
{
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
let (
|
|
|
|
commands,
|
|
|
|
mut windows,
|
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
) = create_window_system_state.get_mut(&mut app.world);
|
|
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
let (
|
|
|
|
commands,
|
|
|
|
mut windows,
|
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
event_channel,
|
|
|
|
) = create_window_system_state.get_mut(&mut app.world);
|
|
|
|
|
|
|
|
create_windows(
|
|
|
|
event_loop,
|
|
|
|
commands,
|
|
|
|
windows.iter_mut(),
|
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
event_channel,
|
|
|
|
);
|
|
|
|
|
|
|
|
create_window_system_state.apply(&mut app.world);
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
}
|
2023-07-31 21:41:59 +00:00
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
if let Some(t) = runner_state.scheduled_update {
|
|
|
|
let now = Instant::now();
|
|
|
|
let remaining = t.checked_duration_since(now).unwrap_or(Duration::ZERO);
|
|
|
|
runner_state.wait_elapsed = remaining.is_zero();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2020-04-19 19:13:04 +00:00
|
|
|
event::Event::WindowEvent {
|
2023-07-31 21:41:59 +00:00
|
|
|
event, window_id, ..
|
2020-12-27 19:24:31 +00:00
|
|
|
} => {
|
2023-11-03 21:37:25 +00:00
|
|
|
let (mut event_writers, winit_windows, mut windows, access_kit_adapters) =
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writer_system_state.get_mut(&mut app.world);
|
|
|
|
|
|
|
|
let Some(window_entity) = winit_windows.get_window_entity(window_id) else {
|
2023-08-25 12:34:24 +00:00
|
|
|
warn!(
|
|
|
|
"Skipped event {:?} for unknown winit Window Id {:?}",
|
|
|
|
event, window_id
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
};
|
2023-01-19 00:38:28 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
let Ok((mut window, mut cache)) = windows.get_mut(window_entity) else {
|
2023-08-25 12:34:24 +00:00
|
|
|
warn!(
|
|
|
|
"Window {:?} is missing `Window` component, skipping event {:?}",
|
|
|
|
window_entity, event
|
|
|
|
);
|
|
|
|
return;
|
|
|
|
};
|
2020-12-27 19:24:31 +00:00
|
|
|
|
2023-11-03 21:37:25 +00:00
|
|
|
// Allow AccessKit to respond to `WindowEvent`s before they reach
|
|
|
|
// the engine.
|
|
|
|
if let Some(adapter) = access_kit_adapters.get(&window_entity) {
|
|
|
|
if let Some(window) = winit_windows.get_window(window_entity) {
|
|
|
|
// Somewhat surprisingly, this call has meaningful side effects
|
|
|
|
// See https://github.com/AccessKit/accesskit/issues/300
|
|
|
|
// AccessKit might later need to filter events based on this, but we currently do not.
|
|
|
|
// See https://github.com/bevyengine/bevy/pull/10239#issuecomment-1775572176
|
|
|
|
let _ = adapter.on_event(window, &event);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
runner_state.window_event_received = true;
|
2020-12-27 19:24:31 +00:00
|
|
|
|
|
|
|
match event {
|
|
|
|
WindowEvent::Resized(size) => {
|
2023-01-19 00:38:28 +00:00
|
|
|
window
|
|
|
|
.resolution
|
|
|
|
.set_physical_resolution(size.width, size.height);
|
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.window_resized.send(WindowResized {
|
2023-01-19 00:38:28 +00:00
|
|
|
window: window_entity,
|
2020-12-27 19:24:31 +00:00
|
|
|
width: window.width(),
|
|
|
|
height: window.height(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
WindowEvent::CloseRequested => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers
|
2023-01-19 00:38:28 +00:00
|
|
|
.window_close_requested
|
|
|
|
.send(WindowCloseRequested {
|
|
|
|
window: window_entity,
|
|
|
|
});
|
2020-12-27 19:24:31 +00:00
|
|
|
}
|
|
|
|
WindowEvent::KeyboardInput { ref input, .. } => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers
|
2023-01-19 00:38:28 +00:00
|
|
|
.keyboard_input
|
2023-06-16 13:54:06 +00:00
|
|
|
.send(converters::convert_keyboard_input(input, window_entity));
|
2020-12-27 19:24:31 +00:00
|
|
|
}
|
|
|
|
WindowEvent::CursorMoved { position, .. } => {
|
2023-04-05 22:32:36 +00:00
|
|
|
let physical_position = DVec2::new(position.x, position.y);
|
2023-02-07 14:18:13 +00:00
|
|
|
window.set_physical_cursor_position(Some(physical_position));
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.cursor_moved.send(CursorMoved {
|
2023-01-19 00:38:28 +00:00
|
|
|
window: window_entity,
|
|
|
|
position: (physical_position / window.resolution.scale_factor())
|
|
|
|
.as_vec2(),
|
2020-08-21 00:04:01 +00:00
|
|
|
});
|
|
|
|
}
|
2020-12-27 19:24:31 +00:00
|
|
|
WindowEvent::CursorEntered { .. } => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.cursor_entered.send(CursorEntered {
|
2023-01-19 00:38:28 +00:00
|
|
|
window: window_entity,
|
|
|
|
});
|
2020-12-27 19:24:31 +00:00
|
|
|
}
|
|
|
|
WindowEvent::CursorLeft { .. } => {
|
2023-02-07 14:18:13 +00:00
|
|
|
window.set_physical_cursor_position(None);
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.cursor_left.send(CursorLeft {
|
2023-01-19 00:38:28 +00:00
|
|
|
window: window_entity,
|
|
|
|
});
|
2020-12-27 19:24:31 +00:00
|
|
|
}
|
|
|
|
WindowEvent::MouseInput { state, button, .. } => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.mouse_button_input.send(MouseButtonInput {
|
2020-12-27 19:24:31 +00:00
|
|
|
button: converters::convert_mouse_button(button),
|
|
|
|
state: converters::convert_element_state(state),
|
2023-06-16 13:54:06 +00:00
|
|
|
window: window_entity,
|
2020-08-21 00:04:01 +00:00
|
|
|
});
|
|
|
|
}
|
2023-06-08 20:31:43 +00:00
|
|
|
WindowEvent::TouchpadMagnify { delta, .. } => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers
|
2023-06-08 20:31:43 +00:00
|
|
|
.touchpad_magnify_input
|
|
|
|
.send(TouchpadMagnify(delta as f32));
|
|
|
|
}
|
|
|
|
WindowEvent::TouchpadRotate { delta, .. } => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers
|
2023-06-08 20:31:43 +00:00
|
|
|
.touchpad_rotate_input
|
|
|
|
.send(TouchpadRotate(delta));
|
|
|
|
}
|
2020-12-27 19:24:31 +00:00
|
|
|
WindowEvent::MouseWheel { delta, .. } => match delta {
|
|
|
|
event::MouseScrollDelta::LineDelta(x, y) => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.mouse_wheel_input.send(MouseWheel {
|
2020-12-27 19:24:31 +00:00
|
|
|
unit: MouseScrollUnit::Line,
|
|
|
|
x,
|
|
|
|
y,
|
2023-06-16 13:54:06 +00:00
|
|
|
window: window_entity,
|
2020-12-27 19:24:31 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
event::MouseScrollDelta::PixelDelta(p) => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.mouse_wheel_input.send(MouseWheel {
|
2020-12-27 19:24:31 +00:00
|
|
|
unit: MouseScrollUnit::Pixel,
|
|
|
|
x: p.x as f32,
|
|
|
|
y: p.y as f32,
|
2023-06-16 13:54:06 +00:00
|
|
|
window: window_entity,
|
2020-12-27 19:24:31 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
},
|
|
|
|
WindowEvent::Touch(touch) => {
|
2023-02-06 18:08:49 +00:00
|
|
|
let location = touch.location.to_logical(window.resolution.scale_factor());
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers
|
2023-01-19 00:38:28 +00:00
|
|
|
.touch_input
|
|
|
|
.send(converters::convert_touch_input(touch, location));
|
2020-11-03 19:32:48 +00:00
|
|
|
}
|
2023-07-31 21:41:59 +00:00
|
|
|
WindowEvent::ReceivedCharacter(char) => {
|
|
|
|
event_writers.character_input.send(ReceivedCharacter {
|
2023-01-19 00:38:28 +00:00
|
|
|
window: window_entity,
|
2023-07-31 21:41:59 +00:00
|
|
|
char,
|
2022-02-13 22:33:55 +00:00
|
|
|
});
|
2020-12-27 19:24:31 +00:00
|
|
|
}
|
|
|
|
WindowEvent::ScaleFactorChanged {
|
|
|
|
scale_factor,
|
|
|
|
new_inner_size,
|
|
|
|
} => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.window_backend_scale_factor_changed.send(
|
2023-01-19 00:38:28 +00:00
|
|
|
WindowBackendScaleFactorChanged {
|
|
|
|
window: window_entity,
|
|
|
|
scale_factor,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
let prior_factor = window.resolution.scale_factor();
|
|
|
|
window.resolution.set_scale_factor(scale_factor);
|
|
|
|
let new_factor = window.resolution.scale_factor();
|
|
|
|
|
|
|
|
if let Some(forced_factor) = window.resolution.scale_factor_override() {
|
2023-07-31 21:41:59 +00:00
|
|
|
// This window is overriding the OS-suggested DPI, so its physical size
|
|
|
|
// should be set based on the overriding value. Its logical size already
|
|
|
|
// incorporates any resize constraints.
|
2023-01-19 00:38:28 +00:00
|
|
|
*new_inner_size =
|
|
|
|
winit::dpi::LogicalSize::new(window.width(), window.height())
|
|
|
|
.to_physical::<u32>(forced_factor);
|
2021-09-10 18:46:16 +00:00
|
|
|
} else if approx::relative_ne!(new_factor, prior_factor) {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.window_scale_factor_changed.send(
|
2023-01-19 00:38:28 +00:00
|
|
|
WindowScaleFactorChanged {
|
|
|
|
window: window_entity,
|
|
|
|
scale_factor,
|
|
|
|
},
|
|
|
|
);
|
2020-12-28 20:26:50 +00:00
|
|
|
}
|
|
|
|
|
2023-01-19 00:38:28 +00:00
|
|
|
let new_logical_width = (new_inner_size.width as f64 / new_factor) as f32;
|
|
|
|
let new_logical_height = (new_inner_size.height as f64 / new_factor) as f32;
|
|
|
|
if approx::relative_ne!(window.width(), new_logical_width)
|
|
|
|
|| approx::relative_ne!(window.height(), new_logical_height)
|
2020-12-28 20:26:50 +00:00
|
|
|
{
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.window_resized.send(WindowResized {
|
2023-01-19 00:38:28 +00:00
|
|
|
window: window_entity,
|
|
|
|
width: new_logical_width,
|
|
|
|
height: new_logical_height,
|
2020-12-28 20:26:50 +00:00
|
|
|
});
|
|
|
|
}
|
2023-01-19 00:38:28 +00:00
|
|
|
window
|
|
|
|
.resolution
|
|
|
|
.set_physical_resolution(new_inner_size.width, new_inner_size.height);
|
2020-12-27 19:24:31 +00:00
|
|
|
}
|
|
|
|
WindowEvent::Focused(focused) => {
|
2023-01-19 00:38:28 +00:00
|
|
|
window.focused = focused;
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.window_focused.send(WindowFocused {
|
2023-01-19 00:38:28 +00:00
|
|
|
window: window_entity,
|
2020-12-18 21:08:26 +00:00
|
|
|
focused,
|
2020-12-27 19:24:31 +00:00
|
|
|
});
|
2020-12-18 21:08:26 +00:00
|
|
|
}
|
2021-01-01 21:31:22 +00:00
|
|
|
WindowEvent::DroppedFile(path_buf) => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers
|
|
|
|
.file_drag_and_drop
|
|
|
|
.send(FileDragAndDrop::DroppedFile {
|
|
|
|
window: window_entity,
|
|
|
|
path_buf,
|
|
|
|
});
|
2021-01-01 21:31:22 +00:00
|
|
|
}
|
|
|
|
WindowEvent::HoveredFile(path_buf) => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers
|
|
|
|
.file_drag_and_drop
|
|
|
|
.send(FileDragAndDrop::HoveredFile {
|
|
|
|
window: window_entity,
|
|
|
|
path_buf,
|
|
|
|
});
|
2021-01-01 21:31:22 +00:00
|
|
|
}
|
|
|
|
WindowEvent::HoveredFileCancelled => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.file_drag_and_drop.send(
|
|
|
|
FileDragAndDrop::HoveredFileCanceled {
|
|
|
|
window: window_entity,
|
|
|
|
},
|
|
|
|
);
|
2021-01-01 21:31:22 +00:00
|
|
|
}
|
2021-01-25 04:06:06 +00:00
|
|
|
WindowEvent::Moved(position) => {
|
|
|
|
let position = ivec2(position.x, position.y);
|
2023-01-19 00:38:28 +00:00
|
|
|
window.position.set(position);
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.window_moved.send(WindowMoved {
|
2023-01-19 00:38:28 +00:00
|
|
|
entity: window_entity,
|
2021-01-25 04:06:06 +00:00
|
|
|
position,
|
|
|
|
});
|
|
|
|
}
|
2023-01-29 20:27:29 +00:00
|
|
|
WindowEvent::Ime(event) => match event {
|
|
|
|
event::Ime::Preedit(value, cursor) => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.ime_input.send(Ime::Preedit {
|
2023-01-29 20:27:29 +00:00
|
|
|
window: window_entity,
|
|
|
|
value,
|
|
|
|
cursor,
|
|
|
|
});
|
|
|
|
}
|
Update `Event` send methods to return `EventId` (#10551)
# Objective
- Fixes #10532
## Solution
I've updated the various `Event` send methods to return the sent
`EventId`(s). Since these methods previously returned nothing, and this
information is cheap to copy, there should be minimal negative
consequences to providing this additional information. In the case of
`send_batch`, an iterator is returned built from `Range` and `Map`,
which only consumes 16 bytes on the stack with no heap allocations for
all batch sizes. As such, the cost of this information is negligible.
These changes are reflected for `EventWriter` and `World`. For `World`,
the return types are optional to account for the possible lack of an
`Events` resource. Again, these methods previously returned no
information, so its inclusion should only be a benefit.
## Usage
Now when sending events, the IDs of those events is available for
immediate use:
```rust
// Example of a request-response system where the requester can track handled requests.
/// A system which can make and track requests
fn requester(
mut requests: EventWriter<Request>,
mut handled: EventReader<Handled>,
mut pending: Local<HashSet<EventId<Request>>>,
) {
// Check status of previous requests
for Handled(id) in handled.read() {
pending.remove(&id);
}
if !pending.is_empty() {
error!("Not all my requests were handled on the previous frame!");
pending.clear();
}
// Send a new request and remember its ID for later
let request_id = requests.send(Request::MyRequest { /* ... */ });
pending.insert(request_id);
}
/// A system which handles requests
fn responder(
mut requests: EventReader<Request>,
mut handled: EventWriter<Handled>,
) {
for (request, id) in requests.read_with_id() {
if handle(request).is_ok() {
handled.send(Handled(id));
}
}
}
```
In the above example, a `requester` system can send request events, and
keep track of which ones are currently pending by `EventId`. Then, a
`responder` system can act on that event, providing the ID as a
reference that the `requester` can use. Before this PR, it was not
trivial for a system sending events to keep track of events by ID. This
is unfortunate, since for a system reading events, it is trivial to
access the ID of a event.
---
## Changelog
- Updated `Events`:
- Added `send_batch`
- Modified `send` to return the sent `EventId`
- Modified `send_default` to return the sent `EventId`
- Updated `EventWriter`
- Modified `send_batch` to return all sent `EventId`s
- Modified `send` to return the sent `EventId`
- Modified `send_default` to return the sent `EventId`
- Updated `World`
- Modified `send_event` to return the sent `EventId` if sent, otherwise
`None`.
- Modified `send_event_default` to return the sent `EventId` if sent,
otherwise `None`.
- Modified `send_event_batch` to return all sent `EventId`s if sent,
otherwise `None`.
- Added unit test `test_send_events_ids` to ensure returned `EventId`s
match the sent `Event`s
- Updated uses of modified methods.
## Migration Guide
### `send` / `send_default` / `send_batch`
For the following methods:
- `Events::send`
- `Events::send_default`
- `Events::send_batch`
- `EventWriter::send`
- `EventWriter::send_default`
- `EventWriter::send_batch`
- `World::send_event`
- `World::send_event_default`
- `World::send_event_batch`
Ensure calls to these methods either handle the returned value, or
suppress the result with `;`.
```rust
// Now fails to compile due to mismatched return type
fn send_my_event(mut events: EventWriter<MyEvent>) {
events.send_default()
}
// Fix
fn send_my_event(mut events: EventWriter<MyEvent>) {
events.send_default();
}
```
This will most likely be noticed within `match` statements:
```rust
// Before
match is_pressed {
true => events.send(PlayerAction::Fire),
// ^--^ No longer returns ()
false => {}
}
// After
match is_pressed {
true => {
events.send(PlayerAction::Fire);
},
false => {}
}
```
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Nicola Papale <nicopap@users.noreply.github.com>
2023-11-16 17:20:43 +00:00
|
|
|
event::Ime::Commit(value) => {
|
|
|
|
event_writers.ime_input.send(Ime::Commit {
|
|
|
|
window: window_entity,
|
|
|
|
value,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
event::Ime::Enabled => {
|
|
|
|
event_writers.ime_input.send(Ime::Enabled {
|
|
|
|
window: window_entity,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
event::Ime::Disabled => {
|
|
|
|
event_writers.ime_input.send(Ime::Disabled {
|
|
|
|
window: window_entity,
|
|
|
|
});
|
|
|
|
}
|
2023-01-29 20:27:29 +00:00
|
|
|
},
|
2023-06-05 21:04:22 +00:00
|
|
|
WindowEvent::ThemeChanged(theme) => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.window_theme_changed.send(WindowThemeChanged {
|
2023-06-05 21:04:22 +00:00
|
|
|
window: window_entity,
|
|
|
|
theme: convert_winit_theme(theme),
|
|
|
|
});
|
|
|
|
}
|
2023-07-04 21:50:53 +00:00
|
|
|
WindowEvent::Destroyed => {
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.window_destroyed.send(WindowDestroyed {
|
2023-07-04 21:50:53 +00:00
|
|
|
window: window_entity,
|
|
|
|
});
|
|
|
|
}
|
2020-12-27 19:24:31 +00:00
|
|
|
_ => {}
|
2020-12-07 21:24:25 +00:00
|
|
|
}
|
2023-02-07 14:18:13 +00:00
|
|
|
|
|
|
|
if window.is_changed() {
|
|
|
|
cache.window = window.clone();
|
|
|
|
}
|
2020-12-27 19:24:31 +00:00
|
|
|
}
|
2020-12-07 19:57:15 +00:00
|
|
|
event::Event::DeviceEvent {
|
2022-09-06 14:45:44 +00:00
|
|
|
event: DeviceEvent::MouseMotion { delta: (x, y) },
|
2020-12-07 19:57:15 +00:00
|
|
|
..
|
|
|
|
} => {
|
2023-11-03 21:37:25 +00:00
|
|
|
let (mut event_writers, ..) = event_writer_system_state.get_mut(&mut app.world);
|
2023-07-31 21:41:59 +00:00
|
|
|
event_writers.mouse_motion.send(MouseMotion {
|
2023-01-19 00:38:28 +00:00
|
|
|
delta: Vec2::new(x as f32, y as f32),
|
2020-12-07 19:57:15 +00:00
|
|
|
});
|
2020-08-16 07:30:04 +00:00
|
|
|
}
|
2021-07-16 00:50:39 +00:00
|
|
|
event::Event::Suspended => {
|
2023-11-03 21:37:25 +00:00
|
|
|
let (mut event_writers, ..) = event_writer_system_state.get_mut(&mut app.world);
|
2023-10-23 20:47:55 +00:00
|
|
|
event_writers.lifetime.send(ApplicationLifetime::Suspended);
|
|
|
|
// Mark the state as `WillSuspend`. This will let the schedule run one last time
|
|
|
|
// before actually suspending to let the application react
|
|
|
|
runner_state.active = ActiveState::WillSuspend;
|
2021-07-16 00:50:39 +00:00
|
|
|
}
|
|
|
|
event::Event::Resumed => {
|
2023-11-03 21:37:25 +00:00
|
|
|
let (mut event_writers, ..) = event_writer_system_state.get_mut(&mut app.world);
|
2023-10-23 20:47:55 +00:00
|
|
|
match runner_state.active {
|
|
|
|
ActiveState::NotYetStarted => {
|
|
|
|
event_writers.lifetime.send(ApplicationLifetime::Started);
|
|
|
|
}
|
|
|
|
_ => {
|
|
|
|
event_writers.lifetime.send(ApplicationLifetime::Resumed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
runner_state.active = ActiveState::Active;
|
2023-10-02 13:06:13 +00:00
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
{
|
|
|
|
// Get windows that are cached but without raw handles. Those window were already created, but got their
|
|
|
|
// handle wrapper removed when the app was suspended.
|
|
|
|
let mut query = app
|
|
|
|
.world
|
|
|
|
.query_filtered::<(Entity, &Window), (With<CachedWindow>, Without<bevy_window::RawHandleWrapper>)>();
|
|
|
|
if let Ok((entity, window)) = query.get_single(&app.world) {
|
|
|
|
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
|
|
|
|
let window = window.clone();
|
|
|
|
|
|
|
|
let (
|
|
|
|
_,
|
|
|
|
_,
|
|
|
|
_,
|
|
|
|
mut winit_windows,
|
|
|
|
mut adapters,
|
|
|
|
mut handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
) = create_window_system_state.get_mut(&mut app.world);
|
|
|
|
|
|
|
|
let winit_window = winit_windows.create_window(
|
|
|
|
event_loop,
|
|
|
|
entity,
|
|
|
|
&window,
|
|
|
|
&mut adapters,
|
|
|
|
&mut handlers,
|
|
|
|
&accessibility_requested,
|
|
|
|
);
|
|
|
|
|
|
|
|
let wrapper = RawHandleWrapper {
|
|
|
|
window_handle: winit_window.raw_window_handle(),
|
|
|
|
display_handle: winit_window.raw_display_handle(),
|
|
|
|
};
|
|
|
|
|
|
|
|
app.world.entity_mut(entity).insert(wrapper);
|
|
|
|
}
|
|
|
|
*control_flow = ControlFlow::Poll;
|
|
|
|
}
|
2021-07-16 00:50:39 +00:00
|
|
|
}
|
2020-03-30 18:52:33 +00:00
|
|
|
event::Event::MainEventsCleared => {
|
2023-10-23 20:47:55 +00:00
|
|
|
if runner_state.active.should_run() {
|
|
|
|
if runner_state.active == ActiveState::WillSuspend {
|
|
|
|
runner_state.active = ActiveState::Suspended;
|
|
|
|
#[cfg(target_os = "android")]
|
|
|
|
{
|
|
|
|
// Remove the `RawHandleWrapper` from the primary window.
|
|
|
|
// This will trigger the surface destruction.
|
|
|
|
let mut query =
|
|
|
|
app.world.query_filtered::<Entity, With<PrimaryWindow>>();
|
|
|
|
let entity = query.single(&app.world);
|
|
|
|
app.world.entity_mut(entity).remove::<RawHandleWrapper>();
|
|
|
|
*control_flow = ControlFlow::Wait;
|
|
|
|
}
|
|
|
|
}
|
2023-07-31 21:41:59 +00:00
|
|
|
let (config, windows) = focused_windows_state.get(&app.world);
|
|
|
|
let focused = windows.iter().any(|window| window.focused);
|
|
|
|
let should_update = match config.update_mode(focused) {
|
|
|
|
UpdateMode::Continuous | UpdateMode::Reactive { .. } => {
|
|
|
|
// `Reactive`: In order for `event_handler` to have been called, either
|
|
|
|
// we received a window or raw input event, the `wait` elapsed, or a
|
|
|
|
// redraw was requested (by the app or the OS). There are no other
|
|
|
|
// conditions, so we can just return `true` here.
|
|
|
|
true
|
|
|
|
}
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
UpdateMode::ReactiveLowPower { .. } => {
|
2023-07-31 21:41:59 +00:00
|
|
|
runner_state.wait_elapsed
|
|
|
|
|| runner_state.redraw_requested
|
|
|
|
|| runner_state.window_event_received
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
}
|
2023-07-31 21:41:59 +00:00
|
|
|
};
|
2023-01-19 00:38:28 +00:00
|
|
|
|
fix run-once runners (#10195)
# Objective
- After #9826, there are issues on "run once runners"
- example `without_winit` crashes:
```
2023-10-19T22:06:01.810019Z INFO bevy_render::renderer: AdapterInfo { name: "llvmpipe (LLVM 15.0.7, 256 bits)", vendor: 65541, device: 0, device_type: Cpu, driver: "llvmpipe", driver_info: "Mesa 23.2.1 - kisak-mesa PPA (LLVM 15.0.7)", backend: Vulkan }
2023-10-19T22:06:02.860331Z WARN bevy_audio::audio_output: No audio device found.
2023-10-19T22:06:03.215154Z INFO bevy_diagnostic::system_information_diagnostics_plugin::internal: SystemInfo { os: "Linux 22.04 Ubuntu", kernel: "6.2.0-1014-azure", cpu: "Intel(R) Xeon(R) CPU E5-2673 v3 @ 2.40GHz", core_count: "2", memory: "6.8 GiB" }
thread 'main' panicked at crates/bevy_render/src/pipelined_rendering.rs:91:14:
Unable to get RenderApp. Another plugin may have removed the RenderApp before PipelinedRenderingPlugin
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
```
- example `headless` runs the app twice with the `run_once` schedule
## Solution
- Expose a more complex state of an app than just "ready"
- Also block adding plugins to an app after it has finished or cleaned
up its plugins as that wouldn't work anyway
## Migration Guide
* `app.ready()` has been replaced by `app.plugins_state()` which will
return more details on the current state of plugins in the app
2023-10-23 12:25:02 +00:00
|
|
|
if app.plugins_state() == PluginsState::Cleaned && should_update {
|
2023-07-31 21:41:59 +00:00
|
|
|
// reset these on each update
|
|
|
|
runner_state.wait_elapsed = false;
|
|
|
|
runner_state.window_event_received = false;
|
|
|
|
runner_state.redraw_requested = false;
|
|
|
|
runner_state.last_update = Instant::now();
|
|
|
|
|
|
|
|
app.update();
|
|
|
|
|
|
|
|
// decide when to run the next update
|
|
|
|
let (config, windows) = focused_windows_state.get(&app.world);
|
|
|
|
let focused = windows.iter().any(|window| window.focused);
|
|
|
|
match config.update_mode(focused) {
|
|
|
|
UpdateMode::Continuous => *control_flow = ControlFlow::Poll,
|
|
|
|
UpdateMode::Reactive { wait }
|
|
|
|
| UpdateMode::ReactiveLowPower { wait } => {
|
|
|
|
if let Some(next) = runner_state.last_update.checked_add(*wait) {
|
|
|
|
runner_state.scheduled_update = Some(next);
|
|
|
|
*control_flow = ControlFlow::WaitUntil(next);
|
|
|
|
} else {
|
|
|
|
runner_state.scheduled_update = None;
|
|
|
|
*control_flow = ControlFlow::Wait;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(app_redraw_events) =
|
|
|
|
app.world.get_resource::<Events<RequestRedraw>>()
|
|
|
|
{
|
2023-08-30 14:20:03 +00:00
|
|
|
if redraw_event_reader.read(app_redraw_events).last().is_some() {
|
2023-07-31 21:41:59 +00:00
|
|
|
runner_state.redraw_requested = true;
|
|
|
|
*control_flow = ControlFlow::Poll;
|
2022-06-02 19:42:20 +00:00
|
|
|
}
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
}
|
2023-01-19 00:38:28 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
|
2023-08-30 14:20:03 +00:00
|
|
|
if app_exit_event_reader.read(app_exit_events).last().is_some() {
|
2023-07-31 21:41:59 +00:00
|
|
|
*control_flow = ControlFlow::Exit;
|
|
|
|
}
|
|
|
|
}
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
}
|
2023-01-19 00:38:28 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
// create any new windows
|
|
|
|
// (even if app did not update, some may have been created by plugin setup)
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
|
|
let (
|
|
|
|
commands,
|
|
|
|
mut windows,
|
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
) = create_window_system_state.get_mut(&mut app.world);
|
|
|
|
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
let (
|
|
|
|
commands,
|
|
|
|
mut windows,
|
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
event_channel,
|
|
|
|
) = create_window_system_state.get_mut(&mut app.world);
|
|
|
|
|
|
|
|
create_windows(
|
|
|
|
event_loop,
|
|
|
|
commands,
|
|
|
|
windows.iter_mut(),
|
|
|
|
event_writer,
|
|
|
|
winit_windows,
|
|
|
|
adapters,
|
|
|
|
handlers,
|
|
|
|
accessibility_requested,
|
|
|
|
#[cfg(target_arch = "wasm32")]
|
|
|
|
event_channel,
|
|
|
|
);
|
|
|
|
|
|
|
|
create_window_system_state.apply(&mut app.world);
|
|
|
|
}
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
}
|
2020-03-30 18:52:33 +00:00
|
|
|
_ => (),
|
|
|
|
}
|
2020-08-21 05:37:19 +00:00
|
|
|
};
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
|
2023-07-31 21:41:59 +00:00
|
|
|
trace!("starting winit event loop");
|
Reduce power usage with configurable event loop (#3974)
# Objective
- Reduce power usage for games when not focused.
- Reduce power usage to ~0 when a desktop application is minimized (opt-in).
- Reduce power usage when focused, only updating on a `winit` event, or the user sends a redraw request. (opt-in)
https://user-images.githubusercontent.com/2632925/156904387-ec47d7de-7f06-4c6f-8aaf-1e952c1153a2.mp4
Note resource usage in the Task Manager in the above video.
## Solution
- Added a type `UpdateMode` that allows users to specify how the winit event loop is updated, without exposing winit types.
- Added two fields to `WinitConfig`, both with the `UpdateMode` type. One configures how the application updates when focused, and the other configures how the application behaves when it is not focused. Users can modify this resource manually to set the type of event loop control flow they want.
- For convenience, two functions were added to `WinitConfig`, that provide reasonable presets: `game()` (default) and `desktop_app()`.
- The `game()` preset, which is used by default, is unchanged from current behavior with one exception: when the app is out of focus the app updates at a minimum of 10fps, or every time a winit event is received. This has a huge positive impact on power use and responsiveness on my machine, which will otherwise continue running the app at many hundreds of fps when out of focus or minimized.
- The `desktop_app()` preset is fully reactive, only updating when user input (winit event) is supplied or a `RedrawRequest` event is sent. When the app is out of focus, it only updates on `Window` events - i.e. any winit event that directly interacts with the window. What this means in practice is that the app uses *zero* resources when minimized or not interacted with, but still updates fluidly when the app is out of focus and the user mouses over the application.
- Added a `RedrawRequest` event so users can force an update even if there are no events. This is useful in an application when you want to, say, run an animation even when the user isn't providing input.
- Added an example `low_power` to demonstrate these changes
## Usage
Configuring the event loop:
```rs
use bevy::winit::{WinitConfig};
// ...
.insert_resource(WinitConfig::desktop_app()) // preset
// or
.insert_resource(WinitConfig::game()) // preset
// or
.insert_resource(WinitConfig{ .. }) // manual
```
Requesting a redraw:
```rs
use bevy::window::RequestRedraw;
// ...
fn request_redraw(mut event: EventWriter<RequestRedraw>) {
event.send(RequestRedraw);
}
```
## Other details
- Because we have a single event loop for multiple windows, every time I've mentioned "focused" above, I more precisely mean, "if at least one bevy window is focused".
- Due to a platform bug in winit (https://github.com/rust-windowing/winit/issues/1619), we can't simply use `Window::request_redraw()`. As a workaround, this PR will temporarily set the window mode to `Poll` when a redraw is requested. This is then reset to the user's `WinitConfig` setting on the next frame.
2022-03-07 23:32:05 +00:00
|
|
|
if return_from_run {
|
2020-08-21 05:37:19 +00:00
|
|
|
run_return(&mut event_loop, event_handler);
|
|
|
|
} else {
|
|
|
|
run(event_loop, event_handler);
|
|
|
|
}
|
2020-03-29 07:53:47 +00:00
|
|
|
}
|