Skip to content

Improve Timer ergonomics using const generics with defaults #4380

Closed
@tim-blackbird

Description

@tim-blackbird

What problem does this solve or what need does it fill?

Wouldn't it be cool if we could set the initial values for duration and repeating on Timer at compile-time?

What solution would you like?

Change the definition of Timer from:

pub struct Timer { ... }

To:

pub struct Timer<const S: f32 = 0, const R: bool = false> { ... }

impl<const S: f32, const R: bool> Default for Timer<S, R> {
    fn default() -> Self {
        // verbose to prevent recursion on`Default::default()`
        Self {
            stopwatch: Default::default(),
            duration: Duration::from_secs_f32(S),
            repeating: R,
            finished: false,
            times_finished: 0
        }
    }
}

So we can do this:

fn local_timer(
    time: Res<Time>,
    mut timer: Local<Timer<13.5>>,
    mut repeating_timer: Local<Timer<5., true>>
) {
    if timer.tick(time.delta()).just_finished() {
        info!("One off timer just finished!");
    }

    if repeating_timer.tick(time.delta()).just_finished() {
        info!("Repeating timer just finished!");
    }
}

or this, a modified version of the timers example (with comments removed):

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .init_resource::<Countdown>()
        .add_system(countdown)
        .run();
}

#[derive(Default)]
pub struct Countdown {
    pub percent_trigger: Timer<4., true>,
    pub main_timer: Timer<20.>,
}

fn countdown(time: Res<Time>, mut countdown: ResMut<Countdown>) {
    countdown.main_timer.tick(time.delta());

    if countdown.percent_trigger.tick(time.delta()).just_finished() {

        if !countdown.main_timer.finished() || countdown.main_timer.just_finished() {
            info!(
                "Timer is {:0.0}% complete!",
                countdown.main_timer.percent() * 100.0
            );
        }
    }
}

Blocked

By rust-lang/rust#95174 as we can only use integral types (e.g: i32, bool) in const generics for now.

We could use

pub struct Timer<const M: u64 = 0, const R: bool = false> { ... }

// and then
duration: Duration::from_millis(M);

But...

// It's just not the same :c
Timer<2_500>

// I mean you could...
Timer<{(2.5 * 1000.) as u64}>
// Oh no

Breakage

This change compiled on all of bevy, and was tested on the existing timers example.

I don't think this breaks anything other than custom impl's for Timer.

P.S

I don't think this is doable yet, but it sure is neat.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-UsabilityA targeted quality-of-life change that makes Bevy easier to useS-BlockedThis cannot move forward until something else changes

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions