Skip to content

RequestRedraw doesn't work as expected #16817

Open
@lomirus

Description

@lomirus

Bevy version

0.15.0

Relevant system information

AdapterInfo { name: "NVIDIA GeForce RTX 4070 SUPER", vendor: 4318, device: 10115, device_type: DiscreteGpu, driver: "NVIDIA", driver_info: "565.90", backend: Vulkan }

What you did

There is a white button. When you press it, it becomes black with a transition in 0.2 s. This is a desktop app, so I need to send RequestRedraw manually. If the transition has not finished, it will call redraw_request_events.send(RequestRedraw), so this system will still be called to redraw next frame.

fn update_background_color(
    mut buttons: Query<(&mut CustomButton, &mut BackgroundColor)>,
    mut redraw_request_events: EventWriter<RequestRedraw>,
) {
    for (mut button, mut background_color) in buttons.iter_mut() {
        if let Some(transition) = &button.transition {
            /// Unit: second
            const TRANSITION_DURATION: f32 = 0.2;
            let t = transition.start_time.elapsed().unwrap().as_secs_f32();

            info!("t = {t} s");
            if t > TRANSITION_DURATION {
                button.transition = None;
                info!("transition finished");
                continue;
            }
            let y = EasingCurve::new(0.0, 1.0, EaseFunction::SineIn)
                .sample(t / TRANSITION_DURATION)
                .unwrap();
            background_color.0 = transition.start.mix(&transition.end, y);
            redraw_request_events.send(RequestRedraw);
        }
    }
}

However, the output is:

2024-12-14T09:16:26.746642Z  INFO bevy_demo: t = 0.0000055 s
2024-12-14T09:16:27.136166Z  INFO bevy_demo: t = 0.3895302 s
2024-12-14T09:16:27.136300Z  INFO bevy_demo: transition finished

It shows that nearly 400ms after 09:16:26.746642, the next redraw is called.

Click me to expand the `main.rs`
use bevy::{prelude::*, window::RequestRedraw, winit::WinitSettings};
use std::time::SystemTime;

const DEFAULT_BUTTON_BACKGROUND_COLOR: Color = Color::WHITE;
const PRESSED_BUTTON_BACKGROUND_COLOR: Color = Color::BLACK;

#[derive(Component)]
#[require(
    Button,
    BackgroundColor(|| BackgroundColor(DEFAULT_BUTTON_BACKGROUND_COLOR)),
)]
pub struct CustomButton {
    transition: Option<Transition>,
}

struct Transition {
    start: Color,
    end: Color,
    start_time: std::time::SystemTime,
}

fn on_interaction(
    mut buttons: Query<(&mut CustomButton, &BackgroundColor, &Interaction), Changed<Interaction>>,
) {
    for (mut button, background_color, interaction) in buttons.iter_mut() {
        match interaction {
            Interaction::Pressed => {
                button.transition = Some(Transition {
                    start: background_color.0,
                    end: PRESSED_BUTTON_BACKGROUND_COLOR,
                    start_time: SystemTime::now(),
                });
            }
            Interaction::None => {
                button.transition = Some(Transition {
                    start: background_color.0,
                    end: DEFAULT_BUTTON_BACKGROUND_COLOR,
                    start_time: SystemTime::now(),
                });
            }
            _ => {}
        }
    }
}

fn update_background_color(
    mut buttons: Query<(&mut CustomButton, &mut BackgroundColor)>,
    mut redraw_request_events: EventWriter<RequestRedraw>,
) {
    for (mut button, mut background_color) in buttons.iter_mut() {
        if let Some(transition) = &button.transition {
            /// Unit: second
            const TRANSITION_DURATION: f32 = 0.2;
            let t = transition.start_time.elapsed().unwrap().as_secs_f32();

            info!("t = {t} s");
            if t > TRANSITION_DURATION {
                button.transition = None;
                info!("transition finished");
                continue;
            }
            let y = EasingCurve::new(0.0, 1.0, EaseFunction::SineIn)
                .sample(t / TRANSITION_DURATION)
                .unwrap();
            background_color.0 = transition.start.mix(&transition.end, y);
            redraw_request_events.send(RequestRedraw);
        }
    }
}

fn setup(mut commands: Commands) {
    commands.spawn(Camera2d);
    commands.spawn((
        CustomButton {
            transition: None,
        },
        Node {
            width: Val::Px(200.0),
            height: Val::Px(100.0),
            ..default()
        },
    ));
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .insert_resource(WinitSettings::desktop_app())
        .add_systems(Startup, setup)
        .add_systems(Update, (on_interaction, update_background_color))
        .run();
}

What went wrong

It should be like this:

2024-12-14T09:16:13.352178Z  INFO bevy_demo: t = 0.0000044 s
2024-12-14T09:16:13.360099Z  INFO bevy_demo: t = 0.0079271 s
2024-12-14T09:16:13.360687Z  INFO bevy_demo: t = 0.0085149 s
2024-12-14T09:16:13.361251Z  INFO bevy_demo: t = 0.0090788 s
// ... 
2024-12-14T09:16:13.540491Z  INFO bevy_demo: t = 0.1883187 s
2024-12-14T09:16:13.544745Z  INFO bevy_demo: t = 0.19257231 s
2024-12-14T09:16:13.550594Z  INFO bevy_demo: t = 0.19842151 s
2024-12-14T09:16:13.556571Z  INFO bevy_demo: t = 0.2043975 s
2024-12-14T09:16:13.556679Z  INFO bevy_demo: transition finished

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-RenderingDrawing game state to the screenC-BugAn unexpected or incorrect behaviorS-Needs-InvestigationThis issue requires detective work to figure out what's going wrong

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions