Skip to content
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

New subsystem API #221

Merged
merged 7 commits into from
Apr 8, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions ppb/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self, first_scene: Type, *,
self.event_extensions = defaultdict(dict)
self.running = False
self.entered = False
self.last_heartbeat = None

# Systems
self.systems_classes = systems
Expand Down Expand Up @@ -80,12 +81,20 @@ def run(self):

def start(self):
self.running = True
self.last_heartbeat = time.monotonic()
self.activate({"scene_class": self.first_scene,
"kwargs": self.scene_kwargs})

def main_loop(self):
while self.running:
time.sleep(0)
now = time.monotonic()
self.signal(events.Heartbeat(now - self.last_heartbeat))
self.last_heartbeat = now
# Okay, doing this twice is hacky as hell, but fine while I remove
# the old system.
while self.events:
self.publish()
for system in self.systems:
for event in system.activate(self):
self.signal(event)
Expand Down
9 changes: 9 additions & 0 deletions ppb/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,15 @@ class StopScene:
scene: Scene = None


@dataclass
pathunstrom marked this conversation as resolved.
Show resolved Hide resolved
class Heartbeat:
"""
An engine plumbing event to pump timing information to subsystems.
"""
time_delta: float
scene: Scene = None


@dataclass
class Update:
"""
Expand Down
8 changes: 5 additions & 3 deletions ppb/testutils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@
from typing import Callable

from ppb.engine import GameEngine
from ppb.events import Heartbeat
from ppb.events import Quit
from ppb.systems import System


class Failer(System):

def __init__(self, *, fail: Callable[[GameEngine], bool], message: str,
run_time: float=1, **kwargs):
run_time: float=1, engine, **kwargs):
super().__init__(**kwargs)
self.fail = fail
self.message = message
self.start = time.monotonic()
self.run_time = run_time
self.engine = engine

def activate(self, engine):
def on_heartbeat(self, heartbeat_event: Heartbeat, signal):
if time.monotonic() - self.start > self.run_time:
raise AssertionError("Test ran too long.")
if self.fail(engine):
if self.fail(self.engine):
raise AssertionError(self.message)
return ()

Expand Down
30 changes: 23 additions & 7 deletions tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,9 @@ def fail(engine):
if parent.count > 0 and engine.current_scene != parent:
return True

failer = Failer(fail=fail, message="ParentScene should not be counting while a child exists.")
engine = GameEngine(ParentScene, systems=[Updater(time_step=0.001), failer])
engine = GameEngine(ParentScene,
systems=[Updater(time_step=0.001), Failer], fail=fail,
message="ParentScene should not be counting while a child exists.")
engine.run()


Expand All @@ -114,8 +115,8 @@ def __init__(self, *args, **kwargs):
def change(self):
return super().change()

failer = Failer(fail=lambda n: False, message="Will only time out.")
with GameEngine(Scene, systems=[Updater, failer]) as ge:
with GameEngine(Scene, systems=[Updater, Failer], fail=lambda n: False,
message="Will only time out.") as ge:
ge.run()


Expand Down Expand Up @@ -175,6 +176,7 @@ def on_scene_started(self, event, signal):
class Tester(System):
listening = False

# TODO: This is going to break with the new change.
def activate(self, engine):
if self.listening:
assert isinstance(engine.current_scene, SecondScene)
Expand Down Expand Up @@ -209,7 +211,7 @@ def on_scene_started(self, event, signal):
class TestFailer(Failer):

def __init__(self, engine):
super().__init__(fail=self.fail, message="Will not call")
super().__init__(fail=self.fail, message="Will not call", engine=engine)
self.first_scene_ended = False

def on_scene_stopped(self, event, signal):
Expand Down Expand Up @@ -240,8 +242,7 @@ def on_scene_stopped(self, event, signal):
assert event.scene is self
test_function()

failer = Failer(fail=lambda x: False, message="Will only time out.")
with GameEngine(TestScene, systems=[Updater, failer]) as ge:
with GameEngine(TestScene, systems=[Updater, Failer], fail=lambda x: False, message="Will only time out.") as ge:
ge.run()

test_function.assert_called()
Expand All @@ -258,3 +259,18 @@ def test_flush_events():
ge.flush_events()

assert len(ge.events) == 0


def test_heartbeats():
"""This test confirms that Heartbeats work."""
was_called = False

class TestSystem(System):

def on_heartbeat(self, event: events.Heartbeat, signal):
global was_called
was_called = True
signal(events.Quit())

with GameEngine(BaseScene, systems=[TestSystem, Failer], fail=lambda x: False, message="Can only time out.") as ge:
ge.run()
9 changes: 5 additions & 4 deletions tests/test_testutil.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from pytest import raises

import ppb.testutils as testutil
from ppb.events import Heartbeat
from ppb.events import Quit


Expand All @@ -18,20 +19,20 @@ def test_quitter(loop_count):


def test_failer_immediate():
failer = testutil.Failer(fail=lambda e: True, message="Expected failure.")
failer = testutil.Failer(fail=lambda e: True, message="Expected failure.", engine=None)

with raises(AssertionError):
failer.activate(None)
failer.__event__(Heartbeat(0.0), lambda x: None)


def test_failer_timed():
failer = testutil.Failer(fail=lambda e: False, message="Should time out", run_time=0.1)
failer = testutil.Failer(fail=lambda e: False, message="Should time out", run_time=0.1, engine=None)

start_time = monotonic()

while True:
try:
failer.activate(None)
failer.__event__(Heartbeat(0.0), lambda x: None)
except AssertionError as e:
if e.args[0] == "Test ran too long.":
end_time = monotonic()
Expand Down