英文:
What is the proper way to have a separate Update Loop?
问题
I'm using C++ with raylib to create a game. I'm running Windows x64 compiling with MinGW in Eclipse.
For the game loop, the normal tutorials give you something like this:
int main()
{
InitWindow(800, 600, "Window Title");
SetWindowState(FLAG_VSYNC_HINT);
while (!WindowShouldClose())
{
Update(); // your function to do game update logic
BeginDrawing();
ClearBackground((Color){0, 0, 0, 255});
Draw(); // your function to do game drawing
EndDrawing();
}
}
However, this is bad practice. So it sets the VSYNC flag to make sure the drawing only happens at the same rate as the monitor refresh rate. That's fine but if you insert your update logic into the same loop, then your update rate is locked to the draw rate.
This is foolish because different users have different refresh rates. Mines uses 60 Hz, in Europe they use 50 Hz, and lots of people use 120, 144, 240, or any custom refresh rate they want.
So that means the game will run at very different speeds. Not what I want.
What I want instead is the update logic to always be 60 FPS (actually 16 ms intervals so it's 62.5 FPS), while the draw logic runs on the VSYNC loop.
So here's what I tried next:
std::thread ULT(scr_run_updateloop); // spawn a new thread that calls scr_run_updateloop() immediately
while (!WindowShouldClose())
{
// no more Update() call in the draw loop!
BeginDrawing();
ClearBackground((Color){0, 0, 0, 255});
Draw(); // your function to do game drawing
EndDrawing();
}
and the function called by the thread...
void scr_run_updateloop()
{
auto time_current = chrono::steady_clock::now();
auto time_last = time_current;
uint64_t time_d;
while (true)
{
time_current = chrono::steady_clock::now();
time_d = duration_cast<milliseconds>(time_current - time_last).count();
if (time_d >= 16) // 16 ms interval is 62.5 FPS
{
Update();
time_last = time_current;
}
}
}
This code works. However, that thread is hogging CPU like crazy. My task manager says 25% and I'm on a 4-core system.
What can I do? I've read about sleep and thread::sleep_for but the problem is the sleep time is not guaranteed and can be off by many milliseconds because the OS could be busy with whatever.
Note: I don't need the thread to sync with the main thread at all. The Update loop can be completely separate from the Draw Loop, AFAIK they will never need to share a locked resource.
I've also read about a completely alternate approach where your time between Updates is NOT fixed, but uses deltaTime. Then you interpolate positions, speeds and other stuff to make sure things happen at the same overall speed. That seems extraordinarily overcomplicated and unnecessary. If you can VSYNC to a draw refresh rate than I don't see why you can't set up a custom loop to call once every fixed interval. There must be a way to do this, but how?
英文:
I'm using C++ with raylib to create a game. I'm running Windows x64 compiling with MinGW in Eclipse.
For the game loop, the normal tutorials give you something like this:
int main()
{
InitWindow(800, 600, ("Window Title").c_str());
SetWindowState(FLAG_VSYNC_HINT);
while (!WindowShouldClose())
{
Update(); // your function to do game update logic
BeginDrawing();
ClearBackground((Color){0, 0, 0, 255});
Draw(); // your function to do game drawing
EndDrawing();
}
}
However, this is bad practice. So it sets the VSYNC flag to make sure the drawing only happens at the same rate as the monitor refresh rate. That's fine but if you insert your update logic into the same loop, then your update rate is locked to the draw rate.
This is foolish because different users have different refresh rates. Mines uses 60 Hz, in Europe they use 50 Hz, and lots of people use 120, 144, 240, or any custom refresh rate they want.
So that means the game will run at very different speeds. Not what I want.
What I want instead is the update logic to always be 60 FPS (actually 16 ms intervals so it's 62.5 FPS), while the draw logic runs on the VSYNC loop.
So here's what I tried next:
std::thread ULT(scr_run_updateloop); // spawn a new thread that calls scr_run_updateloop() immediately
while (!WindowShouldClose())
{
// no more Update() call in the draw loop!
BeginDrawing();
ClearBackground((Color){0, 0, 0, 255});
Draw(); // your function to do game drawing
EndDrawing();
}
and the function called by the thread...
void scr_run_updateloop()
{
auto time_current = chrono::steady_clock::now();
auto time_last = time_current;
uint64_t time_d;
while (true)
{
time_current = chrono::steady_clock::now();
time_d = duration_cast<milliseconds>(time_current - time_last).count();
if (time_d >= 16) // 16 ms interval is 62.5 FPS
{
Update();
time_last = time_current;
}
}
}
This code works. However, that thread is hogging CPU like crazy. My task manager says 25% and I'm on a 4-core system.
What can I do? I've read about sleep and thread::sleep_for but the problem is the sleep time is not guaranteed and can be off by many milliseconds because the OS could be busy with whatever.
Note: I don't need the thread to sync with the main thread at all. The Update loop can be completely separate from the Draw Loop, AFAIK they will never need to share a locked resource.
I've also read about a completely alternate approach where your time between Updates is NOT fixed, but uses deltaTime. Then you interpolate positions, speeds and other stuff to make sure things happen at the same overall speed. That seems extraordinarily overcomplicated and unnecessary. If you can VSYNC to a draw refresh rate than I don't see why you can't set up a custom loop to call once every fixed interval. There must be a way to do this, but how?
答案1
得分: 2
您的 CPU 负载较高是因为您的循环在等待足够的时间以满足调用 Update
的条件时会非常快速地旋转。为了降低 CPU 使用率,您需要让线程休眠,即在等待时什么也不做。
但是,在您的循环中没有必要插入调用特殊休眠函数的代码,因为您已经有一个允许您休眠的工具:您的 Draw()
函数!启用垂直同步(Vsync)后,此函数会自然地休眠,直到显示器准备好显示下一帧。
现在您需要注意的唯一事项是确保您的更新函数考虑到了这段休眠时间。所以,您不再需要在每个循环中更新一次,而是想要检查自上次更新以来经过了多长时间,然后相应地更新您的状态。
auto time_current = std::chrono::steady_clock::now();
auto time_last = time_current;
auto const update_interval = std::chrono::milliseconds{16};
while (true)
{
time_current = std::chrono::steady_clock::now();
auto time_d = time_current - time_last;
while (time_d >= update_interval) // 每个经过的时间间隔执行一次更新
{
Update();
time_d -= update_interval;
time_last += update_interval;
}
Draw(); // 这就是休眠发生的地方
}
英文:
Your CPU load is high because your loop will be spinning really fast while waiting for enough time to pass to meet the condition for calling Update
. To bring the CPU usage down, instead of having your thread spin, you want it to sleep, that is, to do nothing while it waits.
But there's no need to insert a call to special sleep function into your loop, as you already have a facility that allows you to sleep: Your Draw()
function! With Vsync enabled, this function will naturally sleep until the monitor is ready to display the next frame.
The only thing you need to take care of now is make sure that your update function takes this sleep time into account. So instead of updating once every loop, you will want to inspect how much time has passed since the last update and then update your state accordingly.
auto time_current = std::chrono::steady_clock::now();
auto time_last = time_current;
auto const update_interval = std::chrono::milliseconds{16};
while (true)
{
time_current = std::chrono::steady_clock::now();
auto time_d = time_current - time_last;
while (time_d >= update_interval) // perform one update for every interval passed
{
Update();
time_d -= update_interval;
time_last += update_interval;
}
Draw(); // this is where the sleep happens
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论