Since we’ve previously had a Discord discussion about the design of clocks in Amethyst, and currently a relevant GH issue is going stale with no action, I figured I’d make a central topic for discussing the management of Father Time in Amethyst. There’s no single aim for this thread I have in mind, except to start with talking about previous discussion, our current design, and at least some possible improvements, and thereafter we could also discuss usecases for Father Time’s Amethyst API.
Currently, Amethyst handles the passage of time with the (wait for it…)
Time resource, which (via simplification) only tracks the following:
- Frames/time accumulated since application start
- Delta time between frames
- Universal frame delta scaling:
- Rate/delta of the universal fixed update:
- Remainder accumulator for fixed update:
Additionally, there is the
FrameLimiter resource, which contains a frame limiting strategy, the interval to limit the framerate to, and a timestamp to track when the last frame ended and the current one began.
The way this is all managed is with the following generalized pseudocode for what happens in the
Start frame_timer Handle State Transitions Run CallbackQueue Drain all events into active State's event handler Increment Time::fixed_time_accumulator with Time::delta_time While Time::fixed_time_accumulator >= Time::fixed_time Decrement Time::fixed_time_accumulator with Time::fixed_time Call active state's fixed_update method Call active state's update method (which also usually calls the main dispatcher) Maintain World Unless the Unlimited FrameLimiter strategy is chosen, Wait with the FrameLimiter for (hopefully around) the duration of... FrameLimiter::frame_duration - (Instant::now - FrameLimiter::last_call) Update the FrameLimiter::last_call timestamp to Instant::now Increment Time::frame_number Update Time::delta_time with result of frame_timer
Drawbacks with this implementation include:
fixed_updatecannot be parallelized as normal alongside the other, normal updates, except in the case of using a worker thread for calculation outside the ECS, and synchronizing the results briefly within the fixed_update.
- As mentioned in @AndreaCatania’s Github issue above, there is no failsafe against a spiral of death in certain conditions where frame deadlines are missed causing the number of required fixed update cycles to escalate every frame as the accumulator chases its tail.
- There’s only a single, simple, regular timer available for dispatch (the one fixed timer).
The first issue may depend on how a certain elephant’s cards fall. There are possibilities here, including the usage of Batch Systems, but I’ll leave it to subsequent replies to explore these possibilities, although I recommend each possibility (no matter the issue) is accompanied with a use-case and an informed prediction for the commonality of that use-case.
Resolving the second issue is completely dependent on how the application handles its fixed step code, although I could argue we should at least provide a sensible default which may be switched off in place of more complete and solution-specific strategies @jaynus had mentioned such as invalidating & resyncing (“rubber banding”).
The third issue is brought up by @kabergstrom, who brought up the post “Determinism in League of Legends: Unified Clock” by… the people who make League of Legends. The post mentions the issues of dealing with a multiplicity of clocks, each being initialized at different times, some clocks being implemented with floating point numbers, and many clocks relying on completely isolated individual synchronization implementations. While thankfully in Rust we have great integer-based time primitives (
Instant ), it’s up to Amethyst to provide a nice platform for clock management, something that could possibly also integrate into the networking systems for both a common-case implementation and a structure to hoist special-cases.
This post is getting quite long, and I’ve hit the character limit for posts on here before (ahem Legion thread). So I’m going to cut this off here for now. If you come up with timing API use-cases, issues with the current one, or solutions to either of the previous two items, please post them below. Also, if anybody wants to know the call order of State methods now you know where to direct them!