Open engiwengi opened 3 years ago
I remember Amethyst had an API for choosing the sleep behavior for frame limiting, precisely because of this issue: some operating systems can sleep your thread for longer than indicated, so you cannot reliably guarantee that you will wake up on time for the next frame.
You could select whether to use sleep
, or to busy-loop and call yield
instead (which was the default). Yielding lets the OS schedule other processes/threads that want to run, but keeps our thread ready for the OS to come back to it immediately. You can also do a hybrid approach: sleep for a shorter period (to save some power/cpu) and then yield in a loop to wait out the remaining time until the next frame.
I don't know if this is the best solution to the problem (it's ugly!), or what other/production games use, but this is what I remember from Amethyst.
Unfortunately I cannot link you to Amethyst docs to show their solution, because their docs and website are broken as of right now, so api docs are not available anywhere.
Usually this problem is fixed by an internal dispatcher provided by a framework and it should be there too (just started here, so has no idea on internals). The dispatcher works in a loop checking for messages from the operating system and running updates, but at the start of each cycle when system event handling is done it verifies user mode timers which are stored in a sorted list as tuples of a time to trigger and a handler. This method doesn't cause high CPU usage and provides more precise scheduling.
Yes, using some sort of timer API to schedule interrupts at specific times definitely sounds like the way to go.
Amethyst's "solution" seems like a hack/workaround for using the wrong API (sleep for duration) in the first place.
Here's a link to a related discussion on Amethyst: https://github.com/amethyst/amethyst/issues/2083
Hi there!
I'm working on the Amethyst issue on the same topic (see https://github.com/bevyengine/bevy/issues/1501#issuecomment-786257435). Although analysis is not yet 100% complete, there is the conclusion is that there is a minimum amount of strategies that provide a different balance of pro/cons (in short: CPU occupation <> precision).
I think this is an issue that a game engine should definitely address. Based on a brief check, Bevy currently uses a simple thread::sleep()
.
Is there any interest from the maintainers in addressing this problem? I can look into it. It needs to be thought though, how to expose this to the developer. For example, would an additional optional field in RunMode::Loop
work? If so, which should be the default? Which should be the semantic? Should it describe the implementation (e.g. Sleep, Yield), or indicate where on the spectrum of precision it resides (Fast, Precise)?
So, I've actually missed YohDeadfall's comment above when posting here (as a consequence, ignore the proposal to implement different strategy).
In the current state, Bevy's logic is (as far as I understand) a standard loop with sleep (RunLoop), which is not different from Amethyst.
The current issue could be mitigated with the canonical timeBeginPeriod
. In the longer term, the described event scheduler is an ideal solutiom, but in the meanwhile, timeBeginPeriod
should be a simple enough intermediate step.
Usually this problem is fixed by an internal dispatcher provided by a framework and it should be there too (just started here, so has no idea on internals). The dispatcher works in a loop checking for messages from the operating system and running updates, but at the start of each cycle when system event handling is done it verifies user mode timers which are stored in a sorted list as tuples of a time to trigger and a handler. This method doesn't cause high CPU usage and provides more precise scheduling.
This is an interesting design. Can you provide some references to implementations (or even just theory)? I wonder what's the technical implementation, that is, how much it is an event listener, and how much a loop in a strict sense.
Bevy version
master
Operating system & version
Windows 10 - this issue may be isolated to Windows?
What you did
Create and run an app with ScheduleRunnerPlugin and set the expected FPS to an arbitrary number.
What you expected to happen
FPS should be almost exactly 20 FPS
What actually happened
FPS hovers between 15 and 16 FPS
Additional information
I swapped only the
std::thread::sleep(delay)
with the spin sleep crate'sspin_sleep::sleep(delay)
and get almost exactly 20 FPS as expected.