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