Using MVC when the model is dynamic

P

pysim

Hi, I have a couple of general requests for pointers to python
examples and design advice.

I'm looking for examples of MVC-based GUI controls done in python
(model-view-controller).

Also, examples where something like a simulation model running in its
own thread sends fast updates to a GUI in near real-time. The only
examples I've seen, such as SimPy, wait until the model is finished
running before outputting data.

Most MVC controls I see in java for example are only designed for
infrequent user-driven changes, like resizing a window or mouse
clicks, not cases where the underlying model is changing itself over
time.
 
M

Mitch Chapman

pysim said:
Hi, I have a couple of general requests for pointers to python
examples and design advice.

I'm looking for examples of MVC-based GUI controls done in python
(model-view-controller).

Also, examples where something like a simulation model running in its
own thread sends fast updates to a GUI in near real-time. The only
examples I've seen, such as SimPy, wait until the model is finished
running before outputting data.

Most MVC controls I see in java for example are only designed for
infrequent user-driven changes, like resizing a window or mouse
clicks, not cases where the underlying model is changing itself over
time.

Here's a baroque example. Maybe it will inspire others to respond
with something more concise :)

The attached example app has a text-based "GUI", as I wanted it to
be more or less self-contained. The app prints a sinusoidal
line of "#"s, until you type "q" and hit return. E.g.

#
#
#
#
#
#
#
#
#
#
#
#
....

The classes in the example include:

Observable -- maintains a set of observers, notifying them whenever
the value of the Observable changes.

Model -- runs a simple computation in a background thread. The
Model's state is an Observable.

View -- displays a model's state as shown above. Also processes
keyboard input, looking for a line starting with 'Q' or 'q' as
a shutdown request.

App -- acts like a controller, but also includes the application's
main event loop, which blocks until keyboard input is available.


The Model and View know nothing about each other. The
App glues them together; it tells the View to update whenever
the Model state changes. It also routes user input (keyboard
input, in this case) to the View. This is a common way
to build MVC applications, and it addresses your concern about
controls which are designed only for user-driven changes.


There's a lot of synchronization overhead in updating the view.
If you have lots of views on the same model, or if your views
take a long time to pain, performance and responsiveness will
suffer. In such cases it might be good to have the model publish
its state only at regular intervals -- store its current state
in a protected attribute and only occasionally copy that state
into an Observable.

Or, if you want to write really baroque code (like me,
apparently :) you could use a "delayed" Observable which
propagates model state changes at low rates. For example, the
model might publish its state using an instance of this
(untested) class:

class DelayedObservable(Observable):
...
def __init__(self, notifyInterval=1.0):
self._tLastChange = time.time()
self._notifyInterval = notifyInterval # Seconds

def _notify(self, force=0):
dt = time.time() - self._tLastChange
if force or (dt >= self._notifyInterval):
for observer in self._observers:
...
self._tLastChange = time.time()


This is all pretty verbose, but maybe it will be helpful.

For better ideas on limiting the rate of propagation of model
state changes, it might be good to look at the documentation
for NSNotificationQueue. It's part of the Cocoa framework of
Mac OS X.

--
Mitch

#!/usr/bin/env python
"""Demo a basic MVC app structure in which
The model is a simulation running in its own thread.
The model undergoes frequent, View-able state changes
"""

import sys, sets, threading, select, math


class Observable(object):
"""An Observable notifies its observers whenever its value changes.
Observers are just Python callables having the signature
callMe(sender)

Lots of MT-safe overhead, here. So...
TO DO: Demonstrate asynchronous, coalesced notifications."""
def __init__(self):
self._observers = sets.Set()
self._lock = threading.RLock()
self._value = None

def addObserver(self, newObserver):
self._observers.add(newObserver) # This oughtta be locked...

def removeObserver(self, anObserver):
self._observers.remove(anObserver) # This oughtta be locked...

def _notify(self):
for observer in self._observers: # This oughtta be locked...
try:
observer(self)
except:
pass # Don't let one broken observer gum up everything

def _getValue(self):
self._lock.acquire()
result = self._value
self._lock.release()
return result

