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

Last change on this file since 83 was 74, checked in by umoeller, 24 years ago

Misc updates

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 20.4 KB
Line 
1
2/*
3 *@@sourcefile timer.c:
4 * XTimers, which can be used to avoid excessive PM
5 * timer usage.
6 *
7 * The functions here allow you to share a single PM
8 * timer for many windows on the same thread. Basically,
9 * these start a "master PM timer", to whose WM_TIMER
10 * message your window procedure must respond by calling
11 * tmrTimerTick, which will "distribute" the timer tick
12 * to those XTimers which have elapsed.
13 *
14 * So to use XTimers, do the following:
15 *
16 * 1. Create a timer set with tmrCreateSet. Specify
17 * an owner window and the timer ID of the master
18 * PM timer.
19 *
20 * 2. You can then start and stop XTimers for windows
21 * on the same thread by calling tmrStartXTimer and
22 * tmrStopXTimer, respectively.
23 *
24 * 3. In the window proc of the owner window, respond
25 * to WM_TIMER for the master PM timer by calling
26 * tmrTimerTick. This will call the window procs
27 * of those windows with WM_TIMER messages directly
28 * whose XTimers have elapsed.
29 *
30 * The main advantage of the XTimers is that these
31 * are not a limited resource (as opposed to PM timers).
32 * I don't know how many PM timers exist on the system,
33 * but PMREF says that the no. of remaining timers can
34 * be queried with WinQuerySysValue(SV_CTIMERS).
35 *
36 * There are a few limitations with the XTimers though:
37 *
38 * -- If you start a timer with a timeout < 100 ms,
39 * the first WM_TIMER might not appear before
40 * 100 ms have elapsed. This may or be not the
41 * case, depending on whether other timers are
42 * running.
43 *
44 * -- tmrTimerTick (which you must call when the
45 * "master" WM_TIMER comes in) does not post WM_TIMER
46 * messages to the windows specified in the subtimers,
47 * but calls the window procedure of the target window
48 * directly. This makes sure that timers work even if
49 * some thread is hogging the SIQ.
50 *
51 * This however requires that all XTimers must run on
52 * the same thread as the owner window of the master
53 * timer which was specified with tmrCreateSet.
54 *
55 * -- Queue timers (with HWND == NULLHANDLE) are not
56 * supported.
57 *
58 * -- When a window is destroyed, its timers are not
59 * automatically cleaned up. tmrTimerTick does
60 * detect invalid windows and removes them from the
61 * timers list before posting, but to be on the safe
62 * side, always call tmrStopAllTimers when WM_DESTROY
63 * comes into a window which has used timers.
64 *
65 * Function prefixes:
66 * -- tmr* timer functions
67 *
68 *@@header "helpers\timer.h"
69 *@@added V0.9.7 (2000-12-04) [umoeller]
70 */
71
72/*
73 * Copyright (C) 2000 Ulrich M”ller.
74 * This file is part of the "XWorkplace helpers" source package.
75 * This is free software; you can redistribute it and/or modify
76 * it under the terms of the GNU General Public License as published
77 * by the Free Software Foundation, in version 2 as it comes in the
78 * "COPYING" file of the XWorkplace main distribution.
79 * This program is distributed in the hope that it will be useful,
80 * but WITHOUT ANY WARRANTY; without even the implied warranty of
81 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
82 * GNU General Public License for more details.
83 */
84
85// OS2 includes
86
87#define OS2EMX_PLAIN_CHAR
88 // this is needed for "os2emx.h"; if this is defined,
89 // emx will define PSZ as _signed_ char, otherwise
90 // as unsigned char
91
92#define INCL_DOSEXCEPTIONS
93#define INCL_DOSPROCESS
94#define INCL_DOSSEMAPHORES
95#define INCL_DOSMISC
96#define INCL_DOSERRORS
97
98#define INCL_WINWINDOWMGR
99#define INCL_WINMESSAGEMGR
100#define INCL_WINTIMER
101#include <os2.h>
102
103#include <stdio.h>
104#include <setjmp.h>
105
106#include "setup.h" // code generation and debugging options
107
108#include "helpers\datetime.h"
109#include "helpers\except.h"
110#include "helpers\linklist.h"
111#include "helpers\threads.h"
112#include "helpers\timer.h"
113
114/*
115 *@@category: Helpers\PM helpers\Timer replacements
116 * see timer.c.
117 */
118
119/* ******************************************************************
120 *
121 * Private declarations
122 *
123 ********************************************************************/
124
125/*
126 *@@ XTIMER:
127 * one of these represents an XTimer.
128 * These are stored in a linked list in
129 * an _XTIMERSET.
130 */
131
132typedef struct _XTIMER
133{
134 USHORT usTimerID; // timer ID, as passed to tmrStartXTimer
135 HWND hwndTarget; // target window, as passed to tmrStartXTimer
136 ULONG ulTimeout; // timer's timeout (in ms)
137 ULONG ulNextFire; // next time scalar (from dtGetULongTime) to fire at
138} XTIMER, *PXTIMER;
139
140/* ******************************************************************
141 *
142 * Global variables
143 *
144 ********************************************************************/
145
146HMTX G_hmtxTimers = NULLHANDLE; // timers lock mutex
147
148/* ******************************************************************
149 *
150 * Private functions
151 *
152 ********************************************************************/
153
154/*
155 *@@ LockTimers:
156 *
157 *@@added V0.9.12 (2001-05-12) [umoeller]
158 */
159
160BOOL LockTimers(VOID)
161{
162 if (!G_hmtxTimers)
163 return (!DosCreateMutexSem(NULL,
164 &G_hmtxTimers,
165 0,
166 TRUE)); // request!
167 else
168 return (!WinRequestMutexSem(G_hmtxTimers, SEM_INDEFINITE_WAIT));
169}
170
171/*
172 *@@ UnlockTimers:
173 *
174 *@@added V0.9.12 (2001-05-12) [umoeller]
175 */
176
177VOID UnlockTimers(VOID)
178{
179 DosReleaseMutexSem(G_hmtxTimers);
180}
181
182/*
183 *@@ FindTimer:
184 * returns the XTIMER structure from the global
185 * linked list which matches the given window
186 * _and_ timer ID.
187 *
188 * Internal function. Caller must hold the mutex.
189 */
190
191PXTIMER FindTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
192 HWND hwnd, // in: timer target window
193 USHORT usTimerID) // in: timer ID
194{
195 if (pSet && pSet->pvllXTimers)
196 {
197 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
198 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
199 while (pNode)
200 {
201 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
202 if ( (pTimer->usTimerID == usTimerID)
203 && (pTimer->hwndTarget == hwnd)
204 )
205 {
206 return (pTimer);
207 }
208
209 pNode = pNode->pNext;
210 }
211 }
212
213 return (NULL);
214}
215
216/*
217 *@@ RemoveTimer:
218 * removes the specified XTIMER structure from
219 * the global linked list of running timers.
220 *
221 * Internal function. Caller must hold the mutex.
222 */
223
224VOID RemoveTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
225 PXTIMER pTimer) // in: timer to remove.
226{
227 if (pSet && pSet->pvllXTimers)
228 {
229 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
230 lstRemoveItem(pllXTimers,
231 pTimer); // auto-free!
232 }
233}
234
235/*
236 *@@ AdjustPMTimer:
237 * goes thru all XTimers in the sets and starts
238 * or stops the PM timer with a decent frequency.
239 *
240 * Internal function. Caller must hold the mutex.
241 *
242 *@@added V0.9.9 (2001-03-07) [umoeller]
243 */
244
245VOID AdjustPMTimer(PXTIMERSET pSet)
246{
247 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
248 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
249 if (!pNode)
250 {
251 // no XTimers running:
252 if (pSet->idPMTimerRunning)
253 {
254 // but PM timer running:
255 // stop it
256 WinStopTimer(pSet->hab,
257 pSet->hwndOwner,
258 pSet->idPMTimer);
259 pSet->idPMTimerRunning = 0;
260 }
261
262 pSet->ulPMTimeout = 0;
263 }
264 else
265 {
266 // we have timers:
267 ULONG ulOldPMTimeout = pSet->ulPMTimeout;
268 pSet->ulPMTimeout = 1000;
269
270 while (pNode)
271 {
272 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
273
274 if ( (pTimer->ulTimeout / 2) < pSet->ulPMTimeout )
275 pSet->ulPMTimeout = pTimer->ulTimeout / 2;
276
277 pNode = pNode->pNext;
278 }
279
280 if ( (pSet->idPMTimerRunning == 0) // timer not running?
281 || (pSet->ulPMTimeout != ulOldPMTimeout) // timeout changed?
282 )
283 // start or restart PM timer
284 pSet->idPMTimerRunning = WinStartTimer(pSet->hab,
285 pSet->hwndOwner,
286 pSet->idPMTimer,
287 pSet->ulPMTimeout);
288 }
289}
290
291/* ******************************************************************
292 *
293 * Exported functions
294 *
295 ********************************************************************/
296
297/*
298 *@@ tmrCreateSet:
299 * creates a "timer set" for use with the XTimer functions.
300 * This is the first step if you want to use the XTimers.
301 *
302 * hwndOwner must specify the "owner window", the target
303 * window for the master PM timer. This window must respond
304 * to a WM_TIMER message with the specified usPMTimerID and
305 * invoke tmrTimerTick then.
306 *
307 * Note that the master timer is not started until an XTimer
308 * is started.
309 *
310 * Use tmrDestroySet to free all resources again.
311 *
312 *@@added V0.9.9 (2001-02-28) [umoeller]
313 */
314
315PXTIMERSET tmrCreateSet(HWND hwndOwner, // in: owner window
316 USHORT usPMTimerID)
317{
318 PXTIMERSET pSet = NULL;
319
320 pSet = (PXTIMERSET)malloc(sizeof(*pSet));
321 if (pSet)
322 {
323 pSet->hab = WinQueryAnchorBlock(hwndOwner);
324 pSet->hwndOwner = hwndOwner;
325 pSet->idPMTimer = usPMTimerID;
326 pSet->idPMTimerRunning = 0;
327 pSet->ulPMTimeout = 0;
328
329 pSet->pvllXTimers = (PVOID)lstCreate(TRUE);
330 }
331
332 return (pSet);
333}
334
335/*
336 *@@ tmrDestroySet:
337 * destroys a timer set previously created using
338 * tmrCreateSet.
339 *
340 * Of course, this will stop all XTimers which
341 * might still be running with this set.
342 *
343 *@@added V0.9.9 (2001-02-28) [umoeller]
344 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
345 */
346
347VOID tmrDestroySet(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
348{
349 if (LockTimers())
350 {
351 if (pSet)
352 {
353 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
354 if (pllXTimers)
355 {
356 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
357 while (pTimerNode)
358 {
359 PLISTNODE pNext = pTimerNode->pNext;
360 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
361 RemoveTimer(pSet, pTimer);
362 pTimerNode = pNext;
363 }
364
365 lstFree(&pllXTimers);
366 pSet->pvllXTimers = NULL;
367 }
368 }
369
370 if (pSet->idPMTimerRunning)
371 {
372 WinStopTimer(pSet->hab,
373 pSet->hwndOwner,
374 pSet->idPMTimer);
375 pSet->idPMTimerRunning = 0;
376 }
377
378 free(pSet);
379
380 UnlockTimers();
381 }
382}
383
384/*
385 *@@ tmrTimerTick:
386 * implements a PM timer tick.
387 *
388 * When your window procs receives WM_TIMER for the
389 * one PM timer which is supposed to trigger all the
390 * XTimers, it must call this function. This will
391 * evaluate all XTimers on the list and "fire" them
392 * by calling the window procs directly with the
393 * WM_TIMER message.
394 *
395 *@@added V0.9.9 (2001-02-28) [umoeller]
396 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
397 *@@changed V0.9.12 (2001-05-24) [umoeller]: fixed crash if timer was deleted during winproc's WM_TIMER processing
398 */
399
400VOID tmrTimerTick(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
401{
402 if (LockTimers())
403 {
404 if (pSet)
405 {
406 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
407 if (pllXTimers)
408 {
409 // go thru all XTimers and see which one
410 // has elapsed; for all of these, post WM_TIMER
411 // to the target window proc
412 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
413
414 if (!pTimerNode)
415 {
416 // no timers left:
417 if (pSet->idPMTimerRunning)
418 {
419 // but PM timer running:
420 // stop it
421 WinStopTimer(pSet->hab,
422 pSet->hwndOwner,
423 pSet->idPMTimer);
424 pSet->idPMTimerRunning = 0;
425 }
426
427 pSet->ulPMTimeout = 0;
428 }
429 else
430 {
431 // we have timers:
432 BOOL fFoundInvalid = FALSE;
433
434 // get current time
435 ULONG ulTimeNow = 0;
436 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
437 &ulTimeNow, sizeof(ulTimeNow));
438
439 while (pTimerNode)
440 {
441 // get next node first because the
442 // list can get modified while processing
443 // V0.9.12 (2001-05-24) [umoeller]
444 PLISTNODE pNext = pTimerNode->pNext;
445
446 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
447
448 if ( (pTimer)
449 && (pTimer->ulNextFire < ulTimeNow)
450 )
451 {
452 // this timer has elapsed:
453 // fire!
454 if (WinIsWindow(pSet->hab,
455 pTimer->hwndTarget))
456 {
457 // window still valid:
458 // get the window's window proc
459 PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
460 QWP_PFNWP);
461 // call the window proc DIRECTLY
462 pfnwp(pTimer->hwndTarget,
463 WM_TIMER,
464 (MPARAM)pTimer->usTimerID,
465 0);
466 // V0.9.12 (2001-05-24) [umoeller]
467 // if the winproc chooses to start or
468 // stop a timer, pNext still points
469 // to a valid node...
470 // -- if a timer is removed, that's OK
471 // -- if a timer is added, it is added to
472 // the list, so we'll see it in this loop
473
474 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
475 }
476 else
477 {
478 // window has been destroyed:
479 lstRemoveNode(pllXTimers,
480 pTimerNode);
481 // pNext is still valid
482
483 fFoundInvalid = TRUE;
484 }
485
486 } // end if (pTimer->ulNextFire < ulTimeNow)
487
488 // next timer
489 pTimerNode = pNext; // V0.9.12 (2001-05-24) [umoeller]
490 } // end while (pTimerNode)
491
492 // destroy invalid timers, if any
493 if (fFoundInvalid)
494 AdjustPMTimer(pSet);
495
496 } // end else if (!pTimerNode)
497 } // end if (pllXTimers)
498 } // end if (pSet)
499
500 UnlockTimers();
501 } // end if (LockTimers())
502}
503
504/*
505 *@@ tmrStartXTimer:
506 * starts an XTimer.
507 *
508 * Any window can request an XTimer using
509 * this function. This operates similar to
510 * WinStartTimer, except that the number of
511 * XTimers is not limited.
512 *
513 * Returns a new timer or resets an existing
514 * timer (if usTimerID is already used with
515 * hwnd). Use tmrStopXTimer to stop the timer.
516 *
517 * The timer is _not_ stopped automatically
518 * when the window is destroyed.
519 *
520 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
521 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
522 */
523
524USHORT XWPENTRY tmrStartXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
525 HWND hwnd, // in: target window for XTimer
526 USHORT usTimerID, // in: timer ID for XTimer's WM_TIMER
527 ULONG ulTimeout) // in: XTimer's timeout
528{
529 USHORT usrc = 0;
530
531 // _Pmpf((__FUNCTION__ ": entering"));
532
533 if (LockTimers())
534 {
535 if (pSet)
536 {
537 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
538
539 if ((pllXTimers) && (hwnd) && (ulTimeout))
540 {
541 PXTIMER pTimer;
542
543 // check if this timer exists already
544 pTimer = FindTimer(pSet,
545 hwnd,
546 usTimerID);
547 if (pTimer)
548 {
549 // exists already: reset only
550 ULONG ulTimeNow;
551 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
552 &ulTimeNow, sizeof(ulTimeNow));
553 pTimer->ulNextFire = ulTimeNow + ulTimeout;
554 usrc = pTimer->usTimerID;
555 }
556 else
557 {
558 // new timer needed:
559 pTimer = (PXTIMER)malloc(sizeof(XTIMER));
560 if (pTimer)
561 {
562 ULONG ulTimeNow;
563 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
564 &ulTimeNow, sizeof(ulTimeNow));
565 pTimer->usTimerID = usTimerID;
566 pTimer->hwndTarget = hwnd;
567 pTimer->ulTimeout = ulTimeout;
568 pTimer->ulNextFire = ulTimeNow + ulTimeout;
569
570 lstAppendItem(pllXTimers,
571 pTimer);
572 usrc = pTimer->usTimerID;
573 }
574 }
575
576 if (usrc)
577 // timer created or reset:
578 AdjustPMTimer(pSet);
579
580 } // if ((hwnd) && (ulTimeout))
581 }
582
583 UnlockTimers();
584 }
585
586 return (usrc);
587}
588
589/*
590 *@@ tmrStopXTimer:
591 * similar to WinStopTimer, this stops the
592 * specified timer (which must have been
593 * started with the same hwnd and usTimerID
594 * using tmrStartXTimer).
595 *
596 * Returns TRUE if the timer was stopped.
597 *
598 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
599 */
600
601BOOL XWPENTRY tmrStopXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
602 HWND hwnd,
603 USHORT usTimerID)
604{
605 BOOL brc = FALSE;
606 if (LockTimers())
607 {
608 if (pSet && pSet->pvllXTimers)
609 {
610 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
611 BOOL fLocked = FALSE;
612
613 PXTIMER pTimer = FindTimer(pSet,
614 hwnd,
615 usTimerID);
616 if (pTimer)
617 {
618 RemoveTimer(pSet, pTimer);
619 // recalculate
620 AdjustPMTimer(pSet);
621 brc = TRUE;
622 }
623 }
624
625 UnlockTimers();
626 }
627
628 return (brc);
629}
630
631
Note: See TracBrowser for help on using the repository browser.