Skip to content

Commit

Permalink
Merge #431
Browse files Browse the repository at this point in the history
431: Use time.perf_counter for frame target calculation, more precise than… r=astronouth7303 a=ironfroggy

… monotonic and explicitly used for comparisons.

Co-authored-by: Calvin Spealman <ironfroggy@gmail.com>
Co-authored-by: ironfroggy <ironfroggy@gmail.com>
  • Loading branch information
bors[bot] and ironfroggy authored May 18, 2020
2 parents 1929b45 + eb8cdb4 commit c4199f3
Show file tree
Hide file tree
Showing 10 changed files with 42 additions and 18 deletions.
6 changes: 6 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions examples/frame_count/framecount.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand All @@ -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}")

Expand Down
1 change: 1 addition & 0 deletions ppb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions ppb/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(...)

Expand Down Expand Up @@ -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})

Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion ppb/features/animation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand Down
7 changes: 4 additions & 3 deletions ppb/systems/clocks.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import time

import ppb
import ppb.events as events
from ppb.systemslib import System

Expand All @@ -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:
Expand Down
6 changes: 3 additions & 3 deletions ppb/systems/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import io
import logging
import random
from time import monotonic

import sdl2
import sdl2.ext
Expand Down Expand Up @@ -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__)

Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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())
Expand Down
5 changes: 3 additions & 2 deletions ppb/testutils.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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)
Expand Down
15 changes: 14 additions & 1 deletion ppb/utils.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand Down
7 changes: 4 additions & 3 deletions tests/test_testutil.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from time import monotonic

from unittest.mock import Mock

from pytest import mark
Expand All @@ -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)))
Expand All @@ -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
Expand Down

0 comments on commit c4199f3

Please sign in to comment.