Using MVC when the model is dynamic

Collapse
This topic is closed.
X
X
 
  • Time
  • Show
Clear All
new posts
  • pysim

    Using MVC when the model is dynamic

    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.
  • Mitch Chapman

    #2
    Re: Using MVC when the model is dynamic

    pysim wrote:[color=blue]
    > 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.[/color]

    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 DelayedObservab le(Observable):
    ...
    def __init__(self, notifyInterval= 1.0):
    self._tLastChan ge = time.time()
    self._notifyInt erval = notifyInterval # Seconds

    def _notify(self, force=0):
    dt = time.time() - self._tLastChan ge
    if force or (dt >= self._notifyInt erval):
    for observer in self._observers :
    ...
    self._tLastChan ge = 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 NSNotificationQ ueue. 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(obje ct):
    """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(sel f, newObserver):
    self._observers .add(newObserve r) # This oughtta be locked...

    def removeObserver( self, anObserver):
    self._observers .remove(anObser ver) # 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.acqu ire()
    result = self._value
    self._lock.rele ase()
    return result

    def _setValue(self, newValue):
    self._lock.acqu ire()
    self._value = newValue
    self._lock.rele ase()
    self._notify()

    value = property(_getVa lue, _setValue, None, "The observable value")


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

    def onStateChange(s elf, observer):
    self._state.add Observer(observ er)

    def removeStateChan ge(self, observer):
    self._state.rem oveObserver(obs erver)

    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.val ue = 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 modelStateChang ed(self, modelState):
    valueBar = " " * int((1 + modelState.valu e) * 10)
    print "%s#" % valueBar

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

    def handleInput(sel f, 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._terminate d = 0

    self._model.onS tateChange(self ._view.modelSta teChanged)
    self._view.onQu it(self._quitAp p)

    def run(self):
    self._model.sta rt()

    self._terminate d = 0
    while not self._terminate d:
    ins, outs, errs = select.select([sys.stdin], [], [])
    if ins:
    self._view.hand leInput(raw_inp ut())

    self._model.joi n()

    def _quitApp(self, *args):
    self._terminate d = 1
    self._model.sto p()
    self._model.rem oveStateChange( self._view.mode lStateChanged)


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

    if __name__ == "__main__":
    main()

    Comment

    • Brian Kelley

      #3
      Re: Using MVC when the model is dynamic

      Mitch Chapman wrote:
      [color=blue]
      > 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.[/color]

      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 LongRunningTask s 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

      Comment

      • Cameron Laird

        #4
        Re: Using MVC when the model is dynamic

        In article <3f7af8bf$0$564 $b45e6eb0@senat or-bedfellow.mit.e du>,
        Brian Kelley <bkelley@wi.mit .edu> wrote:

        Comment

        • Brian Kelley

          #5
          Re: Using MVC when the model is dynamic

          Cameron Laird wrote:[color=blue][color=green]
          >>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
          >>occasionall y poll for new instructions from the view or have it's own
          >>event-style manager.
          >>[/color]
          > 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?[/color]

          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

          Comment

          • Mitch Chapman

            #6
            Re: Using MVC when the model is dynamic

            Brian Kelley wrote:[color=blue]
            > Cameron Laird wrote:
            >[color=green][color=darkred]
            >>> 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.
            >>>[/color]
            >> 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?[/color]
            >
            >
            > 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 :)[/color]

            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 responsibilitie s 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.

            --
            Mitch

            Comment

            • Cameron Laird

              #7
              Re: Using MVC when the model is dynamic

              In article <YkPeb.10891$RW 4.8963@newsread 4.news.pas.eart hlink.net>,
              Mitch Chapman <mitchchapman@e arthlink.net> wrote:

              Comment

              • Brian Kelley

                #8
                Re: Using MVC when the model is dynamic

                Cameron Laird wrote:
                [color=blue]
                > In article <YkPeb.10891$RW 4.8963@newsread 4.news.pas.eart hlink.net>,
                > Mitch Chapman <mitchchapman@e arthlink.net> wrote:
                >[color=green]
                >>I'm confused by the comments about the model needing to poll for new
                >>instruction s from the view. (BTW does this mean you usually prefer to
                >>combine view and controller responsibilitie s in a single entity, rather
                >>than implement them separately?)[/color]
                >[/color]

                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.val ue = 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.
                [color=blue][color=green]
                >>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.[/color][/color]

                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.

                Comment

                Working...