首页 > 解决方案 > Rust:保持相同帧速率的宏不起作用

问题描述

为了保持一定的帧速率,我一直在std::thread::sleep()等待足够的时间过去。计算它的睡眠时间会使代码有点混乱,所以我尝试为它制作一个宏。现在就是这样,但它不起作用:

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));
        }

    };
}

我想像这样使用它:

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

当我不将它实现为宏时(通过直接将代码粘贴到循环中),它可以工作,并且每秒保持 29 帧(我猜不是 30 帧,因为睡眠计算的开销很小)。它在编译期间给出的错误状态是:no rules expected the token 'p'p我在宏中使用的对象/结构实例在哪里。

有什么建议么?

标签: rustmacros

解决方案


问题在于对;in 的处理:

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

当你想接受一个;单独的表达式列表时,你应该写$($($body: expr;)*)$($($body: expr);*)。前者表示以 - 结尾的;表达式列表,而后者是 -;分隔表达式的列表。

差异是微妙但重要的。如果同时添加两者,则需要编写两个;;来分隔每个表达式。

如果您接受;终止的表达式列表会更好,所以这样做:

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

然后你在宏的主体中有几个错误,也与;. 你错过了;扩展之前的$body

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

而且您;在自身的扩展中遗漏了$body,因为;不是被捕获的一部分expr

$($body;)*

通过这些更改,它可以工作,除非您尝试:

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

因为没有;后面的if表达式!!!您可以尝试切换到:

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

但这也行不通,现在因为;表达式之间没有!

您可以接受一个$body: block,但随后您将需要编写额外的几个{}. 不理想...

如果您真的想接受任何类型的代码块,我建议您接受令牌树列表( tt)。并且在扩展它们时,将它们括在 a{}中,以防它不以 a 结尾;

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

现在您的宏将接受任何类型的语法,并将在宏内部无声地扩展它。

您甚至可以添加$body具有类型和值的可能性,并对该值进行fps30评估:

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

作为一个额外的好处,如果你写了一个语法错误,它会在编译代码时失败,而不是在扩展宏时,这更容易调试。


推荐阅读