source: trunk/src/helpers/timer.c@ 20

Last change on this file since 20 was 19, checked in by umoeller, 25 years ago

Reworked tooltip.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 17.3 KB
Line 
1
2/*
3 *@@sourcefile timer.c:
4 * XTimers, which can replace the PM timers.
5 *
6 * These timers operate similar to PM timers started
7 * with WinStartTimer. These are implemented thru a
8 * separate thread (fntTimersThread) which posts
9 * WM_TIMER messages regularly.
10 *
11 * Instead of WinStartTimer, use tmrStartTimer.
12 * Instead of WinStopTimer, use tmrStopTimer.
13 *
14 * The main advantage of the XTimers is that these
15 * are not a limited resource (as opposed to PM timers).
16 * I don't know how many PM timers exist on the system,
17 * but PMREF says that the no. of remaining timers can
18 * be queried with WinQuerySysValue(SV_CTIMERS).
19 *
20 * There are a few limitations with the XTimers though:
21 *
22 * -- If you start a timer with a timeout < 100 ms,
23 * the first WM_TIMER might not appear before
24 * 100 ms have elapsed. This may or be not the
25 * case, depending on whether other timers are
26 * running.
27 *
28 * -- XTimers post WM_TIMER messages regardless of
29 * whether previous WM_TIMER messages have already
30 * been processed. For this reason, be careful with
31 * small timer timeouts, this might flood the
32 * message queue.
33 *
34 * -- Queue timers (with HWND == NULLHANDLE) are not
35 * supported.
36 *
37 * -- When a window is deleted, its timers are not
38 * automatically cleaned up. The timer thread does
39 * detect invalid windows and removes them from the
40 * timers list before posting, but to be on the safe
41 * side, always call tmrStopAllTimers when WM_DESTROY
42 * comes into a window which has used timers.
43 *
44 * Function prefixes:
45 * -- tmr* timer functions
46 *
47 *@@header "helpers\timer.h"
48 *@@added V0.9.7 (2000-12-04) [umoeller]
49 */
50
51/*
52 * Copyright (C) 2000 Ulrich M”ller.
53 * This file is part of the "XWorkplace helpers" source package.
54 * This is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published
56 * by the Free Software Foundation, in version 2 as it comes in the
57 * "COPYING" file of the XWorkplace main distribution.
58 * This program is distributed in the hope that it will be useful,
59 * but WITHOUT ANY WARRANTY; without even the implied warranty of
60 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
61 * GNU General Public License for more details.
62 */
63
64// OS2 includes
65
66#define OS2EMX_PLAIN_CHAR
67 // this is needed for "os2emx.h"; if this is defined,
68 // emx will define PSZ as _signed_ char, otherwise
69 // as unsigned char
70
71#define INCL_DOSEXCEPTIONS
72#define INCL_DOSPROCESS
73#define INCL_DOSSEMAPHORES
74#define INCL_DOSMISC
75#define INCL_DOSERRORS
76
77#define INCL_WINMESSAGEMGR
78#include <os2.h>
79
80#include <stdio.h>
81#include <setjmp.h>
82
83#include "setup.h" // code generation and debugging options
84
85#include "helpers\datetime.h"
86#include "helpers\except.h"
87#include "helpers\linklist.h"
88#include "helpers\threads.h"
89#include "helpers\timer.h"
90
91/*
92 *@@category: Helpers\PM helpers\Timer replacements
93 */
94
95/* ******************************************************************
96 *
97 * Private declarations
98 *
99 ********************************************************************/
100
101/*
102 *@@ XTIMER:
103 * one of these represents an XTimer.
104 * These are stored in a global linked list.
105 */
106
107typedef struct _XTIMER
108{
109 USHORT usTimerID; // timer ID, as passed to tmrStartTimer
110 HWND hwndTarget; // target window, as passed to tmrStartTimer
111 ULONG ulTimeout; // timer's timeout (in ms)
112 ULONG ulNextFire; // next time scalar (from dtGetULongTime) to fire at
113} XTIMER, *PXTIMER;
114
115/* ******************************************************************
116 *
117 * Global variables
118 *
119 ********************************************************************/
120
121// timers thread
122HMTX G_hmtxTimers = NULLHANDLE; // timers lock mutex
123THREADINFO G_tiTimers = {0}; // timers thread (only running
124 // if any timers were requested)
125BOOL G_fTimersThreadRunning = FALSE;
126LINKLIST G_llTimers; // linked list of XTIMER pointers
127
128/* ******************************************************************
129 *
130 * Timer helpers
131 *
132 ********************************************************************/
133
134/*
135 *@@ LockTimers:
136 * locks the global timer resources.
137 * You MUST call UnlockTimers after this.
138 */
139
140BOOL LockTimers(VOID)
141{
142 BOOL brc = FALSE;
143 if (G_hmtxTimers == NULLHANDLE)
144 {
145 // this initializes all globals
146 lstInit(&G_llTimers,
147 TRUE); // auto-free
148
149 brc = (DosCreateMutexSem(NULL, // unnamed
150 &G_hmtxTimers,
151 0, // unshared
152 TRUE) // lock!
153 == NO_ERROR);
154 }
155 else
156 brc = (WinRequestMutexSem(G_hmtxTimers, SEM_INDEFINITE_WAIT)
157 == NO_ERROR);
158 return (brc);
159}
160
161/*
162 *@@ UnlockTimers:
163 * the reverse to LockTimers.
164 */
165
166VOID UnlockTimers(VOID)
167{
168 DosReleaseMutexSem(G_hmtxTimers);
169}
170
171/*
172 *@@ fntTimersThread:
173 * the actual thread which fires the timers by
174 * posting WM_TIMER messages to the respecive
175 * target windows when a timer has elapsed.
176 *
177 * This thread is dynamically started when the
178 * first timer is started thru tmrStartTimer.
179 * It is automatically stopped (to be precise:
180 * it terminates itself) when the last timer
181 * is stopped thru tmrStopTimer, which then
182 * sets the thread's fExit flag to TRUE.
183 *
184 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
185 */
186
187void _Optlink fntTimersThread(PTHREADINFO ptiMyself)
188{
189 ULONG ulInterval = 25;
190 HAB hab = WinInitialize(0);
191 BOOL fLocked = FALSE;
192
193 // linked list of timers found to be invalid;
194 // this holds LISTNODE pointers from the global
195 // list to be removed
196 LINKLIST llInvalidTimers;
197 lstInit(&llInvalidTimers,
198 FALSE); // no auto-free
199
200 #ifdef __DEBUG__
201 DosBeep(3000, 30);
202 #endif
203
204 // keep running while we have timers
205 while (!ptiMyself->fExit)
206 {
207 // ULONG ulNesting = 0;
208
209 ULONG ulTimeNow;
210
211 DosSleep(ulInterval);
212
213 // minimum interval: 100 ms; this is lowered
214 // if we find any timers in the list which
215 // have a lower timeout to make sure we can
216 // fire at a lower interval...
217 ulInterval = 100;
218
219 // DosEnterMustComplete(&ulNesting);
220
221 TRY_LOUD(excpt1)
222 {
223 fLocked = LockTimers();
224 if (fLocked)
225 {
226 // go thru all XTimers and see which one
227 // has elapsed; for all of these, post WM_TIMER
228 // to the target window proc
229 PLISTNODE pTimerNode = lstQueryFirstNode(&G_llTimers);
230 if (!pTimerNode)
231 // no more timers left:
232 // terminate thread
233 ptiMyself->fExit = TRUE;
234 else
235 {
236 // we have timers:
237 BOOL fFoundInvalid = FALSE;
238
239 // get current time
240 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
241 &ulTimeNow, sizeof(ulTimeNow));
242
243 while ((pTimerNode) && (!ptiMyself->fExit))
244 {
245 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
246
247 if (pTimer->ulNextFire < ulTimeNow)
248 {
249 // this timer has elapsed:
250 // fire!
251 if (WinIsWindow(hab,
252 pTimer->hwndTarget))
253 {
254 // window still valid:
255 WinPostMsg(pTimer->hwndTarget,
256 WM_TIMER,
257 (MPARAM)pTimer->usTimerID,
258 0);
259 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
260 }
261 else
262 {
263 // window has been destroyed:
264 #ifdef __DEBUG__
265 DosBeep(100, 100);
266 #endif
267 lstAppendItem(&llInvalidTimers,
268 (PVOID)pTimerNode);
269 fFoundInvalid = TRUE;
270 }
271 } // end if (pTimer->ulNextFire < ulTimeNow)
272
273 // adjust DosSleep interval
274 if (pTimer->ulTimeout < ulInterval)
275 ulInterval = pTimer->ulTimeout;
276
277 // next timer
278 pTimerNode = pTimerNode->pNext;
279 } // end while (pTimerNode)
280
281 // destroy invalid timers, if any
282 if ((fFoundInvalid) && (!ptiMyself->fExit))
283 {
284 PLISTNODE pNodeNode = lstQueryFirstNode(&llInvalidTimers);
285 while (pNodeNode)
286 {
287 PLISTNODE pNode2Remove = (PLISTNODE)pNodeNode->pItemData;
288 lstRemoveNode(&G_llTimers,
289 pNode2Remove);
290 pNodeNode = pNodeNode->pNext;
291 }
292 lstClear(&llInvalidTimers);
293 }
294 } // end else if (!pTimerNode)
295 } // end if (fLocked)
296 }
297 CATCH(excpt1) { } END_CATCH();
298
299 if (fLocked)
300 {
301 UnlockTimers();
302 fLocked = FALSE;
303 }
304
305 // DosExitMustComplete(&ulNesting);
306
307 } // end while (!ptiMyself->fExit)
308
309 WinTerminate(hab);
310
311 #ifdef __DEBUG__
312 DosBeep(1500, 30);
313 #endif
314}
315
316/*
317 *@@ FindTimer:
318 * returns the XTIMER structure from the global
319 * linked list which matches the given window
320 * _and_ timer ID.
321 *
322 * Internal function.
323 *
324 * Preconditions:
325 *
326 * -- The caller must call LockTimers() first.
327 */
328
329PXTIMER FindTimer(HWND hwnd,
330 USHORT usTimerID)
331{
332 PLISTNODE pNode = lstQueryFirstNode(&G_llTimers);
333 while (pNode)
334 {
335 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
336 if ( (pTimer->usTimerID == usTimerID)
337 && (pTimer->hwndTarget == hwnd)
338 )
339 {
340 return (pTimer);
341 }
342
343 pNode = pNode->pNext;
344 }
345 return (NULL);
346}
347
348/*
349 *@@ RemoveTimer:
350 * removes the specified XTIMER structure from
351 * the global linked list of running timers.
352 *
353 * Internal function.
354 *
355 * Preconditions:
356 *
357 * -- The caller must call LockTimers() first.
358 */
359
360VOID RemoveTimer(PXTIMER pTimer)
361{
362 lstRemoveItem(&G_llTimers,
363 pTimer); // auto-free!
364 /* if (lstCountItems(&G_llTimers) == 0)
365 // no more timers left:
366 // stop the main timer
367 thrClose(&G_tiTimers); */
368}
369
370/*
371 *@@ tmrStartTimer:
372 * starts an XTimer.
373 *
374 * Any window can request an XTimer using
375 * this function. This operates similar to
376 * WinStartTimer, except that the number of
377 * XTimers is not limited.
378 *
379 * Returns a new timer or resets an existing
380 * timer (if usTimerID is already used with
381 * hwnd). Use tmrStopTimer to stop the timer.
382 *
383 * The timer is _not_ stopped automatically
384 * when the window is destroyed.
385 *
386 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
387 */
388
389USHORT APIENTRY tmrStartTimer(HWND hwnd,
390 USHORT usTimerID,
391 ULONG ulTimeout)
392{
393 USHORT usrc = 0;
394 BOOL fLocked = FALSE;
395
396 if ((hwnd) && (ulTimeout))
397 {
398 ULONG ulNesting = 0;
399 DosEnterMustComplete(&ulNesting);
400
401 TRY_LOUD(excpt1)
402 {
403 fLocked = LockTimers();
404 if (fLocked)
405 {
406 PXTIMER pTimer;
407
408 // if the timers thread is not yet running,
409 // start it now (i.e. this is the first timer)
410 if (!G_fTimersThreadRunning)
411 {
412 // main timer not yet running:
413 thrCreate(&G_tiTimers,
414 fntTimersThread,
415 &G_fTimersThreadRunning,
416 THRF_WAIT, // no msgq, but wait
417 0);
418 // raise priority
419 DosSetPriority(PRTYS_THREAD,
420 PRTYC_REGULAR, // 3
421 PRTYD_MAXIMUM,
422 G_tiTimers.tid);
423 }
424
425 // check if this timer exists already
426 pTimer = FindTimer(hwnd,
427 usTimerID);
428 if (pTimer)
429 {
430 // exists already: reset only
431 ULONG ulTimeNow;
432 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
433 &ulTimeNow, sizeof(ulTimeNow));
434 pTimer->ulNextFire = ulTimeNow + ulTimeout;
435 usrc = pTimer->usTimerID;
436 }
437 else
438 {
439 // new timer needed:
440 pTimer = (PXTIMER)malloc(sizeof(XTIMER));
441 if (pTimer)
442 {
443 ULONG ulTimeNow;
444 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
445 &ulTimeNow, sizeof(ulTimeNow));
446 pTimer->usTimerID = usTimerID;
447 pTimer->hwndTarget = hwnd;
448 pTimer->ulTimeout = ulTimeout;
449 pTimer->ulNextFire = ulTimeNow + ulTimeout;
450
451 lstAppendItem(&G_llTimers,
452 pTimer);
453 usrc = pTimer->usTimerID;
454 }
455 }
456 }
457 }
458 CATCH(excpt1) { } END_CATCH();
459
460 // unlock the sems outside the exception handler
461 if (fLocked)
462 {
463 UnlockTimers();
464 fLocked = FALSE;
465 }
466
467 DosExitMustComplete(&ulNesting);
468 } // if ((hwnd) && (ulTimeout))
469
470 return (usrc);
471}
472
473/*
474 *@@ tmrStopTimer:
475 * similar to WinStopTimer, this stops the
476 * specified timer (which must have been
477 * started with the same hwnd and usTimerID
478 * using tmrStartTimer).
479 *
480 * Returns TRUE if the timer was stopped.
481 */
482
483BOOL APIENTRY tmrStopTimer(HWND hwnd,
484 USHORT usTimerID)
485{
486 BOOL brc = FALSE;
487 BOOL fLocked = FALSE;
488
489 ULONG ulNesting = 0;
490 DosEnterMustComplete(&ulNesting);
491
492 TRY_LOUD(excpt1)
493 {
494 fLocked = LockTimers();
495 if (fLocked)
496 {
497 PXTIMER pTimer = FindTimer(hwnd,
498 usTimerID);
499 if (pTimer)
500 {
501 RemoveTimer(pTimer);
502 brc = TRUE;
503 }
504 }
505 }
506 CATCH(excpt1) { } END_CATCH();
507
508 // unlock the sems outside the exception handler
509 if (fLocked)
510 {
511 UnlockTimers();
512 fLocked = FALSE;
513 }
514
515 DosExitMustComplete(&ulNesting);
516
517 return (brc);
518}
519
520/*
521 *@@ tmrStopAllTimers:
522 * stops all timers which are running for the
523 * specified window. This is a useful helper
524 * that you should call during WM_DESTROY of
525 * a window that has started timers.
526 */
527
528VOID tmrStopAllTimers(HWND hwnd)
529{
530 BOOL fLocked = FALSE;
531
532 ULONG ulNesting = 0;
533 DosEnterMustComplete(&ulNesting);
534
535 TRY_LOUD(excpt1)
536 {
537 fLocked = LockTimers();
538 if (fLocked)
539 {
540 PLISTNODE pTimerNode = lstQueryFirstNode(&G_llTimers);
541 while (pTimerNode)
542 {
543 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
544 if (pTimer->hwndTarget == hwnd)
545 {
546 RemoveTimer(pTimer);
547 // start over
548 pTimerNode = lstQueryFirstNode(&G_llTimers);
549 }
550 else
551 pTimerNode = pTimerNode->pNext;
552 }
553 }
554 }
555 CATCH(excpt1) { } END_CATCH();
556
557 // unlock the sems outside the exception handler
558 if (fLocked)
559 {
560 UnlockTimers();
561 fLocked = FALSE;
562 }
563
564 DosExitMustComplete(&ulNesting);
565}
566
567
Note: See TracBrowser for help on using the repository browser.