Skip to content

Commit

Permalink
Add fixed clock deltatime so we can close #1543 (#2421)
Browse files Browse the repository at this point in the history
* Add max deltatime to `arcade.Clock`

* clock doc string and unit test

* reseting global clock state between unit tests
  • Loading branch information
DragonMoffon authored Oct 17, 2024
1 parent e442b96 commit 8f5068d
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 0 deletions.
25 changes: 25 additions & 0 deletions arcade/clock.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

__all__ = (
"Clock",
"FixedClock",
Expand Down Expand Up @@ -35,13 +37,18 @@ def __init__(
self._tick_delta_time: float = 0.0
self._tick_speed: float = tick_speed

self._max_deltatime: float | None = None

def tick(self, delta_time: float):
"""
Update the clock with the time that has passed since the last tick.
Args:
delta_time: The amount of time that has passed since the last tick.
"""
if self._max_deltatime is not None:
delta_time = min(self._max_deltatime, delta_time)

self._tick_delta_time = delta_time * self._tick_speed
self._elapsed_time += self._tick_delta_time
self._tick += 1
Expand All @@ -56,6 +63,20 @@ def set_tick_speed(self, new_tick_speed: float):
"""
self._tick_speed = new_tick_speed

def set_max_deltatime(self, max_deltatime: float | None = None):
"""
Set the maximum deltatime that the clock will allow. If a large dt is passed into
the clock's tick method it will be clamped. This will desync the game's time with the
real world elapsed time, but can help protect against lag-spikes, debugger pauses, and
other pauses to the event loop. This impacts the 'raw' dt so it does not take the clock's
tick speed into account
Args:
max_deltatime: The maximum number of seconds that a clock can have as it's deltatime.
If set to None the clock has no limit. Defaults to None.
"""
self._max_deltatime = max_deltatime

def time_since(self, time: float) -> float:
"""
Calculate the amount of time that has passed since the given time.
Expand All @@ -74,6 +95,10 @@ def ticks_since(self, tick: int) -> int:
"""
return self._tick - tick

@property
def max_deltatime(self) -> float | None:
return self._max_deltatime

@property
def time(self) -> float:
"""The total number of seconds that have elapsed for this clock"""
Expand Down
8 changes: 8 additions & 0 deletions tests/integration/examples/test_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import arcade
import pytest

import arcade.clock

# File path, module path
EXAMPLE_LOCATIONS = [
(
Expand Down Expand Up @@ -83,6 +85,12 @@ def test_examples(window_proxy, module_path, file_path, allow_stdout):
# Manually load the module as __main__ so it runs on import
loader = SourceFileLoader("__main__", str(file_path))
loader.exec_module(loader.load_module())

# Reset the global clock's tick speed
# is this a good argument against a global scope clock?
# yes.
arcade.clock.GLOBAL_CLOCK.set_tick_speed(1.0)


if not allow_stdout:
output = stdout.getvalue()
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/test_clock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import pytest as pytest

from arcade.clock import Clock, FixedClock, GLOBAL_CLOCK, GLOBAL_FIXED_CLOCK

def test_clock():
# GLOBAL_CLOCK.set_tick_speed(1.0)
time = GLOBAL_CLOCK.time
ticks = GLOBAL_CLOCK.ticks
GLOBAL_CLOCK.tick(1.0/60.0)
assert GLOBAL_CLOCK.time == time + 1.0/60.0
assert GLOBAL_CLOCK.ticks == ticks + 1
assert GLOBAL_CLOCK.delta_time == 1.0/60.0

GLOBAL_CLOCK.set_max_deltatime(1.0/100.0)
GLOBAL_CLOCK.tick(1.0 / 60.0)
assert GLOBAL_CLOCK.time == time + 1.0/60.0 + 1.0 / 100.0
assert GLOBAL_CLOCK.ticks == ticks + 2
assert GLOBAL_CLOCK.delta_time == 1.0/100.0

GLOBAL_CLOCK.set_max_deltatime()

with pytest.raises(ValueError):
GLOBAL_FIXED_CLOCK.tick(1.0)

0 comments on commit 8f5068d

Please sign in to comment.