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

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