unit UMTimer; {Contributed by Edward J. Huff } {Copyright is hereby waived: UMTimer.p is in the public domain.} {User Macro "Timer" package. } {Macros which use these extensions should specify "requiresUser('Timer',1)".} {These instructions apply if you have received only UMTimer.p and} {a copy of UMacroDef.p and UMacroRun.p, and want to install the code} {into your copy of NIH Image.} {You need the Think Pascal compiler to use this source code. } {Installation instructions. At the moment, the UMacroDef.p and UMacroRun.p} {have not been accepted for distribution with standard Image. If they are, then} {these instructions will apply. Otherwise, you will have to obtain a copy of image} {with UMacroDef.p etc. already installed. This will typically be available in} {the /pub/nih-image/contrib directory of zippy.nimh.nih.gov and the file} {name will include "UMX".} {If you have no other user macro extension packages installed,} {then just replace the default UMacroDef.p and UMacroRun.p with the enclosed} {versions. Otherwise, you must merge the changes, which should be found only} {at well marked places. Generally, this means simply adding lines, although you } {may also need to add a comma here and there. Changes should be found at} {one place in UMacroDef.p and seven places in UMacroRun.p.} {You will also have to use the "Project/Add File..." menu item to tell Think Pascal } {to compile this file, and use the "Windows/Image.proj" item to get the project window, } {and drag this file from the bottom of the list to somewhere between UMacroDef.p and } {UMacroRun.p.} {Special for UMTimer.p: you must add the "timer.p" interface module} {to the project. Choose "Project/Add File..." Then navigate to the} {Think Pascal Folder, and open the Interfaces Folder. Choose timer.p.} {Then type command zero and drag timer from the bottom of the} {list to just before UMTimer.p} {Finally, recompile and rebuild: Use "Run/Build" (command B) and } {"Project/Build Application...".} interface uses Timer, QuickDraw, Palettes, PrintTraps, Globals, Utilities, UMacroDef; procedure UMTimerInit; procedure UMTimerFinal; procedure UMTimerAdd; procedure UMTimerLookup (var uma: UserMacroArgs); procedure UMTimerRun (var uma: UserMacroArgs); {These functions are provided in case another UM package needs to use timers.} {Note that those functions should call AllocTimer in the UMxxxInit procedure} {but NUST NOT call StartTimer then.} {Also note that the proc argument of AllocTimer must be coded carefully.} {Basically it shouldn't do anything except set a global variable or two.} {It must not call other functions which might be in another segment.} {This might even include certain built in functions that Pascal calls} {implicitly. It must not use any system calls that move memory.} function AllocTimer (proc: ProcPtr; deferred: Boolean): Ptr; {AllocTimer should be called from UMxxxInit to avoid heap fragmentation.} function StartTimer (TimerP: Ptr; time: LongInt): OSErr; {DO NOT CALL StartTimer from UMxxxInit or from UMxxxFinal.} function StopTimer (TimerP: Ptr): LongInt; {DO NOT CALL StopTimer from UMxxxInit or from UMxxxFinal.} { These functions take precisely the same arguments as the macro functions. } { There is no way to report the macro errors, however, so to check for } { an error, you must call TimerError } procedure StartElapsed (t: integer); function MeasureElapsed (t: integer): extended; procedure StartDelay (t: integer; delay: extended); procedure KillDelay (t: integer); procedure WaitDelay (t: integer); procedure StartPeriodic (t: integer; interval: extended); procedure KillPeriodic (t: integer); procedure AdjustPeriodic (t: integer; interval, phase: extended); function WaitPeriodic (t: integer; n: longInt): longInt; function TimerError: Boolean; implementation {Local ("static") variables used by UMTimer.p} {Space used here counts against the 32k byte limit on global variables,} {so allocate the space dynamically only if any of the macros are actually called.} {A general purpose time manager / deferred task manager interface} {which supports accessing global variables from the timeout routine,} {using a deferred task instead of timeout, and restarting the timer} {from the timeout.} type MyTMInfoPtr = ^MyTMInfo; MyTMInfo = record {Time Manager information record} atmTask: TMTask; {extended TM task record} myA5: LongInt; {space to pass address of A5 world} myProc: ProcPtr; {Procedure to execute on timeout} myTmLink: MyTMInfoPtr; {Link used at Finalize time} adTask: DeferredTask; {deferred task manager record} deferred: Boolean; {true if proc should be called by deferred task manager} busy: Boolean; {true until deferred task has run} installed: Boolean; {true if InsTime or InsXTime has been called} end; var TimerList: MyTMInfoPtr; tmVersion: LongInt; finalized: Boolean; {Specific timers used by the macro commands.} TimerErrorOccurred: Boolean; {used for User.p callable routines} ElapsedTimerA, ElapsedTimerB, DelayTimer, PeriodicTimer: Ptr; delayExpired: Boolean; PeriodicInterval: LongInt; PeriodicPhase: LongInt; PeriodicCount: LongInt; ShareTicks: LongInt; {$PUSH} {$D-} procedure MacsBug (str: str255); inline $abff; {All timers must be stopped prior to exit. } {Make sure to set TimerList := nil prior to first possible call to Finalize} {gestaltTimeMgrVersion Returns a value that indicates the } { version of the Time Manager that is present. } {CONST gestaltStandardTimeMgr = 1;} { gestaltRevisedTimeMgr = 2;} { gestaltExtendedTimeMgr = 3;} {This code will absolutely fail for StandardTimeMgr for many reasons,} {e.g. standard tm does not put address of the TMTask record in A1.} procedure InitFinalize; begin TimerList := nil; if Gestalt(gestaltTimeMgrVersion, tmVersion) <> noErr then tmVersion := gestaltStandardTimeMgr; finalized := false; end; {This must be executed before ExitToShell if any timers are installed.} {As of version 1.51, there were no calls to ExitToShell after initialization} {except for one which is "impossible" to reach and the one in the system} {error handler. Therefore this is called from UMTimerFinal.} procedure Finalize; var TimerP: MyTMInfoPtr; begin while TimerList <> nil do begin TimerP := TimerList; TimerList := TimerP^.myTmLink; RmvTime(QElemPtr(TimerP)); DisposPtr(Ptr(TimerP)); end; finalized := true; end; {Time manager passes an argument in A1 to timer task} function GetTMInfo: MyTMInfoPtr; inline $2E89;{MOVE.L A1,(SP)} {Call the procedure passed to AllocTimer, after A5 is set} procedure CallTmProc (TimerP: MyTMInfoPtr; proc: ProcPtr); inline $205F,{MOVEA.L (A7)+,A0} $4E90;{JSR (A0)} {Apple Mac DTS Tech note #208 introduces new "totally cool" A5 functions SetA5 } {and SetCurrentA5, which are better than the old SetUpA5 and RestoreA5, but} {they are still junk IMHO. I use these instead.} function GetA5: LongInt; inline $2E8D;{MOVE.L A5,(A7)} procedure PutA5 (newA5: LongInt); inline $2A5F;{MOVEA.L (A7)+,A5} {This is the timer "Task" code called from the time manager interrupt.} {The argument is in A1 and A5 must be set prior to accessing global variables.} {Original tm does not set A1, and this code cannot work with original tm.} procedure DoTmProc; var oldA5: LongInt; {A5 when task is called} TimerP: MyTMInfoPtr; {A1 when task is called} begin TimerP := GetTMInfo; {first get my record from A1} oldA5 := GetA5; {save interrupted code's A5} PutA5(TimerP^.myA5); {set A5 to app's A5 world} if TimerP^.deferred then begin TimerP^.busy := true; {Assume that the deferred task manager leaves adTask in good shape} {so that it does not need to be initialized every time.} if DTInstall(QElemPtr(@TimerP^.adTask)) <> noErr then MacsBug('DoTmProc'); end else CallTmProc(TimerP, TimerP^.myProc); {Call the proc, pass TimerP} PutA5(oldA5); {restore original A5 } end; {This is the deferred task code called from deferred task manager.} {All interrupt routines check to see if the deferred task queue contains} {any entries, and if so, they are all processed prior to return to the application.} procedure DoDTProc; var oldA5: LongInt; {A5 when task is called} TimerP: MyTMInfoPtr; {A1 when task is called} begin TimerP := GetTMInfo; {first get my record from A1} oldA5 := GetA5; {save interrupted code's A5} PutA5(TimerP^.myA5); {set A5 to app's A5 world} TimerP^.busy := false; CallTmProc(TimerP, TimerP^.myProc); {Call the proc, pass TimerP} PutA5(oldA5); {restore original A5 } end; {Allocate a timer node from the heap and record the timeout procedure.} {There could be a heap fragmentation problem caused by this.} {Avoid it by allocating the timers early.} {If proc is nil, no procedure will execute on timeout interrupt.} {if deferred is true, the proc will execute at processor priority zero,} {called from the deferred task manager rather than the time manager.} {This is intended for cases where a substantial amount of computation} {is to be done on completion of the timeout, and might be used in conjunction} {with video capture to copy data.} {Note that the timer is not actually installed on the system timer queue} {until it is started the first time. This permits calling AllocTimer early} {during initialization without the need for guaranteeing that the} {the timer will be removed in case initialization fails.} function AllocTimer (proc: ProcPtr; deferred: Boolean): Ptr; var TimerP: MyTMInfoPtr; begin if finalized then TimerP := nil else if (tmVersion <> gestaltExtendedTimeMgr) and (tmVersion <> gestaltRevisedTimeMgr) then TimerP := nil else begin TimerP := MyTMInfoPtr(newPtr(sizeof(MyTMInfo))); if TimerP <> nil then begin TimerP^.myTmLink := TimerList; {remember this timer for finalize} TimerList := TimerP; TimerP^.myProc := proc; {get address of timeout or deferred proc} TimerP^.deferred := deferred; if proc = nil then TimerP^.atmTask.tmAddr := nil else TimerP^.atmTask.tmAddr := @DoTmProc; TimerP^.atmTask.tmWakeUp := 0; {initialize tmWakeUp} TimerP^.atmTask.tmReserved := 0; {initialize tmReserved} TimerP^.myA5 := GetA5; {store address of my A5 world} {Also initialize the deferred task record in TimerP^} {Must fill in all fields of TimerP^.adTask except qLink:} TimerP^.adTask.qType := ORD(dtQType); {"must always be ORD(dtQType)"} TimerP^.adTask.dtFlags := 0; {"reserved"} TimerP^.adTask.dtAddr := @DoDTProc; TimerP^.adTask.dtParm := LongInt(TimerP); {This value will be in A1 on entry to DoDTProc} TimerP^.adTask.dtReserved := 0; {"reserved--should be zero"} TimerP^.busy := false; TimerP^.installed := false; {StartTimer will install the timer on first call.} end; end; AllocTimer := Ptr(TimerP); end; {time is positive for milliseconds or negative for microseconds} {"The high-order bit of the qType field of the task record is } {a flag to indicate whether the task timer is active." --revised tm} {If StartTimer is called from the timeout proc, the delay refers} {to the previous expiration time, NOT the current time, because} {InsXTime was used to install the timer -- extended tm} {If run under system 6, the revised tm is used.} {Take care using short delays to avoid starving the application.} {Call from application or from a timer proc or deferred proc} {but not from an interrupt that could interrupt the timer proc} function StartTimer (TimerP: Ptr; time: LongInt): OSErr; var tp: MyTMInfoPtr; begin tp := MyTMInfoPtr(TimerP); if finalized then StartTimer := -1 else if tp = nil then StartTimer := -1 else if (tp^.atmTask.qType < 0) or (tp^.busy) then begin {Cannot start a timer that has not timed out} {Busy flag is needed in case the timeout routine has run but} {the deferred task has not -- could happen if two timers} {are starting each other from timeout proc.} StartTimer := -1; {pick some reasonable err value???} end else begin if not tp^.installed then begin if tmVersion = gestaltExtendedTimeMgr then InsXTime(QElemPtr(tp)) {install the info record} else InsTime(QElemPtr(tp)); tp^.installed := true; end; PrimeTime(QElemPtr(tp), time); StartTimer := noErr; end; end; {Function value is remaining time, negative if microseconds.} {This can be used to make very accurate time measurements if} {the overhead is measured separately.} function StopTimer (TimerP: Ptr): LongInt; var tp: MyTMInfoPtr; begin tp := MyTMInfoPtr(TimerP); StopTimer := 0; if not finalized then if tp <> nil then begin RmvTime(QElemPtr(tp)); tp^.atmTask.tmWakeUp := 0; {initialize tmWakeUp} tp^.atmTask.tmReserved := 0; {initialize tmReserved} if tmVersion = gestaltExtendedTimeMgr then InsXTime(QElemPtr(tp)) {install the info record} else InsTime(QElemPtr(tp)); StopTimer := tp^.atmTask.tmCount; end; end; {end general support for time manager} {$POP} procedure DoStopTimer (TimerP: Ptr); var junk: LongInt; begin junk := StopTimer(TimerP); end; procedure DelayTimeout (timerP: Ptr); begin delayExpired := true; end; procedure PeriodicTimeout (timerP: Ptr); begin PeriodicCount := PeriodicCount + 1; if StartTimer(timerP, PeriodicInterval + PeriodicPhase) <> noErr then ; PeriodicPhase := 0; end; {Called from procedure InitUserMacros in UMacroRun.p, } {which is called from Image.p early in initialization.} {Do not start timers in this function.} procedure UMTimerInit; begin InitFinalize; {MUST be called before AllocTimer} ElapsedTimerA := AllocTimer(nil, false); ElapsedTimerB := AllocTimer(nil, false); DelayTimer := AllocTimer(@DelayTimeout, false); PeriodicTimer := AllocTimer(@PeriodicTimeout, false); delayExpired := false; PeriodicCount := 0; PeriodicPhase := 0; PeriodicInterval := -1000; ShareTicks := 0; end; {Called from procedure FinalUserMacros in UMacroRun,p.} {This is guaranteed to run prior to any exit which happens after a call} {to DoUserMacro, and is intended for things which MUST be done prior } {to exit, like removing timers from the system timer list.} {Note well that it is NOT guaranteed to be called prior to any exit} {which might happen after InitUserMacros but before DoUserMacro.} {For this reason, InsTime or InsXTime are not called from AllocTimer.} procedure UMTimerFinal; begin Finalize; end; {AddUMSym calls:} {Add one call for each macro command, function, or string function} {you wish to add to the macro language.} {First argument is a string, case is ignored, truncated to SymbolSize characters.} {Second argument must be one of UserCommandT, UserFuncT, or UserStrFuncT} {Third argument is the UserCommandType item associated with the name.} {Called from procedure AddUserMacros in UMacroRun.p.} {This runs once each time macros are loaded from a file or a text window.} procedure UMTimerAdd; begin AddUMSym('StartElapsed', UserCommandT, StartElapsedUC); AddUMSym('MeasureElapsed', UserFuncT, MeasureElapsedUC); AddUMSym('StartDelay', UserCommandT, StartDelayUC); AddUMSym('KillDelay', UserCommandT, KillDelayUC); AddUMSym('WaitDelay', UserCommandT, WaitDelayUC); AddUMSym('StartPeriodic', UserCommandT, StartPeriodicUC); AddUMSym('KillPeriodic', UserCommandT, KillPeriodicUC); AddUMSym('AdjustPeriodic', UserCommandT, AdjustPeriodicUC); AddUMSym('WaitPeriodic', UserFuncT, WaitPeriodicUC); end; {Called from procedure LookupUserMacro in UMMacroRun.p} {This runs every time the macro is executed, just prior to} {parsing the arguments.} procedure UMTimerLookup (var uma: UserMacroArgs); begin with uma do case UserMacroCommand of StartElapsedUC: begin nArgs := 1; arg[1].atype := UMATinteger; {which timer} end; MeasureElapsedUC: begin nArgs := 1; arg[1].atype := UMATinteger; {which timer} end; StartDelayUC: begin nArgs := 2; arg[1].atype := UMATinteger; {which timer} arg[2].atype := UMATreal; {delay time} end; KillDelayUC: begin nArgs := 1; arg[1].atype := UMATinteger; {which timer} end; WaitDelayUC: begin nArgs := 1; arg[1].atype := UMATinteger; {which timer} end; StartPeriodicUC: begin nArgs := 2; arg[1].atype := UMATinteger; {which timer} arg[2].atype := UMATreal; {interval time} end; KillPeriodicUC: begin nArgs := 1; arg[1].atype := UMATinteger; {which timer} end; AdjustPeriodicUC: begin nArgs := 3; arg[1].atype := UMATinteger; {which timer} arg[2].atype := UMATreal; {new interval time} arg[3].atype := UMATreal; {phase adjustment, - = sooner, + = later} end; WaitPeriodicUC: begin nArgs := 2; arg[1].atype := UMATinteger; {which timer} arg[2].atype := UMATinteger; {number of intervals since start} end; otherwise begin ErrorOccurred := true; str := 'UMTimer.p LookupUserMacro'; end; end; end; procedure xStartElapsed (var uma: UserMacroArgs); var t: integer; begin with uma do begin t := arg[1].ival; if t = 1 then begin if StartTimer(ElapsedTimerA, -2000000000) <> noErr then begin errorOccurred := true; str := 'Timer already running'; DoStopTimer(ElapsedTimerA); {so it doesn't keep happening} end; end else if t = 2 then begin if StartTimer(ElapsedTimerB, -2000000000) <> noErr then begin errorOccurred := true; str := 'Timer already running'; DoStopTimer(ElapsedTimerB); {so it doesn't keep happening} end; end else begin errorOccurred := true; str := 'No such timer'; end; end; end; procedure xMeasureElapsed (var uma: UserMacroArgs); var t: integer; begin with uma do begin funcResult := 0.0; t := arg[1].ival; if t = 1 then begin funcResult := (StopTimer(ElapsedTimerA) + 2000000000) / 1000000.0; end else if t = 2 then begin funcResult := (StopTimer(ElapsedTimerB) + 2000000000) / 1000000.0; end else begin errorOccurred := true; str := 'No such timer'; end; if funcResult > 1999.0 then begin errorOccurred := true; str := 'Elapsed time timer overflow'; end; end; end; procedure xStartDelay (var uma: UserMacroArgs); var t: integer; begin with uma do begin t := arg[1].ival; delayExpired := false; if t <> 1 then begin errorOccurred := true; str := 'No such timer'; end else if (arg[2].aval < 0.0) or (arg[2].aval > 2000.0) then begin errorOccurred := true; str := 'Delay out of range (0 to 2000 sec.)'; end else if StartTimer(DelayTimer, -trunc(arg[2].aval * 1000000.0)) <> noErr then begin errorOccurred := true; str := 'Timer already running'; end; if errorOccurred then delayExpired := true; end; end; procedure xKillDelay (var uma: UserMacroArgs); var t: integer; begin with uma do begin t := arg[1].ival; if t <> 1 then begin errorOccurred := true; str := 'No such timer'; end else begin delayExpired := true; DoStopTimer(DelayTimer); end; end; end; procedure xWaitDelay (var uma: UserMacroArgs); var t: integer; theEvent: EventRecord; begin ShareTicks := TickCount + 10; with uma do begin t := arg[1].ival; if t <> 1 then begin errorOccurred := true; str := 'No such timer'; end else while not delayExpired do if TickCount > ShareTicks then begin ShareTicks := TickCount + 10; if EventAvail(everyEvent, theEvent) then ; {Allows background tasks to run} if CommandPeriod then begin ErrorOccurred := true; str := 'Delay aborted by command period'; delayExpired := true; DoStopTimer(DelayTimer); end; end; end; end; procedure xStartPeriodic (var uma: UserMacroArgs); var t: integer; begin with uma do begin t := arg[1].ival; if t <> 1 then begin errorOccurred := true; str := 'No such timer'; end else if (arg[2].aval < 0.0) or (arg[2].aval > 2000.0) then begin errorOccurred := true; str := 'Delay out of range (0 to 2000 sec.)'; end else begin PeriodicInterval := -trunc(arg[2].aval * 1000000.0); PeriodicPhase := 0; PeriodicCount := 0; if StartTimer(PeriodicTimer, PeriodicInterval) <> noErr then begin errorOccurred := true; str := 'Timer already running'; DoStopTimer(PeriodicTimer); {so it doesn't keep happening} end; end; end; end; procedure xKillPeriodic (var uma: UserMacroArgs); var t: integer; begin with uma do begin t := arg[1].ival; if t <> 1 then begin errorOccurred := true; str := 'No such timer'; end else DoStopTimer(PeriodicTimer); end; end; procedure xAdjustPeriodic (var uma: UserMacroArgs); var t: integer; begin with uma do begin t := arg[1].ival; if t <> 1 then begin errorOccurred := true; str := 'No such timer'; end else if (arg[2].aval < 0.0) or (arg[2].aval > 2000.0) then begin errorOccurred := true; str := 'Delay out of range (0 to 2000 sec.)'; end else if (arg[2].aval + arg[3].aval < 0.0) or (arg[2].aval + arg[3].aval > 2000.0) then begin errorOccurred := true; str := 'Phase+Delay out of range (0 to 2000 sec.)'; end else begin PeriodicInterval := -trunc(arg[2].aval * 1000000.0); PeriodicPhase := -trunc(arg[3].aval * 1000000.0); if PeriodicInterval + PeriodicPhase >= 0 then PeriodicPhase := -PeriodicInterval - 1; {mimimum delay 1 microsecond} end end; end; procedure xWaitPeriodic (var uma: UserMacroArgs); var t: integer; c: LongInt; theEvent: EventRecord; begin ShareTicks := TickCount + 10; with uma do begin t := arg[1].ival; if t <> 1 then begin errorOccurred := true; str := 'No such timer'; end else begin c := arg[2].ival; while c > PeriodicCount do if TickCount > ShareTicks then begin ShareTicks := TickCount + 10; if EventAvail(everyEvent, theEvent) then ; {Allows background tasks to run} if CommandPeriod then begin ErrorOccurred := true; str := 'Delay aborted by command period'; c := PeriodicCount; end; end; end; funcResult := PeriodicCount + 1; end; end; {Called from procedure DoUserMacro in UMacroRun.p .} {Do not change uma.nArgs or uma.arg[i].argt here.} {This runs once each time the macro is used, after parsing the arguments.} procedure UMTimerRun (var uma: UserMacroArgs); begin with uma do case UserMacroCommand of StartElapsedUC: xStartElapsed(uma); MeasureElapsedUC: xMeasureElapsed(uma); StartDelayUC: xStartDelay(uma); KillDelayUC: xKillDelay(uma); WaitDelayUC: xWaitDelay(uma); StartPeriodicUC: xStartPeriodic(uma); KillPeriodicUC: xKillPeriodic(uma); AdjustPeriodicUC: xAdjustPeriodic(uma); WaitPeriodicUC: xWaitPeriodic(uma); otherwise begin ErrorOccurred := true; str := 'UMTimer.p DoUserMacro'; end; end; end; {These procedures can be called from User.p with the same } {calling sequence as the macros.} procedure StartElapsed (t: integer); var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; xStartElapsed(uma); TimerErrorOccurred := uma.errorOccurred; end; function MeasureElapsed (t: integer): extended; var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; xMeasureElapsed(uma); TimerErrorOccurred := uma.errorOccurred; MeasureElapsed := uma.funcResult; end; procedure StartDelay (t: integer; delay: extended); var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; uma.arg[2].aval := delay; xStartDelay(uma); TimerErrorOccurred := uma.errorOccurred; end; procedure KillDelay (t: integer); var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; xKillDelay(uma); TimerErrorOccurred := uma.errorOccurred; end; procedure WaitDelay (t: integer); var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; xWaitDelay(uma); TimerErrorOccurred := uma.errorOccurred; end; procedure StartPeriodic (t: integer; interval: extended); var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; uma.arg[2].aval := interval; xStartPeriodic(uma); TimerErrorOccurred := uma.errorOccurred; end; procedure KillPeriodic (t: integer); var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; xKillPeriodic(uma); TimerErrorOccurred := uma.errorOccurred; end; procedure AdjustPeriodic (t: integer; interval, phase: extended); var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; uma.arg[2].aval := interval; uma.arg[3].aval := phase; xAdjustPeriodic(uma); TimerErrorOccurred := uma.errorOccurred; end; function WaitPeriodic (t: integer; n: longInt): longInt; var uma: UserMacroArgs; begin uma.errorOccurred := false; uma.arg[1].ival := t; uma.arg[2].ival := n; xWaitPeriodic(uma); TimerErrorOccurred := uma.errorOccurred; WaitPeriodic := trunc(uma.funcResult); end; function TimerError: Boolean; begin TimerError := TimerErrorOccurred; end; end.