-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a failsafe for cascading FixedUpdate
time accumulation
#8544
Conversation
I've tested out an inelegant end user version of this fix in Leafwing-Studios/Emergence#829 and it completely resolves the issue for me in my game. Because it's not networked, I don't care about FixedUpdate falling behind. |
This definitely seems like a necessary fix, but I feel that 3 ticks per frame is very low as a default. I wonder if it would be beneficial/feasible to dynamically compute the maximum number of steps based on the length of the previous frame: that way if we're in the middle of a lag spike we won't compound it by running FixedUpdate a hundred times, but once we've recovered from a lag spike the FixedUpdate schedule would be able to run enough times to let it catch up. |
Yeah, I wanted to keep this simple for now with conservative defaults. For my game I want to have a fixed update period that's substantially less than the frame rate (as it's a relaxed factory builder), but I'm definitely open to other defaults or schemes. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that we can leave the dynamic maximum idea for the future. Although, I think the more conservative approach would be to have a high maximum number of ticks per frame -- a max of 3 could be a footgun. Let's say a user sets their fixed update to run 1000 times per second (potentially due to a fast-forwarded simulation), but their game is only running at 60 FPS due to VSync. Even if their computer is powerful enough to run FixedUpdate that often, bevy is going to throttle it and say it's doing too much work.
I'm not exactly sure what a good default would be, but I feel that something in the range of 20-ish would be less likely to get in the way. The default maximum only needs to be low enough to prevent a spiral of doom IMO, users can tune the settings themselves if they want to iron out all of their frame hiccups.
Otherwise, LGTM.
Bumped to 20; it's easy to tweak in individual games. |
Limiting the number of ticks per frame is a good change. And yeah, it's only effective if the spike is temporary and you're just weathering it by stretching it out over a few frames. The player's machine still needs to be capable of simulating at the requested rate most of the time. It'd be awesome if we can follow this up with either the "have two |
I'd make the limit a |
Agreed! I can tackle that on Monday if you'd like. |
This feel like a surprise bug for people who expect a fixed time update when using the fixed time update. Instead of limiting the number of ticks to an arbitrary 20, which would be a perfectly reasonable number of iterations to hit depending on your use case, shouldn't this look at the time it actually took and compare it to the period? The warn message could also be a lot more explicit on what to do. I think an error code/page with more details would be better, with example code on what to do. It should also mention increasing the maximum number of ticks as a solution which can be valid. As it is now, there's also a chance that it will be displayed very often, which we want to avoid. |
Unity seems to cap the amount of time spent during fixed timesteps, and not the amount of timesteps https://docs.unity3d.com/Manual/class-TimeManager.html |
Unity's "Maximum Allowed Timestep" only caps the amount of time that can accumulate towards new simulation steps each frame. It is not related how long
Ideally, we'd collect statistics about how long one
But we can do that later. A manual setting will work well enough to prevent a temporary hiccup from turning into a death spiral. 20 steps per frame is very conservative. By default, steps only accumulate at a rate of 60 per second. You won't hit the limit without having some real performance hiccups. |
Agreed: I started out with a "max time per frame", but realized that was a pointless level of indirection. I think that slowing down the simulation is substantially more dangerous than load-balancing in most applications.
I'm not opposed to that, but I wanted to keep this PR relatively simple and then iterate.
The existing behavior is very bad: it takes a "slow but playable" game state and turns it into a complete hang, with no indication as to what's going on. The critical fixed time behavior is preserved (the time steps are always exactly the same length), under this behavior the simulation just slows down (equivalent to Unity's behavior, but more robust since we don't start leaking time).
Good call: I'll try to improve this and reduce its spam. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what the policy is on warnings, so this pull may be okay. It's fine otherwise, so I'm tending on the side that this improves things from the current behaviour.
// and needs to run more and more times per frame to catch up. | ||
if let Some(max_ticks) = maybe_max_ticks { | ||
if ticks_this_frame > max_ticks { | ||
warn!("The fixed update schedule is falling behind. Consider increasing the period or decreasing the amount of work done in the fixed update schedule."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the game enters the "death spiral", this warning will print out three times per second, constantly. I'm not sure what the policy on warning spam is, so this may be okay.
Also, if the game freezes for a while for any reason (OS freeze for 5 seconds, memory swapping, big blocking loading operation inside game, whatever), these warnings will be printed out for a while (freeze duration seconds * 3 times) until the situation resolves itself. In this case, saying that update schedule is falling behind
is incorrect, as it is rather catching up to realtime after a freeze.
And if there is a longer freeze, like OS suspend, that lasts for hours or days, then after resuming we'll be stuck doing maximum amount of fixed updates for a very long time as the game catches up to realtime with 20x acceleration but no faster – and the warnings are printed out for the entire duration it is catching up. However, fixing this is out of scope for this pull, so we don't need to care about the behaviour in this case.
Closing in favor of a more thoughtful solution in #8915.
|
Closing in favor of working out a more comprehensive solution in #8915. |
Objective
FixedUpdate
schedules that take too long enter a cascading failure mode #8543.Solution
max_ticks_per_frame
field toFixedTime
. If this is set toSome
, the fixed update schedule will give up after that many runs of the schedule in a single pass of the main schedule.Changelog
The
FixedUpdate
schedule will no longer fall further and further behind when too much work is requested.Instead, time will begin to accumulate and a warning will be emitted.
You can configure this behavior by setting the
max_ticks_per_frame
field of theFixedTime
resource.