为了保持一定的帧速率,我一直在使用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
是我在宏中使用的对象/结构实例。
有什么建议吗?
问题在于中;
的处理
$($($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
(的列表。当展开它们时,将它们封装在{}
中,以防它没有以;
结束。
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
额外的好处是,如果编写语法错误,它将在编译代码时失败,而不是在扩展宏时失败,这更容易调试。