Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ControlFlow::Wait suspends app even if redraw is requested. #1619

Open
zakarumych opened this issue Jul 9, 2020 · 18 comments
Open

ControlFlow::Wait suspends app even if redraw is requested. #1619

zakarumych opened this issue Jul 9, 2020 · 18 comments
Labels
C - needs investigation Issue must be confirmed and researched DS - windows

Comments

@zakarumych
Copy link
Contributor

I set flow to ControlFlow::Wait in order to not waste CPU on doing nothing.
When application calls Window::request_redraw() I would expect RedrawRequest to arrive right away (after MainRequestCleared if there are queued events). Yet no ReadrawRequest is received until other events happen. Particularly if mouse and keyboard are untouched app would wait for ReadrawRequest indefinitely.

I tried to call Window::request_redraw() at different moments. Nothing helps. Then I found that using ControlFlow::Poll causes RedrawRequest to be received as expected, without any input.

Is it expected behavior? I couldn't find anything like this in the docs for ControlFlow.

Platform: Windows 10.

@zakarumych zakarumych changed the title ControlFlow::Wait suspends event if redraw is requested. ControlFlow::Wait suspends app even if redraw is requested. Jul 9, 2020
@Osspial
Copy link
Contributor

Osspial commented Jul 15, 2020

Could you provide some example code and relevant console logs? I’m unable to reproduce the issue you’re describing with this code:

    event::{ElementState, Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop},
    window::WindowBuilder,
};

fn main() {
    simple_logger::init().unwrap();
    let event_loop = EventLoop::new();

    let window = WindowBuilder::new()
        .with_title("A fantastic window!")
        .build(&event_loop)
        .unwrap();

    event_loop.run(move |event, _, control_flow| {
        println!("{:?}", event);

        *control_flow = ControlFlow::Wait;

        match event {
            Event::WindowEvent { event, .. } => match event {
                WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
                _ => (),
            },
            Event::MainEventsCleared => {
                window.request_redraw();
            }
            Event::RedrawRequested(_) => {
                println!("\nredrawing!\n");
            }
            _ => (),
        }
    });
}

@Osspial Osspial added DS - windows C - needs investigation Issue must be confirmed and researched labels Jul 15, 2020
@zakarumych
Copy link
Contributor Author

I copied your snippet into crate (with little modifications) and it waits for events.
https://github.com/zakarumych/winit-wait-bug

What additional info I can provide to you?

@zakarumych
Copy link
Contributor Author

zakarumych commented Jul 16, 2020

A bit of debugging revealed this.

After some events thread_event_target_callback handles WM_PAINT, which is followed by flush_paint_messages to peek WM_PAINT message and dispatch it, causing public_window_callback to receive WM_PAINT and generate RedrawRequested event.

After that, if no input occur, windows::event_loop::thread_event_target_callback receives WM_PAINT and that's it.

Logs look like this:

2020-07-16 14:03:05,781 INFO  [winit_wait] DeviceEvent { device_id: DeviceId(DeviceId(65596)), event: Motion { axis: 0, value: -1.0 } }
2020-07-16 14:03:05,782 INFO  [winit_wait] DeviceEvent { device_id: DeviceId(DeviceId(65596)), event: MouseMotion { delta: (-1.0, 0.0) } }
2020-07-16 14:03:05,782 INFO  [winit_wait] DeviceEvent { device_id: DeviceId(DeviceId(65596)), event: Motion { axis: 0, value: -1.0 } }
2020-07-16 14:03:05,782 INFO  [winit_wait] DeviceEvent { device_id: DeviceId(DeviceId(65596)), event: MouseMotion { delta: (-1.0, 0.0) } }
2020-07-16 14:03:05,782 ERROR [winit::platform_impl::platform::event_loop] Thread WM_PAINT received
2020-07-16 14:03:05,783 INFO  [winit_wait] MainEventsCleared
2020-07-16 14:03:05,783 ERROR [winit::platform_impl::platform::event_loop] Peeked WM_PAINT message
2020-07-16 14:03:05,783 ERROR [winit::platform_impl::platform::event_loop] Window WM_PAINT received
2020-07-16 14:03:05,783 INFO  [winit_wait] RedrawRequested(WindowId(WindowId(0x280c68)))
2020-07-16 14:03:05,783 INFO  [winit_wait] redrawing!
2020-07-16 14:03:05,783 INFO  [winit_wait] RedrawEventsCleared
2020-07-16 14:03:05,784 ERROR [winit::platform_impl::platform::event_loop] Thread WM_PAINT received

