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

Last change on this file since 73 was 69, checked in by umoeller, 24 years ago

New folder sorting. Updated folder refresh. Misc other changes.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 19.8 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 *
237 * Exported functions
238 *
239 ********************************************************************/
240
241/*
242 *@@ tmrCreateSet:
243 * creates a "timer set" for use with the XTimer functions.
244 * This is the first step if you want to use the XTimers.
245 *
246 * hwndOwner must specify the "owner window", the target
247 * window for the master PM timer. This window must respond
248 * to a WM_TIMER message with the specified usPMTimerID and
249 * invoke tmrTimerTick then.
250 *
251 * Note that the master timer is not started until an XTimer
252 * is started.
253 *
254 * Use tmrDestroySet to free all resources again.
255 *
256 *@@added V0.9.9 (2001-02-28) [umoeller]
257 */
258
259PXTIMERSET tmrCreateSet(HWND hwndOwner, // in: owner window
260 USHORT usPMTimerID)
261{
262 PXTIMERSET pSet = NULL;
263
264 pSet = (PXTIMERSET)malloc(sizeof(*pSet));
265 if (pSet)
266 {
267 pSet->hab = WinQueryAnchorBlock(hwndOwner);
268 pSet->hwndOwner = hwndOwner;
269 pSet->idPMTimer = usPMTimerID;
270 pSet->idPMTimerRunning = 0;
271 pSet->ulPMTimeout = 0;
272
273 pSet->pvllXTimers = (PVOID)lstCreate(TRUE);
274 }
275
276 return (pSet);
277}
278
279/*
280 *@@ tmrDestroySet:
281 * destroys a timer set previously created using
282 * tmrCreateSet.
283 *
284 * Of course, this will stop all XTimers which
285 * might still be running with this set.
286 *
287 *@@added V0.9.9 (2001-02-28) [umoeller]
288 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
289 */
290
291VOID tmrDestroySet(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
292{
293 if (pSet)
294 {
295 if (pSet->pvllXTimers)
296 {
297 if (LockTimers())
298 {
299 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
300
301 PLISTNODE pTimerNode;
302
303 while (pTimerNode = lstQueryFirstNode(pllXTimers))
304 {
305 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
306 RemoveTimer(pSet, pTimer);
307 }
308
309 lstFree(pllXTimers);
310
311 UnlockTimers();
312 }
313 }
314
315 if (pSet->idPMTimerRunning)
316 WinStopTimer(pSet->hab,
317 pSet->hwndOwner,
318 pSet->idPMTimer);
319
320 free(pSet);
321 }
322}
323
324/*
325 *@@ AdjustPMTimer:
326 * goes thru all XTimers in the sets and starts
327 * or stops the PM timer with a decent frequency.
328 *
329 * Internal function. Caller must hold the mutex.
330 *
331 *@@added V0.9.9 (2001-03-07) [umoeller]
332 */
333
334VOID AdjustPMTimer(PXTIMERSET pSet)
335{
336 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
337 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
338 if (!pNode)
339 {
340 // no XTimers running:
341 if (pSet->idPMTimerRunning)
342 {
343 // but PM timer running:
344 // stop it
345 WinStopTimer(pSet->hab,
346 pSet->hwndOwner,
347 pSet->idPMTimer);
348 pSet->idPMTimerRunning = 0;
349 }
350
351 pSet->ulPMTimeout = 0;
352 }
353 else
354 {
355 // we have timers:
356 ULONG ulOldPMTimeout = pSet->ulPMTimeout;
357 pSet->ulPMTimeout = 1000;
358
359 while (pNode)
360 {
361 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
362
363 if ( (pTimer->ulTimeout / 2) < pSet->ulPMTimeout )
364 pSet->ulPMTimeout = pTimer->ulTimeout / 2;
365
366 pNode = pNode->pNext;
367 }
368
369 if ( (pSet->idPMTimerRunning == 0) // timer not running?
370 || (pSet->ulPMTimeout != ulOldPMTimeout) // timeout changed?
371 )
372 // start or restart PM timer
373 pSet->idPMTimerRunning = WinStartTimer(pSet->hab,
374 pSet->hwndOwner,
375 pSet->idPMTimer,
376 pSet->ulPMTimeout);
377 }
378}
379
380/*
381 *@@ tmrTimerTick:
382 * implements a PM timer tick.
383 *
384 * When your window procs receives WM_TIMER for the
385 * one PM timer which is supposed to trigger all the
386 * XTimers, it must call this function. This will
387 * evaluate all XTimers on the list and "fire" them
388 * by calling the window procs directly with the
389 * WM_TIMER message.
390 *
391 *@@added V0.9.9 (2001-02-28) [umoeller]
392 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
393 */
394
395VOID tmrTimerTick(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
396{
397 if (pSet && pSet->pvllXTimers)
398 {
399 if (LockTimers())
400 {
401 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
402 // go thru all XTimers and see which one
403 // has elapsed; for all of these, post WM_TIMER
404 // to the target window proc
405 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
406
407 if (!pTimerNode)
408 {
409 // no timers left:
410 if (pSet->idPMTimerRunning)
411 {
412 // but PM timer running:
413 // stop it
414 WinStopTimer(pSet->hab,
415 pSet->hwndOwner,
416 pSet->idPMTimer);
417 pSet->idPMTimerRunning = 0;
418 }
419
420 pSet->ulPMTimeout = 0;
421 }
422 else
423 {
424 // we have timers:
425 BOOL fFoundInvalid = FALSE;
426
427 ULONG // ulInterval = 100,
428 ulTimeNow = 0;
429
430 // linked list of timers found to be invalid;
431 // this holds LISTNODE pointers from the global
432 // list to be removed
433 LINKLIST llInvalidTimers;
434 lstInit(&llInvalidTimers,
435 FALSE); // no auto-free
436
437 // get current time
438 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
439 &ulTimeNow, sizeof(ulTimeNow));
440
441 while (pTimerNode)
442 {
443 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
444
445 if ( (pTimer)
446 && (pTimer->ulNextFire < ulTimeNow)
447 )
448 {
449 // this timer has elapsed:
450 // fire!
451 if (WinIsWindow(pSet->hab,
452 pTimer->hwndTarget))
453 {
454 // window still valid:
455 // get the window's window proc
456 PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
457 QWP_PFNWP);
458 // call the window proc DIRECTLY
459 pfnwp(pTimer->hwndTarget,
460 WM_TIMER,
461 (MPARAM)pTimer->usTimerID,
462 0);
463 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
464 }
465 else
466 {
467 // window has been destroyed:
468 lstAppendItem(&llInvalidTimers,
469 (PVOID)pTimerNode);
470 fFoundInvalid = TRUE;
471 }
472 } // end if (pTimer->ulNextFire < ulTimeNow)
473
474 // next timer
475 pTimerNode = pTimerNode->pNext;
476 } // end while (pTimerNode)
477
478 // destroy invalid timers, if any
479 if (fFoundInvalid)
480 {
481 PLISTNODE pNodeNode = lstQueryFirstNode(&llInvalidTimers);
482 while (pNodeNode)
483 {
484 PLISTNODE pNode2Remove = (PLISTNODE)pNodeNode->pItemData;
485 lstRemoveNode(pllXTimers,
486 pNode2Remove);
487 pNodeNode = pNodeNode->pNext;
488 }
489 lstClear(&llInvalidTimers);
490
491 AdjustPMTimer(pSet);
492 }
493 } // end else if (!pTimerNode)
494
495 UnlockTimers();
496 } // end if (LockTimers())
497 } // end if (pSet && pSet->pvllXTimers)
498}
499
500/*
501 *@@ tmrStartXTimer:
502 * starts an XTimer.
503 *
504 * Any window can request an XTimer using
505 * this function. This operates similar to
506 * WinStartTimer, except that the number of
507 * XTimers is not limited.
508 *
509 * Returns a new timer or resets an existing
510 * timer (if usTimerID is already used with
511 * hwnd). Use tmrStopXTimer to stop the timer.
512 *
513 * The timer is _not_ stopped automatically
514 * when the window is destroyed.
515 *
516 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
517 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
518 */
519
520USHORT XWPENTRY tmrStartXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
521 HWND hwnd, // in: target window for XTimer
522 USHORT usTimerID, // in: timer ID for XTimer's WM_TIMER
523 ULONG ulTimeout) // in: XTimer's timeout
524{
525 USHORT usrc = 0;
526
527 // _Pmpf((__FUNCTION__ ": entering"));
528
529 if (pSet && pSet->pvllXTimers)
530 {
531 if (LockTimers())
532 {
533 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
534
535 if ((hwnd) && (ulTimeout))
536 {
537 PXTIMER pTimer;
538
539 // check if this timer exists already
540 pTimer = FindTimer(pSet,
541 hwnd,
542 usTimerID);
543 if (pTimer)
544 {
545 // exists already: reset only
546 ULONG ulTimeNow;
547 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
548 &ulTimeNow, sizeof(ulTimeNow));
549 pTimer->ulNextFire = ulTimeNow + ulTimeout;
550 usrc = pTimer->usTimerID;
551 }
552 else
553 {
554 // new timer needed:
555 pTimer = (PXTIMER)malloc(sizeof(XTIMER));
556 if (pTimer)
557 {
558 ULONG ulTimeNow;
559 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
560 &ulTimeNow, sizeof(ulTimeNow));
561 pTimer->usTimerID = usTimerID;
562 pTimer->hwndTarget = hwnd;
563 pTimer->ulTimeout = ulTimeout;
564 pTimer->ulNextFire = ulTimeNow + ulTimeout;
565
566 lstAppendItem(pllXTimers,
567 pTimer);
568 usrc = pTimer->usTimerID;
569 }
570 }
571
572 if (usrc)
573 {
574 // timer created or reset:
575 AdjustPMTimer(pSet);
576 }
577 } // if ((hwnd) && (ulTimeout))
578
579 UnlockTimers();
580 }
581 }
582
583 return (usrc);
584}
585
586/*
587 *@@ tmrStopXTimer:
588 * similar to WinStopTimer, this stops the
589 * specified timer (which must have been
590 * started with the same hwnd and usTimerID
591 * using tmrStartXTimer).
592 *
593 * Returns TRUE if the timer was stopped.
594 *
595 *@@changed V0.9.12 (2001-05-12) [umoeller]: added mutex protection
596 */
597
598BOOL XWPENTRY tmrStopXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
599 HWND hwnd,
600 USHORT usTimerID)
601{
602 BOOL brc = FALSE;
603 if (pSet && pSet->pvllXTimers)
604 {
605 if (LockTimers())
606 {
607 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
608 BOOL fLocked = FALSE;
609
610 PXTIMER pTimer = FindTimer(pSet,
611 hwnd,
612 usTimerID);
613 if (pTimer)
614 {
615 RemoveTimer(pSet, pTimer);
616 // recalculate
617 AdjustPMTimer(pSet);
618 brc = TRUE;
619 }
620
621 UnlockTimers();
622 }
623 }
624
625 return (brc);
626}
627
628
Note: See TracBrowser for help on using the repository browser.