def _setValue(self, newValue):
self._lock.acquire()
self._value = newValue
self._lock.release()
self._notify()

value = property(_getValue, _setValue, None, "The observable value")


class Model(threading.Thread):
"""Computes new values asynchronously. Notifies observers whenever
its state changes."""
def __init__(self, **kw):
threading.Thread.__init__(self, **kw)
self._stopped = 0
self._state = Observable()

def onStateChange(self, observer):
self._state.addObserver(observer)

def removeStateChange(self, observer):
self._state.removeObserver(observer)

def run(self):
"""Run the model in its own thread."""
self._stopped = 0
i = 0.0
di = math.pi / 8.0
while not self._stopped:
self._state.value = math.sin(i)
i += di

def stop(self):
self._stopped = 1


class View:
"""Dummy 'view' just prints the model's current value whenever
that value changes, and responds to keyboard input."""
def __init__(self):
self._onQuitCB = None

def modelStateChanged(self, modelState):
valueBar = " " * int((1 + modelState.value) * 10)
print "%s#" % valueBar

def onQuit(self, newOnQuitCB):
self._onQuitCB = newOnQuitCB

def handleInput(self, userInput):
if userInput.lower().startswith("q"):
if self._onQuitCB:
self._onQuitCB(self)


class App:
"""This sample application computes and displays garbage, at
a high rate of speed, until the user quits."""
def __init__(self):
# Yep, this is really a controller and not just an app runner.
self._model = Model()
self._view = View()
self._terminated = 0

self._model.onStateChange(self._view.modelStateChanged)
self._view.onQuit(self._quitApp)

def run(self):
self._model.start()

self._terminated = 0
while not self._terminated:
ins, outs, errs = select.select([sys.stdin], [], [])
if ins:
self._view.handleInput(raw_input())

self._model.join()

def _quitApp(self, *args):
self._terminated = 1
self._model.stop()
self._model.removeStateChange(self._view.modelStateChanged)


def main():
"""Module mainline (for standalone execution)"""
theApp = App()
theApp.run()

if __name__ == "__main__":
main()
 
B

Brian Kelley

Mitch said:
Model -- runs a simple computation in a background thread. The
Model's state is an Observable.

View -- displays a model's state as shown above. Also processes
keyboard input, looking for a line starting with 'Q' or 'q' as
a shutdown request.

App -- acts like a controller, but also includes the application's
main event loop, which blocks until keyboard input is available.


The Model and View know nothing about each other. The
App glues them together; it tells the View to update whenever
the Model state changes. It also routes user input (keyboard
input, in this case) to the View. This is a common way
to build MVC applications, and it addresses your concern about
controls which are designed only for user-driven changes.

Hi Mitch :) Note that Mitch's example won't work on windows since you
can't use select on stdin (only sockets).

I have been playing with two approaches to this issue of dynamically
changing events from a model.

1) I've recently become a fan of using the GUI's native events to
control state from the model to the view. They are thread-safe and easy
to implement. wxPython has a good example in their demo of doing this.
See the Threads entry of "Process and Events"

Model -> state is an observable ->| generates an event |<- View responds

when the observable is altered a GUI event is posted through the
callback scheme. The gui can then asynchronously respond to changes in
states. Warning: sometimes multiple instances of the same event
handler can be run simultaneously so be careful here. I usually prevent
this through some locking mechanism.

This is pretty much the same as Mitch's example except the observable is
decoupled from the View through the event mechanism. I expect the same
could be accomplished using a Queue and a timer.

2) Spawn an independent process and simply monitor the processes
standard output and respond accordingly. See the LongRunningTasks entry
on http://staffa.wi.mit.edu/people/kelley

Both of these gloss over the issue of how does the view talk to the
model. The only real answer I have is polling. The model must
occasionally poll for new instructions from the view or have it's own
event-style manager.

Brian Kelley
 
C

Cameron Laird

.
.
.
1) I've recently become a fan of using the GUI's native events to
control state from the model to the view. They are thread-safe and easy
to implement. wxPython has a good example in their demo of doing this.
See the Threads entry of "Process and Events"

