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

Last change on this file since 38 was 21, checked in by umoeller, 25 years ago

Final changes for 0.9.7, i hope...

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