docs/bevy/examples/window/low_power.rs 8 K · 206 lines · raw · history

1 //! This example illustrates how to run a winit window in a reactive, low power mode.
2 //!
3 //! This is useful for making desktop applications, or any other program that doesn't need to be
4 //! running the event loop non-stop.
5
6 use bevy::{
7 prelude::*,
8 window::{PresentMode, RequestRedraw, WindowPlugin},
9 winit::{EventLoopProxyWrapper, WinitSettings, WinitUserEvent},
10 };
11 use core::time::Duration;
12
13 fn main() {
14 App::new()
15 // Continuous rendering for games - bevy's default.
16 .insert_resource(WinitSettings::game())
17 // Power-saving reactive rendering for applications.
18 .insert_resource(WinitSettings::desktop_app())
19 // You can also customize update behavior with the fields of [`WinitSettings`]
20 .insert_resource(WinitSettings {
21 focused_mode: bevy::winit::UpdateMode::Continuous,
22 unfocused_mode: bevy::winit::UpdateMode::reactive_low_power(Duration::from_millis(10)),
23 })
24 .insert_resource(ExampleMode::Game)
25 .add_plugins(DefaultPlugins.set(WindowPlugin {
26 primary_window: Some(Window {
27 // Turn off vsync to maximize CPU/GPU usage
28 present_mode: PresentMode::AutoNoVsync,
29 ..default()
30 }),
31 ..default()
32 }))
33 .add_systems(Startup, test_setup::setup)
34 .add_systems(
35 Update,
36 (
37 test_setup::cycle_modes,
38 test_setup::rotate_cube,
39 test_setup::update_text,
40 update_winit,
41 ),
42 )
43 .run();
44 }
45
46 #[derive(Resource, Debug)]
47 enum ExampleMode {
48 Game,
49 Application,
50 ApplicationWithRequestRedraw,
51 ApplicationWithWakeUp,
52 }
53
54 /// Update winit based on the current `ExampleMode`
55 fn update_winit(
56 mode: Res<ExampleMode>,
57 mut winit_config: ResMut<WinitSettings>,
58 event_loop_proxy: Res<EventLoopProxyWrapper>,
59 mut redraw_request_writer: MessageWriter<RequestRedraw>,
60 ) {
61 use ExampleMode::*;
62 *winit_config = match *mode {
63 Game => {
64 // In the default `WinitSettings::game()` mode:
65 // * When focused: the event loop runs as fast as possible
66 // * When not focused: the app will update when the window is directly interacted with
67 // (e.g. the mouse hovers over a visible part of the out of focus window), a
68 // [`RequestRedraw`] event is received, or one sixtieth of a second has passed
69 // without the app updating (60 Hz refresh rate max).
70 WinitSettings::game()
71 }
72 Application => {
73 // While in `WinitSettings::desktop_app()` mode:
74 // * When focused: the app will update any time a winit event (e.g. the window is
75 // moved/resized, the mouse moves, a button is pressed, etc.), a [`RequestRedraw`]
76 // event is received, or after 5 seconds if the app has not updated.
77 // * When not focused: the app will update when the window is directly interacted with
78 // (e.g. the mouse hovers over a visible part of the out of focus window), a
79 // [`RequestRedraw`] event is received, or one minute has passed without the app
80 // updating.
81 WinitSettings::desktop_app()
82 }
83 ApplicationWithRequestRedraw => {
84 // Sending a `RequestRedraw` event is useful when you want the app to update the next
85 // frame regardless of any user input. For example, your application might use
86 // `WinitSettings::desktop_app()` to reduce power use, but UI animations need to play even
87 // when there are no inputs, so you send redraw requests while the animation is playing.
88 // Note that in this example the RequestRedraw winit event will make the app run in the same
89 // way as continuous
90 redraw_request_writer.write(RequestRedraw);
91 WinitSettings::desktop_app()
92 }
93 ApplicationWithWakeUp => {
94 // Sending a `WakeUp` event is useful when you want the app to update the next
95 // frame regardless of any user input. This can be used from outside Bevy, see example
96 // `window/custom_user_event.rs` for an example usage from outside.
97 // Note that in this example the `WakeUp` winit event will make the app run in the same
98 // way as continuous
99 let _ = event_loop_proxy.send_event(WinitUserEvent::WakeUp);
100 WinitSettings::desktop_app()
101 }
102 };
103 }
104
105 /// Everything in this module is for setting up and animating the scene, and is not important to the
106 /// demonstrated features.
107 pub(crate) mod test_setup {
108 use crate::ExampleMode;
109 use bevy::{
110 color::palettes::basic::{LIME, YELLOW},
111 prelude::*,
112 window::RequestRedraw,
113 };
114
115 /// Switch between update modes when the spacebar is pressed.
116 pub(crate) fn cycle_modes(
117 mut mode: ResMut<ExampleMode>,
118 button_input: Res<ButtonInput<KeyCode>>,
119 ) {
120 if button_input.just_pressed(KeyCode::Space) {
121 *mode = match *mode {
122 ExampleMode::Game => ExampleMode::Application,
123 ExampleMode::Application => ExampleMode::ApplicationWithRequestRedraw,
124 ExampleMode::ApplicationWithRequestRedraw => ExampleMode::ApplicationWithWakeUp,
125 ExampleMode::ApplicationWithWakeUp => ExampleMode::Game,
126 };
127 }
128 }
129
130 #[derive(Component)]
131 pub(crate) struct Rotator;
132
133 /// Rotate the cube to make it clear when the app is updating
134 pub(crate) fn rotate_cube(
135 time: Res<Time>,
136 mut cube_transform: Query<&mut Transform, With<Rotator>>,
137 ) {
138 for mut transform in &mut cube_transform {
139 transform.rotate_x(time.delta_secs());
140 transform.rotate_local_y(time.delta_secs());
141 }
142 }
143
144 #[derive(Component)]
145 pub struct ModeText;
146
147 pub(crate) fn update_text(
148 mut frame: Local<usize>,
149 mode: Res<ExampleMode>,
150 text: Single<Entity, With<ModeText>>,
151 mut writer: TextUiWriter,
152 ) {
153 *frame += 1;
154 let mode = match *mode {
155 ExampleMode::Game => "game(), continuous, default",
156 ExampleMode::Application => "desktop_app(), reactive",
157 ExampleMode::ApplicationWithRequestRedraw => {
158 "desktop_app(), reactive, RequestRedraw sent"
159 }
160 ExampleMode::ApplicationWithWakeUp => "desktop_app(), reactive, WakeUp sent",
161 };
162 *writer.text(*text, 2) = mode.to_string();
163 *writer.text(*text, 4) = frame.to_string();
164 }
165
166 /// Set up a scene with a cube and some text
167 pub fn setup(
168 mut commands: Commands,
169 mut meshes: ResMut<Assets<Mesh>>,
170 mut materials: ResMut<Assets<StandardMaterial>>,
171 mut request_redraw_writer: MessageWriter<RequestRedraw>,
172 ) {
173 commands.spawn((
174 Mesh3d(meshes.add(Cuboid::new(0.5, 0.5, 0.5))),
175 MeshMaterial3d(materials.add(Color::srgb(0.8, 0.7, 0.6))),
176 Rotator,
177 ));
178
179 commands.spawn((
180 DirectionalLight::default(),
181 Transform::from_xyz(1.0, 1.0, 1.0).looking_at(Vec3::ZERO, Vec3::Y),
182 ));
183 commands.spawn((
184 Camera3d::default(),
185 Transform::from_xyz(-2.0, 2.0, 2.0).looking_at(Vec3::ZERO, Vec3::Y),
186 ));
187 request_redraw_writer.write(RequestRedraw);
188 commands.spawn((
189 Text::default(),
190 Node {
191 align_self: AlignSelf::FlexStart,
192 position_type: PositionType::Absolute,
193 top: px(12),
194 left: px(12),
195 ..default()
196 },
197 ModeText,
198 children![
199 TextSpan::new("Press space bar to cycle modes\n"),
200 (TextSpan::default(), TextColor(LIME.into())),
201 (TextSpan::new("\nFrame: "), TextColor(YELLOW.into())),
202 (TextSpan::new(""), TextColor(YELLOW.into())),
203 ],
204 ));
205 }
206 }