pausing pyglet's main event loop

668 views
Skip to first unread message

infinite8s

unread,
Mar 15, 2008, 7:16:42 PM3/15/08
to pyglet-users

I noticed the recent discussion on networking with pyglet, and saw the
post on the PausingReactor. Currently I'm writing a turn based
strategy game, and realized the same pausing approach would let me
avoid having to use callbacks when coupling my GUI to the game. Has
anybody tried to implement some sort of inverted pyglet event loop
(similar to using coroutines)?

With the old version of pyglet (and my original gui in pygame), I had
a function that simulated the event loop, but would exit after a
certain action. This way, I didn't have to convolute my turn-based
structure with callback code:

class Player:
def getSelection(self):
input("Pick object")

class Game:
def run(self):
while not self.game_over:
player.getSelection()

class GUI(pyglet.window)
def input(self, prompt):
self.logmessage.set(prompt)
while not self.has_exit or self.user_action:
dt = clock.tick()
update(dt)

self.dispatch_events()
self.clear()
self.draw()
self.flip()
return user_action
run = input

def new_game(self):
player = Player("Bob")
game = Game()
player.input = self.input
game.run()

gui = GUI()
gui.run()

When the game is started, the player input function is set to
GUI.input(), so that the game can ask the player for input (while
keeping the UI responsive) and exit the loop after a player action.
This is kind of hackish and ugly (and recursive), but keeps my game
logic pure. What I'd like to do is use this idea with pyglet's new
event loop, but subclass EventLoop so I can have it pause and resume
(similar to the PausingReactor). The problem is I can't completely
wrap my head around the way PausingReactor works, even though I feel
like it's the right approach. Any suggestions?

infinite8s

unread,
Mar 18, 2008, 1:58:25 PM3/18/08
to pyglet-users

After reading through a lot of posts on cooperative multitasking, I've
realized that I need some sort of trampolining with generators to do
what I want. This means I need to change the app.run function to
become a generator, so that I can pause and resume it. Is there a way
to do this without having to change each of the system dependent
classes? I was thinking that I could override idle() to suspend when
the user generates a game event, but unfortunately python's generators
can only yield to their calling functions.

I'm beginning to think there is no way to do what I want since python
doesn't allow for symmetric coroutines. Am I missing something?

Drew Smathers

unread,
Mar 18, 2008, 3:54:33 PM3/18/08
to pyglet...@googlegroups.com
On Tue, Mar 18, 2008 at 1:58 PM, infinite8s
<naveen.mic...@gmail.com> wrote:
>
>
> After reading through a lot of posts on cooperative multitasking, I've
> realized that I need some sort of trampolining with generators to do
> what I want. This means I need to change the app.run function to
> become a generator, so that I can pause and resume it. Is there a way
> to do this without having to change each of the system dependent
> classes? I was thinking that I could override idle() to suspend when
> the user generates a game event, but unfortunately python's generators
> can only yield to their calling functions.
>

As far as foreign event loop integration is concergned, I think what's
needed is much simpler than this. Extract the inner parts of pyglet's
while loop into a function (pump or whatever) than can be called by
another event loop for each iteration. The next trick is dealing with
clock - which is alo somewhat simple since you can override pyglet's
default with your own - it would be nice of course to have a a few
custom ones builtin (batteries included). If you were using Twisted,
for example, you would want to use the scheduling provided by twisted
or ... bad things would likely happen.

Beyond tweaking pyglet.app as above, one problem I've spotted (in the
case of twisted) is pyglet's BaseEventLoop idle method which has a
blocking call:

dt = clock.tick(True)

This would necessitate that you use _threadedselect which really isn't
ideal. One way to deal with this maybe to add callable argument to
tick which custom clock implmentations could deal with. So idle would
become:

def idle(self):
clock.tick(True, callback=self._redraw)

def _redraw(self, dt):
# Redraw all windows
for window in windows:
window.switch_to()
window.dispatch_event('on_draw')
window.flip()
# Update timeout
return clock.get_sleep_time(True)

Richard - if you're reading this - what kind of insights did you get
from the twisted developers at pycon regarding possible integration
with pyglet?


> I'm beginning to think there is no way to do what I want since python
> doesn't allow for symmetric coroutines. Am I missing something?

Horray! Someone finally admits python doesn't have `true' (or
symmetric) coroutines.


--
\\\\\/\"/\\\\\\\\\\\
\\\\/ // //\/\\\\\\\
\\\/ \\// /\ \/\\\\
\\/ /\/ / /\/ /\ \\\
\/ / /\/ /\ /\\\ \\
/ /\\\ /\\\ \\\\\/\
\/\\\\\/\\\\\/\\\\\\
d.p.s

infinite8s

unread,
Mar 18, 2008, 4:11:14 PM3/18/08
to pyglet-users


