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

Last change on this file since 18 was 17, checked in by umoeller, 25 years ago

Miscellanous updates.

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