diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9b843ea4..537afea2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,6 +83,12 @@ Start working, try to solve the problem. If you get stuck, ask questions in your open PR. The maintainers and active contributors are here to help. +Make sure the PPB tests pass by installing the test dependencies and +running the full test suite. + + pip install -r requirements-tests.txt + pytest + Once you think it's ready, time to remove "WIP" from the title or hit the "Ready for review" button. Now someone senior in the project will review. They'll either ask for changes or approve your PR. If you need diff --git a/examples/frame_count/framecount.py b/examples/frame_count/framecount.py index 9560b6fa..6abd6804 100644 --- a/examples/frame_count/framecount.py +++ b/examples/frame_count/framecount.py @@ -22,13 +22,13 @@ def __init__(self, *p, **kw): super().__init__(*p, **kw) self.frames = 0 - self.start_time = time.monotonic() + self.start_time = ppb.get_time() def on_update(self, event, signal): """ Fires at the update rate (~60 times a second) """ - t = time.monotonic() - self.start_time + t = ppb.get_time() - self.start_time if t >= self.duration: signal(ppb.events.Quit()) @@ -38,7 +38,7 @@ def on_pre_render(self, event, signal): The frame rate is variable and different from the update rate. """ - t = time.monotonic() - self.start_time + t = ppb.get_time() - self.start_time self.frames += 1 print(f"Frame {self.frames} rendered at {t}") diff --git a/ppb/__init__.py b/ppb/__init__.py index 68043972..57df82e1 100644 --- a/ppb/__init__.py +++ b/ppb/__init__.py @@ -46,6 +46,7 @@ from ppb.systems import Sound from ppb.systems import Font from ppb.systems import Text +from ppb.utils import get_time __all__ = ( # Shortcuts diff --git a/ppb/engine.py b/ppb/engine.py index aaa08228..60fd8089 100644 --- a/ppb/engine.py +++ b/ppb/engine.py @@ -19,6 +19,7 @@ from ppb.systems import Updater from ppb.utils import LoggingMixin from ppb.utils import camel_to_snake +from ppb.utils import get_time _ellipsis = type(...) @@ -151,7 +152,7 @@ def start(self): another event loop. """ self.running = True - self._last_idle_time = time.monotonic() + self._last_idle_time = get_time() self.activate({"scene_class": self.first_scene, "kwargs": self.scene_kwargs}) @@ -176,7 +177,7 @@ def loop_once(self): if not self.entered: raise ValueError("Cannot run before things have started", self.entered) - now = time.monotonic() + now = get_time() self.signal(events.Idle(now - self._last_idle_time)) self._last_idle_time = now while self.events: diff --git a/ppb/features/animation.py b/ppb/features/animation.py index 39025e45..f892a1b3 100644 --- a/ppb/features/animation.py +++ b/ppb/features/animation.py @@ -15,7 +15,7 @@ class Animation: An "image" that actually rotates through numbered files at the specified rate. """ # Override this to change the clock used for frames. - clock = time.monotonic + clock = ppb.get_time def __init__(self, filename, frames_per_second): """ diff --git a/ppb/systems/clocks.py b/ppb/systems/clocks.py index a6f0a198..0d7d2f95 100644 --- a/ppb/systems/clocks.py +++ b/ppb/systems/clocks.py @@ -1,5 +1,6 @@ import time +import ppb import ppb.events as events from ppb.systemslib import System @@ -13,12 +14,12 @@ def __init__(self, time_step=0.016, **kwargs): self.time_step = time_step def __enter__(self): - self.start_time = time.monotonic() + self.start_time = ppb.get_time() def on_idle(self, idle_event: events.Idle, signal): if self.last_tick is None: - self.last_tick = time.monotonic() - this_tick = time.monotonic() + self.last_tick = ppb.get_time() + this_tick = ppb.get_time() self.accumulated_time += this_tick - self.last_tick self.last_tick = this_tick while self.accumulated_time >= self.time_step: diff --git a/ppb/systems/renderer.py b/ppb/systems/renderer.py index 3a2bcec3..350fe7db 100644 --- a/ppb/systems/renderer.py +++ b/ppb/systems/renderer.py @@ -2,7 +2,6 @@ import io import logging import random -from time import monotonic import sdl2 import sdl2.ext @@ -46,6 +45,7 @@ from ppb.camera import Camera from ppb.systems._sdl_utils import SdlSubSystem, sdl_call, img_call, ttf_call from ppb.systems._utils import ObjectSideData +from ppb.utils import get_time logger = logging.getLogger(__name__) @@ -123,7 +123,7 @@ def __init__( self.target_camera_width = target_camera_width self.target_frame_rate = target_frame_rate self.target_frame_length = 1 / self.target_frame_rate - self.target_clock = monotonic() + self.target_frame_length + self.target_clock = get_time() + self.target_frame_length self._texture_cache = ObjectSideData() @@ -154,7 +154,7 @@ def __exit__(self, *exc): super().__exit__(*exc) def on_idle(self, idle_event: events.Idle, signal): - t = monotonic() + t = get_time() if t >= self.target_clock: signal(events.PreRender()) signal(events.Render()) diff --git a/ppb/testutils.py b/ppb/testutils.py index 4a47bfee..fa1fdb3c 100644 --- a/ppb/testutils.py +++ b/ppb/testutils.py @@ -1,6 +1,7 @@ import time from typing import Callable +import ppb from ppb.engine import GameEngine from ppb.events import Idle from ppb.events import Quit @@ -14,12 +15,12 @@ def __init__(self, *, fail: Callable[[GameEngine], bool], message: str, super().__init__(**kwargs) self.fail = fail self.message = message - self.start = time.monotonic() + self.start = ppb.get_time() self.run_time = run_time self.engine = engine def on_idle(self, idle_event: Idle, signal): - if time.monotonic() - self.start > self.run_time: + if ppb.get_time() - self.start > self.run_time: raise AssertionError("Test ran too long.") if self.fail(self.engine): raise AssertionError(self.message) diff --git a/ppb/utils.py b/ppb/utils.py index 8f71862d..9bb8c86b 100644 --- a/ppb/utils.py +++ b/ppb/utils.py @@ -1,8 +1,9 @@ import logging import re import sys +from time import perf_counter -__all__ = 'LoggingMixin', 'camel_to_snake' +__all__ = 'LoggingMixin', 'camel_to_snake', 'get_time' # Dictionary mapping file names -> module names @@ -30,6 +31,18 @@ def camel_to_snake(txt): return _boundaries_finder_2.sub(r'\1_\2', s1).lower() +def get_time(): + """ + Returns the time via the default timer. + + Currently uses :func:`time.perf_counter` as the default timer. + + .. warning:: This is not a globally synchronized timer, it's just simply a system time. It is intended + to make sure all timers in ppb code use the same function. + """ + return perf_counter() + + def _get_module(file_name): """ Find the module name for the given file name, or raise KeyError if it's diff --git a/tests/test_testutil.py b/tests/test_testutil.py index fa0310eb..c054e38c 100644 --- a/tests/test_testutil.py +++ b/tests/test_testutil.py @@ -1,4 +1,4 @@ -from time import monotonic + from unittest.mock import Mock from pytest import mark @@ -7,6 +7,7 @@ import ppb.testutils as testutil from ppb.events import Idle from ppb.events import Quit +from ppb.utils import get_time @mark.parametrize("loop_count", list(range(1, 6))) @@ -31,14 +32,14 @@ def test_failer_immediate(): def test_failer_timed(): failer = testutil.Failer(fail=lambda e: False, message="Should time out", run_time=0.1, engine=None) - start_time = monotonic() + start_time = get_time() while True: try: failer.on_idle(Idle(0.0), lambda x: None) except AssertionError as e: if e.args[0] == "Test ran too long.": - end_time = monotonic() + end_time = get_time() break else: raise