Zeus is a kernel for both multi-view editors and algorithm animation systems. To a first approximation, it follows the spirit of BALSA, but implemented in a multi-threaded environment using an object-oriented, strongly-typed language.
For historical reasons, it is the ZeusPanel and ZeusClass interfaces, not this interface, that are of primary interest to clients. This interface explains what's going on behind the scenes; it is used by ZeusPanel (to provide a system for algorithm animation ) and systems doing multi-view editing (like the Modula-2+ version of formsedit). If you are animating an algorithm and find yourself needing to import this interface, then something is probably wrong. Let us know.
INTERFACEOverview...Zeus ; IMPORT Algorithm, RefList, Thread, View, ZeusClass; <* PRAGMA LL *>
Zeus provides a domain-independent framework and methodology for
associating multiple client-defined views
with sets of client-defined
events.
A view is implemented as a ZeusClass.T with additional suites
of procedures to handle client-defined events. The appropriate
event-handling procedure is invoked by Zeus in response to an event
generated by one of the other views.
One of the views is designated as the algorithm.
This view is special
because it acts as the server
for event distribution: events that
originate in the algorithm are distributed to all views, and events that
originate in a view are distributed to the algorithm. The algorithm is
also special because it can never be detached from its instance, once
the instance is created.
Clients who use Zeus for algorithm animation should probably use the
ZeusPanel interface. That interface provides a control panel
in a
Trestle window. The control panel allows the user to choose algorithms
and views, and to control the execution of algorithms. It also provides
a very simple client-programmer interface.
Clients who use Zeus for controling a multi-view editor should use this interface directly. The ControlPanel implementation is a good example of how this interface is used in practice.
By now, you are probably wondering how algorithm animation differs from multi-view editing. The basic difference is that in multi-view editing all events are generated in response to user actions in views. In algorithm animation, a subroutine associated with the algorithm is invoked to generate events. When using the ZeusPanel interface, the user can single-step or suspend the execution in terms of events generated by algorithms.
TYPE (* Access to a Session is controlled by a reader/writer lock. *) Session <: PublicSession; PublicSession = ROOT OBJECT alg : Algorithm.T := NIL; views: RefList.T (* of View.T *) := NIL; METHODS init (): Session; pre (initiator: ZeusClass.T; style : EventStyle; priority : INTEGER; t : TEXT ) RAISES {Thread.Alerted}; post (initiator: ZeusClass.T; style : EventStyle; priority : INTEGER; t : TEXT ) RAISES {Thread.Alerted}; END;
ZeusPanel uses priority for determining event for Stepping; it's likely that other clients of Zeus will not need to use it.
CONST MaxPriority = 9; TYPE EventStyle = {Output, Update, Edit, Notify, Broadcast, Code};About events...
There are events that go from an algorithm to all views, and events from a view to the algorithm. There's also a special event, called a Broadcast event, that goes first to the algorithm, and then to all views.
There are three types of events that go from an algorithm to views: Output, Update, and Code. The Output events and Code events are for algorithm animations that utilize the ZeusPanel. The ZeusPanel will gain control before and after each event, in order to implement Stepping, Stopping, and Resuming. Code events are used, along with the ZeusCodeView interface, for textual display of the program that the algorithm is executing. Update events bypass the ZeusPanel. They are used for responding to events from a view, usually to dispatch information to all other views.
There are two types of events that go from a view to its algorithm: Edit and Notify. Notify events are used in algorithm animation; Edit events are not. Whenever a view interprets some user gestures into an action, it generates either an Edit or Notify event to the algorithm. The algorithm makes the appropriate changes to its data structures, and then generates an Update event to the views. The views update themselves based on these events. (In response to an Edit, Notify, or Update event, the event handler can inquire which view initiated the editing action.)
Each Zeus instance has an editing lock. The lock prevents other views from issuing Edit events while another view is in the midst of a command that requires multiple Edit events. When an Edit event is attempted while the editing lock is held by some other view, an exception is raised. The Notify event should be used to bypass the editing lock (for example, for a view to set a selection).
Here's a summary of the six types of events in Zeus:
* Output: alg -> views, with ZeusPanel intervention
* Code: alg -> views (ZeusCodeView only), with ZeusPanel intervention
* Update: alg -> views, bypassing ZeusPanel (responding to Edit/Notify)
* Edit: view -> alg, checking the edit lock
* Notify: view -> alg, bypassing the edit lock
* Broadcast: view -> alg, bypassing the edit lock alg -> views, bypassing ZeusPanel
In some special circumstances, it is reasonable for views to directly communicate with each other (e.g., only one of the views supports Undo). It's also reasonable for algorithms to directly communicate with a particular view (e.g., to bring it up to date when it is first installed).
About concurrency and locking...
Most of the procedures in this interface are called when VBT.mu is held. That is, they are intended to be called in reponse to some user mouse or keyboard activity. Although not documented as such, they may not be called concurrently.
A notable exception is that Output events are not necessarily generated with LL=VBT.mu, and it isn't reasonable to lock the window system on each event. Consequently, this interface does its own locking to ensure that ChangeAlg, AttachView, DetachView, and Destroy cannot be entered while there is an event (an event of any flavor) in progress; conversely, calls to ChangeAlg, AttachView, DetachView, or Destroy are serialized, and will block any call to Dispatch.
While on the subject of locking, it's important to keep in mind that repaint and reformat requests arrive from the window system asynchronously with respect to the executing algorithm. (They do happen with VBT.mu locked by some ancestor, however.) It's the responsibility of each view to synchronize its repaint and reformat requests with Output events that it processes. If a view uses an algorithm's data structures (as opposed to local copies), it must synchronize its access to these structures in its repaint and reformat procedures.
Multi-threaded algorithms require a bit more work. By design, Zeus does not serialize any of the Output events. Therefore, multiple Output events may happen simultaneously, while other threads in the algorithm are still executing. The view must be careful to coordinate access to both the data structures it shares with the algorithm and the data structures that various event handlers share.
EXCEPTION Error(REFANY);
This exception should be raised by an event handling procedure to report back to the initiator of the event. Although Update and Output events invoke the event handlers of multiple views, it reports back to the algorithm a single exception, chosen non-deterministically from among all views that raised the exception.
PROCEDURE AttachAlg (zeus: Session; alg: Algorithm.T);
Attach the specified alg to this session. Session is put on alg's property list. From now on, alg will get all Edit and Notify events generated by views attached to the instance. The pre method, if non-NIL, will be invoked just after Dispatch is entered with Dispatch's parameters initiator, style, and eventName; the postProc, if non-NIL, will be invoked on just before Dispatch is exited, with the same parameters. However, neither preProc nor postProc will be called for events whose style = Broadcast. LL = VBT.mu
PROCEDURE AttachView (zeus: Session; view: View.T);
Attach the specified view to this session. Session is put on view's property list. Then, the session's alg and all attached views (including the new one) are informed of the new view, by invoking each one's ConfigProc. From here on, view will get all Update and Output events generated by zeus's alg. LL = VBT.mu
PROCEDURE DetachView (view: View.T);
Detach the specified view from its session and remove the session from view's property list. Inform all remaining attached views this by invoking each one's ConfigProc. From here on, view will not be informed of any events generated by this instance's alg. If view was not previously attached, then this procedure is a noop. LL = VBT.mu
PROCEDURE Destroy(zeus: Session); (* Detach all views from the instance as in DetachView, and then detach the alg itself and remove zeus from its method list. This procedure is more efficient than calling DetachView multiple times: the ConfigProc of the instance's alg and views are never invoked. LL = VBT.mu
*) PROCEDURE Initiator (session: Session): ZeusClass.T;
Who initiated the current editing action?
This procedure is undefined
if called while an Edit, Notify, or Broadcast event is not active. LL =
VBT.mu
PROCEDURE Resolve (v: ZeusClass.T): Session;
Return the zeus session to which v belongs. LL = VBT.mu
**** Accessing a session (read-only) ****
PROCEDURE Acquire (zeus: Session); PROCEDURE Release (zeus: Session); <* LL <= VBT.mu *>
Access to the list of views is controlled by a reader/writer lock. AttachView and DettachView uses a writer lock to modify the list of views; dispatching events uses a reader lock. If a client needs to look at the fields of zeus, it must do so under a reader lock, acquired and released by these procedures.The lock acquired and released by these procedures is > VBT.mu. That is, code that uses these procedures is not allowed to lock VBT.mu while the reader/writer lock is held.
**** Synchronizing Editing Actions ****
EXCEPTION Locked(TEXT);
Used by Dispatch to report that the editing lock is held by a view other than the one initiating the editing event.
PROCEDURE Lock (zeus: Session; view: View.T; msg: TEXT): BOOLEAN;
If the editing lock is owned by any view, return FALSE. Otherwise, lock it for the specified view, notify all views that the lock has changed, and return TRUE. Whenever editing action is attempted by a view other than the one holding the lock, Error is raised with the specified text. LL = VBT.mu
PROCEDURE Unlock (zeus: Session; view: View.T): BOOLEAN;
If the editing lock is not owned by view, return FALSE. Otherwise, unlock it, notify all views that the lock has changed, and return TRUE. LL = VBT.mu
PROCEDURE LockInfo (zeus: Session; VAR view: View.T; VAR msg: TEXT): BOOLEAN;
If the editing lock is not owned by any view, return FALSE. Otherwise, set view to the view that owns the lock, set msg to the message that was registered when the lock was acquired, and return TRUE. LL = VBT.mu
PROCEDURE IsLocked (zeus: Session): BOOLEAN;
Is the editing lock held by any view?
This procedure returns the same
value as LockInfo, but the caller doesn't need to find out who owns the
lock and what message was registered when the lock was acquired. LL =
VBT.mu
**** Dispatching Events ****
TYPE DispatchProc = PROCEDURE (z: ZeusClass.T; args: REFANY) RAISES {Thread.Alerted}; PROCEDURE Dispatch (initiator : ZeusClass.T; style : EventStyle; priority : INTEGER; eventName : TEXT; dispatchProc: DispatchProc; evtArgs : REFANY ) RAISES {Error, Locked, Thread.Alerted};
Dispatch is called by an IE routine, the body of which was generated by the zume preprocessor. The initiator and style are provided by the client as arguments to the IE; the other arguments are created by the IE routine iteself. The pre-dispatch callback will be called, if one has been registered. Then dispatchProc will be called for the appropriate ZeusVBTs to invoke v with the specified evtArgs, properly unpackaged. Finally, the post-dispatch callback will be called, if one has been registered.Output, Code, and Broadcast events will be called with LL < VBT.mu (i.e., VBT.mu not locked); Update, Edit, and Notify events will be called with LL = VBT.mu. The same LL must hold when Dispatch is called.
**** Miscellaneous ****
VAR stdoutMu: MUTEX; stderrMu: MUTEX;
Views that write to stdout or stderr can use these mutices to serialize themselves. A Wr.Flush should be called after each print statement and before the lock is released.
END Zeus.