GLS.AsyncTimer.pas 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.AsyncTimer;
  5. (*
  6. Asynchronous timer component (actual 1 ms resolution).
  7. This component is based on ThreadedTimer by Carlos Barbosa.
  8. *)
  9. interface
  10. {$I GLScene.inc}
  11. uses
  12. System.Classes,
  13. System.SysUtils,
  14. System.SyncObjs,
  15. GLS.Utils;
  16. const
  17. cDEFAULT_TIMER_INTERVAL = 1000;
  18. type
  19. (* Asynchronous timer component
  20. (actual 1 ms resolution, if CPU fast enough).
  21. Keep in mind timer resolution is obtained in-between events, but
  22. events are not triggered every x ms. For instance if you set the interval to
  23. 5 ms, and your Timer event takes 1 ms to complete, Timer events will actually
  24. be triggered every 5+1=6 ms (that's why it's "asynchronous").
  25. This component is based on ThreadedTimer by Carlos Barbosa. *)
  26. TGLAsyncTimer = class(TComponent)
  27. private
  28. FEnabled: Boolean;
  29. FOnTimer: TNotifyEvent;
  30. FTimerThread: TThread;
  31. FMutex: TCriticalSection;
  32. protected
  33. procedure SetEnabled(Value: Boolean);
  34. function GetInterval: Word;
  35. procedure SetInterval(Value: Word);
  36. function GetThreadPriority: TThreadPriority;
  37. procedure SetThreadPriority(Value: TThreadPriority);
  38. procedure DoTimer;
  39. public
  40. constructor Create(AOwner: TComponent); override;
  41. destructor Destroy; override;
  42. published
  43. property Enabled: Boolean read FEnabled write SetEnabled default False;
  44. property Interval: Word read GetInterval write SetInterval
  45. default cDEFAULT_TIMER_INTERVAL;
  46. property OnTimer: TNotifyEvent read FOnTimer write FOnTimer;
  47. property ThreadPriority: TThreadPriority read GetThreadPriority
  48. write SetThreadPriority default tpTimeCritical;
  49. end;
  50. // ------------------------------------------------------------------
  51. implementation
  52. // ------------------------------------------------------------------
  53. type
  54. TTimerThread = class(TThread)
  55. private
  56. FOwner: TGLAsyncTimer;
  57. FInterval: Word;
  58. protected
  59. procedure Execute; override;
  60. public
  61. constructor Create(CreateSuspended: Boolean); virtual;
  62. end;
  63. constructor TTimerThread.Create(CreateSuspended: Boolean);
  64. begin
  65. inherited Create(CreateSuspended);
  66. end;
  67. procedure TTimerThread.Execute;
  68. var
  69. lastTick, nextTick, curTick, perfFreq: Int64;
  70. begin
  71. QueryPerformanceFrequency(perfFreq);
  72. QueryPerformanceCounter(lastTick);
  73. nextTick := lastTick + (FInterval * perfFreq) div 1000;
  74. while not Terminated do
  75. begin
  76. FOwner.FMutex.Acquire;
  77. FOwner.FMutex.Release;
  78. while not Terminated do
  79. begin
  80. QueryPerformanceCounter(lastTick);
  81. if lastTick >= nextTick then
  82. break;
  83. Sleep(1);
  84. end;
  85. if not Terminated then
  86. begin
  87. // if time elapsed run user-event
  88. Synchronize(FOwner.DoTimer);
  89. QueryPerformanceCounter(curTick);
  90. nextTick := lastTick + (FInterval * perfFreq) div 1000;
  91. if nextTick <= curTick then
  92. begin
  93. // CPU too slow... delay to avoid monopolizing what's left
  94. nextTick := curTick + (FInterval * perfFreq) div 1000;
  95. end;
  96. end;
  97. end;
  98. end;
  99. //-----------------------------------------
  100. // TGLAsyncTimer
  101. //-----------------------------------------
  102. constructor TGLAsyncTimer.Create(AOwner: TComponent);
  103. begin
  104. inherited Create(AOwner);
  105. // create timer thread
  106. FMutex := TCriticalSection.Create;
  107. FMutex.Acquire;
  108. FTimerThread := TTimerThread.Create(False);
  109. with TTimerThread(FTimerThread) do
  110. begin
  111. FOwner := Self;
  112. FreeOnTerminate := False;
  113. Priority := tpTimeCritical;
  114. FInterval := cDEFAULT_TIMER_INTERVAL;
  115. end;
  116. end;
  117. destructor TGLAsyncTimer.Destroy;
  118. begin
  119. Enabled := False;
  120. FTimerThread.Terminate;
  121. FMutex.Release;
  122. CheckSynchronize;
  123. // wait & free
  124. FTimerThread.WaitFor;
  125. FTimerThread.Free;
  126. FMutex.Free;
  127. inherited Destroy;
  128. end;
  129. procedure TGLAsyncTimer.DoTimer;
  130. begin
  131. if Enabled and Assigned(FOnTimer) then
  132. FOnTimer(Self);
  133. end;
  134. procedure TGLAsyncTimer.SetEnabled(Value: Boolean);
  135. begin
  136. if Value <> FEnabled then
  137. begin
  138. FEnabled := Value;
  139. if FEnabled then
  140. begin
  141. // When enabled resume thread
  142. if TTimerThread(FTimerThread).FInterval > 0 then
  143. FMutex.Release;
  144. end
  145. else
  146. FMutex.Acquire;
  147. end;
  148. end;
  149. function TGLAsyncTimer.GetInterval: Word;
  150. begin
  151. Result := TTimerThread(FTimerThread).FInterval;
  152. end;
  153. procedure TGLAsyncTimer.SetInterval(Value: Word);
  154. begin
  155. if Value <> TTimerThread(FTimerThread).FInterval then
  156. begin
  157. TTimerThread(FTimerThread).FInterval := Value;
  158. end;
  159. end;
  160. function TGLAsyncTimer.GetThreadPriority: TThreadPriority;
  161. begin
  162. Result := FTimerThread.Priority;
  163. end;
  164. procedure TGLAsyncTimer.SetThreadPriority(Value: TThreadPriority);
  165. begin
  166. FTimerThread.Priority := Value;
  167. end;
  168. //-----------------------------------------------------------
  169. initialization
  170. //-----------------------------------------------------------
  171. RegisterClass(TGLAsyncTimer);
  172. end.