Model -> state is an observable ->| generates an event |<- View responds .
.
.
Both of these gloss over the issue of how does the view talk to the
model. The only real answer I have is polling. The model must
occasionally poll for new instructions from the view or have it's own
event-style manager.

Brian Kelley

My summary: yes, what the original poster requested ab-
solutely is feasible, and even convenient, with Python.

If, that is, I understand him or her correctly. I admit
to confusion, though. He or she wrote:
# Also, examples where something like a simulation model running in its
# own thread sends fast updates to a GUI in near real-time. The only
# examples I've seen, such as SimPy, wait until the model is finished
# running before outputting data.
#
# Most MVC controls I see in java for example are only designed for
# infrequent user-driven changes, like resizing a window or mouse
# clicks, not cases where the underlying model is changing itself over
# time.
Was an interest for fast communications from user to model
desired? Or was the question solely along the lines of, is
there an architecture that'll permit the View to keep up
with the Model more-or-less in real time?
 
B

Brian Kelley

Cameron said:
Was an interest for fast communications from user to model
desired? Or was the question solely along the lines of, is
there an architecture that'll permit the View to keep up
with the Model more-or-less in real time?

You are correct in what the original poster wanted (i.e. is
there an architecture that'll permit the View to keep up
with the Model more-or-less in real time). I was just being pedantic :)

Brian
 
M

Mitch Chapman

Brian said:
You are correct in what the original poster wanted (i.e. is
there an architecture that'll permit the View to keep up
with the Model more-or-less in real time). I was just being pedantic :)

Hi Brian :)

I'm confused by the comments about the model needing to poll for new
instructions from the view. (BTW does this mean you usually prefer to
combine view and controller responsibilities in a single entity, rather
than implement them separately?)

Why not just have the model provide control methods which clients can
invoke directly (t. ex. the stop() method in the example I posted)?
Are you saying this doesn't fit well in wxPython, that it's more
natural in that environment to communicate via event codes and queues?

If that's the case, can you define a model-controller class which
receives view events and translates them into method invocations on
an associated model? That does seem like a lot of work, but it
would let the model remain ignorant of -- loosely coupled to --
its observers.
 
C

Cameron Laird

.
.
.
I'm confused by the comments about the model needing to poll for new
instructions from the view. (BTW does this mean you usually prefer to
combine view and controller responsibilities in a single entity, rather
than implement them separately?)
This *is* an apt question. Is there something
about wxWindows that pushes one in this direc-
tion, or was it just an abbreviation of the
evident fact that both the View and Controller
connect to the same end-user?
Why not just have the model provide control methods which clients can
invoke directly (t. ex. the stop() method in the example I posted)?
Are you saying this doesn't fit well in wxPython, that it's more
natural in that environment to communicate via event codes and queues?

If that's the case, can you define a model-controller class which
receives view events and translates them into method invocations on
an associated model? That does seem like a lot of work, but it
would let the model remain ignorant of -- loosely coupled to --
its observers.
My experience in this area is that this sort
of reliance on synthetic events is *not* "a
lot of work". I've found it quite rewarding.
You're right: it healthily decouples M from
V from C.
.
.
.
 
B

Brian Kelley

Maybe when I say polling it is a symantic issue, but in your run method:

def run(self):
"""Run the model in its own thread."""
self._stopped = 0
i = 0.0
di = math.pi / 8.0
while not self._stopped:
self._state.value = math.sin(i)
i += di

isn't "while not self._stopped" a method of polling? This variable is
still set asynchronously. This is the only point that I was getting at,
for example if you had many states in your model that can be altered,
you still need to periodically check to affect these state changes.

Yes. This is actually what I do. I still consider this in the realm of
polling though (note that I am talking about receiving the model change
event and converting it to a method invocation here). Using the normal
threading model the polling is hidden as in your example, i.e. keep
checking to see if self._stopped is true.

Perhaps polling is not the proper way to talk about this. But, tt helps
my mental picture of what is going on though in that "at any time there
could be a state change so if you are interested in it, keep checking
for the change." This mental picture works (for me at least) when
running the model in a thread or running the model in another process.

Brian.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,777
Messages
2,569,604
Members
45,227
Latest member
Daniella65

Latest Threads

Top