fptimer.pp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 1999-2000 by Michael Van Canneyt.
  4. See the file COPYING.FPC, included in this distribution,
  5. for details about the copyright.
  6. This program is distributed in the hope that it will be useful,
  7. but WITHOUT ANY WARRANTY; without even the implied warranty of
  8. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  9. **********************************************************************}
  10. {
  11. A generic timer component. Can be used in GUI and non-GUI apps.
  12. Based heavily on an idea by Graeme Geldenhuys, extended so
  13. the tick mechanism is pluggable.
  14. Note that the system implementation will only work for timers
  15. in the main thread, as it uses synchronize to do the job.
  16. You need to enable threads in your application for the system
  17. implementation to work.
  18. A nice improvement would be an implementation that works
  19. in all threads, such as the threadedtimer of IBX for linux.
  20. Replaced SLEEP with TEvent for those platforms supporting threading:
  21. Windows, Linux, BSD.
  22. On the other platforms, use sleep. This unfortunately has a high overhead
  23. resulting in drift. A five minute timer could be up to 40 seconds late
  24. do to entering and returning (linux x64). MOdified to check the absolute
  25. time every minute, has reduced that lag to about 0.100 second. This is
  26. still greater than TEvent, where the delay is only a few milliseconds (0-3).
  27. }
  28. unit fptimer;
  29. {$mode objfpc}{$H+}
  30. {
  31. Windows, or any platform that uses Cthreads has TEvent with a timed wait
  32. which can include android and embedded.
  33. You can force the use of the Sleep() based timer by defining USESLEEP
  34. }
  35. {$IFNDEF USESLEEP}
  36. {$if Defined(MSWINDOWS) or (Defined(UNIX) and not Defined(BEOS))}
  37. {$define Has_EventWait}
  38. {$endif}
  39. {$ENDIF}
  40. interface
  41. uses
  42. Classes;
  43. type
  44. TFPTimerDriver = Class;
  45. { TFPCustomTimer }
  46. TFPCustomTimer = class(TComponent)
  47. private
  48. FDriver : TFPTimerDriver;
  49. FOnStartTimer : TNotifyEvent;
  50. FOnStopTimer : TNotifyEvent;
  51. FOnTimer : TNotifyEvent;
  52. FInterval : Cardinal;
  53. FActive : Boolean;
  54. FEnabled : Boolean;
  55. FUseTimerThread : Boolean;
  56. procedure SetEnabled(const AValue: Boolean );
  57. procedure SetInterval(const AValue: Cardinal);
  58. protected
  59. property Active: Boolean read FActive write FActive;
  60. Function CreateTimerDriver : TFPTimerDriver;
  61. procedure Timer; virtual;
  62. public
  63. Constructor Create(AOwner: TComponent); override;
  64. Destructor Destroy; override;
  65. procedure StartTimer; virtual;
  66. procedure StopTimer; virtual;
  67. protected
  68. property Enabled: Boolean read FEnabled write SetEnabled;
  69. property Interval: Cardinal read FInterval write SetInterval;
  70. property UseTimerThread: Boolean read FUseTimerThread write FUseTimerThread;
  71. property OnTimer: TNotifyEvent read FOnTimer write FOnTimer;
  72. property OnStartTimer: TNotifyEvent read FOnStartTimer write FOnStartTimer;
  73. property OnStopTimer: TNotifyEvent read FOnStopTimer write FOnStopTimer;
  74. end;
  75. TFPTimer = Class(TFPCustomTimer)
  76. Published
  77. Property Enabled;
  78. Property Interval;
  79. Property UseTimerThread;
  80. Property OnTimer;
  81. Property OnStartTimer;
  82. Property OnStopTimer;
  83. end;
  84. { TFPTimerDriver }
  85. TFPTimerDriver = Class(TObject)
  86. Protected
  87. FTimer : TFPCustomTimer;
  88. FTimerStarted : Boolean;
  89. procedure SetInterval(const AValue: Cardinal); virtual;
  90. Public
  91. Constructor Create(ATimer : TFPCustomTimer); virtual;
  92. Procedure StartTimer; virtual; abstract;
  93. Procedure StopTimer; virtual; abstract;
  94. Property Timer : TFPCustomTimer Read FTimer;
  95. property TimerStarted: Boolean read FTimerStarted;
  96. end;
  97. TFPTimerDriverClass = Class of TFPTimerDriver;
  98. Var
  99. DefaultTimerDriverClass : TFPTimerDriverClass = Nil;
  100. implementation
  101. uses
  102. SysUtils;
  103. { ---------------------------------------------------------------------
  104. TFPTimer
  105. ---------------------------------------------------------------------}
  106. constructor TFPCustomTimer.Create(AOwner: TComponent);
  107. begin
  108. inherited;
  109. FDriver:=CreateTimerDriver;
  110. end;
  111. destructor TFPCustomTimer.Destroy;
  112. begin
  113. StopTimer;
  114. FDriver.FTimer:=Nil;
  115. FreeAndNil(FDriver);
  116. Inherited;
  117. end;
  118. Function TFPCustomTimer.CreateTimerDriver : TFPTimerDriver;
  119. begin
  120. Result:=DefaultTimerDriverClass.Create(Self);
  121. end;
  122. procedure TFPCustomTimer.SetEnabled(const AValue: Boolean);
  123. begin
  124. if AValue <> FEnabled then
  125. begin
  126. FEnabled := AValue;
  127. if FEnabled then
  128. StartTimer
  129. else
  130. StopTimer;
  131. end;
  132. end;
  133. procedure TFPCustomTimer.SetInterval(const AValue: Cardinal);
  134. begin
  135. if FInterval <> AValue then
  136. begin
  137. fInterval := AValue;
  138. if FActive and (fInterval > 0) then
  139. FDriver.SetInterval(AValue) // Allow driver to update Interval
  140. else
  141. StopTimer; // Timer not required
  142. end;
  143. end;
  144. procedure TFPCustomTimer.StartTimer;
  145. var
  146. IsActive: Boolean;
  147. begin
  148. IsActive:=FEnabled and (fInterval > 0) and Assigned(FOnTimer);
  149. If IsActive and not fActive and Not (csDesigning in ComponentState) then
  150. begin
  151. FDriver.StartTimer;
  152. if FDriver.TimerStarted then
  153. begin
  154. FActive := True;
  155. if Assigned(OnStartTimer) then
  156. OnStartTimer(Self);
  157. end;
  158. end;
  159. end;
  160. procedure TFPCustomTimer.StopTimer;
  161. begin
  162. if FActive then
  163. begin
  164. FDriver.StopTimer;
  165. if not FDriver.TimerStarted then
  166. begin
  167. FActive:=False;
  168. if Assigned(OnStopTimer) then
  169. OnStopTimer(Self);
  170. end;
  171. end;
  172. end;
  173. procedure TFPCustomTimer.Timer;
  174. begin
  175. { We check on FEnabled: If by any chance a tick comes in after it was
  176. set to false, the user won't notice, since no event is triggered.}
  177. If FActive and Assigned(FOnTimer) then
  178. FOnTimer(Self);
  179. end;
  180. { ---------------------------------------------------------------------
  181. TFPTimerDriver
  182. ---------------------------------------------------------------------}
  183. Constructor TFPTimerDriver.Create(ATimer : TFPCustomTimer);
  184. begin
  185. FTimer:=ATimer;
  186. end;
  187. procedure TFPTimerDriver.SetInterval(const AValue: Cardinal);
  188. begin
  189. // Default implementation is to restart the timer on Interval change
  190. if TimerStarted then
  191. begin
  192. StopTimer;
  193. FTimerStarted := (AValue > 0);
  194. if FTimerStarted then
  195. StartTimer;
  196. end;
  197. end;
  198. { ---------------------------------------------------------------------
  199. Default implementation. Threaded timer, one thread per timer.
  200. ---------------------------------------------------------------------}
  201. const
  202. cMilliSecs: Extended = 60.0 * 60.0 * 24.0 * 1000.0;
  203. Type
  204. { TFPTimerThread }
  205. TFPTimerThread = class(TThread)
  206. private
  207. FTimerDriver: TFPTimerDriver;
  208. FStartTime : TDateTime;
  209. {$ifdef Has_EventWait}
  210. FWaitEvent: PEventState;
  211. {$else}
  212. fSignaled: Boolean;
  213. {$endif}
  214. fInterval: Cardinal;
  215. Function Timer : TFPCustomTimer;
  216. Function GetWakeTime(var AInterval,Counter : Int64; Out WakeInterval : Integer; Out WakeTime : TDateTime) : Boolean;
  217. public
  218. procedure Execute; override;
  219. constructor CreateTimerThread(ATimerDriver: TFPTimerDriver);
  220. procedure Terminate;
  221. procedure SetInterval(const AValue: Cardinal);
  222. end;
  223. { TFPThreadedTimerDriver }
  224. TFPThreadedTimerDriver = Class(TFPTimerDriver)
  225. Private
  226. FThread : TFPTimerThread;
  227. protected
  228. Procedure SetInterval(const AValue: cardinal); override;
  229. Public
  230. Procedure StartTimer; override;
  231. Procedure StopTimer; override;
  232. end;
  233. { ---------------------------------------------------------------------
  234. TFPTimerThread
  235. ---------------------------------------------------------------------}
  236. constructor TFPTimerThread.CreateTimerThread(ATimerDriver: TFPTimerDriver);
  237. begin
  238. inherited Create(True);
  239. FTimerDriver:=ATimerDriver;
  240. {$ifdef Has_EventWait}
  241. FWaitEvent := BasicEventCreate(nil,false,false,'');
  242. {$else}
  243. fSignaled := False;
  244. {$endif}
  245. fInterval := ATimerDriver.Timer.Interval;
  246. FreeOnTerminate := True;
  247. end;
  248. procedure TFPTimerThread.Terminate;
  249. begin
  250. inherited Terminate;
  251. {$ifdef Has_EventWait}
  252. BasicEventSetEvent(fWaitEvent);
  253. {$else}
  254. fSignaled := True;
  255. {$endif}
  256. end;
  257. procedure TFPTimerThread.SetInterval(const AValue: Cardinal);
  258. begin
  259. if fInterval <> AValue then
  260. begin
  261. fInterval := AValue;
  262. {$ifdef Has_EventWait}
  263. BasicEventSetEvent(fWaitEvent); // Wake thread
  264. {$else}
  265. fSignaled := True;
  266. {$endif}
  267. end;
  268. end;
  269. Function TFPTimerThread.Timer : TFPCustomTimer;
  270. begin
  271. If Assigned(FTimerDriver) Then
  272. Result:=FTimerDriver.FTimer
  273. else
  274. Result:=Nil;
  275. end;
  276. Function TFPTimerThread.GetWakeTime(var AInterval,Counter : Int64; Out WakeInterval : Longint; Out WakeTime : TDateTime) : Boolean;
  277. Var
  278. Diff: Extended;
  279. begin
  280. Result:=False;
  281. { Use Counter*fInterval to avoid numerical errors resulting from adding
  282. small values (AInterval/cMilliSecs) to a large real number (TDateTime),
  283. even when using Extended precision }
  284. WakeTime := FStartTime + (Counter*AInterval / cMilliSecs);
  285. Diff := (WakeTime - Now);
  286. if Diff > 0 then
  287. begin
  288. WakeInterval := Trunc(Diff * cMilliSecs);
  289. if WakeInterval < 10 then
  290. WakeInterval := 10; // Provide a minimum wait time
  291. end
  292. else
  293. begin
  294. WakeInterval:=MaxInt;
  295. // Time has already expired, execute Timer and restart wait loop
  296. try
  297. if not Timer.UseTimerThread then
  298. Synchronize(@Timer.Timer) // Call user event
  299. else
  300. Timer.Timer;
  301. except
  302. // Trap errors to prevent this thread from terminating
  303. end;
  304. Inc(Counter);
  305. Result:=True;
  306. end;
  307. end;
  308. {$ifdef Has_EventWait}
  309. procedure TFPTimerThread.Execute;
  310. var
  311. WakeTime, StartTime: TDateTime;
  312. WakeInterval: Integer;
  313. Counter: int64; { use Int64 to avoid overflow with Counter*fInterval (~49 days)}
  314. AInterval: int64;
  315. Diff: Extended;
  316. Const
  317. wrSignaled = 0;
  318. wrTimeout = 1;
  319. wrAbandoned= 2;
  320. wrError = 3;
  321. begin
  322. WakeInterval := MaxInt;
  323. Counter := 1;
  324. AInterval := fInterval;
  325. FStartTime := Now;
  326. while not Terminated do
  327. begin
  328. if GetWakeTime(AInterval,Counter,WakeInterval,WakeTime) then
  329. Continue;
  330. if not Terminated then
  331. case BasicEventWaitFor(WakeInterval,fWaitEvent) of
  332. wrTimeout:
  333. begin
  334. if Terminated then
  335. Break
  336. else
  337. begin
  338. try
  339. if not Timer.UseTimerThread then
  340. // If terminate is called while here, then the Synchronize will be
  341. // queued while the stoptimer is being processed.
  342. // StopTimer cannot wait until thread completion as this would deadlock
  343. Synchronize(@Timer.Timer) // Call user event
  344. else
  345. Timer.Timer;
  346. except
  347. // Trap errors to prevent this thread from terminating
  348. end;
  349. Inc(Counter); // Next interval
  350. end;
  351. end;
  352. wrSignaled:
  353. begin
  354. if Terminated then
  355. Break
  356. else
  357. begin // Interval has changed
  358. Counter := 1; // Restart timer without creating new thread
  359. AInterval := fInterval;
  360. FStartTime := Now;
  361. end;
  362. end;
  363. else
  364. Break;
  365. end
  366. end;
  367. BasicEventDestroy(fWaitEvent);
  368. end;
  369. {$ELSE Has_EventWait}
  370. procedure TFPTimerThread.Execute;
  371. var
  372. WakeTime, StartTime: TDateTime;
  373. WakeInterval: Integer;
  374. Counter: int64; { use Int64 to avoid overflow with Counter*fInterval (~49 days)}
  375. AInterval: int64;
  376. Diff: Extended;
  377. S,Last: Cardinal;
  378. RecheckTimeCounter: integer;
  379. const
  380. cSleepTime = 500; // 0.5 second, better than every 5 milliseconds
  381. cRecheckTimeCount = 120; // Recheck clock every minute, as the sleep loop can loose time
  382. begin
  383. WakeInterval := MaxInt;
  384. Counter := 1;
  385. AInterval := fInterval;
  386. FStartTime := Now;
  387. while not Terminated do
  388. begin
  389. if GetWakeTime(AInterval,Counter,WakeInterval,WakeTime) then
  390. Continue;
  391. if not Terminated then
  392. begin
  393. RecheckTimeCounter := cRecheckTimeCount;
  394. s := cSleepTime;
  395. repeat
  396. if s > WakeInterval then
  397. s := WakeInterval;
  398. sleep(s);
  399. if fSignaled then // Terminated or interval has changed
  400. begin
  401. if not Terminated then
  402. begin
  403. fSignaled := False;
  404. Counter := 1; // Restart timer
  405. AInterval := fInterval;
  406. StartTime := Now;
  407. end;
  408. break; // Need to break out of sleep loop
  409. end;
  410. dec(WakeInterval,s); // Update total wait time
  411. dec(RecheckTimeCounter); // Do we need to recheck current time
  412. if (RecheckTimeCounter < 0) and (WakeInterval > 0) then
  413. begin
  414. Diff := (WakeTime - Now);
  415. WakeInterval := Trunc(Diff * cMilliSecs);
  416. RecheckTimeCounter := cRecheckTimeCount;
  417. s := cSleepTime;
  418. end;
  419. until (WakeInterval<=0) or Terminated;
  420. if WakeInterval <= 0 then
  421. try
  422. inc(Counter);
  423. if not Timer.UseTimerThread then
  424. // If terminate is called while here, then the Synchronize will be
  425. // queued while the stoptimer is being processed.
  426. // StopTimer cannot wait until thread completion as this would deadlock
  427. Synchronize(@Timer.Timer) // Call user event
  428. else
  429. Timer.Timer;
  430. except
  431. // Trap errors to prevent this thread from terminating
  432. end;
  433. end
  434. end;
  435. end;
  436. {$ENDIF Has_EventWait}
  437. { ---------------------------------------------------------------------
  438. TFPThreadedTimerDriver
  439. ---------------------------------------------------------------------}
  440. procedure TFPThreadedTimerDriver.SetInterval(const AValue: cardinal);
  441. begin
  442. if FThread <> nil then
  443. begin
  444. if AValue > 0 then
  445. FThread.SetInterval(AValue)
  446. else
  447. StopTimer;
  448. end;
  449. end;
  450. Procedure TFPThreadedTimerDriver.StartTimer;
  451. begin
  452. if FThread = nil then
  453. begin
  454. FThread:=TFPTimerThread.CreateTimerThread(Self);
  455. FThread.Start;
  456. FTimerStarted := True;
  457. end;
  458. end;
  459. Procedure TFPThreadedTimerDriver.StopTimer;
  460. begin
  461. if FThread <> nil then
  462. begin
  463. try
  464. // Cannot wait on thread in case
  465. // 1. this is called in a Synchonize method and the FThread is
  466. // about to run a synchronize method. In these cases we would have a deadlock
  467. // 2. In a DLL and this is called as part of DLLMain, which never
  468. // returns endthread (hence WaitFor) until DLLMain is exited
  469. FThread.Terminate; // Will call FThread.Wake;
  470. finally
  471. FThread := nil;
  472. end;
  473. FTimerStarted := False;
  474. end;
  475. end;
  476. Initialization
  477. DefaultTimerDriverClass:=TFPThreadedTimerDriver;
  478. end.