(* Copyright 1992 Digital Equipment Corporation. *)
(* Distributed only by permission. *)
(* Last modified on Wed Jun 23 15:02:34 PDT 1993 by steveg  *)
(*      modified on Thu Feb 11 17:52:22 PST 1993 by johnh   *)
(* modified on Wed May 13 07:42:05 1992 by mhb *)


(* 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. *)

INTERFACE Zeus;

IMPORT Algorithm, RefList, Thread, View, ZeusClass;
<* PRAGMA LL *>

(* Overview...

   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.
