Rust: макрос для сохранения одинаковой частоты кадров не работает

#rust #macros

#Ржавчина #макросы

Вопрос:

Чтобы сохранить определенную частоту кадров, я использовал 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 — экземпляр объекта / структуры, который я использую в макросе.

Есть предложения?

Комментарии:

1. Макрос не должен оказывать никакого влияния на производительность системы. Если вы хотите убедиться, что вы соответствуете частоте кадров или определенной доле частоты кадров, вам следует проверить, поддерживает ли используемая вами графическая библиотека vsync. Если это так, он также, вероятно, поддерживает использование доли скорости vsync (например: синхронизацию с каждым вторым кадром на мониторе с частотой 60 Гц). Если это так, это помогло бы устранить любые догадки / предположения, связанные с тем, когда отрисовывается кадр.

2. В чем ваш актуальный вопрос? Макрос работает некорректно, или вы ищете альтернативные подходы по какой-то другой причине?

3. @user4815162342 Да, это не работает, извините, забыл упомянуть…

4. @Locke, это моя собственная библиотека, просто использующая OpenGL, но я посмотрю на использование vsync, спасибо!

5. Пожалуйста, отредактируйте вопрос, указав, что макрос не работает, и как вы пришли к такому выводу. Например. он не компилируется, не переходит в режим ожидания в течение нужного промежутка времени или что-то еще? Работает ли тот же подход, если это не макрос — вы спрашиваете о макросах Rust или вообще о спящем режиме в Rust?

Ответ №1:

Проблема заключается в обработке ; в:

 $($($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
  

В качестве дополнительного бонуса, если вы напишете синтаксическую ошибку, произойдет сбой при компиляции кода, а не при развертывании макроса, который намного проще отлаживать.