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

Last change on this file since 51 was 44, 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: 20.2 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
146// timers thread
147// HMTX G_hmtxTimers = NULLHANDLE; // timers lock mutex
148// THREADINFO G_tiTimers = {0}; // timers thread (only running
149 // if any timers were requested)
150// BOOL G_fTimersThreadRunning = FALSE;
151// LINKLIST G_llTimers; // linked list of XTIMER pointers
152
153/*
154 *@@ fntTimersThread:
155 * the actual thread which fires the timers by
156 * posting WM_TIMER messages to the respecive
157 * target windows when a timer has elapsed.
158 *
159 * This thread is dynamically started when the
160 * first timer is started thru tmrStartXTimer.
161 * It is automatically stopped (to be precise:
162 * it terminates itself) when the last timer
163 * is stopped thru tmrStopXTimer, which then
164 * sets the thread's fExit flag to TRUE.
165 *
166 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
167 */
168
169/* void _Optlink fntTimersThread(PTHREADINFO ptiMyself)
170{
171 ULONG ulInterval = 25;
172 HAB hab = WinInitialize(0);
173 BOOL fLocked = FALSE;
174
175 // linked list of timers found to be invalid;
176 // this holds LISTNODE pointers from the global
177 // list to be removed
178 LINKLIST llInvalidTimers;
179 lstInit(&llInvalidTimers,
180 FALSE); // no auto-free
181
182 #ifdef __DEBUG__
183 DosBeep(3000, 30);
184 #endif
185
186 // keep running while we have timers
187 while (!ptiMyself->fExit)
188 {
189 // ULONG ulNesting = 0;
190
191 ULONG ulTimeNow;
192
193 DosSleep(ulInterval);
194
195 // minimum interval: 100 ms; this is lowered
196 // if we find any timers in the list which
197 // have a lower timeout to make sure we can
198 // fire at a lower interval...
199 ulInterval = 100;
200
201 // DosEnterMustComplete(&ulNesting);
202
203 TRY_LOUD(excpt1)
204 {
205 fLocked = LockTimers();
206 if (fLocked)
207 {
208 } // end if (fLocked)
209 }
210 CATCH(excpt1) { } END_CATCH();
211
212 if (fLocked)
213 {
214 UnlockTimers();
215 fLocked = FALSE;
216 }
217
218 // DosExitMustComplete(&ulNesting);
219
220 } // end while (!ptiMyself->fExit)
221
222 WinTerminate(hab);
223
224 #ifdef __DEBUG__
225 DosBeep(1500, 30);
226 #endif
227} */
228
229/* ******************************************************************
230 *
231 * Private functions
232 *
233 ********************************************************************/
234
235/*
236 *@@ FindTimer:
237 * returns the XTIMER structure from the global
238 * linked list which matches the given window
239 * _and_ timer ID.
240 *
241 * Internal function.
242 */
243
244PXTIMER FindTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
245 HWND hwnd, // in: timer target window
246 USHORT usTimerID) // in: timer ID
247{
248 if (pSet && pSet->pvllXTimers)
249 {
250 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
251 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
252 while (pNode)
253 {
254 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
255 if ( (pTimer->usTimerID == usTimerID)
256 && (pTimer->hwndTarget == hwnd)
257 )
258 {
259 return (pTimer);
260 }
261
262 pNode = pNode->pNext;
263 }
264 }
265
266 return (NULL);
267}
268
269/*
270 *@@ RemoveTimer:
271 * removes the specified XTIMER structure from
272 * the global linked list of running timers.
273 *
274 * Internal function.
275 */
276
277VOID RemoveTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
278 PXTIMER pTimer) // in: timer to remove.
279{
280 if (pSet && pSet->pvllXTimers)
281 {
282 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
283 lstRemoveItem(pllXTimers,
284 pTimer); // auto-free!
285 }
286}
287
288/* ******************************************************************
289 *
290 * Exported functions
291 *
292 ********************************************************************/
293
294/*
295 *@@ tmrCreateSet:
296 * creates a "timer set" for use with the XTimer functions.
297 * This is the first step if you want to use the XTimers.
298 *
299 * hwndOwner must specify the "owner window", the target
300 * window for the master PM timer. This window must respond
301 * to a WM_TIMER message with the specified usPMTimerID and
302 * invoke tmrTimerTick then.
303 *
304 * Note that the master timer is not started until an XTimer
305 * is started.
306 *
307 * Use tmrDestroySet to free all resources again.
308 *
309 *@@added V0.9.9 (2001-02-28) [umoeller]
310 */
311
312PXTIMERSET tmrCreateSet(HWND hwndOwner, // in: owner window
313 USHORT usPMTimerID)
314{
315 PXTIMERSET pSet = NULL;
316
317 pSet = (PXTIMERSET)malloc(sizeof(*pSet));
318 if (pSet)
319 {
320 pSet->hab = WinQueryAnchorBlock(hwndOwner);
321 pSet->hwndOwner = hwndOwner;
322 pSet->idPMTimer = usPMTimerID;
323 pSet->idPMTimerRunning = 0;
324 pSet->ulPMTimeout = 0;
325
326 pSet->pvllXTimers = (PVOID)lstCreate(TRUE);
327 }
328
329 return (pSet);
330}
331
332/*
333 *@@ tmrDestroySet:
334 * destroys a timer set previously created using
335 * tmrCreateSet.
336 *
337 * Of course, this will stop all XTimers which
338 * might still be running with this set.
339 *
340 *@@added V0.9.9 (2001-02-28) [umoeller]
341 */
342
343VOID tmrDestroySet(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
344{
345 if (pSet)
346 {
347 if (pSet->pvllXTimers)
348 {
349 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
350
351 PLISTNODE pTimerNode;
352
353 while (pTimerNode = lstQueryFirstNode(pllXTimers))
354 {
355 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
356 RemoveTimer(pSet, pTimer);
357 }
358
359 lstFree(pllXTimers);
360 }
361
362 if (pSet->idPMTimerRunning)
363 WinStopTimer(pSet->hab,
364 pSet->hwndOwner,
365 pSet->idPMTimer);
366
367 free(pSet);
368 }
369}
370
371/*
372 *@@ AdjustPMTimer:
373 * goes thru all XTimers in the sets and starts
374 * or stops the PM timer with a decent frequency.
375 *
376 *@@added V0.9.9 (2001-03-07) [umoeller]
377 */
378
379VOID AdjustPMTimer(PXTIMERSET pSet)
380{
381 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
382 PLISTNODE pNode = lstQueryFirstNode(pllXTimers);
383 if (!pNode)
384 {
385 // no XTimers running:
386 if (pSet->idPMTimerRunning)
387 {
388 // but PM timer running:
389 // stop it
390 WinStopTimer(pSet->hab,
391 pSet->hwndOwner,
392 pSet->idPMTimer);
393 pSet->idPMTimerRunning = 0;
394 }
395
396 pSet->ulPMTimeout = 0;
397 }
398 else
399 {
400 // we have timers:
401 ULONG ulOldPMTimeout = pSet->ulPMTimeout;
402 pSet->ulPMTimeout = 1000;
403
404 while (pNode)
405 {
406 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
407
408 if ( (pTimer->ulTimeout / 2) < pSet->ulPMTimeout )
409 pSet->ulPMTimeout = pTimer->ulTimeout / 2;
410
411 pNode = pNode->pNext;
412 }
413
414 if ( (pSet->idPMTimerRunning == 0) // timer not running?
415 || (pSet->ulPMTimeout != ulOldPMTimeout) // timeout changed?
416 )
417 // start or restart PM timer
418 pSet->idPMTimerRunning = WinStartTimer(pSet->hab,
419 pSet->hwndOwner,
420 pSet->idPMTimer,
421 pSet->ulPMTimeout);
422 }
423}
424
425/*
426 *@@ tmrTimerTick:
427 * implements a PM timer tick.
428 *
429 * When your window procs receives WM_TIMER for the
430 * one PM timer which is supposed to trigger all the
431 * XTimers, it must call this function. This will
432 * evaluate all XTimers on the list and "fire" them
433 * by calling the window procs directly with the
434 * WM_TIMER message.
435 *
436 *@@added V0.9.9 (2001-02-28) [umoeller]
437 */
438
439VOID tmrTimerTick(PXTIMERSET pSet) // in: timer set (from tmrCreateSet)
440{
441 if (pSet && pSet->pvllXTimers)
442 {
443 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
444 // go thru all XTimers and see which one
445 // has elapsed; for all of these, post WM_TIMER
446 // to the target window proc
447 PLISTNODE pTimerNode = lstQueryFirstNode(pllXTimers);
448
449 if (!pTimerNode)
450 {
451 // no timers left:
452 if (pSet->idPMTimerRunning)
453 {
454 // but PM timer running:
455 // stop it
456 WinStopTimer(pSet->hab,
457 pSet->hwndOwner,
458 pSet->idPMTimer);
459 pSet->idPMTimerRunning = 0;
460 }
461
462 pSet->ulPMTimeout = 0;
463 }
464 else
465 {
466 // we have timers:
467 BOOL fFoundInvalid = FALSE;
468
469 ULONG // ulInterval = 100,
470 ulTimeNow = 0;
471
472 // linked list of timers found to be invalid;
473 // this holds LISTNODE pointers from the global
474 // list to be removed
475 LINKLIST llInvalidTimers;
476 lstInit(&llInvalidTimers,
477 FALSE); // no auto-free
478
479 // get current time
480 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
481 &ulTimeNow, sizeof(ulTimeNow));
482
483 while (pTimerNode)
484 {
485 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
486
487 if (pTimer->ulNextFire < ulTimeNow)
488 {
489 // this timer has elapsed:
490 // fire!
491 if (WinIsWindow(pSet->hab,
492 pTimer->hwndTarget))
493 {
494 // window still valid:
495 // get the window's window proc
496 PFNWP pfnwp = (PFNWP)WinQueryWindowPtr(pTimer->hwndTarget,
497 QWP_PFNWP);
498 // call the window proc DIRECTLY
499 pfnwp(pTimer->hwndTarget,
500 WM_TIMER,
501 (MPARAM)pTimer->usTimerID,
502 0);
503 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
504 }
505 else
506 {
507 // window has been destroyed:
508 lstAppendItem(&llInvalidTimers,
509 (PVOID)pTimerNode);
510 fFoundInvalid = TRUE;
511 }
512 } // end if (pTimer->ulNextFire < ulTimeNow)
513
514 // next timer
515 pTimerNode = pTimerNode->pNext;
516 } // end while (pTimerNode)
517
518 // destroy invalid timers, if any
519 if (fFoundInvalid)
520 {
521 PLISTNODE pNodeNode = lstQueryFirstNode(&llInvalidTimers);
522 while (pNodeNode)
523 {
524 PLISTNODE pNode2Remove = (PLISTNODE)pNodeNode->pItemData;
525 lstRemoveNode(pllXTimers,
526 pNode2Remove);
527 pNodeNode = pNodeNode->pNext;
528 }
529 lstClear(&llInvalidTimers);
530
531 AdjustPMTimer(pSet);
532 }
533 } // end else if (!pTimerNode)
534 }
535}
536
537/*
538 *@@ tmrStartXTimer:
539 * starts an XTimer.
540 *
541 * Any window can request an XTimer using
542 * this function. This operates similar to
543 * WinStartTimer, except that the number of
544 * XTimers is not limited.
545 *
546 * Returns a new timer or resets an existing
547 * timer (if usTimerID is already used with
548 * hwnd). Use tmrStopXTimer to stop the timer.
549 *
550 * The timer is _not_ stopped automatically
551 * when the window is destroyed.
552 *
553 *@@changed V0.9.7 (2000-12-08) [umoeller]: got rid of dtGetULongTime
554 */
555
556USHORT XWPENTRY tmrStartXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
557 HWND hwnd, // in: target window for XTimer
558 USHORT usTimerID, // in: timer ID for XTimer's WM_TIMER
559 ULONG ulTimeout) // in: XTimer's timeout
560{
561 USHORT usrc = 0;
562
563 // _Pmpf((__FUNCTION__ ": entering"));
564
565 if (pSet && pSet->pvllXTimers)
566 {
567 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
568
569 if ((hwnd) && (ulTimeout))
570 {
571 PXTIMER pTimer;
572
573 // check if this timer exists already
574 pTimer = FindTimer(pSet,
575 hwnd,
576 usTimerID);
577 if (pTimer)
578 {
579 // exists already: reset only
580 ULONG ulTimeNow;
581 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
582 &ulTimeNow, sizeof(ulTimeNow));
583 pTimer->ulNextFire = ulTimeNow + ulTimeout;
584 usrc = pTimer->usTimerID;
585 }
586 else
587 {
588 // new timer needed:
589 pTimer = (PXTIMER)malloc(sizeof(XTIMER));
590 if (pTimer)
591 {
592 ULONG ulTimeNow;
593 DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT,
594 &ulTimeNow, sizeof(ulTimeNow));
595 pTimer->usTimerID = usTimerID;
596 pTimer->hwndTarget = hwnd;
597 pTimer->ulTimeout = ulTimeout;
598 pTimer->ulNextFire = ulTimeNow + ulTimeout;
599
600 lstAppendItem(pllXTimers,
601 pTimer);
602 usrc = pTimer->usTimerID;
603 }
604 }
605
606 if (usrc)
607 {
608 // timer created or reset:
609 AdjustPMTimer(pSet);
610 }
611 } // if ((hwnd) && (ulTimeout))
612 }
613
614 // _Pmpf((__FUNCTION__ ": leaving, returning %d", usrc));
615
616 return (usrc);
617}
618
619/*
620 *@@ tmrStopXTimer:
621 * similar to WinStopTimer, this stops the
622 * specified timer (which must have been
623 * started with the same hwnd and usTimerID
624 * using tmrStartXTimer).
625 *
626 * Returns TRUE if the timer was stopped.
627 */
628
629BOOL XWPENTRY tmrStopXTimer(PXTIMERSET pSet, // in: timer set (from tmrCreateSet)
630 HWND hwnd,
631 USHORT usTimerID)
632{
633 BOOL brc = FALSE;
634 if (pSet && pSet->pvllXTimers)
635 {
636 PLINKLIST pllXTimers = (PLINKLIST)pSet->pvllXTimers;
637 BOOL fLocked = FALSE;
638
639 PXTIMER pTimer = FindTimer(pSet,
640 hwnd,
641 usTimerID);
642 if (pTimer)
643 {
644 RemoveTimer(pSet, pTimer);
645 // recalculate
646 AdjustPMTimer(pSet);
647 brc = TRUE;
648 }
649 }
650
651 return (brc);
652}
653
654
Note: See TracBrowser for help on using the repository browser.