Twisted + wxPython example


This is an example of nice cohabitation between Twisted and wxPython. Each event loop has its own thread and a proxy is used to communicate between each other. Thus invokation of callbacks in the other event loop just looks like a normal method call.

From author's posting to Twisted-Python:



In general I think trying to mix two event loops is a bad approach,
especially if you have timers, idle events and the like. You always end
up having one loop starving the other, unless you do some busy waiting
which eats up your CPU.


I'm using another approach which is to have two threads, one for the wx
event loop and one for the Twisted event loop. The two threads
communicate quite simply using some proxy objects which forward method
calls.


import wx, wx.lib.newevent
import threading

from twisted.internet import reactor
from twisted.python import threadable
threadable.init(1)

#DaemonEvent = wx.NewEventType()

UI_EXIT = wx.ID_EXIT
UI_DUMP = wx.NewId()
UI_REPEAT_ON = wx.NewId()
UI_REPEAT_OFF = wx.NewId()

#EVT_DAEMON = wx.PyEventBinder(DaemonEvent, 1)
DaemonEvent, EVT_DAEMON = wx.lib.newevent.NewEvent()
UIProxyEvent, EVT_PROXY = wx.lib.newevent.NewEvent()


class DaemonLoop(threading.Thread):
    def __init__(self, wxEvtHandler):
        super(DaemonLoop, self).__init__()
        self.repeat_hello = True
        self.repeat_count = 0
        self.ui = UIProxy(wxEvtHandler)

    def run(self):
        # Dummy demo stuff
        reactor.callLater(3, self.helloWorld)

        def twoSecondsPassed():
            print "two seconds passed"
        reactor.callLater(2, twoSecondsPassed)

        # Run reactor
        reactor.run(installSignalHandlers=0)

    def helloWorld(self):
        print "hello, world"
        self.repeat_count += 1
        self.ui.setTextLabel("count: " + str(self.repeat_count))
        if self.repeat_hello:
            reactor.callLater(1, self.helloWorld)

    def UI_dump(self, s):
        print s

    def setRepeat(self, value):
        if not self.repeat_hello and value:
            reactor.callLater(1, self.helloWorld)
        self.repeat_hello = value


class TwistedProxy(object):
    def __init__(self, realobj):
        self._target = realobj

    def __getattr__(self, name):
        print "proxying daemon __getattr__"
        attr = self._target.__getattribute__(name)
        if callable(attr):
            def fun(*args, **kargs):
                reactor.callFromThread(attr, *args, **kargs)
            self.__setattr__(name, fun)
            return fun
        raise AttributeError("can only proxy object methods")


class UIProxy(object):
    def __init__(self, realobj):
        if not isinstance(realobj, wx.EvtHandler):
            raise TypeError(`type(realobj)` + " is not a subclass of wxEvtHandler")
        self._target = realobj

    def __getattr__(self, name):
        print "proxying UI __getattr__"
        attr = self._target.__getattribute__(name)
        if callable(attr):
            def fun(*args, **kargs):
                evt = UIProxyEvent(fun = lambda: attr(*args, **kargs))
                wx.PostEvent(self._target, evt)
            self.__setattr__(name, fun)
            return fun
        raise AttributeError("can only proxy object methods")


class UIProxyReceiver(object):
    def __init__(self):
        self.Bind(EVT_PROXY, self.doProxyEvent)

    def doProxyEvent(self, event):
        event.fun()


class MyFrame(wx.Frame, UIProxyReceiver):
    def __init__(self, parent, ID, title):
        wx.Frame.__init__(self, parent, ID, title, wx.DefaultPosition, wx.Size(300, 200))
        UIProxyReceiver.__init__(self)

        menu = wx.Menu()
        menu.Append(UI_DUMP, "Dump", "Dump stuff on the console")
        menu.Append(UI_REPEAT_ON, "Activate timer", "")
        menu.Append(UI_REPEAT_OFF, "Desactivate timer", "")
        menu.Append(UI_EXIT, "E&xit", "Terminate the program")
        menu_bar = wx.MenuBar()
        menu_bar.Append(menu, "&File");
        self.static_text = wx.StaticText(self,-1,'dupadupa')
        self.SetMenuBar(menu_bar)
        wx.EVT_MENU(self, UI_EXIT, self.doExit)
        wx.EVT_MENU(self, UI_DUMP, self.doDump)
        wx.EVT_MENU(self, UI_REPEAT_ON, self.doRepeatOn)
        wx.EVT_MENU(self, UI_REPEAT_OFF, self.doRepeatOff)

        _daemon = DaemonLoop(self)
        _daemon.daemon = True
        _daemon.start()
        self.daemon = TwistedProxy(_daemon)

    def doExit(self, event):
        print "closing"
        self.Close(True)

    def doDump(self, event):
        self.daemon.UI_dump(`event.GetId()`)

    def doRepeatOn(self, event):
        self.daemon.setRepeat(True)

    def doRepeatOff(self, event):
        self.daemon.setRepeat(False)

    def setTextLabel(self, label):
        self.static_text.SetLabel(label)

class MyApp(wx.App):
    def OnInit(self):
        frame = MyFrame(None, -1, "Hello, world")
        frame.Show(True)
        self.SetTopWindow(frame)
        return True

def demo():
    app = MyApp(0)
    app.MainLoop()

if __name__ == '__main__':
    demo()