@parasyte
Copy link

Seeing the same problem, but request_redraw is called from within RedrawEventsCleared. Trying to conditionally spin the event loop with ControlFlow::Wait.

@parasyte
Copy link

Also this looks similar to #987

@alvinhochun
Copy link
Contributor

I copied your snippet into crate (with little modifications) and it waits for events.
https://github.com/zakarumych/winit-wait-bug

What additional info I can provide to you?

I tried this code with winit master, but it doesn't reproduce the issue. I can see in the output that RedrawRequested and redrawing! does appear after every MainEventsCleared. Do you need to do anything special to trigger the issue?

@zakarumych
Copy link
Contributor Author

zakarumych commented Sep 25, 2020

RedrawRequested and redrawing! does appear after every MainEventsCleared

But new sets of those

2020-09-25 09:42:10,694 INFO  [winit_wait] MainEventsCleared
2020-09-25 09:42:10,694 INFO  [winit_wait] RedrawRequested(WindowId(X(WindowId(85983233))))
2020-09-25 09:42:10,694 INFO  [winit_wait] redrawing!
2020-09-25 09:42:10,694 INFO  [winit_wait] RedrawEventsCleared

appear only when I move a mouse, press a key or some other even occurs

2020-09-25 09:42:10,693 INFO  [winit_wait] DeviceEvent { device_id: DeviceId(X(DeviceId(10))), event: Button { button: 1, state: Released } }

Just tried same code on Arch and it's still reproducible (originally I tried it on Windows).

@alvinhochun
Copy link
Contributor

But new sets of those [...] appear only when I move a mouse, press a key or some other even occurs

Is this not the expected behaviour? For each sets of events, the event handler is called with events in this order:

  1. NewEvents(start_cause)
  2. DeviceEvent { .. }, ..
  3. UserEvent(_), ..
  4. WindowEvent { .. }, ..
  5. MainEventsCleared
  6. RedrawRequested(_), ..
  7. RedrawEventsCleared

Since you call request_redraw in MainEventsCleared (step 5), the redraw is queued and then immediately emitted afterwards (step 6). ControlFlow::Wait will then cause winit to wait for new events before going through the about steps again.

It sounds like you're expecting request_redraw will trigger a new sets of events and that the RedrawRequested event will cross over the next NewEvents. I don't see why this should be the case. Is it mentioned anywhere in the docs that I might have missed?

@zakarumych
Copy link
Contributor Author

Yes, I guess it can be an expected behavior if request_redraw is called in MainEventsCleared.
But it is the same if request_redraw is called in RedrawRequested or RedrawEventsCleared, i.e. after last redraw_request is triggered and new one is requested but it still waits for something else to happen.

I don't know if this was mentioned anywhere in the docs, maybe I should do something else to achieve desired behavior - similar to invalidating window after redrawing it on Windows or calling window.requestAnimationFrame(callback) in the callback on web platform.

@filnet
Copy link
Contributor

filnet commented Sep 27, 2020 via email

@alvinhochun
Copy link
Contributor

Yes, I guess it can be an expected behavior if request_redraw is called in MainEventsCleared.
But it is the same if request_redraw is called in RedrawRequested or RedrawEventsCleared, i.e. after last redraw_request is triggered and new one is requested but it still waits for something else to happen.

At least we can now agree that the current behaviour of request_redraw in MainEventsCleared is expected. There are two reasons I specifically referred to MainEventsCleared in my comments:

  1. It is what the original report and the example codes focused on.
  2. I do not have a clear answer to how winit should behave if request_redraw is called in RedrawRequested or RedrawEventsCleared. I know for sure that the web backend does not properly handle request_redraw being called anywhere after MainEventsCleared and before NewEvents but I do not know what the correct behaviour should be.

