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 <math.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <time.h>
40 #include <unistd.h>
41 #include <sys/types.h>
42 #include <sys/time.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  */
50 typedef struct
51 {
52     int in_use; /* flag to mark this record is active */
53     int fd;     /* fd descriptor to watch for read */
54     void *ud;   /* user's data handle */
55     CBF *fp;    /* callback function */
56 } CB;
57 static CB *cback;    /* malloced list of callbacks */
58 static int ncback;   /* n entries in cback[] */
59 static int ncbinuse; /* n entries in cback[] marked in_use */
60 static int lastcb;   /* cback index of last cb called */
61 
62 /* info about one registered timer function.
63  * the entries are kept sorted by decreasing time from epoch, ie,
64  *   the next entry to fire is at the end of the array.
65  */
66 typedef struct
67 {
68     double tgo; /* trigger time, ms from epoch */
69     void *ud;   /* user's data handle */
70     TCF *fp;    /* timer function */
71     int tid;    /* unique id for this timer */
72 } TF;
73 static TF *timef;  /* malloced list of timer functions */
74 static int ntimef; /* n entries in ntimef[] */
75 static int tid;    /* source of unique timer ids */
76 #define EPOCHDT(tp) /* ms from epoch to timeval *tp */ (((tp)->tv_usec) / 1000.0 + ((tp)->tv_sec) * 1000.0)
77 
78 /* info about one registered work procedure.
79  * the malloced array wproc is never shrunk, entries are reused. new id's are
80  * the index of first unused slot in array (and thus reused like unix' open(2)).
81  */
82 typedef struct
83 {
84     int in_use; /* flag to mark this record is active */
85     void *ud;   /* user's data handle */
86     WPF *fp;    /* work proc function function */
87 } WP;
88 static WP *wproc;    /* malloced list of work procedures */
89 static int nwproc;   /* n entries in wproc[] */
90 static int nwpinuse; /* n entries in wproc[] marked in-use */
91 static int lastwp;   /* wproc index of last workproc called*/
92 
93 static void runWorkProc(void);
94 static void callCallback(fd_set *rfdp);
95 static void checkTimer();
96 static void oneLoop(void);
97 static void deferTO(void *p);
98 
99 /* inf loop to dispatch callbacks, work procs and timers as necessary.
100  * never returns.
101  */
eventLoop()102 void eventLoop()
103 {
104     /* run loop forever */
105     while (1)
106         oneLoop();
107 }
108 
109 /* allow other timers/callbacks/workprocs to run until time out in maxms
110  * or *flagp becomes non-0. wait forever if maxms is 0.
111  * return 0 if flag did flip, else -1 if never changed and we timed out.
112  * the expected usage for this is for the caller to arrange for a T/C/W to set
113  *   a flag, then give caller an in-line way to wait for the flag to change.
114  */
deferLoop(int maxms,int * flagp)115 int deferLoop(int maxms, int *flagp)
116 {
117     int toflag = 0;
118     int totid  = maxms ? addTimer(maxms, deferTO, &toflag) : 0;
119 
120     while (!*flagp)
121     {
122         oneLoop();
123         if (toflag)
124             return (-1); /* totid already dead */
125     }
126 
127     if (totid)
128         rmTimer(totid);
129     return (0);
130 }
131 
132 /* allow other timers/callbacks/workprocs to run until time out in maxms
133  * or *flagp becomes 0. wait forever if maxms is 0.
134  * return 0 if flag did flip, else -1 if never changed and we timed out.
135  * the expected usage for this is for the caller to arrange for a T/C/W to set
136  *   a flag, then give caller an in-line way to wait for the flag to change.
137  */
deferLoop0(int maxms,int * flagp)138 int deferLoop0(int maxms, int *flagp)
139 {
140     int toflag = 0;
141     int totid  = maxms ? addTimer(maxms, deferTO, &toflag) : 0;
142 
143     while (*flagp)
144     {
145         oneLoop();
146         if (toflag)
147             return (-1); /* totid already dead */
148     }
149 
150     if (totid)
151         rmTimer(totid);
152     return (0);
153 }
154 
155 /* register a new callback, fp, to be called with ud as arg when fd is ready.
156  * return a unique callback id for use with rmCallback().
157  */
addCallback(int fd,CBF * fp,void * ud)158 int addCallback(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     {
168         cback = cback ? (CB *)realloc(cback, (ncback + 1) * sizeof(CB)) : (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  */
rmCallback(int cid)186 void rmCallback(int cid)
187 {
188     CB *cp;
189 
190     /* validate id */
191     if (cid < 0 || cid >= ncback)
192         return;
193     cp = &cback[cid];
194     if (!cp->in_use)
195         return;
196 
197     /* mark for reuse */
198     cp->in_use = 0;
199     ncbinuse--;
200 }
201 
202 /* register a new timer function, fp, to be called with ud as arg after ms
203  * milliseconds. add to list in order of decreasing time from epoch, ie,
204  * last entry runs soonest. return id for use with rmTimer().
205  */
addTimer(int ms,TCF * fp,void * ud)206 int addTimer(int ms, TCF *fp, void *ud)
207 {
208     struct timeval t;
209     TF *tp;
210 
211     /* get time now */
212     gettimeofday(&t, NULL);
213 
214     /* add one entry */
215     timef = timef ? (TF *)realloc(timef, (ntimef + 1) * sizeof(TF)) : (TF *)malloc(sizeof(TF));
216     tp    = &timef[ntimef++];
217 
218     /* init new entry */
219     tp->ud  = ud;
220     tp->fp  = fp;
221     tp->tgo = EPOCHDT(&t) + ms;
222 
223     /* insert maintaining sort */
224     for (; tp > timef && tp[0].tgo > tp[-1].tgo; tp--)
225     {
226         TF tmptf = tp[-1];
227         tp[-1]   = tp[0];
228         tp[0]    = tmptf;
229     }
230 
231     /* store and return new unique id */
232     return (tp->tid = ++tid);
233 }
234 
235 /* remove the timer with the given id, as returned from addTimer().
236  * silently ignore if id not found.
237  */
rmTimer(int timer_id)238 void rmTimer(int timer_id)
239 {
240     TF *tp;
241 
242     /* find it */
243     for (tp = timef; tp < &timef[ntimef]; tp++)
244         if (tp->tid == timer_id)
245             break;
246     if (tp == &timef[ntimef])
247         return;
248 
249     /* bubble it out */
250     for (++tp; tp < &timef[ntimef]; tp++)
251         tp[-1] = tp[0];
252 
253     /* shrink list */
254     ntimef--;
255     if (ntimef == 0)
256     {
257         free(timef);
258         timef = NULL;
259         return;
260     }
261     timef = (TF *)realloc(timef, ntimef * sizeof(TF));
262 }
263 
264 /* add a new work procedure, fp, to be called with ud when nothing else to do.
265  * return unique id for use with rmWorkProc().
266  */
addWorkProc(WPF * fp,void * ud)267 int addWorkProc(WPF *fp, void *ud)
268 {
269     WP *wp;
270 
271     /* reuse first unused slot or grow */
272     for (wp = wproc; wp < &wproc[nwproc]; wp++)
273         if (!wp->in_use)
274             break;
275     if (wp == &wproc[nwproc])
276     {
277         wproc = wproc ? (WP *)realloc(wproc, (nwproc + 1) * sizeof(WP)) : (WP *)malloc(sizeof(WP));
278         wp    = &wproc[nwproc++];
279     }
280 
281     /* init new entry */
282     wp->in_use = 1;
283     wp->fp     = fp;
284     wp->ud     = ud;
285     nwpinuse++;
286 
287     /* id is index into array */
288     return (wp - wproc);
289 }
290 
291 /* remove the work proc with the given id, as returned from addWorkProc().
292  * silently ignore if id not found.
293  */
rmWorkProc(int wid)294 void rmWorkProc(int wid)
295 {
296     WP *wp;
297 
298     /* validate id */
299     if (wid < 0 || wid >= nwproc)
300         return;
301     wp = &wproc[wid];
302     if (!wp->in_use)
303         return;
304 
305     /* mark for reuse */
306     wp->in_use = 0;
307     nwpinuse--;
308 }
309 
310 /* run next work procedure */
runWorkProc()311 static void runWorkProc()
312 {
313     WP *wp;
314 
315     /* skip if list is empty */
316     if (!nwpinuse)
317         return;
318 
319     /* find next */
320     do
321     {
322         lastwp = (lastwp + 1) % nwproc;
323         wp     = &wproc[lastwp];
324     } while (!wp->in_use);
325 
326     /* run */
327     (*wp->fp)(wp->ud);
328 }
329 
330 /* run next callback whose fd is listed as ready to go in rfdp */
callCallback(fd_set * rfdp)331 static void callCallback(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     {
342         lastcb = (lastcb + 1) % ncback;
343         cp     = &cback[lastcb];
344     } while (!cp->in_use || !FD_ISSET(cp->fd, rfdp));
345 
346     /* run */
347     (*cp->fp)(cp->fd, cp->ud);
348 }
349 
350 /* run the next timer callback whose time has come, if any. all we have to do
351  * is is check the last entry in timef[] because it is sorted in decreasing
352  * order of time from epoch to run, ie, last entry runs soonest.
353  */
checkTimer()354 static void checkTimer()
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     {
369         ntimef--; /* pop then call */
370         (*tp->fp)(tp->ud);
371     }
372 }
373 
374 /* check fd's from each active callback.
375  * if any ready, call their callbacks else call each registered work procedure.
376  */
oneLoop()377 static void oneLoop()
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     {
389         if (cp->in_use)
390         {
391             FD_SET(cp->fd, &rfd);
392             if (cp->fd > maxfd)
393                 maxfd = cp->fd;
394         }
395     }
396 
397     /* determine timeout:
398 	 * if there are work procs
399 	 *   set delay = 0
400 	 * else if there is at least one timer func
401 	 *   set delay = time until soonest timer func expires
402 	 * else
403 	 *   set delay = forever
404 	 */
405     if (nwpinuse > 0)
406     {
407         tvp         = &tv;
408         tvp->tv_sec = tvp->tv_usec = 0;
409     }
410     else if (ntimef > 0)
411     {
412         struct timeval now;
413         double late;
414         gettimeofday(&now, NULL);
415         late = timef[ntimef - 1].tgo - EPOCHDT(&now); /* ms late */
416         if (late < 0)
417             late = 0;
418         late /= 1000.0; /* secs late */
419         tvp          = &tv;
420         tvp->tv_sec  = (long)floor(late);
421         tvp->tv_usec = (long)floor((late - tvp->tv_sec) * 1000000.0);
422     }
423     else
424         tvp = NULL;
425 
426     /* check file descriptors, timeout depending on pending work */
427     ns = select(maxfd + 1, &rfd, NULL, NULL, tvp);
428     if (ns < 0)
429     {
430         perror("select");
431         return;
432     }
433 
434     /* dispatch */
435     checkTimer();
436     if (ns == 0)
437         runWorkProc();
438     else
439         callCallback(&rfd);
440 }
441 
442 /* timer callback used to implement deferLoop().
443  * arg is pointer to int which we set to 1
444  */
deferTO(void * p)445 static void deferTO(void *p)
446 {
447     *(int *)p = 1;
448 }
449 
450 #if defined(MAIN_TEST)
451 /* make a small stand-alone test program.
452  */
453 
454 #include <unistd.h>
455 #include <sys/time.h>
456 
457 int mycid;
458 int mywid;
459 int mytid;
460 
461 int user_a;
462 int user_b;
463 int counter;
464 
wp(void * ud)465 void wp(void *ud)
466 {
467     struct timeval tv;
468 
469     gettimeofday(&tv, NULL);
470     printf("workproc @ %ld.%03ld %d %d\n", (long)tv.tv_sec, (long)tv.tv_usec / 1000, counter, ++(*(int *)ud));
471 }
472 
to(void * ud)473 void to(void *ud)
474 {
475     printf("timeout %d\n", (int)ud);
476 }
477 
stdinCB(int fd,void * ud)478 void stdinCB(int fd, void *ud)
479 {
480     char c;
481 
482     if (read(fd, &c, 1) != 1)
483     {
484         perror("read");
485         return;
486     }
487 
488     switch (c)
489     {
490         case '+':
491             counter++;
492             break;
493         case '-':
494             counter--;
495             break;
496 
497         case 'W':
498             mywid = addWorkProc(wp, &user_b);
499             break;
500         case 'w':
501             rmWorkProc(mywid);
502             break;
503 
504         case 'c':
505             rmCallback(mycid);
506             break;
507 
508         case 't':
509             rmTimer(mytid);
510             break;
511         case '1':
512             mytid = addTimer(1000, to, (void *)1);
513             break;
514         case '2':
515             mytid = addTimer(2000, to, (void *)2);
516             break;
517         case '3':
518             mytid = addTimer(3000, to, (void *)3);
519             break;
520         case '4':
521             mytid = addTimer(4000, to, (void *)4);
522             break;
523         case '5':
524             mytid = addTimer(5000, to, (void *)5);
525             break;
526         default:
527             return; /* silently absorb other chars like \n */
528     }
529 
530     printf("callback: %d\n", ++(*(int *)ud));
531 }
532 
main(int ac,char * av[])533 int main(int ac, char *av[])
534 {
535     (void)addCallback(0, stdinCB, &user_a);
536     eventLoop();
537     exit(0);
538 }
539 
540 #endif
541