twisted reactor for panda3d and window close problem.

Most of this code is just copied from the wxreactor that is included with the twisted networking module.

This works for me on linux and mac os 10.4.10

The only issue i am having is getting reactor.stop() to call when the main window is destroyed. I’ve seen a few topics on this but assigning base.exitfunc or base.exitFunc doesn’t appear to be working. Comments, advice, etc would be most appreciated.

"""
 	This module provides panda3d event loop support for Twisted.
 	
 	In order to use this support, simply do the following::
 	    |  import pandaReactor
	    |  pandaReactor.install() 	
	Then, when your root pandaApp has been created:: 	
 	    | from twisted.internet import reactor
 	    | reactor.registerPandaApp(Your DirectObject.DirectObject instance)
 	    | reactor.run()
        Finally when you want to exit the application::
            | reactor.stop()
"""
import Queue as sysQueue
from twisted.python import log, runtime
from twisted.internet import _threadedselect
import direct.directbase.DirectStart
from direct.showbase.PythonUtil import *
from direct.task.Task import Task

import sys
class pandaReactor(_threadedselect.ThreadedSelectReactor):
    
    _stopping = False
    def registerPandaApp(self, pandaApp):
        self.pandaApp = pandaApp

    def _installSignalHandlersAgain(self):
        try:
            import signal
            signal.signal(signal.SIGINT, signal.default_int_handler)
        except ImportError:
            return
        self._handleSignals()
    
    def stop(self):
        if self._stopping :
            return
        self._stopping = True
        _threadedselect.ThreadedSelectReactor.stop(self)

    def _runInMainThread(self, f):
        if hasattr(self, "pandaApp") and self.pandaApp != None:
            self.f = f
            taskMgr.add(self.taskWrapper, 'taskWrapper', -52)
        else:
            self._postQueue.put(f)
    
    def taskWrapper(self, task):
        self.f()
        return None
    def _stopPanda(self):
        if hasattr(self, "pandaApp"):
           self.pandaApp.stopGame()
    
    def run(self, installSignalHandlers=True):
        self._postQueue = sysQueue.Queue()
        if not hasattr(self, "pandaApp"):
            log.msg("registerPandaApp() was not called on reactor.. bye. ")
            return
        self.interleave(self._runInMainThread, installSignalHandlers=installSignalHandlers)
        if installSignalHandlers:
            self.callLater(0, self._installSignalHandlersAgain)
        self.addSystemEventTrigger("after", "shutdown", self._stopPanda)
        self.addSystemEventTrigger("after", "shutdown",
                                   lambda: self._postQueue.put(None))
        run()
        pandaApp = self.pandaApp
        del self.pandaApp

        if not self._stopping:
            self.stop()

            while 1:
                try:
                    f = self._postQueue.get(timeout=0.01)
                except Queue.Empty:
                    continue
                else:
                    if f is None:
                        break
                    try:
                        f()
                    except:
                        log.err()
 	
def install():
    reactor = pandaReactor()
    from twisted.internet.main import installReactor
    installReactor(reactor)
    return reactor 	
__all__ = ['install']	

my reactor, no warranty, radiation expected:



from twisted.internet import selectreactor

class StepReactor(selectreactor.SelectReactor):
   
    def getready(self, installSignalHandlers=1):
        """ prepare the reactor """
        self.startRunning(installSignalHandlers=installSignalHandlers)
        self.step()
        
    def step(self,delay=0):
        """ one step of the reactor """
        if self.running:
            try:
                if self.running:
                    # Advance simulation time in delayed event
                    # processors.
                    self.runUntilCurrent()                   
                    self.doIteration(delay)
            except:
                log.msg("Unexpected error in main loop.")
                log.deferr()
                
    def stop(self):
        """ stop the reactor """
        selectreactor.SelectReactor.stop(self)
        for i in range(10):
            self.step()
      
def install():
    """
        Configure the twisted mainloop to be run inside the mainloop.
    """  
    global reactor
    reactor = StepReactor()
    from twisted.internet.main import installReactor
    installReactor(reactor)
    reactor.getready()
    return reactor

# install up on import   
install()

Very nice!!! Thanks a lot :slight_smile:
I don’t really need threads at the moment, but I still think it’d be nice to have. sooo, still wondering how i can get some processor time between the main window being destroyed and panda exiting.

python -> threads = bad idea :slight_smile:

python can only run on one core, so running threaded python and non threaded will yield the same performance. While threaded might be difficult to code - semaphores and crap.

twisted is asynchronous, which is another “school of thought” different from threads. I personly like asynchrocity better then threading so i use it instead.

that is my $.02

“some processor time between the main window being destroyed and panda exiting” ~ isn’t this unrelated to twisted?

To be clear:

Essentially I want to handle the shutdown process from the 2nd thread (ie. call reactor._stopPanda via reactor.stop), or at least notify it that shutdown is occurring. I suppose I could handle my window events manually but I’d rather keep panda in tact as much as possible. From what I’ve read ShowBase.exitfunc is designed specifically for this purpose, but it doesn’t appear to be firing unfortunately.

A bit off topic but I’ll take the bait :smiley:

The limitations introduced by GIL aside, i find the python threading implementation to be one of the simplest to use when compared to other languages, and it definitely has its place. I think a lot of people would agree that saying “threading in python is bad” is far from the truth. It just doesn’t completely adhere to the concept inferred by most people from the term “threading”.

Maybe you meant “twisted -> threading = bad?” To that i have no comment as i’ve just started using twisted :smiley:

Yes, twisted has little to do with it, only that I’m trying to call back to my reactor when a user clicks the X at the corner of the window.

I don’t know why ShowBase.exitFunc() is not working. It should certainly be called when the window closes and ShowBase shuts itself down; it works fine for me. This is all ordinary Python code; you could try to figure out what is going on with pdb or even by inserting print statements in strategic places in ShowBase.py.

You could also just listen for the window event yourself. ShowBase knows the window has been closed when it hears the event “window-event”. You can see this code in ShowBase.py too. You could rename the window-event or completely replace this code in ShowBase and do your own behavior when the window is closed.

David

Exactly what I wanted to confirm. Thanks! I’m sure i’ve managed to do something dumb to break it.

edit: Dumb indeed. yes, base.exitFunc is working. Thanks again guys.