@kchibisov Do you have any input on this issue?

@kchibisov
Copy link
Member

@kchibisov Do you have any input on this issue?

Not really, other than winit should become more consistent here when it should send new redraw requested.

I can also say that when event loop starts processing events on Wayland, if you ask for redraw events cleared, it'll arrive on the 'next' even loop processing tick, which should differ from all platforms in winit, I guess.

In general, if you want to consult how redraw expected should be used there's an extensive discussion wrt its design #1041 . If the current docs are not clear when to call and what to expect, they should be updated, since the issue I've linked mentions redraw_requested.

@alvinhochun
Copy link
Contributor

I can also say that when event loop starts processing events on Wayland, if you ask for redraw events cleared, it'll arrive on the 'next' even loop processing tick, which should differ from all platforms in winit, I guess.

Even on or before MainEventsCleared? Is it due to practical limitations on Wayland, or is it just not implemented in the "usual" way yet?

In general, if you want to consult how redraw expected should be used there's an extensive discussion wrt its design #1041 . If the current docs are not clear when to call and what to expect, they should be updated, since the issue I've linked mentions redraw_requested.

Thanks, I'll read that issue when I have the time.

@filnet
Copy link
Contributor

filnet commented Sep 27, 2020

We might be in XY problem situation :)

Anyways..

One way to trigger/force a new event loop iteration in ControlFlow::Wait mode is to switch to ControlFlow::Poll mode (instead of calling request_redraw()) and then, in the next iteration, switch back to ControlFlow::Wait if needed.

PS: you can change the control flow mode with *control_flow = ControlFlow::Poll;.

@kchibisov
Copy link
Member

Even on or before MainEventsCleared? Is it due to practical limitations on Wayland, or is it just not implemented in the "usual" way yet?

no, that's how it is implemented, could be done the other way around. But winit lacks precise docs on what should be done and when, and I hate hunting random issues to get answers. But in my understand of how asking for a frame works, is that you call is right before you do drawing yourself, so calling it not from redraw requested event doesn't make any sense, and so in that case it'll properly enqueue event into event loop and make you process it on the next loop tick.

@zakarumych
Copy link
Contributor Author

zakarumych commented Sep 27, 2020

One way to trigger/force a new event loop iteration in ControlFlow::Wait mode is to switch to ControlFlow::Poll mode (instead of calling request_redraw()) and then, in the next iteration, switch back to ControlFlow::Wait if needed.

Wouldn't ControlFlow::Poll cause multiple empty event iterations (NewEvents, MainEventsCleared, RedrawEventsCleared) without RedrawRequested events on some platforms?
My goal is not trigger next event loop iteration, but receive RedrawRequested when platform is ready for redrawing.

@QuantumBadger
Copy link

QuantumBadger commented Mar 4, 2021

I've just been caught out by this, and found this ticket after spending a while debugging. The consensus above seems to be that this behaviour is expected, however to me personally it seems quite surprising that a pending Event::RedrawRequested event doesn't wake up the event loop, and may only fire e.g. when the user next moves the mouse.

The behaviour also seems to differ on Windows and Linux -- on Linux it wakes up, on Windows it doesn't.

In any case, the workaround from @filnet works and was easy enough to implement: QuantumBadger/Speedy2D@d0b8812

@aevyrie
Copy link

aevyrie commented Feb 18, 2022

I ran into this today, also expecting request_redraw() to trigger another winit loop when using Wait.

Another workaround is to create a custom event, and send a custom event instead of using the redraw method.

bors bot pushed a commit to bevyengine/bevy that referenced this issue Mar 7, 2022
# 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 (rust-windowing/winit#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.
aevyrie added a commit to aevyrie/bevy that referenced this issue Jun 7, 2022
# 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 (rust-windowing/winit#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.
ItsDoot pushed a commit to ItsDoot/bevy that referenced this issue Feb 1, 2023
# 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 (rust-windowing/winit#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.
Subserial pushed a commit to Subserial/bevy_winit_hook that referenced this issue Jan 24, 2024
# 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 (rust-windowing/winit#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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
C - needs investigation Issue must be confirmed and researched DS - windows
Development

No branches or pull requests

8 participants