(* Ulm's Oberon Library
   Copyright (C) 1989-1994 by University of Ulm, SAI, D-89069 Ulm, Germany
   ----------------------------------------------------------------------------
   Ulm's Oberon Library is free software; you can redistribute it
   and/or modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either version
   2 of the License, or (at your option) any later version.

   Ulm's Oberon Library is distributed in the hope that it will be
   useful, but WITHOUT ANY WARRANTY; without even the implied warranty
   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with this library; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
   ----------------------------------------------------------------------------
   E-mail contact: oberon@mathematik.uni-ulm.de
   ----------------------------------------------------------------------------
   $Id: Events.om,v 1.4 2004/03/30 17:48:14 borchert Exp $
   ----------------------------------------------------------------------------
   $Log: Events.om,v $
   Revision 1.4  2004/03/30 17:48:14  borchert
   support of external queue handling added

   Revision 1.3  1996/01/04 17:07:20  borchert
   event types are now an extension of Services.Object

   Revision 1.2  1994/07/18  14:17:17  borchert
   unused variables of Raise (oldevent + newevent) removed

   Revision 1.1  1994/02/22  20:07:41  borchert
   Initial revision

   ----------------------------------------------------------------------------
   AFB 8/89
   ----------------------------------------------------------------------------
*)

