{Begin SubSec Timers and Duration Functions} {Title Timers and Duration Functions} {Text {Begin Comment} File: <Lisp>Fugue>Duration.tty Created:Jan 10, 1983 by JonL White Revised:Feb 21, Jun 29, and Jul 12, 1983 by JonL translated into IM format: Mon, 18 Jul 83 11:40 PDT by Sannella {End Comment} Often one needs to loop over some code, stopping when a certain interval of time has passed. Some systems provide an "alarmclock" facility, which provides an asynchronous interrupt when a time interval runs out. This is not particularly feasible in the current Interlisp-D envirornment, so the following facilities are supplied for efficiently testing for the expiration of a time interval in a loop context. Three functions are provided: {fn SETUPTIMER}, {fn SETUPTIMER.DATE}, and {fn TIMEREXPIRED?}. Also several new i.s.oprs have been defined: {lisp forDuration}, {lisp during}, {lisp untilDate}, {lisp timerUnits}, {lisp usingTimer}, and {lisp resourceName} (reasonable variations on upper/lower case are permissible). {note These facilites have been tested out on the D machines, and on Interlisp-10; they are expected to work on Interlisp/VAX.} {Tag Timers} {index *PRIMARY* Timers} {index SECONDS (Timer Unit)} {index MILLISECONDS (Timer Unit)} {index TICKS (Timer Unit)} These functions use an object called a Timer, which encodes a future clock time at which a signal is desired. A Timer is constructed by the functions {fn SETUPTIMER} and {fn SETUPTIMER.DATE}, and is created with a basic clock "unit" selected from among {lisp SECONDS}, {lisp MILLISECONDS}, or {lisp TICKS}. The first two timer units provide a machine/system independent interface, and the latter provides access to the "real", basic strobe unit of the machine's clock on which the program is running. The default unit is {lisp MILLISECONDS}. {note Actually, it should come as no surprise that, for now, a TIMER is merely a FIXP; but it's possible that, in the future, some more structure may be involved. [At any rate, the "internal" function \TIMER.TIMERP provides an adequate test for 'being a timer'.]} Currently, the {lisp TICKS} unit is the same as the {lisp MILLISECONDS} unit for Interlisp-10 and Interlisp/VAX. In Interlisp-D, the {lisp TICKS} unit is a function of the particular machine that Interlisp-D is running on: The Xerox 1100 and 1132 have about 0.5952 microseconds per tick (1680 ticks per millisecond); The Xerox 1108 has about 28.78 microseconds per tick (34.746 ticks per millisecond). The advantage of using {lisp TICKS} rather than one of the uniform interfaces is primarily speed; e.g., on a Xerox 1100, it may take as much as 400 microseconds to interface the milliseconds clock (a software facility actually based over the real clock), whereas reading the real clock itself should take less than about ten microseconds. The disadvantage of the {lisp TICKS} unit is its short roll-over interval (about 20 minutes) compared to the {lisp MILLISECONDS} roll-over interval (about about two weeks), and also the dependency on particular machine parameters. {FnDef {Name SETUPTIMER} {Args INTERVAL OLDTIMER? TIMERUNITS INTERVALUNITS} {Text {fn SETUPTIMER} returns a Timer that will "go off" (as tested by {fn TIMEREXPIRED?}) after a specified time-interval measured from the current clock time. {fn SETUPTIMER} has one required and three optional arguments: {arg INTERVAL} must be a integer specifying how long an interval is desired. {arg TIMERUNITS} specifies the units of measure for the interval (defaults to {lisp MILLISECONDS}). If {arg OLDTIMER?} is a Timer, it will be reused and returned, rather than allocating a new Timer. {arg INTERVALUNITS} specifies the units in which the {arg OLDTIMER?} is expressed (defaults to the value of {arg TIMERUNITS}. {note since "timers" are currently fixp values, then Interlisp/VAX (and often Interlisp-10) do not actually allocate new cells in order to cons up a new timer; thus OldTimer? is often moot and would be ignored} }} {FnDef {Name SETUPTIMER.DATE} {Args DTS OLDTIMER?} {Text {fn SETUPTIMER.DATE} returns a Timer (using the {lisp SECONDS} time unit) that will "go off" at a specified date and time. {arg DTS} is a Date/Time string such as {fn IDATE} accepts ({PageRef Fn IDATE}). If {arg OLDTIMER?} is a Timer, it will be reused and returned, rather than allocating a new Timer. {fn SETUPTIMER.DATE} operates by first subtracting {lisp (IDATE)} from {lisp (IDATE {arg DTS})}, so there may be some large integer creation involved, even if {arg OLDTIMER?} is given. }} {note Implementation note: Both SETUPTIMER and SETUPTIMER.DATE have compiler macro properties which "open-code" them into a call on the appropriate clock function. (CLOCK 0) and (SETUPTIMER 0) do the same thing; similarly, (SETUPTIMER 0 <...> 'TICKS) provides a way to read the hardware clock.} {FnDef {Name TIMEREXPIRED?} {Args TIMER CLOCKVALUE.OR.TIMERUNITS} {Text If {arg TIMER} is a Timer, and {arg CLOCKVALUE.OR.TIMERUNITS} is the time-unit of {arg TIMER}, {fn TIMEREXPIRED?} returns true if {arg TIMER} has "gone off". {note eventually, when TIMERs are implemented so that they include their time unit, it will not be necessary to specify it here} {arg CLOCKVALUE.OR.TIMERUNITS} can also be a Timer, in which case {fn TIMEREXPIRED?} compares the two timers (which must be in the same timer units). If {lisp X} and {lisp Y} are Timers, then {lisp (TIMEREXPIRED? X Y)} is true if {lisp X} is set for an {it earlier} time than {lisp Y}. {note Of course, from the Implementation note on SETUPTIMER, one can see that X and Y could also just be the output of any of the clock functions, and TIMEREXPIRED? would be somewhat equivalent to GREATERP for them.} }} {note Implementation note: It must be noted that timer storage is not open-ended -- on the D machines it is 32 bits, on the PDP10 is is 36 bits, and on the VAX it is 31 bits. Thus, (TIMEREXPIRED? X Y), where Y is an integer and N is the number of bits of storage, is really (ILESSP (MOD (IDIFFERENCE Y X) 2↑N) 2↑<N-1>) The upshot of this is that no "edge-effects" will be noticed due to the finite nature of the storage box, but the effective "roll-over" period will be 2↑<N-1> units rather than 2↑N.} There are a number of i.s.oprs that make it easier to use Timers in iterative statements ({PageRef Tag IterativeStatement}). These i.s.oprs are given below in the "canonical" form, with the second "word" capitalized, but the all-caps and all-lower-case versions are also acceptable. {Def {Type (I.S. Operator)} {Name forDuration} {Args INTERVAL} {NoParens} } {Def {Type (I.S. Operator)} {Name during} {Args INTERVAL} {NoParens} {Text {arg INTERVAL} is an integer specifying an interval of time during which the iterative statement will loop. }} {Def {Type (I.S. Operator)} {Name timerUnits} {Args UNITS} {NoParens} {Text {arg UNITS} specifies the time units of the {arg INTERVAL} specified in {lisp forDuration}. }} {Def {Type (I.S. Operator)} {Name untilDate} {Args DTS} {NoParens} {Text {arg DTS} is a Date/Time string (such as {fn IDATE} accepts) specifying when the iterative statement should stop looping. }} {Def {Type (I.S. Operator)} {Name usingTimer} {Args TIMER} {NoParens} {Text If {lisp usingTimer} is given, {arg TIMER} is reused as the timer for {lisp forDuration} or {lisp untilDate}, rather than creating a new timer. This can reduce allocation if one of these i.s.oprs is used within another loop. }} {Def {Type (I.S. Operator)} {Name resourceName} {Args RESOURCE} {NoParens} {Text {arg RESOURCE} specifies a {lisp GLOBALRESOURCES} name to be used as the timer storage. If {arg RESOURCE}={lisp T}, it will be converted to a common internal name. }} Some examples: {lispcode (during 6MONTHS timerUnits 'SECS until (TENANT-VACATED? HouseHolder) do (DISMISS <for-about-a-day>) (HARRASS HouseHolder) finally (if (NOT (TENANT-VACATED? HouseHolder)) then (EVICT-TENANT HouseHolder)))} This humorous little example shows that how is is possible to have two termination condition: (1) when the time interval of {lisp 6MONTHS} has elapsed, or (2) when the predicate {lisp (TENANT-VACATED? HouseHolder)} becomes true. Note that the "finally" clause is executed regardless of which termination condition caused it. {note Note also that SECS is an acceptable spelling of SECONDS; also MS and MILLISECS are acceptable for MILLISECONDS. [Since the millisecond clock will "roll over" about every two weeks, "6MONTHS" wouldn't be an appropriate interval if the timer units were the default case, namely MILLISECONDS.] } {lispcode (do (forDuration (CONSTANT (ITIMES 10 24 60 60 1000)) do (CARRY.ON.AS.USUAL) finally (PROMPTPRINT "Have you had your 10-day check-up?")))} This infinite loop breaks out with a warning message every 10 days. One could question whether the millisecond clock, which is used by default, is appropriate for this loop, since it rolls-over about every two weeks. {lispcode (SETQ \RandomTimer (SETUPTIMER 0)) (untilDate "31-DEC-83 23:59:59" usingTimer \RandomTimer when (WINNING?) do (RETURN) finally (ERROR "You've been losing this whole year!"))} Here we see a usage of an explicit date for the time interval; also, the user has squirreled away some storage (as the value of {lisp \RandomTimer}) for use by the call to {fn SETUPTIMER} in this loop. {lispcode (forDuration SOMEINTERVAL resourcename '\INNERLOOPBOX timerunits 'TICKS do (CRITICAL.INNER.LOOP))} For this loop, the user doesn't want any {fn CONS}ing to take place, so {lisp \INNERLOOPBOX} will be defined as a {lisp GLOBALRESOURCES} which "caches" a timer cell (if it isn't already so defined), and wraps the entire statement in a {lisp GLOBALRESOURCE} call. Furthermore, he has specified a time unit of {lisp TICKS}, for lower overhead in this critical inner loop. In fact specifying a {lisp resourcename} of {lisp T} would have been the same as specifying it to be {lisp \ForDurationOfBox}; this is just a simpler way to specify that a {lisp GLOBALRESOURCE} is wanted, without having to think up a name. }{End SubSec Timers and Duration Functions}