{*********************************************************} {* VPTIMERPOOL.PAS 1.03 *} {*********************************************************} {* ***** BEGIN LICENSE BLOCK ***** *} {* Version: MPL 1.1 *} {* *} {* The contents of this file are subject to the Mozilla Public License *} {* Version 1.1 (the "License"); you may not use this file except in *} {* compliance with the License. You may obtain a copy of the License at *} {* http://www.mozilla.org/MPL/ *} {* *} {* Software distributed under the License is distributed on an "AS IS" basis, *} {* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License *} {* for the specific language governing rights and limitations under the *} {* License. *} {* *} {* The Original Code is TurboPower Visual PlanIt *} {* *} {* The Initial Developer of the Original Code is TurboPower Software *} {* *} {* Portions created by TurboPower Software Inc. are Copyright (C) 2002 *} {* TurboPower Software Inc. All Rights Reserved. *} {* *} {* Contributor(s): *} {* *} {* ***** END LICENSE BLOCK ***** *} {$I vp.inc} unit VpTimerPool; {-Timer Pool Class} interface uses {$IFDEF LCL} LMessages, LCLProc, LCLType, LCLIntf, {$ELSE} Windows, Messages, {$ENDIF} Classes, SysUtils, Forms, VpException; type TVpTimerTriggerEvent = procedure(Sender : TObject; Handle : Integer; Interval : Cardinal; ElapsedTime : LongInt) of object; type PEventRec = ^TEventRec; TEventRec = packed record erHandle : Integer; {handle of this event record} erInitTime : LongInt; {time when trigger was created} erElapsed : LongInt; {total elapsed time (ms)} erInterval : Cardinal; {trigger interval} erLastTrigger : LongInt; {time last trigger was fired} erOnTrigger : TVpTimerTriggerEvent; {method to call when fired} erEnabled : Boolean; {true if trigger is active} erRecurring : Boolean; {false for one time trigger} end; type TVpTimerPool = class(TComponent) protected {private} {property variables} FOnAllTriggers : TVpTimerTriggerEvent; {internal variables} tpList : TList; {list of event TEventRec records} tpHandle : hWnd; {our window handle} tpInterval : Cardinal; {the actual Window's timer interval} tpEnabledCount : Integer; {count of active triggers} {property methods} function GetElapsedTriggerTime(Handle : Integer) : LongInt; function GetElapsedTriggerTimeSec(Handle : Integer) : LongInt; function GetOnTrigger(Handle : Integer) : TVpTimerTriggerEvent; function GetTriggerCount : Integer; function GetTriggerEnabled(Handle : Integer) : Boolean; function GetTriggerInterval(Handle : Integer) : Cardinal; procedure SetOnTrigger(Handle : Integer; Value: TVpTimerTriggerEvent); procedure SetTriggerEnabled(Handle : Integer; Value: Boolean); procedure SetTriggerInterval(Handle : Integer; Value: Cardinal); {internal methods} procedure tpCalcNewInterval; {-calculates the needed interval for the window's timer} function tpCountEnabledTriggers : Integer; {-returns the number of enabled/active timer triggers} function tpCreateTriggerHandle : Integer; {-returns a unique timer trigger handle} function tpEventIndex(Handle : Integer) : Integer; {-returns the internal list index corresponding to the trigger handle} procedure tpSortTriggers; {-sorts the internal list of timer trigger event records} procedure tpTimerWndProc(var Msg: {$IFDEF DELPHI}TMessage{$ELSE}TLMessage{$ENDIF}); {-window procedure to catch timer messages} procedure tpUpdateTimer; {-re-create the windows timer with a new timer interval} protected procedure DoTriggerNotification; virtual; {-conditionally sends notification of all events} public constructor Create(AOwner: TComponent); override; destructor Destroy; override; function AddOneShot(OnTrigger : TVpTimerTriggerEvent; Interval : Cardinal) : Integer; {-adds or updates one timer trigger. removed automatically after one firing} function AddOneTime(OnTrigger : TVpTimerTriggerEvent; Interval : Cardinal) : Integer; {-adds a new timer trigger. removed automatically after one firing} function Add(OnTrigger : TVpTimerTriggerEvent; Interval : Cardinal) : Integer; {-adds a new timer trigger and returns a handle} procedure Remove(Handle : Integer); {-removes the timer trigger} procedure RemoveAll; {-disable and destroy all timer triggers} procedure ResetElapsedTime(Handle : Integer); {-resets ElapsedTime for a given Trigger to 0} {public properties} property Count : Integer read GetTriggerCount; property ElapsedTime[Handle : Integer] : LongInt read GetElapsedTriggerTime; property ElapsedTimeSec[Handle : Integer] : LongInt read GetElapsedTriggerTimeSec; property Enabled[Handle : Integer] : Boolean read GetTriggerEnabled write SetTriggerEnabled; property Interval[Handle : Integer] : Cardinal read GetTriggerInterval write SetTriggerInterval; {events} property OnTrigger[Handle : Integer] : TVpTimerTriggerEvent read GetOnTrigger write SetOnTrigger; property OnAllTriggers : TVpTimerTriggerEvent read FOnAllTriggers write FOnAllTriggers; end; implementation {$R-,Q-} const tpDefMinInterval = 55; {smallest timer interval allowed} tpDefHalfMinInterval = tpDefMinInterval div 2; {*** internal routines ***} function NewEventRec : PEventRec; begin GetMem(Result, SizeOf(TEventRec)); FillChar(Result^, SizeOf(TEventRec), #0); end; procedure FreeEventRec(ER : PEventRec); begin if (ER <> nil) then FreeMem(ER, SizeOf(TEventRec)); end; {*** TVpTimerPool ***} constructor TVpTimerPool.Create(AOwner: TComponent); begin inherited Create(AOwner); {create internal list for trigger event records} tpList := TList.Create; {allocate a window handle for the timer} {$IFDEF DELPHI} tpHandle := {$IFDEF VERSION6}Classes.{$ENDIF}AllocateHWnd(tpTimerWndProc); {$ELSE} // ToDo: tpHandle := AllocateHWnd(tpTimerWndProc); {$ENDIF} end; destructor TVpTimerPool.Destroy; var I : Integer; begin {force windows timer to be destroyed} tpInterval := 0; tpUpdateTimer; {free contents of list} for I := 0 to tpList.Count-1 do FreeEventRec(tpList[I]); {destroy the internal list} tpList.Free; tpList := nil; {deallocate our window handle} {$IFDEF DELPHI} {$IFDEF VERSION6}Classes.{$ENDIF}DeallocateHWnd(tpHandle); {$ELSE} // ToDo: DeallocateHWnd(tpHandle); {$ENDIF} inherited Destroy; end; function TVpTimerPool.AddOneShot(OnTrigger : TVpTimerTriggerEvent; Interval : Cardinal) : Integer; {-adds or updates one timer trigger. removed automatically after one firing} var I : Integer; begin {if this OnTrigger handler is already installed, remove it} if Assigned(OnTrigger) then begin for I := 0 to tpList.Count-1 do with PEventRec(tpList[I])^ do if @erOnTrigger = @OnTrigger then begin Remove(erHandle); Break; end; end; {add the one-time trigger} Result := AddOneTime(OnTrigger, Interval); end; function TVpTimerPool.AddOneTime(OnTrigger : TVpTimerTriggerEvent; Interval : Cardinal) : Integer; {-adds a new timer trigger. removed automatically after one firing} var I : Integer; begin {add trigger} Result := Add(OnTrigger, Interval); {if added, set to non-recurring} if (Result > -1) then begin I := tpEventIndex(Result); if I > -1 then PEventRec(tpList[I])^.erRecurring := False else Result := -1; end; end; function TVpTimerPool.Add(OnTrigger : TVpTimerTriggerEvent; Interval : Cardinal) : Integer; {-adds a new timer trigger and returns a handle} var ER : PEventRec; begin Result := -1; {assume error} {create new event record} ER := NewEventRec; if (ER = nil) then Exit; {force interval to be at least the minimum} if Interval < tpDefMinInterval then Interval := tpDefMinInterval; {fill event record} with ER^ do begin erEnabled := True; erHandle := tpCreateTriggerHandle; erInitTime := GetTickCount; erElapsed := 0; erInterval := Interval; erLastTrigger := erInitTime; erOnTrigger := OnTrigger; erRecurring := True; end; {add trigger record to the list} tpList.Add(ER); {return the trigger event handle} Result := ER^.erHandle; {re-calculate the number of active triggers} tpEnabledCount := tpCountEnabledTriggers; {calculate new interval for the windows timer} tpCalcNewInterval; tpSortTriggers; tpUpdateTimer; end; procedure TVpTimerPool.DoTriggerNotification; {-conditionally sends notification for all events} var ER : PEventRec; TC : LongInt; I : Integer; ET : longint; begin TC := GetTickCount; {cycle through all triggers} I := 0; while I < tpList.Count do begin ER := PEventRec(tpList[I]); if ER^.erEnabled then begin {is it time to fire this trigger} if (TC < ER^.erLastTrigger) then ET := (High(LongInt) - ER^.erLastTrigger) + (TC - Low(LongInt)) else ET := TC - ER^.erLastTrigger; if (ET >= LongInt(ER^.erInterval)-tpDefHalfMinInterval) then begin {update event record with this trigger time} ER^.erLastTrigger := TC; {check if total elapsed time for trigger >= MaxLongInt} if ((MaxLongInt - ER^.erElapsed) < ET) then ER^.erElapsed := MaxLongInt else ER^.erElapsed := ER^.erElapsed + ET; {call user event handler, if assigned} if Assigned(ER^.erOnTrigger) then ER^.erOnTrigger(Self, ER^.erHandle, ER^.erInterval, ER^.erElapsed); {call general event handler, if assigned} if Assigned(FOnAllTriggers) then FOnAllTriggers(Self, ER^.erHandle, ER^.erInterval, ER^.erElapsed); if not ER^.erRecurring then begin Remove(ER^.erHandle); Dec(I); {adjust loop index for this deletion} end; end; end; Inc(I); end; end; function TVpTimerPool.GetElapsedTriggerTime(Handle : Integer) : LongInt; {-return the number of miliseconds since the timer trigger was created} var I : Integer; ET : longint; ER : PEventRec; TC : LongInt; begin I := tpEventIndex(Handle); if (I > -1) then begin ER := PEventRec(tpList[I]); if ER^.erElapsed = High(LongInt) then Result := High(LongInt) else begin TC := GetTickCount; if (TC < ER^.erInitTime) then begin ET := (High(LongInt) - ER^.erInitTime) + (TC - Low(LongInt)); if (ET < ER^.erElapsed) then ER^.erElapsed := High(LongInt) else ER^.erElapsed := ET; end else ER^.erElapsed := TC - ER^.erInitTime; Result := ER^.erElapsed; end; end else raise EInvalidTriggerHandle.Create; end; function TVpTimerPool.GetElapsedTriggerTimeSec(Handle : Integer) : LongInt; {-return the number of seconds since the timer trigger was created} begin Result := GetElapsedTriggerTime(Handle) div 1000; end; function TVpTimerPool.GetOnTrigger(Handle : Integer) : TVpTimerTriggerEvent; {-returns the timer trigger's event method address} var I : Integer; begin I := tpEventIndex(Handle); if (I > -1) then Result := PEventRec(tpList[I])^.erOnTrigger else raise EInvalidTriggerHandle.Create; end; function TVpTimerPool.GetTriggerCount : Integer; {-returns the number of maintained timer triggers} begin Result := tpList.Count; end; function TVpTimerPool.GetTriggerEnabled(Handle : Integer) : Boolean; {-returns the timer trigger's enabled status} var I : Integer; begin I := tpEventIndex(Handle); if (I > -1) then Result := PEventRec(tpList[I])^.erEnabled else raise EInvalidTriggerHandle.Create; end; function TVpTimerPool.GetTriggerInterval(Handle : Integer) : Cardinal; {-returns the interval for the timer trigger with Handle} var I : Integer; begin I := tpEventIndex(Handle); if (I > -1) then Result := PEventRec(tpList[I])^.erInterval else raise EInvalidTriggerHandle.Create; end; procedure TVpTimerPool.Remove(Handle : Integer); {-removes the timer trigger} var ER : PEventRec; I : Integer; begin I := tpEventIndex(Handle); if (I > -1) then begin ER := PEventRec(tpList[I]); tpList.Delete(I); FreeEventRec(ER); tpEnabledCount := tpCountEnabledTriggers; tpCalcNewInterval; tpUpdateTimer; end; end; procedure TVpTimerPool.RemoveAll; {-disable and destroy all timer triggers} var ER : PEventRec; I : Integer; begin for I := tpList.Count-1 downto 0 do begin ER := PEventRec(tpList[I]); tpList.Delete(I); FreeEventRec(ER); end; tpEnabledCount := 0; tpInterval := 0; tpUpdateTimer; end; procedure TVpTimerPool.ResetElapsedTime(Handle : Integer); {-resets ElapsedTime for a given Trigger to 0} var I : Integer; begin I := tpEventIndex(Handle); if (I > -1) then PEventRec(tpList[I])^.erInitTime := LongInt(GetTickCount) else raise EInvalidTriggerHandle.Create; end; procedure TVpTimerPool.SetOnTrigger(Handle : Integer; Value: TVpTimerTriggerEvent); {-sets the method to call when the timer trigger fires} var I : Integer; begin I := tpEventIndex(Handle); if (I > -1) then PEventRec(tpList[I])^.erOnTrigger := Value else raise EInvalidTriggerHandle.Create; end; procedure TVpTimerPool.SetTriggerEnabled(Handle : Integer; Value: Boolean); {-sets the timer trigger's enabled status} var I : Integer; begin I := tpEventIndex(Handle); if (I > -1) then begin if (Value <> PEventRec(tpList[I])^.erEnabled) then begin PEventRec(tpList[I])^.erEnabled := Value; {If the timer is being activated, then initialize LastTrigger} if PEventRec(tpList[I])^.erEnabled then PEventRec(tpList[I])^.erLastTrigger := GetTickCount; tpEnabledCount := tpCountEnabledTriggers; tpCalcNewInterval; tpUpdateTimer; end; end else raise EInvalidTriggerHandle.Create; end; procedure TVpTimerPool.SetTriggerInterval(Handle : Integer; Value : Cardinal); {-sets the timer trigger's interval} var I : Integer; begin I := tpEventIndex(Handle); if (I > -1) then begin if Value <> PEventRec(tpList[I])^.erInterval then begin PEventRec(tpList[I])^.erInterval := Value; tpCalcNewInterval; tpUpdateTimer; end; end else raise EInvalidTriggerHandle.Create; end; procedure TVpTimerPool.tpCalcNewInterval; {-calculates the needed interval for the window's timer} var I : Integer; N, V : LongInt; TR : LongInt; ER : PEventRec; TC : LongInt; Done : Boolean; begin {find shortest trigger interval} TC := GetTickCount; tpInterval := High(Cardinal); for I := 0 to tpList.Count-1 do begin ER := PEventRec(tpList[I]); if ER^.erEnabled then begin if (ER^.erInterval < tpInterval) then tpInterval := ER^.erInterval; {is this interval greater than the remaining time on any existing triggers} TR := 0; if (TC < ER^.erLastTrigger) then TR := TR + MaxLongInt else TR := TC - ER^.erLastTrigger; if LongInt(tpInterval) > (LongInt(ER^.erInterval) - TR) then tpInterval := (LongInt(ER^.erInterval) - TR); end; end; {limit to smallest allowable interval} if tpInterval < tpDefMinInterval then tpInterval := tpDefMinInterval; if tpInterval = High(Cardinal) then tpInterval := 0 else begin {find interval that evenly divides into all trigger intervals} V := tpInterval; {use LongInt so it is possible for it to become (-)} repeat Done := True; for I := 0 to tpList.Count-1 do begin N := PEventRec(tpList[I])^.erInterval; if (N mod V) <> 0 then begin Dec(V, N mod V); Done := False; Break; end; end; until Done or (V <= tpDefMinInterval); {limit to smallest allowable interval} if V < tpDefMinInterval then V := tpDefMinInterval; tpInterval := V; end; end; function TVpTimerPool.tpCountEnabledTriggers : Integer; {-returns the number of enabled/active timer triggers} var I : Integer; begin Result := 0; for I := 0 to tpList.Count-1 do if PEventRec(tpList[I])^.erEnabled then Inc(Result); end; function TVpTimerPool.tpCreateTriggerHandle : Integer; {-returns a unique timer trigger handle} var I : Integer; H : Integer; begin Result := 0; for I := 0 to tpList.Count-1 do begin H := PEventRec(tpList[I])^.erHandle; if H >= Result then Result := H + 1; end; end; function TVpTimerPool.tpEventIndex(Handle : Integer) : Integer; {-returns the internal list index corresponding to Handle} var I : Integer; begin Result := -1; for I := 0 to tpList.Count-1 do if PEventRec(tpList[I])^.erHandle = Handle then begin Result := I; Break; end; end; procedure TVpTimerPool.tpSortTriggers; {-sorts the internal list of timer trigger event records} var I : Integer; Done : Boolean; begin repeat Done := True; for I := 0 to tpList.Count-2 do begin if (PEventRec(tpList[I])^.erInterval > PEventRec(tpList[I+1])^.erInterval) then begin tpList.Exchange(I, I+1); Done := False; end; end; until Done; end; procedure TVpTimerPool.tpTimerWndProc(var Msg: {$IFDEF DELPHI}TMessage{$ELSE}TLMessage{$ENDIF}); {-window procedure to catch timer messages} begin with Msg do if Msg = {$IFDEF LCL}LM_TIMER{$ELSE}WM_TIMER{$ENDIF} then try DoTriggerNotification; except Application.HandleException(Self); end {$IFNDEF LCL} else Result := DefWindowProc(tpHandle, Msg, wParam, lParam); {$ENDIF} end; procedure TVpTimerPool.tpUpdateTimer; {-re-create the windows timer with a new timer interval} begin {remove existing timer, if any} //TODO: {$IFNDEF LCL} if KillTimer(tpHandle, 1) then {ignore return value}; if (tpInterval <> 0) and (tpEnabledCount > 0) then if SetTimer(tpHandle, 1, tpInterval, nil) = 0 then raise ENoTimersAvailable.Create; {$ENDIF} end; end.