MODULE ulmEvents;

   IMPORT Objects := ulmObjects, Priorities := ulmPriorities, Services := ulmServices, SYS := ulmSYSTEM, SYSTEM;

   TYPE
      EventType* = POINTER TO EventTypeRec;

   CONST
      (* possibilities on receipt of an event: *)
      default* = 0;		(* causes abortion *)
      ignore* = 1;		(* ignore event *)
      funcs* = 2;		(* call associated event handlers *)

   TYPE
      Reaction* = INTEGER;	(* one of default, ignore, or funcs *)
      Message* = ARRAY 80 OF CHAR;
      Event* = POINTER TO EventRec;
      EventRec* =
	 RECORD
	    (Objects.ObjectRec)
	    type*: EventType;
	    message*: Message;
	    (* private part *)
	    next: Event;		(* queue *)
	 END;
      EventHandler = PROCEDURE (event: Event);

      (* event managers are needed if there is any action necessary
	 on changing the kind of reaction
      *)
      EventManager = PROCEDURE (type: EventType; reaction: Reaction);

      Priority = INTEGER; (* must be non-negative *)

      (* every event with reaction `funcs' has a handler list;
	 the list is in calling order which is reverse to
	 the order of `Handler'-calls
      *)
      HandlerList = POINTER TO HandlerRec;
      HandlerRec* =
	 RECORD
	    (Objects.ObjectRec)
	    handler*: EventHandler;
	    next*: HandlerList;
	 END;
      SaveList = POINTER TO SaveRec;
      SaveRec =
	 RECORD
	    reaction: Reaction;
	    handlers: HandlerList;
	    next: SaveList;
	 END;

      EventTypeRec* =
	 RECORD
	    (Services.ObjectRec)
	    (* private components *)
	    handlers: HandlerList;
	    priority: Priority;
	    reaction: Reaction;
	    manager:  EventManager;
	    savelist: SaveList;
	 END;

      Queue = POINTER TO QueueRec;
      QueueRec =
	 RECORD
	    priority: INTEGER;		(* queue for this priority *)
	    head, tail: Event;
	    next: Queue;		(* queue with lower priority *)
	 END;

   VAR
      eventTypeType: Services.Type;
   
   CONST
      priotabsize = 256;	(* size of a priority table *)
      maxnestlevel = 1024;	(* of Raise-calls (avoids endless recursion) *)

   TYPE
      (* in some cases coroutines uses local priority systems *)
      PrioritySystem* = POINTER TO PrioritySystemRec;
      PrioritySystemRec* =
	 RECORD
	    (Objects.ObjectRec)
	    (* private part *)
	    currentPriority: Priority;
	    priotab: ARRAY priotabsize OF Priority;
	    priotop: INTEGER;
	    overflow: INTEGER;	(* of priority table *)
	 END;

   CONST
      priorityViolation* = 0; (* priority violation (EnterPriority *)
      unbalancedExitPriority* = 1; (* unbalanced call of ExitPriority *)
      unbalancedRestoreReaction* = 2; (* unbalanced call of RestoreReaction *)
      negPriority* = 3; (* negative priority given to SetPriority *)
      errorcodes* = 4;

   TYPE
      ErrorEvent* = POINTER TO ErrorEventRec;
      ErrorEventRec* =
	 RECORD
	    (EventRec)
	    errorcode*: SHORTINT;
	 END;

   VAR
      errormsg*: ARRAY errorcodes OF Message;
      error*: EventType;

   VAR
      (* private part *)
      abort, log, queueHandler: EventHandler;
      nestlevel: INTEGER;	(* of Raise calls *)
      queue: Queue;
      lock: BOOLEAN;		(* lock critical operations *)
      psys: PrioritySystem;	(* current priority system *)

   PROCEDURE ^ Define*(VAR type: EventType);
   PROCEDURE ^ SetPriority*(type: EventType; priority: Priority);
   PROCEDURE ^ Raise*(event: Event);

   PROCEDURE InitErrorHandling;
   BEGIN
      Define(error); SetPriority(error, Priorities.liberrors);
      errormsg[priorityViolation] :=
	 "priority violation (Events.EnterPriority)";
      errormsg[unbalancedExitPriority] :=
	 "unbalanced call of Events.ExitPriority";
      errormsg[unbalancedRestoreReaction] :=
	 "unbalanced call of Events.RestoreReaction";
      errormsg[negPriority] :=
	 "negative priority given to Events.SetPriority";
   END InitErrorHandling;

   PROCEDURE Error(code: SHORTINT);
      VAR event: ErrorEvent;
   BEGIN
      NEW(event); event.type := error;
      event.message := errormsg[code];
      event.errorcode := code;
      Raise(event);
   END Error;

   PROCEDURE NilEventManager(type: EventType; reaction: Reaction);
   END NilEventManager;

   PROCEDURE Init*(type: EventType);
      VAR
	 stype: Services.Type;
   BEGIN
      Services.GetType(type, stype); ASSERT(stype # NIL);
      type.handlers := NIL;
      type.priority := Priorities.default;
      type.reaction := default;
      type.manager := NilEventManager;
      type.savelist := NIL;
   END Init;

   PROCEDURE Define*(VAR type: EventType);
      (* definition of a new event;
	 an unique event number is returned;
	 the reaction on receipt of `type' is defined to be `default'
      *)
   BEGIN
      NEW(type);
      Services.Init(type, eventTypeType);
      Init(type);
   END Define;

   PROCEDURE GetReaction*(type: EventType) : Reaction;
      (* returns either `default', `ignore', or `funcs' *)
   BEGIN
      RETURN type.reaction
   END GetReaction;

   PROCEDURE SetPriority*(type: EventType; priority: Priority);
      (* (re-)defines the priority of an event *)
   BEGIN
      IF priority <= 0 THEN
	 Error(negPriority);
      ELSE
	 type.priority := priority;
      END;
   END SetPriority;

   PROCEDURE GetEventPriority*(type: EventType) : Priority;
      (* return the priority of the given event *)
   BEGIN
      RETURN type.priority
   END GetEventPriority;

   PROCEDURE Manager*(type: EventType; manager: EventManager);
   BEGIN
      type.manager := manager;
   END Manager;

   PROCEDURE Handler*(type: EventType; handler: EventHandler);
      (* add `handler' to the list of handlers for event `type' *)
      VAR
	 newhandler: HandlerList;
   BEGIN
      NEW(newhandler);
      newhandler.handler := handler; newhandler.next := type.handlers;
      type.handlers := newhandler;
      IF type.reaction # funcs THEN
	 type.reaction := funcs; type.manager(type, funcs);
      END;
   END Handler;

   PROCEDURE RemoveHandlers*(type: EventType);
      (* remove list of handlers for event `type';
	 implies default reaction (abortion) on
	 receipt of `type'
      *)
   BEGIN
      type.handlers := NIL;
      IF type.reaction # default THEN
	 type.reaction := default; type.manager(type, default);
      END;
   END RemoveHandlers;

   PROCEDURE Ignore*(type: EventType);
      (* implies RemoveHandlers(type) and causes receipt
	 of `type' to be ignored
      *)
   BEGIN
      type.handlers := NIL;
      IF type.reaction # ignore THEN
	 type.reaction := ignore; type.manager(type, ignore);
      END;
   END Ignore;

   PROCEDURE GetHandlers*(type: EventType; handlers: HandlerList);
      (* returns the list of handlers in `handlers';
	 the reaction of `type' must be `funcs'
      *)
   BEGIN
      handlers := type.handlers;
   END GetHandlers;

   PROCEDURE Log*(loghandler: EventHandler);
      (* call `loghandler' for every event;
	 subsequent calls of `Log' replace the loghandler;
	 the loghandler is not called on default and ignore
      *)
   BEGIN
      log := loghandler;
   END Log;

   PROCEDURE GetLog*(VAR loghandler: EventHandler);
      (* returns the loghandler set by `Log' *)
   BEGIN
      loghandler := log;
   END GetLog;

   PROCEDURE NilHandler*(event: Event);
      (* an empty event handler *)
   END NilHandler;

(* now QueueHandler will translate partly like
        BOOLEAN b;
        handler_EventHandler tmphandler;
        LONGINT i, j;
        i = (LONGINT)handler;
        tmphandler = handler_NilHandler;
        j = (LONGINT)tmphandler;
        b = i != j;
*)
(* changed because voc cannot compara handler and NilHandler -- noch *)

   PROCEDURE QueueHandler*(handler: EventHandler);
      (* setup an alternative handler of events
	 that cannot be processed now because
	 of their unsufficient priority
      *)
      VAR b : BOOLEAN; (* noch *)
      tmphandler : EventHandler;
      i,j : LONGINT;
   BEGIN
      i := SYSTEM.VAL(LONGINT, handler);
      tmphandler := NilHandler;
      j := SYSTEM.VAL(LONGINT, tmphandler);
      b := i # j;
      b := handler # tmphandler;
      (*ASSERT (handler # NilHandler);*)
      ASSERT(b);
      queueHandler := handler;
   END QueueHandler;

   PROCEDURE AbortHandler*(handler: EventHandler);
      (* defines the handler to be called on abortion *)
   BEGIN
      abort := handler;
   END AbortHandler;

   PROCEDURE GetAbortHandler*(VAR handler: EventHandler);
      (* returns the handler set by `AbortHandler' *)
   BEGIN
      handler := abort;
   END GetAbortHandler;

   PROCEDURE ^ CallHandlers(event: Event);

   PROCEDURE WorkupQueue;
      VAR
	 ptr: Event;
   BEGIN
      WHILE (queue # NIL) & (queue.priority > psys.currentPriority) DO
	 IF SYS.TAS(lock) THEN RETURN END;
	 ptr := queue.head; queue := queue.next;
	 lock := FALSE;
	 WHILE ptr # NIL DO
	    CallHandlers(ptr);
	    ptr := ptr.next;
	 END;
      END;
   END WorkupQueue;

   PROCEDURE CallHandlers(event: Event);
      VAR
	 ptr: HandlerList;
	 oldPriority: Priority;
   BEGIN
      CASE event.type.reaction OF
      | default:  abort(event);
      | ignore:
      | funcs:    oldPriority := psys.currentPriority;
		  psys.currentPriority := event.type.priority;
		  log(event);
		  ptr := event.type.handlers;
		  WHILE ptr # NIL DO
		     ptr.handler(event);
		     ptr := ptr.next;
		  END;
		  psys.currentPriority := oldPriority;
      END;
   END CallHandlers;

   PROCEDURE Raise*(event: Event);
      (* call all event handlers (in reverse order)
	    associated with event.type;
	 abort if there are none;
	 some system events may abort in another way
	    (i.e. they do not cause the abortion handler to be called)
      *)
      VAR
	 priority: Priority;

      PROCEDURE AddToQueue(event: Event);
	 VAR
	    prev, ptr: Queue;
      BEGIN
	 event.next := NIL;
	 ptr := queue; prev := NIL;
	 WHILE (ptr # NIL) & (ptr.priority > priority) DO
	    prev := ptr;
	    ptr := ptr.next;
	 END;
	 IF (ptr # NIL) & (ptr.priority = priority) THEN
	    ptr.tail.next := event;
	    ptr.tail := event;
	 ELSE
	    NEW(ptr);
	    ptr.priority := priority;
	    ptr.head := event; ptr.tail := event;
	    IF prev = NIL THEN
	       ptr.next := queue;
	       queue := ptr;
	    ELSE
	       ptr.next := prev.next;
	       prev.next := ptr;
	    END;
	 END;
      END AddToQueue;

   BEGIN (* Raise *)
      INC(nestlevel);
      IF nestlevel >= maxnestlevel THEN
	 abort(event);
      ELSE
	 IF event.type.reaction # ignore THEN
	    priority := event.type.priority;
	    IF psys.currentPriority < priority THEN
	       CallHandlers(event); WorkupQueue;
	    ELSIF queueHandler # NIL THEN
	       queueHandler(event);
	    ELSIF ~SYS.TAS(lock) THEN
	       AddToQueue(event);
	       lock := FALSE;
	    END;
	 END;
      END;
      DEC(nestlevel);
   END Raise;

   PROCEDURE CreatePrioritySystem*(VAR prioritySystem: PrioritySystem);
      (* create and initialize a new priority system *)
   BEGIN
      NEW(prioritySystem);
      prioritySystem.currentPriority := Priorities.base;
      prioritySystem.priotop := 0;
   END CreatePrioritySystem;

   PROCEDURE CurrentPrioritySystem*() : PrioritySystem;
      (* return the priority system currently active *)
   BEGIN
      RETURN psys
   END CurrentPrioritySystem;

   PROCEDURE SwitchPrioritySystem*(prioritySystem: PrioritySystem);
      (* switch to another priority system; this is typically
	 done in case of task switches
      *)
   BEGIN
      psys := prioritySystem;
   END SwitchPrioritySystem;

   PROCEDURE EnterPriority*(priority: Priority);
      (* sets the current priority to `priority';
	 it is an error to give a priority less than
	 the current priority (event `badpriority')
      *)
   BEGIN
      IF psys.currentPriority <= priority THEN
	 IF (psys.overflow = 0) & (psys.priotop < priotabsize) THEN
	    psys.priotab[psys.priotop] := psys.currentPriority;
	    INC(psys.priotop);
	    psys.currentPriority := priority;
	 ELSE
	    INC(psys.overflow);
	 END;
      ELSE
	 Error(priorityViolation);
	 INC(psys.overflow);
      END;
   END EnterPriority;

   PROCEDURE AssertPriority*(priority: Priority);
      (* current priority
	     <  priority:  set the current priority to `priority'
	     >= priority:  the current priority remains unchanged
      *)
   BEGIN
      IF (psys.overflow = 0) & (psys.priotop < priotabsize) THEN
	 psys.priotab[psys.priotop] := psys.currentPriority; INC(psys.priotop);
	 IF psys.currentPriority < priority THEN
	    psys.currentPriority := priority;
	 END;
      ELSE
	 INC(psys.overflow);
      END;
   END AssertPriority;

   PROCEDURE ExitPriority*;
      (* causes the priority before the last effective call
	 of SetPriority or AssertPriority to be restored
      *)
   BEGIN
      IF psys.overflow > 0 THEN
	 DEC(psys.overflow);
      ELSIF psys.priotop = 0 THEN
	 Error(unbalancedExitPriority);
      ELSE
	 DEC(psys.priotop); psys.currentPriority := psys.priotab[psys.priotop];
	 WorkupQueue;
      END;
   END ExitPriority;

   PROCEDURE GetPriority*() : Priority;
      (* returns the current priority *)
   BEGIN
      RETURN psys.currentPriority
   END GetPriority;

   PROCEDURE SaveReaction*(type: EventType);
      (* saves current reaction until call of RestoreReaction;
	 the new reaction of `type' is defined to be `ignore'
	 but can be changed by Events.Handler or Events.RemoveHandlers
      *)
      VAR
	 savelist: SaveList;
   BEGIN
      NEW(savelist);
      savelist.reaction := type.reaction;
      savelist.handlers := type.handlers;
      savelist.next := type.savelist;
      type.savelist := savelist;
      type.handlers := NIL;
      IF type.reaction # ignore THEN
	 type.reaction := ignore; type.manager(type, ignore);
      END;
   END SaveReaction;

   PROCEDURE RestoreReaction*(type: EventType);
      (* restores old reaction;
	 must be properly nested
      *)
      VAR
	 savelist: SaveList;
   BEGIN
      IF type.savelist = NIL THEN
	 Error(unbalancedRestoreReaction);
      ELSE
	 savelist := type.savelist;
	 type.savelist := savelist.next;
	 type.handlers := savelist.handlers;
	 IF type.reaction # savelist.reaction THEN
	    type.reaction := savelist.reaction;
	    type.manager(type, savelist.reaction);
	 END;
      END;
   END RestoreReaction;

BEGIN
   CreatePrioritySystem(psys);

   Services.CreateType(eventTypeType, "Events.EventType", "");

   abort := NilHandler; log := NilHandler; queueHandler := NIL;
   nestlevel := 0;
   queue := NIL;
   lock := FALSE;

   InitErrorHandling;
END ulmEvents.
