infinito - ¿Cómo limitar FPS en un bucle con C++?
ciclo for (4)
Estoy tratando de limitar los cuadros por segundo en un bucle que está realizando la comprobación de intersecciones, usando C ++ con crono y hilo.
Aquí está mi código:
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::chrono::system_clock::time_point lastFrame = std::chrono::system_clock::now();
while (true)
{
// Maintain designated frequency of 5 Hz (200 ms per frame)
now = std::chrono::system_clock::now();
std::chrono::duration<double, std::milli> delta = now - lastFrame;
lastFrame = now;
if (delta.count() < 200.0)
{
std::chrono::duration<double, std::milli> delta_ms(200.0 - delta.count());
auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms);
std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count()));
}
printf("Time: %f /n", delta.count());
// Perform intersection test
}
El problema que tengo es que cada otra salida de delta muestra cantidades minúsculas, en lugar de los ~ 200 ms / fotograma a los que apunto:
Time: 199.253200
Time: 2.067700
Time: 199.420400
Time: 2.408100
Time: 199.494200
Time: 2.306200
Time: 199.586800
Time: 2.253400
Time: 199.864000
Time: 2.156500
Time: 199.293800
Time: 2.075500
Time: 201.787500
Time: 4.426600
Time: 197.304100
Time: 4.530500
Time: 198.457200
Time: 3.482000
Time: 198.365300
Time: 3.415400
Time: 198.467400
Time: 3.595000
Time: 199.730100
Time: 3.373400
¿Alguna idea de por qué esto está sucediendo?
Esto se parece mucho a la respuesta de Galik , pero mantiene la sintaxis de la pregunta del OP y no se reduce a la API de C. Además, crea una unidad personalizada para la duración del cuadro que creo que es importante para la legibilidad:
#include <chrono>
#include <cstdint>
#include <iostream>
#include <thread>
int
main()
{
using namespace std;
using namespace std::chrono;
using frames = duration<int64_t, ratio<1, 5>>; // 5Hz
auto nextFrame = system_clock::now();
auto lastFrame = nextFrame - frames{1};;
while (true)
{
// Perform intersection test
this_thread::sleep_until(nextFrame);
cout << "Time: " // just for monitoring purposes
<< duration_cast<milliseconds>(system_clock::now() - lastFrame).count()
<< "ms/n";
lastFrame = nextFrame;
nextFrame += frames{1};
}
}
Esto me da resultados:
Time: 200ms
Time: 205ms
Time: 205ms
Time: 203ms
Time: 205ms
Time: 205ms
Time: 200ms
Time: 200ms
Time: 200ms
...
Cosas clave a tener en cuenta:
- Una forma concisa de documentar 5Hz:
using frames = duration<int64_t, ratio<1, 5>>;
- Uso de
sleep_until
lugar desleep_for
, que se ocupa de lo desconocido de cuánto tiempo se tarda en realizar el trabajo real. - No se usa
.count()
excepto para E / S, y aquí hay una biblioteca para deshacerse de eso . - Sin conversión manual de unidades (ej.
/ 1000
). - No hay unidades de punto flotante, no hay nada malo en eso.
- Mínima necesidad de especificar o depender de unidades explícitas.
Con la adición de la biblioteca de E / S de duración , aquí se muestra cómo se cambiaría el código anterior:
#include "chrono_io.h"
#include <chrono>
#include <cstdint>
#include <iostream>
#include <thread>
int
main()
{
using namespace date;
using namespace std;
using namespace std::chrono;
using frames = duration<int64_t, ratio<1, 5>>; // 5Hz
auto nextFrame = system_clock::now();
auto lastFrame = nextFrame - frames{1};;
while (true)
{
// Perform intersection test
this_thread::sleep_until(nextFrame);
// just for monitoring purposes
cout << "Time: " << system_clock::now() - lastFrame << ''/n'';
lastFrame = nextFrame;
nextFrame += frames{1};
}
}
La salida diferiría dependiendo de la plataforma (dependiendo de la "duración nativa" de system_clock
). En mi plataforma se ve así:
Time: 200042µs
Time: 205105µs
Time: 205107µs
Time: 200044µs
Time: 205105µs
Time: 200120µs
Time: 204307µs
Time: 205136µs
Time: 201978µs
...
Los tiempos delta alternos surgen de un problema lógico: está agregando un retardo a un cuadro en función de la duración del cuadro anterior (en términos de cómo se calculan las duraciones del cuadro). Esto significa que después de un fotograma largo (~ 200 ms) no se aplica un retraso y se obtiene un fotograma corto (algunos ms), lo que a su vez genera un retraso en el siguiente fotograma, dando un fotograma largo, etc.
Normalmente hago algo como esto:
#include <chrono>
#include <iostream>
int main()
{
using clock = std::chrono::steady_clock;
auto next_frame = clock::now();
while(true)
{
next_frame += std::chrono::milliseconds(1000 / 5); // 5Hz
// do stuff
std::cout << std::time(0) << ''/n''; // 5 for each second
// wait for end of frame
std::this_thread::sleep_until(next_frame);
}
}
Salida: (cinco por cada segundo valor)
1470173964
1470173964
1470173964
1470173964
1470173964
1470173965
1470173965
1470173965
1470173965
1470173965
1470173966
1470173966
1470173966
1470173966
1470173966
Si piensa en cómo funciona su código, descubrirá que funciona exactamente como lo escribió. Delta oscila debido a un error lógico en el código.
Esto es lo que pasa:
- Comenzamos con
delta == 0
. - Debido a que el delta es más pequeño que
200
, el código duerme200 - delta(0) == 200
ms. - Ahora, el delta en sí se acerca a
200
(porque ha medido el tiempo de sueño así como un trabajo real) y duerme200 - delta(200) == 0
ms. - Después de eso se repite el ciclo.
Para solucionar el problema necesitas no medir el tiempo de sueño.
Así es como se puede hacer:
#include <iostream>
#include <cstdio>
#include <chrono>
#include <thread>
std::chrono::system_clock::time_point a = std::chrono::system_clock::now();
std::chrono::system_clock::time_point b = std::chrono::system_clock::now();
int main()
{
while (true)
{
// Maintain designated frequency of 5 Hz (200 ms per frame)
a = std::chrono::system_clock::now();
std::chrono::duration<double, std::milli> work_time = a - b;
if (work_time.count() < 200.0)
{
std::chrono::duration<double, std::milli> delta_ms(200.0 - work_time.count());
auto delta_ms_duration = std::chrono::duration_cast<std::chrono::milliseconds>(delta_ms);
std::this_thread::sleep_for(std::chrono::milliseconds(delta_ms_duration.count()));
}
b = std::chrono::system_clock::now();
std::chrono::duration<double, std::milli> sleep_time = b - a;
// Your code here
printf("Time: %f /n", (work_time + sleep_time).count());
}
}
Este código me da una secuencia constante de deltas:
Time: 199.057206
Time: 199.053581
Time: 199.064718
Time: 199.053515
Time: 199.053307
Time: 199.053415
Time: 199.053164
Time: 199.053511
Time: 199.053280
Time: 199.053283