I present to you threading. Specifically for PyQt there is the QThread class which is actually discussed rather nicely in this PyQtWiki and this page by Jo Plaete which I used for inspiration.
What complicates the whole scene is that we need to realize that we have two independant things we want to accomplish: 1) move particles and 2) update the graphics. Both independent of the GUI.
I started by making my own class called BaseThread which subclasses QThread. The code is
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from PyQt4 import QtCore | |
class BaseThread(QtCore.QThread): | |
def __init__(self): | |
QtCore.QThread.__init__(self) | |
self.exiting = False | |
def __del__(self): | |
self.exiting = True | |
self.wait() |
To come around many of the syncronization problems that arises with threads the Qt framework allows us to use custom signals to make everything talk together. Here is my version of a StepThread which defines the run method (you do NEVER call this explicitly!) and a convenience function called simulate to start a simulation for N steps. The run function emits a custom signal and sleeps for a little while before simulating again.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from time import sleep | |
from PyQt4 import QtCore | |
from basethread import BaseThread | |
class StepThread(BaseThread): | |
def __init__(self): | |
BaseThread.__init__(self) | |
def simulate(self, nsteps): | |
self.nsteps = nsteps | |
self.exiting = False | |
self.start() | |
def run(self): | |
while not self.exiting and self.nsteps > 0: | |
self.emit( QtCore.SIGNAL('TakeStep()') ) | |
self.nsteps -= 1 | |
sleep(0.005) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from time import sleep | |
from PyQt4 import QtCore | |
from basethread import BaseThread | |
class DrawThread(BaseThread): | |
def __init__(self): | |
BaseThread.__init__(self) | |
def run(self): | |
while not self.exiting: | |
self.emit( QtCore.SIGNAL('DrawCanvas()') ) | |
sleep(0.05) |
Finally, I had to make some tweaks to the Simulator class (nothing very fancy) to hook up our custom signals.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from PyQt4 import QtGui, QtCore | |
from simulator_ui import Ui_main | |
from particlecanvas import ParticleCanvas | |
from drawthread import DrawThread | |
from stepthread import StepThread | |
class MainForm(QtGui.QMainWindow): | |
def __init__(self, parent=None): | |
QtGui.QWidget.__init__(self,parent) | |
self.ui = Ui_main() | |
self.ui.setupUi(self) | |
self.connect(self.ui.btnSetup, QtCore.SIGNAL("clicked()"), self.on_setup_clicked) | |
self.connect(self.ui.btnRun, QtCore.SIGNAL("clicked()"), self.on_run_clicked) | |
self.c = ParticleCanvas(self.ui.frame) | |
self.pp = DrawThread() | |
self.mm = StepThread() | |
self.connect( self.pp, QtCore.SIGNAL('DrawCanvas()'), self.c.redraw) | |
self.connect( self.mm, QtCore.SIGNAL('TakeStep()'), self.c.move) | |
def on_setup_clicked(self): | |
self.c.reinitialize(int(self.ui.txtNPart.text())) | |
self.mm.exiting = True | |
self.pp.start() | |
def on_run_clicked(self): | |
self.mm.simulate(int(self.ui.txtNSteps.text())) |
The entire code can be downloaded from the latest gist I made.
This comment has been removed by a blog administrator.
ReplyDelete