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

Last change on this file since 92 was 91, 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: 23.3 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 if (!cTimers)
253 {
254 // no XTimers running:
255 if (pSet->idPMTimerRunning)
256 {
257 // but PM timer running:
258 // stop it
259 WinStopTimer(pSet->hab,
260 pSet->hwndOwner,
261 pSet->idPMTimer);
262 pSet->idPMTimerRunning = 0;
263 }
264
265 pSet->ulPMTimeout = 0;
266 }
267 else
268 {
269 // we have timers:
270
271 ULONG ulOldPMTimeout = pSet->ulPMTimeout;
272
273 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
274 PXTIMER pTimer1 = (PXTIMER)pNode->pItemData;
275
276 if (cTimers == 1)
277 {
278 // only one timer:
279 // that's easy
280 pSet->ulPMTimeout = pTimer1->ulTimeout;
281 }
282 else if (cTimers == 2)
283 {
284 // exactly two timers:
285 // find the greatest common denominator
286 PXTIMER pTimer2 = (PXTIMER)pNode->pNext->pItemData;
287
288 pSet->ulPMTimeout = mathGCD(pTimer1->ulTimeout,
289 pTimer2->ulTimeout);
290 }
291 else
292 {
293 // more than two timers:
294 // run through all timers and find the greatest
295 // common denominator of all frequencies...
296 int *paInts = (int*)_alloca(sizeof(int) * cTimers),
297 i = 0;
298
299 // _Pmpf(("Recalculating, got %d timers", cTimers));
300
301 // fill an array of integers with the
302 // timer frequencies
303 while (pNode)
304 {
305 pTimer1 = (PXTIMER)pNode->pItemData;
306
307 // _Pmpf((" timeout %d is %d", i, pTimer1->ulTimeout));
308
309 paInts[i++] = pTimer1->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) // 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 = NEW(XTIMERSET);
358 if (pSet)
359 {
360 pSet->hab = WinQueryAnchorBlock(hwndOwner);
361 pSet->hwndOwner = hwndOwner;
362 pSet->idPMTimer = usPMTimerID;
363 pSet->idPMTimerRunning = 0;
364 pSet->ulPMTimeout = 0;
365
366 pSet->pvllXTimers = (PVOID)lstCreate(TRUE);
367 }
368
369 return (pSet);
370}
371
372/*
373 *@@ tmrDestroySet:
374 * destroys a timer set previously created using
375 * tmrCreateSet.
376 *
377 * Of course, this will stop all XTimers which
378 * might still be running with this set.
379 *
380 *@@added V0.9.9 (2001-02-28) [umoeller]
381 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
382 */
383
384VOID tmrDestroySet(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
385{
386 if (LockTimers())
387 {
388 if (pSet)
389 {
390 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
391 if (pllXTimers)
392 {
393 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
394 while (pTimerNode)
395 {
396 PLISTNODE pNext = pTimerNode->pNext;
397 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
398 RemoveTimer(pSet, pTimer);
399 pTimerNode = pNext;
400 }
401
402 lstFree(&pllXTimers);
403 pSet->pvllXTimers = NULL;
404 }
405 }
406
407 if (pSet->idPMTimerRunning)
408 {
409 WinStopTimer(pSet->hab,
410 pSet->hwndOwner,
411 pSet->idPMTimer);
412 pSet->idPMTimerRunning = 0;
413 }
414
415 free(pSet);
416
417 UnlockTimers();
418 }
419}
420
421/*
422 *@@ tmrTimerTick:
423 * implements a PM timer tick.
424 *
425 * When your window procs receives WM_TIMER for the
426 * one PM timer which is supposed to trigger all the
427 * XTimers, it must call this function. This will
428 * evaluate all XTimers on the list and "fire" them
429 * by calling the window procs directly with the
430 * WM_TIMER message.
431 *
432 *@@added V0.9.9 (2001-02-28) [umoeller]
433 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
434 *@@changed V0.9.12 (2001-05-24) [umoeller]: fixed crash if this got called during tmrTimerTick
435 *@@changed V0.9.14 (2001-08-01) [umoeller]: fixed mem overwrite which might have caused crashes if this got called during tmrTimerTick
436 */
437
438VOID tmrTimerTick(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
439{
440 if (LockTimers())
441 {
442 if (pSet)
443 {
444 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
445 if (pllXTimers)
446 {
447 // go thru all XTimers and see which one
448 // has elapsed; for all of these, post WM_TIMER
449 // to the target window proc
450 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
451
452 if (!pTimerNode)
453 {
454 // no timers left:
455 if (pSet->idPMTimerRunning)
456 {
457 // but PM timer running:
458 // stop it
459 WinStopTimer(pSet->hab,
460 pSet->hwndOwner,
461 pSet->idPMTimer);
462 pSet->idPMTimerRunning = 0;
463 }
464
465 pSet->ulPMTimeout = 0;
466 }
467 else
468 {
469 // we have timers:
470 BOOL fFoundInvalid = FALSE;
471
472 // get current time
473 ULONG ulTimeNow = 0;
474 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
475 &ulTimeNow, sizeof(ulTimeNow));
476
477 while (pTimerNode)
478 {
479 // get next node first because the
480 // list can get modified while processing
481 // V0.9.12 (2001-05-24) [umoeller]
482 PLISTNODE pNext = pTimerNode->pNext;
483
484 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
485
486 if ( (pTimer)
487 && (pTimer->ulNextFire < ulTimeNow)
488 )
489 {
490 // this timer has elapsed:
491 // fire!
492 if (WinIsWindow(pSet->hab,
493 pTimer->hwndTarget))
494 {
495 // window still valid:
496 // get the window's window proc
497 PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
498 QWP_PFNWP);
499
500 // moved this up V0.9.14 (2001-08-01) [umoeller]
501 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
502
503 // call the window proc DIRECTLY
504 pfnwp(pTimer->hwndTarget,
505 WM_TIMER,
506 (MPARAM)pTimer->usTimerID,
507 0);
508 // V0.9.12 (2001-05-24) [umoeller]
509 // if the winproc chooses to start or
510 // stop a timer, pNext still points
511 // to a valid node...
512 // -- if a timer is removed, that's OK
513 // -- if a timer is added, it is added to
514 // the list, so we'll see it in this loop
515
516 // V0.9.14 (2001-08-01) [umoeller]
517
518 // DO NOT REFERENCE pTimer AFTER THIS CODE;
519 // tmrTimerTick might have removed the timer,
520 // and since the list is auto-free, pTimer
521 // might have been freed!!
522 }
523 else
524 {
525 // window has been destroyed:
526 lstRemoveNode(pllXTimers,
527 pTimerNode);
528 // pNext is still valid
529
530 fFoundInvalid = TRUE;
531 }
532
533 } // end if (pTimer->ulNextFire < ulTimeNow)
534
535 // next timer
536 pTimerNode = pNext; // V0.9.12 (2001-05-24) [umoeller]
537 } // end while (pTimerNode)
538
539 // destroy invalid timers, if any
540 if (fFoundInvalid)
541 AdjustPMTimer(pSet);
542
543 } // end else if (!pTimerNode)
544 } // end if (pllXTimers)
545 } // end if (pSet)
546
547 UnlockTimers();
548 } // end if (LockTimers())
549}
550
551/*
552 *@@ tmrStartXTimer:
553 * starts an XTimer.
554 *
555 * Any window can request an XTimer using
556 * this function. This operates similar to
557 * WinStartTimer, except that the number of
558 * XTimers is not limited.
559 *
560 * Returns a new timer or resets an existing
561 * timer (if usTimerID is already used with
562 * hwnd). Use tmrStopXTimer to stop the timer.
563 *
564 * The timer is _not_ stopped automatically
565 * when the window is destroyed.
566 *
567 * Note: Unless you own the timer set that
568 * your timer runs on, it is strongly recommended
569 * that your timer frequency is set to a multiple
570 * of 125. The PM master timer behind the timer
571 * set will be set to the greatest common divisor
572 * of all frequencies, and if you set one timer
573 * to 2000 and the other one to 2001, you will
574 * cause quite a lot of overhead. This applies
575 * especially to timers started by XCenter widgets.
576 *
577 * For security, all timer frequencies will be
578 * rounded to multiples of 25 anyway. Still,
579 * running two timers at 1000 and 1025 will cause
580 * the master timer to be set to 25, which is
581 * overkill.
582 *
583 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
584 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
585 *@@changed V0.9.14 (2001-07-12) [umoeller]: now rounding freq's to multiples of 25
586 */
587
588USHORT XWPENTRY tmrStartXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
589 HWND hwnd, // in: target window for XTimer
590 USHORT usTimerID, // in: timer ID for XTimer's WM_TIMER
591 ULONG ulTimeout) // in: XTimer's timeout
592{
593 USHORT usrc = 0;
594
595 // _Pmpf((__FUNCTION__ ": entering"));
596
597 if (LockTimers())
598 {
599 if (pSet)
600 {
601 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
602
603 if ((pllXTimers) && (hwnd) && (ulTimeout))
604 {
605 PXTIMER pTimer;
606
607 // fix the timeout... we allow only multiples of
608 // 25, and it must be at least 25 (otherwise our
609 // internal master timer calculations will fail)
610 // V0.9.14 (2001-07-07) [umoeller]
611 if (ulTimeout < 25)
612 ulTimeout = 25;
613 else
614 ulTimeout = (ulTimeout + 10) / 25 * 25;
615
616 // check if this timer exists already
617 if (pTimer = FindTimer(pSet,
618 hwnd,
619 usTimerID))
620 {
621 // exists already: reset only
622 ULONG ulTimeNow;
623 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
624 &ulTimeNow, sizeof(ulTimeNow));
625 pTimer->ulNextFire = ulTimeNow + ulTimeout;
626 usrc = usTimerID;
627 }
628 else
629 {
630 // new timer needed:
631 if (pTimer = NEW(XTIMER))
632 {
633 ULONG ulTimeNow;
634 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
635 &ulTimeNow, sizeof(ulTimeNow));
636 pTimer->usTimerID = usTimerID;
637 pTimer->hwndTarget = hwnd;
638 pTimer->ulTimeout = ulTimeout;
639 pTimer->ulNextFire = ulTimeNow + ulTimeout;
640
641 lstAppendItem(pllXTimers,
642 pTimer);
643 usrc = usTimerID;
644 }
645 }
646
647 if (usrc)
648 // timer created or reset:
649 AdjustPMTimer(pSet);
650
651 } // if ((hwnd) && (ulTimeout))
652 }
653
654 UnlockTimers();
655 }
656
657 return (usrc);
658}
659
660/*
661 *@@ tmrStopXTimer:
662 * similar to WinStopTimer, this stops the
663 * specified timer (which must have been
664 * started with the same hwnd and usTimerID
665 * using tmrStartXTimer).
666 *
667 * Returns TRUE if the timer was stopped.
668 *
669 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
670 */
671
672BOOL XWPENTRY tmrStopXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
673 HWND hwnd,
674 USHORT usTimerID)
675{
676 BOOL brc = FALSE;
677 if (LockTimers())
678 {
679 if (pSet && pSet->pvllXTimers)
680 {
681 // PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
682
683 PXTIMER pTimer = FindTimer(pSet,
684 hwnd,
685 usTimerID);
686 if (pTimer)
687 {
688 RemoveTimer(pSet, pTimer);
689 // recalculate
690 AdjustPMTimer(pSet);
691 brc = TRUE;
692 }
693 }
694
695 UnlockTimers();
696 }
697
698 return (brc);
699}
700
701
Note: See TracBrowser for help on using the repository browser.