0
votes

To keep a certain frame rate, i've been using std::thread::sleep() to wait until enough time has passed. Calculating how much it sleeps messes up the code a bit, so I tried making a macro for it. This is it now, but it does not work:

macro_rules! fps30 {
    ($($body: expr;);*) => {
        let time = std::time::Instant::now()

        $($body)*

        let time_elapsed = time.elapsed().as_micros();
        if FRAME_TIME_30FPS_IN_MICROS > time_elapsed {
            let time_to_sleep = FRAME_TIME_30FPS_IN_MICROS - time_elapsed;
            std::thread::sleep(std::time::Duration::from_micros(time_to_sleep as u64));
        }

    };
}

And I want to use it like this:

loop {
    fps30!{
        // do everything I want to do in the loop
    }
}

When I don't implement it as a macro (by pasting the code in the loop directly) it works, and retains a nice 29 frames per second (I guess not 30 because of the small overhead the sleep calculations give). The error it gives during compilation states: no rules expected the token 'p', where p is an object/struct instance I use in the macro.

Any suggestions?

1
The macro shouldn't have any effect on the performance of the system. If you want to ensure you are lining up with the frame rate or a specific fraction of the frame rate, you should check to see if the graphics library you using supports vsync. If it does, it also likely supports using a fraction of the vsync speed (Ex: Sync to every second frame on a 60Hz monitor). If it does, it would help to remove any guesswork/speculation related to when the frame is drawn. - Locke
What's your actual question? Does the macro not work correctly, or are you looking for alternative approaches for some other reason? - user4815162342
@user4815162342 Yes it does not work, sorry forgot to mention... - Pepijn van der Klei
@Locke, it's my own library, just using openGL, but i'll have a look at using vsync, thanks! - Pepijn van der Klei
Please edit the question to indicate that the macro doesn't work, and how you came to that conclusion. E.g. does it not compile, not sleep for the correct amount of time, or something else? Does the same approach work if it's not a macro - are you asking about Rust macros or generally about sleeping in Rust? - user4815162342

1 Answers

3
votes

The problem is with the handling of the ; in:

$($($body: expr;);*)

When you want to accept a ; separated list of expressions you should either write $($($body: expr;)*) or $($($body: expr);*). The former means a list of ;-terminated expressions, while the latter is a list of ;-separated expressions.

The difference is subtle but important. If you add both then you would need to write two ;; to separate every expression.

It would be better if you accepted a list of ; terminated expressions, so do:

$($($body: expr;)*)

Then you have a couple of errors in the body of the macro, also related to ;. You are missing the ; before the expansion of the $body:

let time = std::time::Instant::now();

And you are missing the ; in the expansion of $body itself, because the ; is not part of the captured expr:

$($body;)*

With these changed it works, except if you try:

fps30!{
    if a {
    }
    if a {
    }
}

Because there is no ; after the if expressions!!! You could try switching to:

$($($body: expr);*)

But it will not work either, now because there is no ; between the expressiones!

You could accept a single $body: block but then you will be required to write an additional couple of {}. Not ideal...

If you really want to accept any kind of block of code, I recommend to accept a list of token trees (tt). And when expanding them, enclose them in a {}, in case it does not end with a ;.

macro_rules! fps30 {
    ($($body: tt)*) => {
        let time = std::time::Instant::now();
        { $($body)* }
        let time_elapsed = time.elapsed().as_micros();
        //...
    };
}

Now your macro will accept any kind of syntax, and will expand it dumbly inside macro.

You could even add the possibility of $body having a type and a value, and make fps30 evaluate to that value`:

let res = { $($body)* };
//...
res

As an additional bonus, if you write a syntax error, it will fail when compiling the code, not when expanding the macro, which is much easier to debug.