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

Last change on this file since 14 was 14, checked in by umoeller, 25 years ago

Major updates; timers, LVM, miscellaneous.

  • Property svn:eol-style set to CRLF
  • Property svn:keywords set to Author Date Id Revision
File size: 16.9 KB
Line 
1
2/*
3 *@@sourcefile timer.c:
4 * XTimers, which can replace the PM timers.
5 *
6 * These timers operate similar to PM timers started
7 * with WinStartTimer. These are implemented thru a
8 * separate thread (fntTimersThread) which posts
9 * WM_TIMER messages regularly.
10 *
11 * Instead of WinStartTimer, use tmrStartTimer.
12 * Instead of WinStopTimer, use tmrStopTimer.
13 *
14 * The main advantage of the XTimers is that these
15 * are not a limited resource (as opposed to PM timers).
16 * I don't know how many PM timers exist on the system,
17 * but PMREF says that the no. of remaining timers can
18 * be queried with WinQuerySysValue(SV_CTIMERS).
19 *
20 * There are a few limitations with the XTimers though:
21 *
22 * -- If you start a timer with a timeout < 100 ms,
23 * the first WM_TIMER might not appear before
24 * 100 ms have elapsed. This may or be not the
25 * case, depending on whether other timers are
26 * running.
27 *
28 * -- XTimers post WM_TIMER messages regardless of
29 * whether previous WM_TIMER messages have already
30 * been processed. For this reason, be careful with
31 * small timer timeouts, this might flood the
32 * message queue.
33 *
34 * -- Queue timers (with HWND == NULLHANDLE) are not
35 * supported.
36 *
37 * -- When a window is deleted, its timers are not
38 * automatically cleaned up. To be on the safe
39 * side, always call tmrStopAllTimers when
40 * WM_DESTROY comes into a window which has used
41 * timers.
42 *
43 * Function prefixes:
44 * -- tmr* timer functions
45 *
46 *@@header "helpers\timer.h"
47 *@@added V0.9.7 (2000-12-04) [umoeller]
48 */
49
50/*
51 * Copyright (C) 2000 Ulrich M”ller.
52 * This file is part of the "XWorkplace helpers" source package.
53 * This is free software; you can redistribute it and/or modify
54 * it under the terms of the GNU General Public License as published
55 * by the Free Software Foundation, in version 2 as it comes in the
56 * "COPYING" file of the XWorkplace main distribution.
57 * This program is distributed in the hope that it will be useful,
58 * but WITHOUT ANY WARRANTY; without even the implied warranty of
59 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
60 * GNU General Public License for more details.
61 */
62
63// OS2 includes
64
65#define OS2EMX_PLAIN_CHAR
66 // this is needed for "os2emx.h"; if this is defined,
67 // emx will define PSZ as _signed_ char, otherwise
68 // as unsigned char
69
70#define INCL_DOSEXCEPTIONS
71#define INCL_DOSPROCESS
72#define INCL_DOSSEMAPHORES
73#define INCL_DOSERRORS
74#include <os2.h>
75
76#include <stdio.h>
77#include <setjmp.h>
78
79#include "setup.h" // code generation and debugging options
80
81#include "helpers\datetime.h"
82#include "helpers\except.h"
83#include "helpers\linklist.h"
84#include "helpers\threads.h"
85#include "helpers\timer.h"
86
87/*
88 *@@category: Helpers\PM helpers\Timer replacements
89 */
90
91/* ******************************************************************
92 *
93 * Private declarations
94 *
95 ********************************************************************/
96
97/*
98 *@@ XTIMER:
99 * one of these represents an XTimer.
100 * These are stored in a global linked list.
101 */
102
103typedef struct _XTIMER
104{
105 USHORT usTimerID; // timer ID, as passed to tmrStartTimer
106 HWND hwndTarget; // target window, as passed to tmrStartTimer
107 ULONG ulTimeout; // timer's timeout (in ms)
108 ULONG ulNextFire; // next time scalar (from dtGetULongTime) to fire at
109} XTIMER, *PXTIMER;
110
111/* ******************************************************************
112 *
113 * Global variables
114 *
115 ********************************************************************/
116
117// timers thread
118HMTX G_hmtxTimers = NULLHANDLE; // timers lock mutex
119THREADINFO G_tiTimers = {0}; // timers thread (only running
120 // if any timers were requested)
121BOOL G_fTimersThreadRunning = FALSE;
122LINKLIST G_llTimers; // linked list of XTIMER pointers
123
124/* ******************************************************************
125 *
126 * Timer helpers
127 *
128 ********************************************************************/
129
130/*
131 *@@ LockTimers:
132 * locks the global timer resources.
133 * You MUST call UnlockTimers after this.
134 */
135
136BOOL LockTimers(VOID)
137{
138 BOOL brc = FALSE;
139 if (G_hmtxTimers == NULLHANDLE)
140 {
141 // this initializes all globals
142 lstInit(&G_llTimers,
143 TRUE); // auto-free
144
145 brc = (DosCreateMutexSem(NULL, // unnamed
146 &G_hmtxTimers,
147 0, // unshared
148 TRUE) // lock!
149 == NO_ERROR);
150 }
151 else
152 brc = (DosRequestMutexSem(G_hmtxTimers, SEM_INDEFINITE_WAIT)
153 == NO_ERROR);
154 return (brc);
155}
156
157/*
158 *@@ UnlockTimers:
159 * the reverse to LockTimers.
160 */
161
162VOID UnlockTimers(VOID)
163{
164 DosReleaseMutexSem(G_hmtxTimers);
165}
166
167/*
168 *@@ tmrOnKill:
169 * on-kill proc for exception handlers.
170 *
171 *@@added V0.9.7 (2000-12-09) [umoeller]
172 */
173
174VOID APIENTRY tmrOnKill(PEXCEPTIONREGISTRATIONRECORD2 pRegRec2)
175{
176 DosBeep(500, 500);
177 UnlockTimers();
178}
179
180/*
181 *@@ fntTimersThread:
182 * the actual thread which fires the timers by
183 * posting WM_TIMER messages to the respecive
184 * target windows when a timer has elapsed.
185 *
186 * This thread is dynamically started when the
187 * first timer is started thru tmrStartTimer.
188 * It is automatically stopped (to be precise:
189 * it terminates itself) when the last timer
190 * is stopped thru tmrStopTimer, which then
191 * sets the thread's fExit flag to TRUE.
192 */
193
194void _Optlink fntTimersThread(PTHREADINFO ptiMyself)
195{
196 ULONG ulInterval = 25;
197 HAB hab = WinInitialize(0);
198 BOOL fLocked = FALSE;
199
200 // linked list of timers found to be invalid;
201 // this holds LISTNODE pointers from the global
202 // list to be removed
203 LINKLIST llInvalidTimers;
204 lstInit(&llInvalidTimers,
205 FALSE); // no auto-free
206
207 #ifdef __DEBUG__
208 DosBeep(3000, 30);
209 #endif
210
211 // keep running while we have timers
212 while (!ptiMyself->fExit)
213 {
214 ULONG ulNesting = 0;
215
216 DosSleep(ulInterval);
217
218 // minimum interval: 100 ms; this is lowered
219 // if we find any timers in the list which
220 // have a lower timeout to make sure we can
221 // fire at a lower interval...
222 ulInterval = 100;
223
224 TRY_LOUD(excpt1)
225 {
226 DosEnterMustComplete(&ulNesting);
227
228 fLocked = LockTimers();
229 if (fLocked)
230 {
231 // go thru all XTimers and see which one
232 // has elapsed; for all of these, post WM_TIMER
233 // to the target window proc
234 PLISTNODE pTimerNode = lstQueryFirstNode(&G_llTimers);
235 if (!pTimerNode)
236 // no more timers left:
237 // terminate thread
238 ptiMyself->fExit = TRUE;
239 else
240 {
241 // we have timers:
242 BOOL fFoundInvalid = FALSE;
243 while (pTimerNode)
244 {
245 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
246 ULONG ulTimeNow = dtGetULongTime();
247
248 if (pTimer->ulNextFire < ulTimeNow)
249 {
250 // this timer has elapsed:
251 // fire!
252 if (WinIsWindow(hab,
253 pTimer->hwndTarget))
254 {
255 // window still valid:
256 WinPostMsg(pTimer->hwndTarget,
257 WM_TIMER,
258 (MPARAM)pTimer->usTimerID,
259 0);
260 pTimer->ulNextFire = ulTimeNow + pTimer->ulTimeout;
261 }
262 else
263 {
264 // window has been destroyed:
265 #ifdef __DEBUG__
266 DosBeep(100, 100);
267 #endif
268 lstAppendItem(&llInvalidTimers,
269 (PVOID)pTimerNode);
270 fFoundInvalid = TRUE;
271 }
272 } // end if (pTimer->ulNextFire < ulTimeNow)
273
274 // adjust DosSleep interval
275 if (pTimer->ulTimeout < ulInterval)
276 ulInterval = pTimer->ulTimeout;
277
278 // next timer
279 pTimerNode = pTimerNode->pNext;
280 } // end while (pTimerNode)
281
282 // destroy invalid timers, if any
283 if (fFoundInvalid)
284 {
285 PLISTNODE pNodeNode = lstQueryFirstNode(&llInvalidTimers);
286 while (pNodeNode)
287 {
288 PLISTNODE pNode2Remove = (PLISTNODE)pNodeNode->pItemData;
289 lstRemoveNode(&G_llTimers,
290 pNode2Remove);
291 pNodeNode = pNodeNode->pNext;
292 }
293 lstClear(&llInvalidTimers);
294 }
295 } // end else if (!pTimerNode)
296 } // end if (fLocked)
297 }
298 CATCH(excpt1) { } END_CATCH();
299
300 if (fLocked)
301 {
302 UnlockTimers();
303 fLocked = FALSE;
304 }
305
306 DosExitMustComplete(&ulNesting);
307
308 } // end while (!ptiMyself->fExit)
309
310 WinTerminate(hab);
311
312 #ifdef __DEBUG__
313 DosBeep(1500, 30);
314 #endif
315}
316
317/*
318 *@@ FindTimer:
319 * returns the XTIMER structure from the global
320 * linked list which matches the given window
321 * _and_ timer ID.
322 *
323 * Internal function.
324 *
325 * Preconditions:
326 *
327 * -- The caller must call LockTimers() first.
328 */
329
330PXTIMER FindTimer(HWND hwnd,
331 USHORT usTimerID)
332{
333 PLISTNODE pNode = lstQueryFirstNode(&G_llTimers);
334 while (pNode)
335 {
336 PXTIMER pTimer = (PXTIMER)pNode->pItemData;
337 if ( (pTimer->usTimerID == usTimerID)
338 && (pTimer->hwndTarget == hwnd)
339 )
340 {
341 return (pTimer);
342 }
343
344 pNode = pNode->pNext;
345 }
346 return (NULL);
347}
348
349/*
350 *@@ RemoveTimer:
351 * removes the specified XTIMER structure from
352 * the global linked list of running timers.
353 *
354 * Internal function.
355 *
356 * Preconditions:
357 *
358 * -- The caller must call LockTimers() first.
359 *
360 *@@added V0.9.7 (2000-12-04) [umoeller]
361 */
362
363VOID RemoveTimer(PXTIMER pTimer)
364{
365 lstRemoveItem(&G_llTimers,
366 pTimer); // auto-free!
367 /* if (lstCountItems(&G_llTimers) == 0)
368 // no more timers left:
369 // stop the main timer
370 thrClose(&G_tiTimers); */
371}
372
373/*
374 *@@ tmrStartTimer:
375 * starts an XTimer.
376 *
377 * Any window can request an XTimer using
378 * this function. This operates similar to
379 * WinStartTimer, except that the number of
380 * XTimers is not limited.
381 *
382 * Returns a new timer or resets an existing
383 * timer (if usTimerID is already used with
384 * hwnd). Use tmrStopTimer to stop the timer.
385 *
386 * The timer is _not_ stopped automatically
387 * when the widget is destroyed.
388 *
389 *@@added V0.9.7 (2000-12-04) [umoeller]
390 */
391
392USHORT APIENTRY tmrStartTimer(HWND hwnd,
393 USHORT usTimerID,
394 ULONG ulTimeout)
395{
396 USHORT usrc = 0;
397 BOOL fLocked = FALSE;
398
399 if ((hwnd) && (ulTimeout))
400 {
401 ULONG ulNesting = 0;
402 DosEnterMustComplete(&ulNesting);
403
404 TRY_LOUD(excpt1)
405 {
406 fLocked = LockTimers();
407 if (fLocked)
408 {
409 PXTIMER pTimer;
410
411 // if the timers thread is not yet running,
412 // start it now (i.e. this is the first timer)
413 if (!G_fTimersThreadRunning)
414 {
415 // main timer not yet running:
416 thrCreate(&G_tiTimers,
417 fntTimersThread,
418 &G_fTimersThreadRunning,
419 THRF_WAIT, // no msgq, but wait
420 0);
421 // raise priority
422 DosSetPriority(PRTYS_THREAD,
423 PRTYC_REGULAR, // 3
424 PRTYD_MAXIMUM,
425 G_tiTimers.tid);
426 }
427
428 // check if this timer exists already
429 pTimer = FindTimer(hwnd,
430 usTimerID);
431 if (pTimer)
432 {
433 // exists already: reset only
434 pTimer->ulNextFire = dtGetULongTime() + ulTimeout;
435 usrc = pTimer->usTimerID;
436 }
437 else
438 {
439 // new timer needed:
440 pTimer = (PXTIMER)malloc(sizeof(XTIMER));
441 if (pTimer)
442 {
443 pTimer->usTimerID = usTimerID;
444 pTimer->hwndTarget = hwnd;
445 pTimer->ulTimeout = ulTimeout;
446 pTimer->ulNextFire = dtGetULongTime() + ulTimeout;
447
448 lstAppendItem(&G_llTimers,
449 pTimer);
450 usrc = pTimer->usTimerID;
451 }
452 }
453 }
454 }
455 CATCH(excpt1) { } END_CATCH();
456
457 // unlock the sems outside the exception handler
458 if (fLocked)
459 {
460 UnlockTimers();
461 fLocked = FALSE;
462 }
463
464 DosExitMustComplete(&ulNesting);
465 } // if ((hwnd) && (ulTimeout))
466
467 return (usrc);
468}
469
470/*
471 *@@ tmrStopTimer:
472 * similar to WinStopTimer, this stops the
473 * specified timer (which must have been
474 * started with the same hwnd and usTimerID
475 * using tmrStartTimer).
476 *
477 * Returns TRUE if the timer was stopped.
478 *
479 *@@added V0.9.7 (2000-12-04) [umoeller]
480 */
481
482BOOL APIENTRY tmrStopTimer(HWND hwnd,
483 USHORT usTimerID)
484{
485 BOOL brc = FALSE;
486 BOOL fLocked = FALSE;
487
488 ULONG ulNesting = 0;
489 DosEnterMustComplete(&ulNesting);
490
491 TRY_LOUD(excpt1)
492 {
493 fLocked = LockTimers();
494 if (fLocked)
495 {
496 PXTIMER pTimer = FindTimer(hwnd,
497 usTimerID);
498 if (pTimer)
499 {
500 RemoveTimer(pTimer);
501 brc = TRUE;
502 }
503 }
504 }
505 CATCH(excpt1) { } END_CATCH();
506
507 // unlock the sems outside the exception handler
508 if (fLocked)
509 {
510 UnlockTimers();
511 fLocked = FALSE;
512 }
513
514 DosExitMustComplete(&ulNesting);
515
516 return (brc);
517}
518
519/*
520 *@@ tmrStopAllTimers:
521 * stops all timers which are running for the
522 * specified window. This is a useful helper
523 * that you should call during WM_DESTROY of
524 * a window that has started timers.
525 *
526 *@@added V0.9.7 (2000-12-04) [umoeller]
527 */
528
529VOID tmrStopAllTimers(HWND hwnd)
530{
531 BOOL fLocked = FALSE;
532
533 ULONG ulNesting = 0;
534 DosEnterMustComplete(&ulNesting);
535
536 TRY_LOUD(excpt1)
537 {
538 fLocked = LockTimers();
539 if (fLocked)
540 {
541 PLISTNODE pTimerNode = lstQueryFirstNode(&G_llTimers);
542 while (pTimerNode)
543 {
544 PXTIMER pTimer = (PXTIMER)pTimerNode->pItemData;
545 if (pTimer->hwndTarget == hwnd)
546 {
547 RemoveTimer(pTimer);
548 // start over
549 pTimerNode = lstQueryFirstNode(&G_llTimers);
550 }
551 else
552 pTimerNode = pTimerNode->pNext;
553 }
554 }
555 }
556 CATCH(excpt1) { } END_CATCH();
557
558 // unlock the sems outside the exception handler
559 if (fLocked)
560 {
561 UnlockTimers();
562 fLocked = FALSE;
563 }
564
565 DosExitMustComplete(&ulNesting);
566}
567
568
Note: See TracBrowser for help on using the repository browser.