The library offers some support for using an actor as a model for
Morphs in Squeak’s
Morphic GUI systems.
Morph can be configured to use an
ActorProxyModel as its model, meaning that when
information is needed from the model for UI display and interaction,
requests are sent to an actor.
Morphic support is experimental.
I am a novice when it comes to Morphic programming. I am no doubt making incorrect assumptions about how it is supposed to work. I’d very much appreciate some help in improving the interaction between Morphic and actors.
Challenges in integrating Morphic with Actors
Morphic really isn’t set up for an actor-style approach to concurrency.
The main problem is that morphs and model objects must run in the UI process, because some of the interplay between morphs and their models is synchronous.
In particular, there’s the back-and-forth dance between
update: that one must remain painfully aware of.
Normal Morphic models use
changed to cause a synchronous
notification to be delivered to dependents (i.e. morphs), in the form
of calls to their
update: methods. This causes problems for using an
actor as a model in two ways:
- The call to
update:has to happen on the UI process, while the call to
changedmust run on the actor’s own process.
- The calls to
update:cannot be synchronous in an actor system, because the view may wish to call back into the actor to read its updated state, and if the actor is blocked waiting for a reply to the
update:message, deadlock will occur.
Synchronous UI actions
It is quite easy to lock up the user interface while developing. The
interrupt key (
Alt-.) is helpful when this happens. However, don’t
rely on it: from time to time you will simply have to kill and restart
your VM. Save often.
Model callbacks invoked by morphs are synchronous, waiting for an
answer from the model before the UI becomes responsive again. For
example, clicking on a
PluggableButtonMorph causes an “action”
selector to be invoked on the morph’s model, and the UI pauses until
that call returns.
A similar, simpler example is the kind of lockup that can occur if you
run a blocking do-it in a Workspace. For example, the following
creates a new actor, and immediately invokes its
halt method in the
actor’s process. The UI process then waits for the reply. Because the
blocking, the UI process will wait forever for the
Promise to be resolved.
ActorBehavior spawn blocking halt.
However, the implementation of
halt queues a request to the UI
process to open a debugger—but the UI process is waiting for the actor
to finish before it checks its work queue.
Alt-. interrupts the wait, bringing up a debugger on the
do-it and allowing the actor’s
halt debugger to open.
In addition, I have implemented heuristic support for preemptively
interrupting the UI process when a
halt happens in a situation where
it is likely that the UI process is waiting for the thing that has
Perhaps (post-hoc: following the logic that exceptions traverse links)
halt should halt all causally dependent processes as well?
Ultimately, new tooling will be needed to properly show the
branching contexts that arise once there are cross-process
DemoSocketTerminal implements a simple TCP/IP terminal program
that can connect to an arbitrary host and port. Input from the server
is displayed in the main panel, and input from the user is accepted in
the lower input text field.
The implementation of
DemoSocketTerminal relies on a component
TerminalOutputMorphActor, which backs a
with an actor. That actor in turn accepts
appendText: messages. A
TerminalOutputMorphActor is the implementation of the main panel in
In the diagram above, on the left we see ordinary Morphic structure,
PluggableSystemWindow having five submorphs. Four of those
submorphs, and the window itself, have an
DemoSocketTerminal actor as their model. The remaining morph,
the large output panel, has an
ActorProxyModel connected to the
TerminalOutputMorphActor as its model.
When connected, the
DemoSocketTerminal has an associated
SocketActor in its
sock instance variable, which in turn has a
pair of worker actors for performing blocking reading and writing
The double orange lines in the diagram denote links between actors. When any actor dies, all the actors linked to it are terminated automatically.
Actors inheriting from
MorphActor, as both
DemoSocketTerminal do, enjoy a
abandons their associated morph. Hence, if the
DemoSocketTerminal actor terminates, its window is automatically
ActorProxyModel is a subclass of
Model, part of the Morphic user
Its purpose is to act as a model for a morph, relaying Morphic callbacks to an actor, thus reconciling the tension between Morphic models needing to be run in the UI process and Actors running in separate processes.
ActorProxyModel are normally constructed by instances
The approach of using an
ActorProxy (or a
BlockingTransientActorProxy) as the model for a morph fails for a
Promises for callback invocations, which Morphic is not expecting;
- a terminated
Actorinvoked via its
Promiserejections and signalled
BrokenPromiseexceptions, which Morphic doesn’t expect; and
BlockingTransientActorProxywaits forever for a reply, which can lead to UI deadlock.
These reasons motivate the existence of
ActorProxyModel directly handle reasons 1 and 3 by way of their
proxySend: method, and handle reason 2 by way of their
method and related functionality.
Relationship between ActorProxyModel and its Actor
ActorProxyModel holds an
ActorProxy for its actor in its
proxy instance variable.
ActorProxyModel installs itself as a dependent of
its actor, via
proxy addDependent: self. When the actor calls
changed on itself, this will trigger the
In turn, the
update:with: methods on
are relayed to its own
relaying the change notification to the dependents of the
ActorProxyModel, such as its associated morphs.
Synchronous requests to the actor
When morphic calls methods on the actor, it blocks waiting for the
reply. In order to avoid some cases of UI lockup, by default
ActorProxyModel makes such calls with a five-second timeout.
Stub behavior after actor termination
In some cases, it is necessary for an
ActorProxyModel to respond to
requests after its associated actor has terminated. Actors may install
an ad-hoc dictionary of behavior by calling
#proxyStub:. Following a call to
will no longer invoke its actor, instead preferring to call blocks in
the ad-hoc dictionary to respond to morphic model methods.
An example of this can be seen in senders and implementors of
MorphActor >> #buildProxyStub.
MorphActor is an Actor behavior responsible for acting as a model to
a Morph, mediated by the
ActorProxyModel held in the
MorphActor’s associated Morph(s) have
model as their model. The
model then delegates their requests to the
MorphActor, taking care
of timeouts, promises, and so forth.
MorphActor to add application-specific functionality. For
Subclasses should implement:
buildSpecWith:(mandatory), to produce a build specification for constructing their main Morph. See
MorphActor >> #openfor the use of
customizeMorph:builtWith:(optional), to invoke methods on newly-constructed morphs that were not catered for in the pluggable-spec API.
buildProxyStub(optional), to provide custom behavior in case the actor terminates but morphic still needs to access the model in the moments before the view is destroyed.
postExitCleanup: method of
MorphActor destroys its main Morph
when the actor terminates.