source: BAORadio/libindi/v1/eventloop.c @ 597

Last change on this file since 597 was 490, checked in by campagne, 14 years ago

import libindi (JEC)

File size: 12.2 KB
Line 
1#if 0
2    INDI
3    Copyright (C) 2003 Elwood C. Downey
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18
19#endif
20
21/* suite of functions to implement an event driven program.
22 *
23 * callbacks may be registered that are triggered when a file descriptor
24 *   will not block when read;
25 *
26 * timers may be registered that will run no sooner than a specified delay from
27 *   the moment they were registered;
28 *
29 * work procedures may be registered that are called when there is nothing
30 *   else to do;
31 *
32 #define MAIN_TEST for a stand-alone test program.
33 */
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <math.h>
38#include <string.h>
39#include <time.h>
40#include <sys/types.h>
41#include <sys/time.h>
42#include <unistd.h>
43
44#include "eventloop.h"
45
46/* info about one registered callback.
47 * the malloced array cback is never shrunk, entries are reused. new id's are
48 * the index of first unused slot in array (and thus reused like unix' open(2)).
49 */
50typedef struct {
51    int in_use;                         /* flag to mark this record is active */
52    int fd;                             /* fd descriptor to watch for read */
53    void *ud;                           /* user's data handle */
54    CBF *fp;                            /* callback function */
55} CB;
56static CB *cback;                       /* malloced list of callbacks */
57static int ncback;                      /* n entries in cback[] */
58static int ncbinuse;                    /* n entries in cback[] marked in_use */
59static int lastcb;                      /* cback index of last cb called */
60
61/* info about one registered timer function.
62 * the entries are kept sorted by decreasing time from epoch, ie,
63 *   the next entry to fire is at the end of the array.
64 */
65typedef struct {
66    double tgo;                         /* trigger time, ms from epoch */
67    void *ud;                           /* user's data handle */
68    TCF *fp;                            /* timer function */
69    int tid;                            /* unique id for this timer */
70} TF;
71static TF *timef;                       /* malloced list of timer functions */
72static int ntimef;                      /* n entries in ntimef[] */
73static int tid;                         /* source of unique timer ids */
74#define EPOCHDT(tp)                     /* ms from epoch to timeval *tp */  \
75        (((tp)->tv_usec)/1000.0 + ((tp)->tv_sec)*1000.0)
76
77/* info about one registered work procedure.
78 * the malloced array wproc is never shrunk, entries are reused. new id's are
79 * the index of first unused slot in array (and thus reused like unix' open(2)).
80 */
81typedef struct {
82    int in_use;                         /* flag to mark this record is active */
83    void *ud;                           /* user's data handle */
84    WPF *fp;                            /* work proc function function */
85} WP;
86static WP *wproc;                       /* malloced list of work procedures */
87static int nwproc;                      /* n entries in wproc[] */
88static int nwpinuse;                    /* n entries in wproc[] marked in-use */
89static int lastwp;                      /* wproc index of last workproc called*/
90
91static void runWorkProc (void);
92static void callCallback(fd_set *rfdp);
93static void checkTimer();
94static void oneLoop(void);
95static void deferTO (void *p);
96
97/* inf loop to dispatch callbacks, work procs and timers as necessary.
98 * never returns.
99 */
100void
101eventLoop()
102{
103        /* run loop forever */
104        while (1)
105            oneLoop();
106}
107
108/* allow other timers/callbacks/workprocs to run until time out in maxms
109 * or *flagp becomes non-0. wait forever if maxms is 0.
110 * return 0 if flag did flip, else -1 if never changed and we timed out.
111 * the expected usage for this is for the caller to arrange for a T/C/W to set
112 *   a flag, then give caller an in-line way to wait for the flag to change.
113 */
114int
115deferLoop (int maxms, int *flagp)
116{
117        int toflag = 0;
118        int totid = maxms ? addTimer (maxms, deferTO, &toflag) : 0;
119
120        while (!*flagp) {
121            oneLoop();
122            if (toflag)
123                return (-1);    /* totid already dead */
124        }
125
126        if (totid)
127            rmTimer (totid);
128        return (0);
129}
130
131/* allow other timers/callbacks/workprocs to run until time out in maxms
132 * or *flagp becomes 0. wait forever if maxms is 0.
133 * return 0 if flag did flip, else -1 if never changed and we timed out.
134 * the expected usage for this is for the caller to arrange for a T/C/W to set
135 *   a flag, then give caller an in-line way to wait for the flag to change.
136 */
137int
138deferLoop0 (int maxms, int *flagp)
139{
140        int toflag = 0;
141        int totid = maxms ? addTimer (maxms, deferTO, &toflag) : 0;
142
143        while (*flagp) {
144            oneLoop();
145            if (toflag)
146                return (-1);    /* totid already dead */
147        }
148
149        if (totid)
150            rmTimer (totid);
151        return (0);
152}
153
154/* register a new callback, fp, to be called with ud as arg when fd is ready.
155 * return a unique callback id for use with rmCallback().
156 */
157int
158addCallback (int fd, CBF *fp, void *ud)
159{
160        CB *cp;
161
162        /* reuse first unused slot or grow */
163        for (cp = cback; cp < &cback[ncback]; cp++)
164            if (!cp->in_use)
165                break;
166        if (cp == &cback[ncback]) {
167            cback = cback ? (CB *) realloc (cback, (ncback+1)*sizeof(CB))
168                          : (CB *) malloc (sizeof(CB));
169            cp = &cback[ncback++];
170        }
171
172        /* init new entry */
173        cp->in_use = 1;
174        cp->fp = fp;
175        cp->ud = ud;
176        cp->fd = fd;
177        ncbinuse++;
178
179        /* id is index into array */
180        return (cp - cback);
181}
182
183/* remove the callback with the given id, as returned from addCallback().
184 * silently ignore if id not valid.
185 */
186void
187rmCallback (int cid)
188{
189        CB *cp;
190
191        /* validate id */
192        if (cid < 0 || cid >= ncback)
193            return;
194        cp = &cback[cid];
195        if (!cp->in_use)
196            return;
197
198        /* mark for reuse */
199        cp->in_use = 0;
200        ncbinuse--;
201}
202
203/* register a new timer function, fp, to be called with ud as arg after ms
204 * milliseconds. add to list in order of decreasing time from epoch, ie,
205 * last entry runs soonest. return id for use with rmTimer().
206 */
207int
208addTimer (int ms, TCF *fp, void *ud)
209{
210        struct timeval t;
211        TF *tp;
212
213        /* get time now */
214        gettimeofday (&t, NULL);
215
216        /* add one entry */
217        timef = timef ? (TF *) realloc (timef, (ntimef+1)*sizeof(TF))
218                      : (TF *) malloc (sizeof(TF));
219        tp = &timef[ntimef++];
220
221        /* init new entry */
222        tp->ud = ud;
223        tp->fp = fp;
224        tp->tgo = EPOCHDT(&t) + ms;
225
226        /* insert maintaining sort */
227        for ( ; tp > timef && tp[0].tgo > tp[-1].tgo; tp--) {
228            TF tmptf = tp[-1];
229            tp[-1] = tp[0];
230            tp[0] = tmptf;
231        }
232
233        /* store and return new unique id */
234        return (tp->tid = ++tid);
235}
236
237/* remove the timer with the given id, as returned from addTimer().
238 * silently ignore if id not found.
239 */
240void
241rmTimer (int timer_id)
242{
243        TF *tp;
244
245        /* find it */
246        for (tp = timef; tp < &timef[ntimef]; tp++)
247            if (tp->tid == timer_id)
248                break;
249        if (tp == &timef[ntimef])
250            return;
251
252        /* bubble it out */
253        for (++tp; tp < &timef[ntimef]; tp++)
254            tp[-1] = tp[0];
255
256        /* shrink list */
257        timef = (TF *) realloc (timef, (--ntimef)*sizeof(TF));
258}
259
260/* add a new work procedure, fp, to be called with ud when nothing else to do.
261 * return unique id for use with rmWorkProc().
262 */
263int
264addWorkProc (WPF *fp, void *ud)
265{
266        WP *wp;
267
268        /* reuse first unused slot or grow */
269        for (wp = wproc; wp < &wproc[nwproc]; wp++)
270            if (!wp->in_use)
271                break;
272        if (wp == &wproc[nwproc]) {
273            wproc = wproc ? (WP *) realloc (wproc, (nwproc+1)*sizeof(WP))
274                          : (WP *) malloc (sizeof(WP));
275            wp = &wproc[nwproc++];
276        }
277
278        /* init new entry */
279        wp->in_use = 1;
280        wp->fp = fp;
281        wp->ud = ud;
282        nwpinuse++;
283
284        /* id is index into array */
285        return (wp - wproc);
286}
287
288
289/* remove the work proc with the given id, as returned from addWorkProc().
290 * silently ignore if id not found.
291 */
292void
293rmWorkProc (int wid)
294{
295        WP *wp;
296
297        /* validate id */
298        if (wid < 0 || wid >= nwproc)
299            return;
300        wp = &wproc[wid];
301        if (!wp->in_use)
302            return;
303
304        /* mark for reuse */
305        wp->in_use = 0;
306        nwpinuse--;
307}
308
309/* run next work procedure */
310static void
311runWorkProc ()
312{
313        WP *wp;
314
315        /* skip if list is empty */
316        if (!nwpinuse)
317            return;
318
319        /* find next */
320        do {
321            lastwp = (lastwp+1) % nwproc;
322            wp = &wproc[lastwp];
323        } while (!wp->in_use);
324
325        /* run */
326        (*wp->fp) (wp->ud);
327}
328
329/* run next callback whose fd is listed as ready to go in rfdp */
330static void
331callCallback(fd_set *rfdp)
332{
333        CB *cp;
334
335        /* skip if list is empty */
336        if (!ncbinuse)
337            return;
338
339        /* find next */
340        do {
341            lastcb = (lastcb+1) % ncback;
342            cp = &cback[lastcb];
343        } while (!cp->in_use || !FD_ISSET (cp->fd, rfdp));
344
345        /* run */
346        (*cp->fp) (cp->fd, cp->ud);
347}
348
349/* run the next timer callback whose time has come, if any. all we have to do
350 * is is check the last entry in timef[] because it is sorted in decreasing
351 * order of time from epoch to run, ie, last entry runs soonest.
352 */
353static void
354checkTimer()
355{
356        struct timeval now;
357        double tgonow;
358        TF *tp;
359
360        /* skip if list is empty */
361        if (!ntimef)
362            return;
363
364        gettimeofday (&now, NULL);
365        tgonow = EPOCHDT (&now);
366        tp = &timef[ntimef-1];
367        if (tp->tgo <= tgonow) {
368            ntimef--;                   /* pop then call */
369            (*tp->fp) (tp->ud);
370        }
371}
372
373/* check fd's from each active callback.
374 * if any ready, call their callbacks else call each registered work procedure.
375 */
376static void
377oneLoop()
378{
379        struct timeval tv, *tvp;
380        fd_set rfd;
381        CB *cp;
382        int maxfd, ns;
383
384        /* build list of callback file descriptors to check */
385        FD_ZERO (&rfd);
386        maxfd = -1;
387        for (cp = cback; cp < &cback[ncback]; cp++) {
388            if (cp->in_use) {
389                FD_SET (cp->fd, &rfd);
390                if (cp->fd > maxfd)
391                    maxfd = cp->fd;
392            }
393        }
394
395        /* determine timeout:
396         * if there are work procs
397         *   set delay = 0
398         * else if there is at least one timer func
399         *   set delay = time until soonest timer func expires
400         * else
401         *   set delay = forever
402         */
403        if (nwpinuse > 0) {
404            tvp = &tv;
405            tvp->tv_sec = tvp->tv_usec = 0;
406        } else if (ntimef > 0) {
407            struct timeval now;
408            double late;
409            gettimeofday (&now, NULL);
410            late = timef[ntimef-1].tgo - EPOCHDT (&now);        /* ms late */
411            if (late < 0)
412                late = 0;
413            late /= 1000.0;                                     /* secs late */
414            tvp = &tv;
415            tvp->tv_sec = (long)floor(late);
416            tvp->tv_usec = (long)floor((late - tvp->tv_sec)*1000000.0);
417        } else
418            tvp = NULL;
419
420        /* check file descriptors, timeout depending on pending work */
421        ns = select (maxfd+1, &rfd, NULL, NULL, tvp);
422        if (ns < 0) {
423            perror ("select");
424            exit(1);
425        }
426       
427        /* dispatch */
428        checkTimer();
429        if (ns == 0)
430            runWorkProc();
431        else
432            callCallback(&rfd);
433}
434
435/* timer callback used to implement deferLoop().
436 * arg is pointer to int which we set to 1
437 */
438static void
439deferTO (void *p)
440{
441        *(int*)p = 1;
442}
443
444#if defined(MAIN_TEST)
445/* make a small stand-alone test program.
446 */
447
448#include <unistd.h>
449#include <sys/time.h>
450
451int mycid;
452int mywid;
453int mytid;
454
455int user_a;
456int user_b;
457int counter;
458
459void
460wp (void *ud)
461{
462        struct timeval tv;
463
464        gettimeofday (&tv, NULL);
465        printf ("workproc @ %ld.%03ld %d %d\n", 
466                (long)tv.tv_sec, (long)tv.tv_usec/1000, counter, ++(*(int*)ud));
467}
468
469void
470to (void *ud)
471{
472        printf ("timeout %d\n", (int)ud);
473}
474
475void
476stdinCB (int fd, void *ud)
477{
478        char c;
479
480        if (read (fd, &c, 1) != 1) {
481            perror ("read");
482            exit(1);
483        }
484
485        switch (c) {
486        case '+': counter++; break;
487        case '-': counter--; break;
488
489        case 'W': mywid = addWorkProc (wp, &user_b); break;
490        case 'w': rmWorkProc (mywid); break;
491
492        case 'c': rmCallback (mycid); break;
493
494        case 't': rmTimer (mytid); break;
495        case '1': mytid = addTimer (1000, to, (void *)1); break;
496        case '2': mytid = addTimer (2000, to, (void *)2); break;
497        case '3': mytid = addTimer (3000, to, (void *)3); break;
498        case '4': mytid = addTimer (4000, to, (void *)4); break;
499        case '5': mytid = addTimer (5000, to, (void *)5); break;
500        default: return;        /* silently absorb other chars like \n */
501        }
502
503        printf ("callback: %d\n", ++(*(int*)ud));
504}
505
506int
507main (int ac, char *av[])
508{
509        (void) addCallback (0, stdinCB, &user_a);
510        eventLoop();
511        exit(0);
512}
513
514#endif
515
Note: See TracBrowser for help on using the repository browser.