(* Ulm's Oberon Library Copyright (C) 1989-2004 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: TimeConditi.om,v 1.5 2004/04/05 16:23:37 borchert Exp $ ---------------------------------------------------------------------------- $Log: TimeConditi.om,v $ Revision 1.5 2004/04/05 16:23:37 borchert bug fix: Test must not call anything which causes directly or indirectly WaitFor to be called; hence we schedule a timer event in all cases where this is possible; the only exception remains Clocks.system where we take it for granted that the clock operations are that simple that they do not lead to WaitFor (was necessary to get RemoteClocks working again) Revision 1.4 2004/02/19 15:23:10 borchert - Init added to support extensions of TimeConditions.Condition - using Clocks.Passed instead of Clocks.GetTime in some instances to reduce the number of system calls needed - Timers event is only generated now if strictly needed, i.e. if SendEvent has been called Revision 1.3 2001/04/30 15:25:12 borchert several improvements / bug fixes in context of domain-oriented condition handling Revision 1.2 1995/04/06 14:36:16 borchert fixes due to changed if & semantics of Conditions Revision 1.1 1994/02/22 20:11:18 borchert Initial revision ---------------------------------------------------------------------------- AFB 1/92 ---------------------------------------------------------------------------- *) MODULE ulmTimeConditions; IMPORT Clocks := ulmClocks, Conditions := ulmConditions, Disciplines := ulmDisciplines, Events := ulmEvents, Op := ulmOperations, Priorities := ulmPriorities, Process := ulmProcess, RelatedEvents := ulmRelatedEvents, Scales := ulmScales, Timers := ulmTimers, Times := ulmTimes, SYSTEM; TYPE Domain = POINTER TO DomainRec; DomainRec = RECORD (Conditions.DomainRec) clock: Clocks.Clock; alarm: Events.EventType; event: Events.Event; (* event of SendEvent *) END; Condition = POINTER TO ConditionRec; ConditionRec* = RECORD (Conditions.ConditionRec) time: Times.Time; passed: BOOLEAN; (* becomes TRUE if the time has passed *) scheduled: BOOLEAN; (* Timer event scheduled? *) domain: Domain; END; TYPE (* this discpline will be attached to clocks *) Discipline = POINTER TO DisciplineRec; DisciplineRec = RECORD (Disciplines.DisciplineRec) domain: Domain; END; VAR disciplineId: Disciplines.Identifier; TYPE WakeupEvent = POINTER TO WakeupEventRec; WakeupEventRec = RECORD (Events.EventRec) condition: Condition; awaked: BOOLEAN; (* set to true by Wakeup event handler *) END; VAR if: Conditions.Interface; PROCEDURE FixTime(VAR time: Times.Time; currentTime: Times.Time; clock: Clocks.Clock); (* convert relative time measures into absolute time specs *) BEGIN IF Scales.IsRelative(time) THEN Clocks.GetTime(clock, currentTime); Op.Add3(SYSTEM.VAL(Op.Operand, time), currentTime, time); END; END FixTime; PROCEDURE Wakeup(event: Events.Event); (* note that we strictly rely on the capability of the underlying clock to raise this event at the appropriate time; we are unable to verify it because that could deadlock us in case of remote clocks *) VAR condevent: Events.Event; (* event requested by SendEvent *) BEGIN WITH event: WakeupEvent DO event.awaked := TRUE; IF event.condition # NIL THEN event.condition.passed := TRUE; event.condition.scheduled := FALSE; condevent := event.condition.domain.event; IF condevent # NIL THEN event.condition.domain.event := NIL; Events.Raise(condevent); END; END; END; END Wakeup; PROCEDURE ScheduleEvent(condition: Condition); VAR wakeup: WakeupEvent; domain: Domain; BEGIN IF ~condition.scheduled THEN domain := condition.domain; ASSERT(domain.alarm # NIL); NEW(wakeup); wakeup.type := domain.alarm; wakeup.awaked := FALSE; wakeup.condition := condition; condition.scheduled := TRUE; Timers.Schedule(domain.clock, condition.time, wakeup); END; END ScheduleEvent; PROCEDURE Init*(condition: Condition; clock: Clocks.Clock; time: Times.Time); (* like Create but without NEW *) VAR clockDisc: Discipline; domain: Domain; desc: Conditions.Description; priorityOfClock: Priorities.Priority; currentTime: Times.Time; BEGIN IF Disciplines.Seek(clock, disciplineId, SYSTEM.VAL(Disciplines.Discipline, clockDisc)) THEN domain := clockDisc.domain; ELSE (* create new domain *) NEW(desc); desc.caps := {}; desc.internal := TRUE; IF clock = Clocks.system THEN desc.caps := desc.caps + {Conditions.timelimit, Conditions.timecond}; END; IF Clocks.timer IN Clocks.Capabilities(clock) THEN Clocks.GetPriority(clock, priorityOfClock); IF priorityOfClock > Priorities.base THEN desc.caps := desc.caps + {Conditions.select, Conditions.async}; desc.internal := priorityOfClock < Priorities.interrupts; END; END; NEW(domain); Conditions.InitDomain(domain, if, desc); domain.clock := clock; IF Clocks.timer IN Clocks.Capabilities(clock) THEN Events.Define(domain.alarm); Events.SetPriority(domain.alarm, priorityOfClock + 1); Events.Handler(domain.alarm, Wakeup); ELSE domain.alarm := NIL; END; NEW(clockDisc); clockDisc.id := disciplineId; clockDisc.domain := domain; Disciplines.Add(clock, clockDisc); domain.event := NIL; END; Conditions.Init(condition, domain); FixTime(time, currentTime, clock); condition.time := time; condition.domain := domain; condition.passed := Clocks.Passed(clock, time); condition.scheduled := FALSE; IF ~condition.passed & (domain.alarm # NIL) & (clock # Clocks.system) THEN ScheduleEvent(condition); END; END Init; PROCEDURE Create*(VAR condition: Conditions.Condition; clock: Clocks.Clock; time: Times.Time); (* create and initialize a time condition: is the current time of the clock greater than or equal to `time'; if time is relative then it is taken relative to the current time *) VAR timeCond: Condition; BEGIN NEW(timeCond); Init(timeCond, clock, time); condition := timeCond; END Create; (* ======== interface procedures ================================ *) PROCEDURE GetTime(clock: Clocks.Clock; VAR currentTime: Times.Time; errors: RelatedEvents.Object) : BOOLEAN; (* get the current time of clock and check for errors *) VAR oldEvents, newEvents: RelatedEvents.Queue; BEGIN RelatedEvents.GetQueue(clock, oldEvents); Clocks.GetTime(clock, currentTime); RelatedEvents.GetQueue(clock, newEvents); IF newEvents # NIL THEN RelatedEvents.AppendQueue(errors, newEvents); END; IF oldEvents # NIL THEN RelatedEvents.AppendQueue(clock, oldEvents); END; IF newEvents # NIL THEN RelatedEvents.AppendQueue(clock, newEvents); END; RETURN newEvents = NIL END GetTime; PROCEDURE Passed(clock: Clocks.Clock; time: Times.Time; VAR passed: BOOLEAN; errors: RelatedEvents.Object) : BOOLEAN; VAR oldEvents, newEvents: RelatedEvents.Queue; BEGIN RelatedEvents.GetQueue(clock, oldEvents); passed := Clocks.Passed(clock, time); RelatedEvents.GetQueue(clock, newEvents); IF newEvents # NIL THEN RelatedEvents.AppendQueue(errors, newEvents); END; IF oldEvents # NIL THEN RelatedEvents.AppendQueue(clock, oldEvents); END; IF newEvents # NIL THEN RelatedEvents.AppendQueue(clock, newEvents); END; RETURN newEvents = NIL END Passed; PROCEDURE Test(domain: Conditions.Domain; condition: Conditions.Condition; errors: RelatedEvents.Object) : BOOLEAN; VAR currentTime: Times.Time; BEGIN WITH domain: Domain DO WITH condition: Condition DO IF condition.passed THEN RETURN TRUE END; IF condition.domain.event # NIL THEN RETURN FALSE END; IF condition.scheduled THEN RETURN FALSE END; IF ~Passed(domain.clock, condition.time, condition.passed, errors) THEN condition.passed := TRUE; RETURN TRUE END; RETURN condition.passed END; END; END Test; PROCEDURE GetMinTime(conditionSet: Conditions.ConditionSet; VAR minTime: Times.Time; VAR minCond: Condition); VAR condition: Condition; BEGIN minTime := NIL; Conditions.ExamineConditions(conditionSet); WHILE Conditions.GetNextCondition(conditionSet, SYSTEM.VAL(Conditions.Condition, condition)) DO IF (minTime = NIL) OR (Op.Compare(condition.time, minTime) < 0) THEN minTime := condition.time; minCond := condition; END; END; Op.Assign(SYSTEM.VAL(Op.Operand, minTime), minTime); (* take a copy *) END GetMinTime; PROCEDURE Select(domain: Conditions.Domain; conditionSet: Conditions.ConditionSet; time: Times.Time; VAR setOfTrueConditions: Conditions.ConditionSet; errors: RelatedEvents.Object; retry: BOOLEAN; VAR interrupted: BOOLEAN) : BOOLEAN; VAR minTime: Times.Time; minCond: Condition; currentTime: Times.Time; (* of Clocks.system *) condition: Condition; wakeup: WakeupEvent; anythingTrue: BOOLEAN; PROCEDURE Failure; (* we are unable to retrieve the time; so we have to mark all conditions as passed and to return the whole set *) VAR condition: Condition; BEGIN Conditions.CreateSet(setOfTrueConditions); Conditions.ExamineConditions(conditionSet); WHILE Conditions.GetNextCondition(conditionSet, SYSTEM.VAL(Conditions.Condition, condition)) DO condition.passed := TRUE; Conditions.Incl(setOfTrueConditions, condition); END; END Failure; BEGIN (* Select *) WITH domain: Domain DO GetMinTime(conditionSet, minTime, minCond); (* block current process, if necessary *) interrupted := FALSE; IF time # NIL THEN Clocks.GetTime(Clocks.system, currentTime); FixTime(time, currentTime, Clocks.system); NEW(wakeup); wakeup.type := domain.alarm; wakeup.condition := NIL; wakeup.awaked := FALSE; Timers.Schedule(Clocks.system, time, wakeup); END; IF ~GetTime(domain.clock, currentTime, errors) THEN Failure; RETURN TRUE END; IF ~minCond.passed THEN LOOP (* goes only into loop if retry = TRUE & we get interrupted *) Process.Pause; IF wakeup.awaked THEN EXIT END; interrupted := ~minCond.passed; IF ~interrupted THEN EXIT END; IF ~retry THEN RETURN FALSE END; END; END; anythingTrue := FALSE; Conditions.CreateSet(setOfTrueConditions); Conditions.ExamineConditions(conditionSet); WHILE Conditions.GetNextCondition(conditionSet, SYSTEM.VAL(Conditions.Condition, condition)) DO IF condition.passed THEN Conditions.Incl(setOfTrueConditions, condition); anythingTrue := TRUE; END; END; RETURN anythingTrue END; END Select; PROCEDURE SendEvent(domain: Conditions.Domain; condition: Conditions.Condition; event: Events.Event; errors: RelatedEvents.Object) : BOOLEAN; BEGIN WITH domain: Domain DO WITH condition: Condition DO IF condition.passed THEN RETURN FALSE ELSE domain.event := event; ScheduleEvent(condition); RETURN TRUE END; END; END; END SendEvent; PROCEDURE GetNextTime(domain: Conditions.Domain; conditionSet: Conditions.ConditionSet; VAR nextTime: Times.Time; VAR nextCond: Conditions.Condition; errors: RelatedEvents.Object); VAR condition: Condition; BEGIN GetMinTime(conditionSet, nextTime, condition); nextCond := condition; END GetNextTime; PROCEDURE InitInterface; BEGIN NEW(if); if.test := Test; if.select := Select; if.sendevent := SendEvent; if.gettime := GetNextTime; END InitInterface; BEGIN disciplineId := Disciplines.Unique(); InitInterface; END ulmTimeConditions.