Skip to content
Open
Changes from all commits
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
88 changes: 73 additions & 15 deletions pgzero/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,15 @@
DISPLAY_FLAGS = 0


def exit():
"""Wait for up to a second for all sounds to play out
and then exit
def exit(exit_status=0):
"""Cleanly exits pgzero and pygame.

Args:
exit_status (int): Exit status. The default value of 0 indicates
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should bother with exit statuses. What you've done makes perfect sense for a general app development framework, but for Pygame Zero I think this adds unnecessary complexity.

We pay a cost for any additional complexity. It makes the documentation longer and adds terms that beginners may not be familiar with (and are unlikely to need). Here I suspect they will not know what an exit status is, and there are very few cases where it would be useful for them.

There is a very slightly stronger case that you can pair up exit(n) with on_exit(n), so you can signal different exit conditions. But there are other ways to deal with this, eg. setting global flags.

I guess our expectations differ. I expect exit() to be very rarely used, because most games don't need to manage their own quitting. Meanwhile on_exit() would be used rarely, eg to save scores or game state.

a successful termination.

"""
t0 = time.time()
while pygame.mixer.get_busy():
time.sleep(0.1)
if time.time() - t0 > 1.0:
break
sys.exit()
sys.exit(exit_status)


def positional_parameters(handler):
Expand Down Expand Up @@ -213,6 +212,55 @@ def get_draw_func(self):
)
return draw

def _call_users_on_enter_func(self):
# Calls the user's on_enter function if defined.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like on_enter() means that "there is more than one way to do it" because we also promote using the module scope to set up the initial game state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think an on_enter() function is useful for encapsulating start up code, but I understand your point.

Would you like me to remove it?

try:
on_enter = self.mod.on_enter
except AttributeError:
# No func defined, so nothing to do.
return

if 0 != on_enter.__code__.co_argcount:
# Put exception string on its own line for a cleaner traceback.
raise TypeError(
'on_enter() must not take any arguments'
)

on_enter()

def _call_users_on_exit_func(self, exit_status):
# Calls the user's on_exit function if defined.
# Supports calling functions with zero or one argument (exit_status).
try:
on_exit = self.mod.on_exit
except AttributeError:
# No func defined, so nothing to do.
return

if 0 == on_exit.__code__.co_argcount:
on_exit()
elif 1 == on_exit.__code__.co_argcount:
on_exit(exit_status)
else:
# Put exception string on its own line for a cleaner traceback.
raise TypeError(
'on_exit() can only take up to one argument'
)

def _on_exit(self, exit_status):
# Exit clean up done here.
# User's on_exit func is called right after exiting the game loop.
self._call_users_on_exit_func(exit_status)

# Wait (up to a second) for all sounds to play out.
t0 = time.time()
while pygame.mixer.get_busy():
time.sleep(0.1)
if time.time() - t0 > 1.0:
break

pygame.quit() # Uninitialize any initialized pygame modules.

def run(self):
"""Invoke the main loop, and then clean up."""
loop = asyncio.get_event_loop()
Expand All @@ -224,13 +272,20 @@ def run(self):
@asyncio.coroutine
def run_as_coroutine(self):
self.running = True
exit_status = 0

try:
yield from self.mainloop()
except SystemExit as e:
exit_status = e.code
finally:
pygame.display.quit()
pygame.mixer.quit()
self._on_exit(exit_status)
self.running = False

# This needs to be outside the finally clause to allow all other
# exceptions to be passed up.
sys.exit(exit_status)

@asyncio.coroutine
def mainloop(self):
"""Run the main loop of Pygame Zero."""
Expand All @@ -242,8 +297,11 @@ def mainloop(self):
self.load_handlers()

pgzclock = pgzero.clock.clock

self.need_redraw = True

# User's on_enter func is called right before entering the game loop.
self._call_users_on_enter_func()

while True:
# TODO: Use asyncio.sleep() for frame delay if accurate enough
yield from asyncio.sleep(0)
Expand All @@ -253,9 +311,9 @@ def mainloop(self):
if event.type == pygame.QUIT:
return
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_q and \
event.mod & (pygame.KMOD_CTRL | pygame.KMOD_META):
sys.exit(0)
if (event.key == pygame.K_q and
event.mod & (pygame.KMOD_CTRL | pygame.KMOD_META)):
return
self.keyboard._press(event.key)
elif event.type == pygame.KEYUP:
self.keyboard._release(event.key)
Expand Down