Investigate smarter approaches to frame limiting
Summary:
In 0.5.6, we switched from doing std::thread::yield_now at the end of the game loop to doing a 1ms std::thread::sleep. This was to avoid issues some people were having with high CPU usage when the game is minimized or when vsync is turned off.
This appears to be due to the fact that std::thread::yield_now doesn't guarentee the thread will yield - it'll only yield if there's work to do elsewhere. std::thread::sleep, on the other hand, guarentees that we'll yield for some amount of time, putting an effective cap on the max FPS and preventing the game from maxing the CPU core. This is the approach used by Love2D's default game loop, and I've not noticed any ill effects from it there.
That said, I would like to investigate whether there's a smarter approach to this, as sleeping for 1ms in all cases seems like a bit of a clunky solution (e.g. if the game is running slowly, you might want that extra headroom rather than a millisecond being wasted). I'm not entirely sure if it's necessary though.
Why is this needed? Just for my own peace of mind, and to make sure that we're not leaving any performance on the table.
By the looks of it, the high CPU usage when minimized is due to MacOS itself disabling vsync for windows that aren't visible 🤦 It's been reported on SDL itself too, and GLFW has implemented a workaround.
After doing a bit of research, it sounds like the most reliable way to do framerate capping is to use something like spin_sleep, which spends as much time as possible sleeping the thread and then busy loops for the remainder - this allows for much higher precision than relying on the OS scheduler alone.
FNA recently moved to using something like this as well (though the logic is a bit weird due to them inheriting the behaviour from XNA - they only do it for fixed timestep, with no built-in support for interpolated rendering).
Most people should probably still use vsync for frame limiting, but I'll look into adding this as another option (maybe in 0.7, as I'd like to tweak some of the timing APIs).
It's off-topic to ask you a question about English, but do English-speaking people refer to doing a busy loop as "spin"?
Yeah, looping to pause the thread while you wait for something to happen (rather than sleeping) is sometimes referred to as a 'spin loop', 'spin waiting', or just 'spinning'.