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

Last change on this file since 88 was 86, checked in by umoeller, 24 years ago

Misc changes.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 22.9 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-2001 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\math.h"
112#include "helpers\threads.h"
113#include "helpers\timer.h"
114
115/*
116 *@@category: Helpers\PM helpers\Timer replacements
117 * see timer.c.
118 */
119
120/* ******************************************************************
121 *
122 * Private declarations
123 *
124 ********************************************************************/
125
126/*
127 *@@ XTIMER:
128 * one of these represents an XTimer.
129 * These are stored in a linked list in
130 * an _XTIMERSET.
131 */
132
133typedef struct _XTIMER
134{
135 USHORT usTimerID; // timer ID, as passed to tmrStartXTimer
136 HWND hwndTarget; // target window, as passed to tmrStartXTimer
137 ULONG ulTimeout; // timer's timeout (in ms)
138 ULONG ulNextFire; // next time scalar (from dtGetULongTime) to fire at
139} XTIMER, *PXTIMER;
140
141/* ******************************************************************
142 *
143 * Global variables
144 *
145 ********************************************************************/
146
147HMTX G_hmtxTimers = NULLHANDLE; // timers lock mutex
148
149/* ******************************************************************
150 *
151 * Private functions
152 *
153 ********************************************************************/
154
155/*
156 *@@ LockTimers:
157 *
158 *@@added V0.9.12 (2001-05-12) [umoeller]
159 */
160
161BOOL LockTimers(VOID)
162{
163 if (!G_hmtxTimers)
164 return (!DosCreateMutexSem(NULL,
165 &G_hmtxTimers,
166 0,
167 TRUE)); // request!
168 else
169 return (!WinRequestMutexSem(G_hmtxTimers, SEM_INDEFINITE_WAIT));
170}
171
172/*
173 *@@ UnlockTimers:
174 *
175 *@@added V0.9.12 (2001-05-12) [umoeller]
176 */
177
178VOID UnlockTimers(VOID)
179{
180 DosReleaseMutexSem(G_hmtxTimers);
181}
182
183/*
184 *@@ FindTimer:
185 * returns the XTIMER structure from the global
186 * linked list which matches the given window
187 * _and_ timer ID.
188 *
189 * Internal function. Caller must hold the mutex.
190 */
191
192PXTIMER FindTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
193 HWND hwnd, // in: timer target window
194 USHORT usTimerID) // in: timer ID
195{
196 if (pSet && pSet->pvllXTimers)
197 {
198 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
199 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
200 while (pNode)
201 {
202 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
203 if ( (pTimer->usTimerID == usTimerID)
204 && (pTimer->hwndTarget == hwnd)
205 )
206 {
207 return (pTimer);
208 }
209
210 pNode = pNode->pNext;
211 }
212 }
213
214 return (NULL);
215}
216
217/*
218 *@@ RemoveTimer:
219 * removes the specified XTIMER structure from
220 * the global linked list of running timers.
221 *
222 * Internal function. Caller must hold the mutex.
223 */
224
225VOID RemoveTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
226 PXTIMER pTimer) // in: timer to remove.
227{
228 if (pSet && pSet->pvllXTimers)
229 {
230 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
231 lstRemoveItem(pllXTimers,
232 pTimer); // auto-free!
233 }
234}
235
236/*
237 *@@ AdjustPMTimer:
238 * goes thru all XTimers in the sets and starts
239 * or stops the PM timer with a decent frequency.
240 *
241 * Internal function. Caller must hold the mutex.
242 *
243 *@@added V0.9.9 (2001-03-07) [umoeller]
244 *@@changed V0.9.14 (2001-07-07) [umoeller]: added GCD optimizations
245 */
246
247VOID AdjustPMTimer(PXTIMERSET pSet)
248{
249 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
250 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
251 if (!pNode)
252 {
253 // no XTimers running:
254 if (pSet->idPMTimerRunning)
255 {
256 // but PM timer running:
257 // stop it
258 WinStopTimer(pSet->hab,
259 pSet->hwndOwner,
260 pSet->idPMTimer);
261 pSet->idPMTimerRunning = 0;
262 }
263
264 pSet->ulPMTimeout = 0;
265 }
266 else
267 {
268 // we have timers:
269
270 ULONG ulOldPMTimeout = pSet->ulPMTimeout;
271
272 if (!pNode->pNext)
273 {
274 // only one timer:
275 // that's easy
276 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
277 pSet->ulPMTimeout = pTimer->ulTimeout;
278 }
279 else if (!pNode->pNext->pNext)
280 {
281 // exactly two timers:
282 // find the greatest common denominator
283 PXTIMER pTimer1 = (PXTIMER)pNode->pItemData,
284 pTimer2 = (PXTIMER)pNode->pNext->pItemData;
285
286 pSet->ulPMTimeout = mathGCD(pTimer1->ulTimeout,
287 pTimer2->ulTimeout);
288 }
289 else
290 {
291 // several timers:
292 // run through all timers and find the greatest
293 // common denominator of all frequencies...
294
295 ULONG cTimers = lstCountItems(pllXTimers);
296 int *paInts = (int*)_alloca(sizeof(int) * cTimers),
297 i = 0;
298
299 _Pmpf(("Recalculating, got %d timers %d", cTimers));
300
301 // fill an array of integers with the
302 // timer frequencies
303 while (pNode)
304 {
305 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
306
307 _Pmpf((" timeout %d is %d", i, pTimer->ulTimeout));
308
309 paInts[i++] = pTimer->ulTimeout;
310
311 pNode = pNode->pNext;
312 }
313
314 pSet->ulPMTimeout = mathGCDMulti(paInts,
315 cTimers);
316 _Pmpf(("--> GCD is %d", pSet->ulPMTimeout));
317 }
318
319 if ( (pSet->idPMTimerRunning == 0) // timer not running?
320 || (pSet->ulPMTimeout != ulOldPMTimeout) // timeout changed?
321 )
322 // start or restart PM timer
323 pSet->idPMTimerRunning = WinStartTimer(pSet->hab,
324 pSet->hwndOwner,
325 pSet->idPMTimer,
326 pSet->ulPMTimeout);
327 }
328}
329
330/* ******************************************************************
331 *
332 * Exported functions
333 *
334 ********************************************************************/
335
336/*
337 *@@ tmrCreateSet:
338 * creates a "timer set" for use with the XTimer functions.
339 * This is the first step if you want to use the XTimers.
340 *
341 * hwndOwner must specify the "owner window", the target
342 * window for the master PM timer. This window must respond
343 * to a WM_TIMER message with the specified usPMTimerID and
344 * invoke tmrTimerTick then.
345 *
346 * Note that the master timer is not started until an XTimer
347 * is started.
348 *
349 * Use tmrDestroySet to free all resources again.
350 *
351 *@@added V0.9.9 (2001-02-28) [umoeller]
352 */
353
354PXTIMERSET tmrCreateSet(HWND hwndOwner, // in: owner window
355 USHORT usPMTimerID)
356{
357 PXTIMERSET pSet = NULL;
358
359 pSet = (PXTIMERSET)malloc(sizeof(*pSet));
360 if (pSet)
361 {
362 pSet->hab = WinQueryAnchorBlock(hwndOwner);
363 pSet->hwndOwner = hwndOwner;
364 pSet->idPMTimer = usPMTimerID;
365 pSet->idPMTimerRunning = 0;
366 pSet->ulPMTimeout = 0;
367
368 pSet->pvllXTimers = (PVOID)lstCreate(TRUE);
369 }
370
371 return (pSet);
372}
373
374/*
375 *@@ tmrDestroySet:
376 * destroys a timer set previously created using
377 * tmrCreateSet.
378 *
379 * Of course, this will stop all XTimers which
380 * might still be running with this set.
381 *
382 *@@added V0.9.9 (2001-02-28) [umoeller]
383 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
384 */
385
386VOID tmrDestroySet(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
387{
388 if (LockTimers())
389 {
390 if (pSet)
391 {
392 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
393 if (pllXTimers)
394 {
395 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
396 while (pTimerNode)
397 {
398 PLISTNODE pNext = pTimerNode->pNext;
399 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
400 RemoveTimer(pSet, pTimer);
401 pTimerNode = pNext;
402 }
403
404 lstFree(&pllXTimers);
405 pSet->pvllXTimers = NULL;
406 }
407 }
408
409 if (pSet->idPMTimerRunning)
410 {
411 WinStopTimer(pSet->hab,
412 pSet->hwndOwner,
413 pSet->idPMTimer);
414 pSet->idPMTimerRunning = 0;
415 }
416
417 free(pSet);
418
419 UnlockTimers();
420 }
421}
422
423/*
424 *@@ tmrTimerTick:
425 * implements a PM timer tick.
426 *
427 * When your window procs receives WM_TIMER for the
428 * one PM timer which is supposed to trigger all the
429 * XTimers, it must call this function. This will
430 * evaluate all XTimers on the list and "fire" them
431 * by calling the window procs directly with the
432 * WM_TIMER message.
433 *
434 *@@added V0.9.9 (2001-02-28) [umoeller]
435 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
436 *@@changed V0.9.12 (2001-05-24) [umoeller]: fixed crash if timer was deleted during winproc's WM_TIMER processing
437 */
438
439VOID tmrTimerTick(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
440{
441 if (LockTimers())
442 {
443 if (pSet)
444 {
445 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
446 if (pllXTimers)
447 {
448 // go thru all XTimers and see which one
449 // has elapsed; for all of these, post WM_TIMER
450 // to the target window proc
451 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
452
453 if (!pTimerNode)
454 {
455 // no timers left:
456 if (pSet->idPMTimerRunning)
457 {
458 // but PM timer running:
459 // stop it
460 WinStopTimer(pSet->hab,
461 pSet->hwndOwner,
462 pSet->idPMTimer);
463 pSet->idPMTimerRunning = 0;
464 }
465
466 pSet->ulPMTimeout = 0;
467 }
468 else
469 {
470 // we have timers:
471 BOOL fFoundInvalid = FALSE;
472
473 // get current time
474 ULONG ulTimeNow = 0;
475 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
476 &ulTimeNow, sizeof(ulTimeNow));
477
478 while (pTimerNode)
479 {
480 // get next node first because the
481 // list can get modified while processing
482 // V0.9.12 (2001-05-24) [umoeller]
483 PLISTNODE pNext = pTimerNode->pNext;
484
485 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
486
487 if ( (pTimer)
488 && (pTimer->ulNextFire < ulTimeNow)
489 )
490 {
491 // this timer has elapsed:
492 // fire!
493 if (WinIsWindow(pSet->hab,
494 pTimer->hwndTarget))
495 {
496 // window still valid:
497 // get the window's window proc
498 PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
499 QWP_PFNWP);
500 // call the window proc DIRECTLY
501 pfnwp(pTimer->hwndTarget,
502 WM_TIMER,
503 (MPARAM)pTimer->usTimerID,
504 0);
505 // V0.9.12 (2001-05-24) [umoeller]
506 // if the winproc chooses to start or
507 // stop a timer, pNext still points
508 // to a valid node...
509 // -- if a timer is removed, that's OK
510 // -- if a timer is added, it is added to
511 // the list, so we'll see it in this loop
512
513 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
514 }
515 else
516 {
517 // window has been destroyed:
518 lstRemoveNode(pllXTimers,
519 pTimerNode);
520 // pNext is still valid
521
522 fFoundInvalid = TRUE;
523 }
524
525 } // end if (pTimer->ulNextFire < ulTimeNow)
526
527 // next timer
528 pTimerNode = pNext; // V0.9.12 (2001-05-24) [umoeller]
529 } // end while (pTimerNode)
530
531 // destroy invalid timers, if any
532 if (fFoundInvalid)
533 AdjustPMTimer(pSet);
534
535 } // end else if (!pTimerNode)
536 } // end if (pllXTimers)
537 } // end if (pSet)
538
539 UnlockTimers();
540 } // end if (LockTimers())
541}
542
543/*
544 *@@ tmrStartXTimer:
545 * starts an XTimer.
546 *
547 * Any window can request an XTimer using
548 * this function. This operates similar to
549 * WinStartTimer, except that the number of
550 * XTimers is not limited.
551 *
552 * Returns a new timer or resets an existing
553 * timer (if usTimerID is already used with
554 * hwnd). Use tmrStopXTimer to stop the timer.
555 *
556 * The timer is _not_ stopped automatically
557 * when the window is destroyed.
558 *
559 * Note: Unless you own the timer set that
560 * your timer runs on, it is strongly recommended
561 * that your timer frequency is set to a multiple
562 * of 125. The PM master timer behind the timer
563 * set will be set to the greatest common divisor
564 * of all frequencies, and if you set one timer
565 * to 2000 and the other one to 2001, you will
566 * cause quite a lot of overhead. This applies
567 * especially to timers started by XCenter widgets.
568 *
569 * For security, all timer frequencies will be
570 * rounded to multiples of 25 anyway. Still,
571 * running two timers at 1000 and 1025 will cause
572 * the master timer to be set to 25, which is
573 * overkill.
574 *
575 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
576 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
577 *@@changed V0.9.14 (2001-07-12) [umoeller]: now rounding freq's to multiples of 25
578 */
579
580USHORT XWPENTRY tmrStartXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
581 HWND hwnd, // in: target window for XTimer
582 USHORT usTimerID, // in: timer ID for XTimer's WM_TIMER
583 ULONG ulTimeout) // in: XTimer's timeout
584{
585 USHORT usrc = 0;
586
587 // _Pmpf((__FUNCTION__ ": entering"));
588
589 if (LockTimers())
590 {
591 if (pSet)
592 {
593 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
594
595 if ((pllXTimers) && (hwnd) && (ulTimeout))
596 {
597 PXTIMER pTimer;
598
599 // fix the timeout... we allow only multiples of
600 // 20, and it must be at least 20 (otherwise our
601 // internal master timer calculations will fail)
602 // V0.9.14 (2001-07-07) [umoeller]
603 if (ulTimeout < 25)
604 ulTimeout = 25;
605 else
606 ulTimeout = (ulTimeout + 10) / 25 * 25;
607
608 // check if this timer exists already
609 pTimer = FindTimer(pSet,
610 hwnd,
611 usTimerID);
612 if (pTimer)
613 {
614 // exists already: reset only
615 ULONG ulTimeNow;
616 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
617 &ulTimeNow, sizeof(ulTimeNow));
618 pTimer->ulNextFire = ulTimeNow + ulTimeout;
619 usrc = pTimer->usTimerID;
620 }
621 else
622 {
623 // new timer needed:
624 pTimer = (PXTIMER)malloc(sizeof(XTIMER));
625 if (pTimer)
626 {
627 ULONG ulTimeNow;
628 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
629 &ulTimeNow, sizeof(ulTimeNow));
630 pTimer->usTimerID = usTimerID;
631 pTimer->hwndTarget = hwnd;
632 pTimer->ulTimeout = ulTimeout;
633 pTimer->ulNextFire = ulTimeNow + ulTimeout;
634
635 lstAppendItem(pllXTimers,
636 pTimer);
637 usrc = pTimer->usTimerID;
638 }
639 }
640
641 if (usrc)
642 // timer created or reset:
643 AdjustPMTimer(pSet);
644
645 } // if ((hwnd) && (ulTimeout))
646 }
647
648 UnlockTimers();
649 }
650
651 return (usrc);
652}
653
654/*
655 *@@ tmrStopXTimer:
656 * similar to WinStopTimer, this stops the
657 * specified timer (which must have been
658 * started with the same hwnd and usTimerID
659 * using tmrStartXTimer).
660 *
661 * Returns TRUE if the timer was stopped.
662 *
663 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
664 */
665
666BOOL XWPENTRY tmrStopXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
667 HWND hwnd,
668 USHORT usTimerID)
669{
670 BOOL brc = FALSE;
671 if (LockTimers())
672 {
673 if (pSet && pSet->pvllXTimers)
674 {
675 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
676 BOOL fLocked = FALSE;
677
678 PXTIMER pTimer = FindTimer(pSet,
679 hwnd,
680 usTimerID);
681 if (pTimer)
682 {
683 RemoveTimer(pSet, pTimer);
684 // recalculate
685 AdjustPMTimer(pSet);
686 brc = TRUE;
687 }
688 }
689
690 UnlockTimers();
691 }
692
693 return (brc);
694}
695
696
Note: See TracBrowser for help on using the repository browser.