fptimer.pp 15 KB

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