low_power/
low_power.rs

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
6use bevy::{
7    prelude::*,
8    window::{PresentMode, RequestRedraw, WindowPlugin},
9    winit::{EventLoopProxyWrapper, WakeUp, WinitSettings},
10};
11use core::time::Duration;
12
13fn 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)]
47enum ExampleMode {
48    Game,
49    Application,
50    ApplicationWithRequestRedraw,
51    ApplicationWithWakeUp,
52}
53
54/// Update winit based on the current `ExampleMode`
55fn update_winit(
56    mode: Res<ExampleMode>,
57    mut winit_config: ResMut<WinitSettings>,
58    event_loop_proxy: Res<EventLoopProxyWrapper<WakeUp>>,
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(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.
107pub(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}