On Mar 18, 3:54 pm, "Drew Smathers" <drew.smath...@gmail.com> wrote:

> As far as foreign event loop integration is concergned, I think what's
> needed is much simpler than this. Extract the inner parts of pyglet's
> while loop into a function (pump or whatever) than can be called by
> another event loop for each iteration. The next trick is dealing with
> clock - which is alo somewhat simple since you can override pyglet's
> default with your own - it would be nice of course to have a a few
> custom ones builtin (batteries included). If you were using Twisted,
> for example, you would want to use the scheduling provided by twisted
> or ... bad things would likely happen.
>

This is not exactly what I'm trying to do. I would like to use
pyglet's event loop, but integrate it with my turn based game. I think
the way to do this would be to add pause and resume functions to
BaseEventLoop, and wrap my game around the GUI. So I would create the
game object, which would create the GUI and start its event loop. When
a game event occurs (for example, start a new game), the pyglet loop
would yield to the game loop, which would setup and start the game.
When user input is required, pyglet's event loop is resumed from the
previous point until the player performs an action that generates a
requested game action, at which point it yields again with the
requested action.

Currently, I create the GUI, and then when a new game is started, the
game loop is started, with a pseudo event loop passed in to be used as
the player input function (since you could require player input
anywhere in the function call tree).

Richard Jones

unread,
Mar 18, 2008, 4:18:33 PM3/18/08
to pyglet...@googlegroups.com
On Tue, Mar 18, 2008 at 2:54 PM, Drew Smathers <drew.s...@gmail.com> wrote:
Richard - if you're reading this - what kind of insights did you get
from the twisted developers at pycon regarding possible integration
with pyglet?

I've not had that conversation with them yet. I need to learn how the pyglet app loop works first :)

Mostly I've been working on Bruce though.


     Richard
 

Drew Smathers

unread,
Mar 18, 2008, 4:23:56 PM3/18/08
to pyglet...@googlegroups.com

Bruce is awesome, by the way. I really enjoyed the presentation at
pycon. A buddy of mine also did a presentation with it a while ago
when it was based on pygame - the audience was well swooned even then.
It's great to know you've rewritten it for pyglet.

Drew Smathers

unread,
Mar 18, 2008, 4:39:58 PM3/18/08
to pyglet...@googlegroups.com
On Tue, Mar 18, 2008 at 4:11 PM, infinite8s
<naveen.mic...@gmail.com> wrote:
>
>
>
> On Mar 18, 3:54 pm, "Drew Smathers" <drew.smath...@gmail.com> wrote:
>
> > As far as foreign event loop integration is concergned, I think what's
> > needed is much simpler than this. Extract the inner parts of pyglet's
> > while loop into a function (pump or whatever) than can be called by
> > another event loop for each iteration. The next trick is dealing with
> > clock - which is alo somewhat simple since you can override pyglet's
> > default with your own - it would be nice of course to have a a few
> > custom ones builtin (batteries included). If you were using Twisted,
> > for example, you would want to use the scheduling provided by twisted
> > or ... bad things would likely happen.
> >
>
> This is not exactly what I'm trying to do. I would like to use
> pyglet's event loop, but integrate it with my turn based game. I think
> the way to do this would be to add pause and resume functions to
> BaseEventLoop, and wrap my game around the GUI. So I would create the
> game object, which would create the GUI and start its event loop. When
> a game event occurs (for example, start a new game), the pyglet loop
> would yield to the game loop, which would setup and start the game.
> When user input is required, pyglet's event loop is resumed from the
> previous point until the player performs an action that generates a
> requested game action, at which point it yields again with the
> requested action.
>

Oh ... I reread your code example in your original post. My
suggestion is to *not* have multiple event loops interacting with each
other - or handing off control to other event loops. But that being
said, I'm actually confused around your `Game' loop. How does this
take care of redrawing the screen, etc? It might simplify matters to
make the Game an actor, like:

class Game:
def update(self, dt):
self.player ...

And then schedule updates on the Game for each frame via clock.

infinite8s

unread,
Mar 18, 2008, 5:01:44 PM3/18/08
to pyglet-users

> > This is not exactly what I'm trying to do. I would like to use
> > pyglet's event loop, but integrate it with my turn based game. I think
> > the way to do this would be to add pause and resume functions to
> > BaseEventLoop, and wrap my game around the GUI. So I would create the
> > game object, which would create the GUI and start its event loop. When
> > a game event occurs (for example, start a new game), the pyglet loop
> > would yield to the game loop, which would setup and start the game.
> > When user input is required, pyglet's event loop is resumed from the
> > previous point until the player performs an action that generates a
> > requested game action, at which point it yields again with the
> > requested action.
>
> Oh ... I reread your code example in your original post. My
> suggestion is to *not* have multiple event loops interacting with each
> other - or handing off control to other event loops. But that being
> said, I'm actually confused around your `Game' loop. How does this
> take care of redrawing the screen, etc? It might simplify matters to
> make the Game an actor, like:

Game loop is a little misleading. Since this is a turn based game (I'm
making a version of the Settlers of Catan board game), most of the
time is spent in the UI (the game code takes minimal processing).
However, the logic for the game is really straightfoward (as it would
be for any board game). So my game code looks like this:

class Game:
def new_game(self):
# Start new game
self.player1 = Player("Andrew")
self.player2 = Player("Bob")
self.run()
def run(self):
game_over = False
while not game_over:
# New turn
self.set_curr_player() # set self.curr_player to either
player1 or player2
self.rollDice()
action = self.curr_player.getAction()
# do something with action
if action == "build_road":
....

class Player:
def __init__(self, name):
self.name = name
self.resources = ...
def getAction(self):
# input is some function that resumes the UI, sets the prompt, and
waits for a user action
# context is constructed so that the action can be filtered
action = self.input("%s build road, settlement, city or trade with
other players"%name, context=lambda ...)
if action == "cancel": return False
else: return action

As you can see, the game logic is straightforward, except it's
polluted by the fact that I need to get data from the player (through
the UI). One way would be to use callbacks, but another way would be
if input() somehow resumed the pyglet event loop, and when the player
does an action required by the game (such as selecting a piece on the
board) then the event loop yields back to the game.

The problem with using updates (as in your example above) is that I
would have to split up the game logic to make it work within the UI
framework.

Alex Holkner

unread,
Mar 18, 2008, 5:09:22 PM3/18/08
to pyglet...@googlegroups.com
On 3/19/08, infinite8s <naveen.mic...@gmail.com> wrote:
>
>
>
> On Mar 18, 3:54 pm, "Drew Smathers" <drew.smath...@gmail.com> wrote:
>
> > As far as foreign event loop integration is concergned, I think what's
> > needed is much simpler than this. Extract the inner parts of pyglet's
> > while loop into a function (pump or whatever) than can be called by
> > another event loop for each iteration. The next trick is dealing with
> > clock - which is alo somewhat simple since you can override pyglet's
> > default with your own - it would be nice of course to have a a few
> > custom ones builtin (batteries included). If you were using Twisted,
> > for example, you would want to use the scheduling provided by twisted
> > or ... bad things would likely happen.
> >
>
>
> This is not exactly what I'm trying to do. I would like to use
> pyglet's event loop, but integrate it with my turn based game. I think
> the way to do this would be to add pause and resume functions to
> BaseEventLoop, and wrap my game around the GUI. So I would create the
> game object, which would create the GUI and start its event loop. When
> a game event occurs (for example, start a new game), the pyglet loop
> would yield to the game loop, which would setup and start the game.
> When user input is required, pyglet's event loop is resumed from the
> previous point until the player performs an action that generates a
> requested game action, at which point it yields again with the
> requested action.

I don't think this is a very good idea, inverting control of the event
loop. It sounds messy, difficult to maintain and likely to cause
subtle, difficult-to-trace bugs.

But if you really want to give it a go, set EventLoop.has_exit = True
in the event handler when you want the loop to quit. Then pick it up
again with pyglet.app.run() when you need to. No need for coroutines.

Alex.

infinite8s

unread,
Mar 18, 2008, 5:40:16 PM3/18/08
to pyglet-users

> I don't think this is a very good idea, inverting control of the event
> loop. It sounds messy, difficult to maintain and likely to cause
> subtle, difficult-to-trace bugs.
>
> But if you really want to give it a go, set EventLoop.has_exit = True
> in the event handler when you want the loop to quit. Then pick it up
> again with pyglet.app.run() when you need to. No need for coroutines.
>
> Alex.

Oh, that's a lot simpler. I was trying to put in yield into app.run.

It sounds messy, but it keeps the game logic pure. The UI just
observes the game state, and all interaction between the player and
the game goes through one function (with the appropriate context set
up, which filters Action objects that are created by the UI in
response to the player). The UI is totally dependent on the game, and
the game is completely independent of the UI (which let me port the
game from pygame to pyglet in an evening, once I had mapped the UI
elements). Unfortunately python doesn't support true coroutines,
otherwise I could avoid all these contortions. I'm basically trying to
map console based interaction to a graphical interface without having
to use callbacks.

Here is an example of what I'm trying to do:
http://groups.google.com/group/pyglet-users/web/inverted_loop.py. I've
used your suggestion (you can see the attempt with yielding commented
out), but I don't yet properly trap window closing to quit the game,
so once you hit n and close the window the game needs to be killed.
Reply all
Reply to author
Forward
0 new messages