MODULE A2Sequencers;	(** AUTHOR "negelef"; PURPOSE "Generic A2 Sequencer"; *)

(*
This module provides a generic sequencer base class  that allows deriving active objects to communicate sequentially over messages.
Messages are handled sequentially and provide atomic and exclusive access to the state of a sequencer. Requests are special messages
which allow the caller to block and wait for the sequencer to handle the request. This is useful to retrieve a set of states of the sequencer.
Code in procedures of a sequencer must make sure that they are called by their own sequencer object (using the SequencerCalledThis
procedure) and have to add a corresponding message otherwise. If sequencers share variables, they can also put it into property objects
which support atomic access to their values and a registration mechanism for notification handlers.
*)

IMPORT Machine, Streams, Objects, Kernel;

CONST
	NoDelay* = 0;

	MaxHandlers = 10;

TYPE
	(* generic property object that provides lock-free access to its value *)
	Property* = OBJECT
	VAR
		locks: LONGINT;
		container*: OBJECT;

		PROCEDURE &InitProperty;
		BEGIN locks := 0; container := NIL;
		END InitProperty;

		PROCEDURE AcquireRead;
		VAR locks: LONGINT;
		BEGIN
			LOOP
				locks := SELF.locks;
				IF (locks >= 0) & (Machine.AtomicCAS (SELF.locks, locks, locks + 1) = locks) THEN EXIT END;
				Objects.Yield;
			END;
		END AcquireRead;

		PROCEDURE ReleaseRead;
		BEGIN Machine.AtomicDec (locks);
		END ReleaseRead;

		PROCEDURE AcquireWrite;
		VAR locks: LONGINT;
		BEGIN
			LOOP
				locks := SELF.locks;
				IF (locks = 0) & (Machine.AtomicCAS (SELF.locks, locks, locks - 1) = locks) THEN EXIT END;
				Objects.Yield;
			END;
		END AcquireWrite;

		PROCEDURE ReleaseWrite;
		BEGIN Machine.AtomicInc (locks);
		END ReleaseWrite;

		PROCEDURE ToStream*(w : Streams.Writer);
		END ToStream;	(* abstract *)

		PROCEDURE FromStream*(r : Streams.Reader);
		END FromStream;	(* abstract *)

	END Property;

	Boolean* = OBJECT (Property)
	VAR
		value: BOOLEAN;
		handlers: ARRAY MaxHandlers OF BooleanHandler;

		PROCEDURE &InitBoolean* (value: BOOLEAN);
		BEGIN InitProperty; SELF.value := value;
		END InitBoolean;

		PROCEDURE Get* (): BOOLEAN;
		VAR value: BOOLEAN;
		BEGIN AcquireRead; value := SELF.value; ReleaseRead; RETURN value;
		END Get;

		PROCEDURE Set* (value: BOOLEAN);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := SELF.value # value; SELF.value := value; ReleaseWrite; IF changed THEN Changed (value) END;
		END Set;

		PROCEDURE Changed (value: BOOLEAN);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO handlers[i] (SELF, value); INC (i) END;
		END Changed;

		PROCEDURE AddHandler* (handler: BooleanHandler);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO INC (i) END; handlers[i] := handler;
		END AddHandler;

	END Boolean;

	Integer* = OBJECT (Property)
	VAR
		value: LONGINT;
		handlers: ARRAY MaxHandlers OF IntegerHandler;

		PROCEDURE &InitInteger* (value: LONGINT);
		BEGIN InitProperty; SELF.value := value;
		END InitInteger;

		PROCEDURE Get* (): LONGINT;
		VAR value: LONGINT;
		BEGIN AcquireRead; value := SELF.value; ReleaseRead; RETURN value;
		END Get;

		PROCEDURE Set* (value: LONGINT);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := SELF.value # value; SELF.value := value; ReleaseWrite; IF changed THEN Changed (value) END;
		END Set;

		PROCEDURE Inc* (step: LONGINT);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := step # 0; INC (value, step); ReleaseWrite; IF changed THEN Changed (value) END;
		END Inc;

		PROCEDURE Dec* (step: LONGINT);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := step # 0; DEC (value, step); ReleaseWrite; IF changed THEN Changed (value) END;
		END Dec;

		PROCEDURE Changed (value: LONGINT);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO handlers[i] (SELF, value); INC (i) END;
		END Changed;

		PROCEDURE AddHandler* (handler: IntegerHandler);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO INC (i) END; handlers[i] := handler;
		END AddHandler;

	END Integer;

	Real* = OBJECT (Property)
	VAR
		value: REAL;
		handlers: ARRAY MaxHandlers OF RealHandler;

		PROCEDURE &InitReal* (value: REAL);
		BEGIN InitProperty; SELF.value := value;
		END InitReal;

		PROCEDURE Get* (): REAL;
		VAR value: REAL;
		BEGIN AcquireRead; value := SELF.value; ReleaseRead; RETURN value;
		END Get;

		PROCEDURE Set* (value: REAL);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := SELF.value # value; SELF.value := value; ReleaseWrite; IF changed THEN Changed (value) END;
		END Set;

		PROCEDURE Changed (value: REAL);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO handlers[i] (SELF, value); INC (i) END;
		END Changed;

		PROCEDURE AddHandler* (handler: RealHandler);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO INC (i) END; handlers[i] := handler;
		END AddHandler;

	END Real;

	Set* = OBJECT (Property)
	VAR
		value: SET;
		handlers: ARRAY MaxHandlers OF SetHandler;

		PROCEDURE &InitSet* (value: SET);
		BEGIN InitProperty; SELF.value := value;
		END InitSet;

		PROCEDURE Get* (): SET;
		VAR value: SET;
		BEGIN AcquireRead; value := SELF.value; ReleaseRead; RETURN value;
		END Get;

		PROCEDURE Set* (value: SET);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := SELF.value # value; SELF.value := value; ReleaseWrite; IF changed THEN Changed (value) END;
		END Set;

		PROCEDURE Incl* (element: LONGINT);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := ~(element IN value); INCL (value, element); ReleaseWrite; IF changed THEN Changed (value) END;
		END Incl;

		PROCEDURE Excl* (element: LONGINT);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := element IN value; EXCL (value, element); ReleaseWrite; IF changed THEN Changed (value) END;
		END Excl;

		PROCEDURE Changed (value: SET);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO handlers[i] (SELF, value); INC (i) END;
		END Changed;

		PROCEDURE AddHandler* (handler: SetHandler);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO INC (i) END; handlers[i] := handler;
		END AddHandler;

	END Set;

	String* = OBJECT (Property)
	VAR
		value: POINTER TO ARRAY OF CHAR;
		handlers: ARRAY MaxHandlers OF StringHandler;

		PROCEDURE &InitString* (CONST value: ARRAY OF CHAR; length: LONGINT);
		BEGIN InitProperty; NEW (SELF.value, length); COPY (value, SELF.value^);
		END InitString;

		PROCEDURE Get* (VAR value: ARRAY OF CHAR);
		BEGIN AcquireRead; COPY (SELF.value^, value); ReleaseRead;
		END Get;

		PROCEDURE Set* (CONST value: ARRAY OF CHAR);
		VAR changed: BOOLEAN;
		BEGIN AcquireWrite; changed := SELF.value^ # value; COPY (value, SELF.value^); ReleaseWrite; IF changed THEN Changed (value) END;
		END Set;

		PROCEDURE Changed (CONST value: ARRAY OF CHAR);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO handlers[i] (SELF, value); INC (i) END;
		END Changed;

		PROCEDURE AddHandler* (handler: StringHandler);
		VAR i: LONGINT;
		BEGIN i := 0; WHILE handlers[i] # NIL DO INC (i) END; handlers[i] := handler;
		END AddHandler;

	END String;

	(* generic message to be handled by the sequencer *)
	Message* = OBJECT
	VAR
		next: Message; time: LONGINT;

		PROCEDURE &InitMessage*;
		BEGIN SELF.next := NIL; time := NoDelay;
		END InitMessage;

		PROCEDURE Handle*;
		END Handle;	(* abstract *)

	END Message;

	TYPE ProcedureMessage*  = OBJECT (Message)
	VAR
		procedure: Procedure;

		PROCEDURE &InitProcedureMessage* (procedure: Procedure);
		BEGIN InitMessage; SELF.procedure := procedure;
		END InitProcedureMessage;

		PROCEDURE Handle;
		BEGIN procedure;
		END Handle;

	END ProcedureMessage;

	TYPE BooleanMessage*  = OBJECT (Message)
	VAR
		value: BOOLEAN;
		procedure: BooleanProcedure;

		PROCEDURE &InitBooleanMessage* (value: BOOLEAN; procedure: BooleanProcedure);
		BEGIN InitMessage; SELF.value := value; SELF.procedure := procedure;
		END InitBooleanMessage;

		PROCEDURE Handle;
		BEGIN procedure (value);
		END Handle;

	END BooleanMessage;

	TYPE IntegerMessage*  = OBJECT (Message)
	VAR
		value: LONGINT;
		procedure: IntegerProcedure;

		PROCEDURE &InitIntegerMessage* (value: LONGINT; procedure: IntegerProcedure);
		BEGIN InitMessage; SELF.value := value; SELF.procedure := procedure;
		END InitIntegerMessage;

		PROCEDURE Handle;
		BEGIN procedure (value);
		END Handle;

	END IntegerMessage;

	TYPE IntegerIntegerMessage*  = OBJECT (Message)
	VAR
		value0, value1: LONGINT;
		procedure: IntegerIntegerProcedure;

		PROCEDURE &InitIntegerIntegerMessage* (value0, value1: LONGINT; procedure: IntegerIntegerProcedure);
		BEGIN InitMessage; SELF.value0 := value0; SELF.value1 := value1; SELF.procedure := procedure;
		END InitIntegerIntegerMessage;

		PROCEDURE Handle;
		BEGIN procedure (value0, value1);
		END Handle;

	END IntegerIntegerMessage;

	TYPE RealMessage*  = OBJECT (Message)
	VAR
		value: REAL;
		procedure: RealProcedure;

		PROCEDURE &InitRealMessage* (value: REAL; procedure: RealProcedure);
		BEGIN InitMessage; SELF.value := value; SELF.procedure := procedure;
		END InitRealMessage;

		PROCEDURE Handle;
		BEGIN procedure (value);
		END Handle;

	END RealMessage;

	TYPE SetMessage*  = OBJECT (Message)
	VAR
		value: SET;
		procedure: SetProcedure;

		PROCEDURE &InitSetMessage* (value: SET; procedure: SetProcedure);
		BEGIN InitMessage; SELF.value := value; SELF.procedure := procedure;
		END InitSetMessage;

		PROCEDURE Handle;
		BEGIN procedure (value);
		END Handle;

	END SetMessage;

	TYPE StringMessage*  = OBJECT (Message)
	VAR
		value: POINTER TO ARRAY OF CHAR;
		procedure: StringProcedure;

		PROCEDURE &InitStringMessage* (CONST value: ARRAY OF CHAR; procedure: StringProcedure);
		VAR length: LONGINT;
		BEGIN
			InitMessage; length := 0;
			WHILE value[length] # 0X DO INC (length); END;
			NEW (SELF.value, length); COPY (value, SELF.value^); SELF.procedure := procedure;
		END InitStringMessage;

		PROCEDURE Handle;
		BEGIN procedure (value^);
		END Handle;

	END StringMessage;

	(* generic request that allows to wait for the message to be handled*)
	Request* = OBJECT (Message)
	VAR
		handled: BOOLEAN;

		PROCEDURE &InitRequest*;
		BEGIN InitMessage; handled := FALSE;
		END InitRequest;

		(* IMPORTANT: to be called at the end of overriding procedures *)
		PROCEDURE Handle;
		BEGIN {EXCLUSIVE} handled := TRUE
		END Handle;

		(* awaits handling by sequencer  *)
		PROCEDURE Await;
		BEGIN {EXCLUSIVE} AWAIT (handled);
		END Await;

	END Request;

	IntegerRequest* = OBJECT (Request)
	VAR
		value: LONGINT;
		procedure: IntegerProcedure;

		PROCEDURE &InitIntegerRequest* (value: LONGINT; procedure: IntegerProcedure);
		BEGIN InitRequest; SELF.value := value; SELF.procedure := procedure;
		END InitIntegerRequest;

		PROCEDURE Handle;
		BEGIN procedure (value); Handle^;
		END Handle;

	END IntegerRequest;

	RequestBoolean* = OBJECT (Request)
	VAR
		procedure: ProcedureBoolean;
		result-: BOOLEAN;

		PROCEDURE &InitRequestBoolean* (procedure: ProcedureBoolean);
		BEGIN InitRequest; SELF.procedure := procedure;
		END InitRequestBoolean;

		PROCEDURE Handle;
		BEGIN result := procedure (); Handle^;
		END Handle;

	END RequestBoolean;

	RequestInteger* = OBJECT (Request)
	VAR
		procedure: ProcedureInteger;
		result-: LONGINT;

		PROCEDURE &InitRequestInteger* (procedure: ProcedureInteger);
		BEGIN InitRequest; SELF.procedure := procedure;
		END InitRequestInteger;

		PROCEDURE Handle;
		BEGIN result := procedure (); Handle^;
		END Handle;

	END RequestInteger;

	RequestReal* = OBJECT (Request)
	VAR
		procedure: ProcedureReal;
		result-: REAL;

		PROCEDURE &InitRequestReal* (procedure: ProcedureReal);
		BEGIN InitRequest; SELF.procedure := procedure;
		END InitRequestReal;

		PROCEDURE Handle;
		BEGIN result := procedure (); Handle^;
		END Handle;

	END RequestReal;

	IntegerRequestBoolean* = OBJECT (Request)
	VAR
		value: LONGINT;
		procedure: IntegerProcedureBoolean;
		result-: BOOLEAN;

		PROCEDURE &InitIntegerRequestBoolean* (value: LONGINT; procedure: IntegerProcedureBoolean);
		BEGIN InitRequest; SELF.value := value; SELF.procedure := procedure;
		END InitIntegerRequestBoolean;

		PROCEDURE Handle;
		BEGIN result := procedure (value); Handle^;
		END Handle;

	END IntegerRequestBoolean;

	RealRequestInteger* = OBJECT (Request)
	VAR
		value: REAL;
		procedure: RealProcedureInteger;
		result-: LONGINT;

		PROCEDURE &InitRealRequestInteger* (value: REAL; procedure: RealProcedureInteger);
		BEGIN InitRequest; SELF.value := value; SELF.procedure := procedure;
		END InitRealRequestInteger;

		PROCEDURE Handle;
		BEGIN result := procedure (value); Handle^;
		END Handle;

	END RealRequestInteger;

	(* generic base message sequencer class *)
	Sequencer* = OBJECT
	VAR
		handling, woken: BOOLEAN; first: Message; timer: Objects.Timer;

		PROCEDURE &InitSequencer*;
		BEGIN handling := TRUE; woken := FALSE; first := NIL; NEW (timer);
		END InitSequencer;

		(* check wether current procedure was called by sequencer or by other active objects *)
		PROCEDURE SequencerCalledThis* (): BOOLEAN;
		BEGIN RETURN Objects.ActiveObject() = SELF;
		END SequencerCalledThis;

		PROCEDURE HandleMessages;
		VAR message: Message;
		BEGIN {EXCLUSIVE}
			WHILE first # NIL DO
				IF (first.time # NoDelay) & (first.time - Kernel.GetTicks () > 0) THEN RETURN END;
				message := first; first := message.next; message.next := NIL; message.Handle;
			END;
		END HandleMessages;

		PROCEDURE Add*(message: Message; time: LONGINT);
		VAR prev, next: Message;
		BEGIN
			BEGIN {EXCLUSIVE}
				ASSERT (~SequencerCalledThis ());
				ASSERT (message.next = NIL);
				prev := NIL; next := first;
				WHILE (next # NIL) & (next.time <= time) DO prev := next; next := next.next END;
				IF prev = NIL THEN first := message; woken := time # NoDelay; ELSE prev.next := message END;
				message.next := next; message.time := time;
			END;
			IF message IS Request THEN message(Request).Await END;
		END Add;

		PROCEDURE AddMessage* (procedure: Procedure);
		VAR message: ProcedureMessage;
		BEGIN NEW (message, procedure); Add (message, NoDelay);
		END AddMessage;

		PROCEDURE AddBooleanMessage* (value: BOOLEAN; procedure: BooleanProcedure);
		VAR message: BooleanMessage;
		BEGIN NEW (message, value, procedure); Add (message, NoDelay);
		END AddBooleanMessage;

		PROCEDURE AddIntegerMessage* (value: LONGINT; procedure: IntegerProcedure);
		VAR message: IntegerMessage;
		BEGIN NEW (message, value, procedure); Add (message, NoDelay);
		END AddIntegerMessage;

		PROCEDURE AddRealMessage* (value: REAL; procedure: RealProcedure);
		VAR message: RealMessage;
		BEGIN NEW (message, value, procedure); Add (message, NoDelay);
		END AddRealMessage;

		PROCEDURE AddSetMessage* (value: SET; procedure: SetProcedure);
		VAR message: SetMessage;
		BEGIN NEW (message, value, procedure); Add (message, NoDelay);
		END AddSetMessage;

		PROCEDURE AddStringMessage* (CONST value: ARRAY OF CHAR; procedure: StringProcedure);
		VAR message: StringMessage;
		BEGIN NEW (message, value, procedure); Add (message, NoDelay);
		END AddStringMessage;

		PROCEDURE AddIntegerIntegerMessage* (value0, value1: LONGINT; procedure: IntegerIntegerProcedure);
		VAR message: IntegerIntegerMessage;
		BEGIN NEW (message, value0, value1, procedure); Add (message, NoDelay);
		END AddIntegerIntegerMessage;

		PROCEDURE AddIntegerRequest* (value: LONGINT; procedure: IntegerProcedure);
		VAR request: IntegerRequest;
		BEGIN NEW (request, value, procedure); Add (request, NoDelay);
		END AddIntegerRequest;

		PROCEDURE AddRequestBoolean* (procedure: ProcedureBoolean): BOOLEAN;
		VAR request: RequestBoolean;
		BEGIN NEW (request, procedure); Add (request, NoDelay); RETURN request.result;
		END AddRequestBoolean;

		PROCEDURE AddRequestInteger* (procedure: ProcedureInteger): LONGINT;
		VAR request: RequestInteger;
		BEGIN NEW (request, procedure); Add (request, NoDelay); RETURN request.result;
		END AddRequestInteger;

		PROCEDURE AddRequestReal* (procedure: ProcedureReal): REAL;
		VAR request: RequestReal;
		BEGIN NEW (request, procedure); Add (request, NoDelay); RETURN request.result;
		END AddRequestReal;

		PROCEDURE AddIntegerRequestBoolean* (value: LONGINT; procedure: IntegerProcedureBoolean): BOOLEAN;
		VAR request: IntegerRequestBoolean;
		BEGIN NEW (request, value, procedure); Add (request, NoDelay); RETURN request.result;
		END AddIntegerRequestBoolean;

		PROCEDURE AddRealRequestInteger* (value: REAL; procedure: RealProcedureInteger): LONGINT;
		VAR request: RealRequestInteger;
		BEGIN NEW (request, value, procedure); Add (request, NoDelay); RETURN request.result;
		END AddRealRequestInteger;

		PROCEDURE Remove*(message: Message);
		VAR prev, next: Message;
		BEGIN
			ASSERT (SequencerCalledThis ());
			IF message = NIL THEN RETURN END;
			prev := NIL; next := first;
			WHILE (next # NIL) & (next # message) DO prev := next; next := next.next END;
			IF next = message THEN
				IF prev = NIL THEN first := message.next; woken := TRUE; ELSE prev.next := message.next END;
			END;
			message.next := NIL;
		END Remove;

		(* this procedure is called sequentially and can be overridden in order to do contiguous work *)
		PROCEDURE Handle*;
		BEGIN
			IF (first # NIL) & (first.time # NoDelay) THEN Objects.SetTimeoutAt (timer, Wakeup, first.time) END;
			AWAIT ((first # NIL) & (first.time = NoDelay) OR ~handling OR woken);
			Objects.CancelTimeout (timer); woken := FALSE;
		END Handle;

		PROCEDURE Wakeup;
		BEGIN {EXCLUSIVE} woken := TRUE;
		END Wakeup;

		PROCEDURE Stop*;
		BEGIN {EXCLUSIVE} handling := FALSE;
		END Stop;

	BEGIN {ACTIVE}
		WHILE handling DO HandleMessages; BEGIN {EXCLUSIVE} Handle END END;
	END Sequencer;

(* helper types *)
	BooleanHandler = PROCEDURE {DELEGATE} (property: Boolean; value: BOOLEAN);
	IntegerHandler = PROCEDURE {DELEGATE} (property: Integer; value: LONGINT);
	RealHandler = PROCEDURE {DELEGATE} (property: Real; value: REAL);
	SetHandler = PROCEDURE {DELEGATE} (property: Set; value: SET);
	StringHandler = PROCEDURE {DELEGATE} (property: String; CONST value: ARRAY OF CHAR);

	Procedure = PROCEDURE {DELEGATE};
	BooleanProcedure = PROCEDURE {DELEGATE} (value: BOOLEAN);
	IntegerProcedure = PROCEDURE {DELEGATE} (value: LONGINT);
	IntegerIntegerProcedure = PROCEDURE {DELEGATE} (value0, value1: LONGINT);
	RealProcedure = PROCEDURE {DELEGATE} (value: REAL);
	SetProcedure = PROCEDURE {DELEGATE} (value: SET);
	StringProcedure = PROCEDURE {DELEGATE} (CONST value: ARRAY OF CHAR);

	ProcedureBoolean = PROCEDURE {DELEGATE} (): BOOLEAN;
	ProcedureInteger = PROCEDURE {DELEGATE} (): LONGINT;
	ProcedureReal = PROCEDURE {DELEGATE} (): REAL;

	IntegerProcedureBoolean = PROCEDURE {DELEGATE} (value: LONGINT): BOOLEAN;
	RealProcedureInteger = PROCEDURE {DELEGATE} (value: REAL): LONGINT;

(* helper function for delayed execution *)
PROCEDURE Delay* (delay: LONGINT): LONGINT;
BEGIN RETURN delay + Kernel.GetTicks ();
END Delay;

END A2Sequencers.

Open issues:
- first parameter of event procedures concrete or abstract property type?
- no concrete request types implemented
- serializable interface of properties not implemented