Instrument Neutral Distributed Interface INDI 2.2.5
eventloop.c
Go to the documentation of this file.
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#if defined(_WIN32) || defined(__CYGWIN__)
45#include <sys/select.h>
46#endif
47
48#include "eventloop.h"
49
50/* info about one registered callback.
51 * the malloced array cback is never shrunk, entries are reused. new id's are
52 * the index of first unused slot in array (and thus reused like unix' open(2)).
53 */
54typedef struct
55{
56 int in_use; /* flag to mark this record is active */
57 int fd; /* fd descriptor to watch for read */
58 void *ud; /* user's data handle */
59 CBF *fp; /* callback function */
60} CB;
61static CB *cback; /* malloced list of callbacks */
62static int ncback; /* n entries in cback[] */
63static int ncbinuse; /* n entries in cback[] marked in_use */
64static int lastcb; /* cback index of last cb called */
65
66/* info about one registered timer function.
67 * the entries are kept sorted by increasing time from epoch, ie,
68 * the next entry to fire is at the begin of the list.
69 */
70typedef struct TF
71{
72 double tgo; /* trigger time, ms from epoch */
73 int interval; /* repeat timer if interval > 0, ms */
74 void *ud; /* user's data handle */
75 TCF *fp; /* timer function */
76 int tid; /* unique id for this timer */
77 struct TF *next; /* pointer to next item */
79static TF timefunc_null = {0, 0, NULL, NULL, 0, NULL};
80static TF *timefunc = &timefunc_null; /* list of timer functions */
81static int tid = 0; /* source of unique timer ids */
82#define EPOCHDT(tp) /* ms from epoch to timeval *tp */ (((tp)->tv_usec) / 1000.0 + ((tp)->tv_sec) * 1000.0)
83
84/* info about one registered work procedure.
85 * the malloced array wproc is never shrunk, entries are reused. new id's are
86 * the index of first unused slot in array (and thus reused like unix' open(2)).
87 */
88typedef struct
89{
90 int in_use; /* flag to mark this record is active */
91 void *ud; /* user's data handle */
92 WPF *fp; /* work proc function function */
93} WP;
94static WP *wproc; /* malloced list of work procedures */
95static int nwproc; /* n entries in wproc[] */
96static int nwpinuse; /* n entries in wproc[] marked in-use */
97static int lastwp; /* wproc index of last workproc called*/
98
99static void runWorkProc(void);
100static void callCallback(fd_set *rfdp);
101static void checkTimer();
102static void oneLoop(void);
103static void deferTO(void *p);
104static void runImmediates();
105
106/* inf loop to dispatch callbacks, work procs and timers as necessary.
107 * never returns.
108 */
110{
111 /* run loop forever */
112 while (1)
113 oneLoop();
114}
115
116/* allow other timers/callbacks/workprocs to run until time out in maxms
117 * or *flagp becomes non-0. wait forever if maxms is 0.
118 * return 0 if flag did flip, else -1 if never changed and we timed out.
119 * the expected usage for this is for the caller to arrange for a T/C/W to set
120 * a flag, then give caller an in-line way to wait for the flag to change.
121 */
122int deferLoop(int maxms, int *flagp)
123{
124 int toflag = 0;
125 int totid = maxms ? addTimer(maxms, deferTO, &toflag) : 0;
126
127 while (!*flagp)
128 {
129 oneLoop();
130 if (toflag)
131 return (-1); /* totid already dead */
132 }
133
134 if (totid)
135 rmTimer(totid);
136 return (0);
137}
138
139/* allow other timers/callbacks/workprocs to run until time out in maxms
140 * or *flagp becomes 0. wait forever if maxms is 0.
141 * return 0 if flag did flip, else -1 if never changed and we timed out.
142 * the expected usage for this is for the caller to arrange for a T/C/W to set
143 * a flag, then give caller an in-line way to wait for the flag to change.
144 */
145int deferLoop0(int maxms, int *flagp)
146{
147 int toflag = 0;
148 int totid = maxms ? addTimer(maxms, deferTO, &toflag) : 0;
149
150 while (*flagp)
151 {
152 oneLoop();
153 if (toflag)
154 return (-1); /* totid already dead */
155 }
156
157 if (totid)
158 rmTimer(totid);
159 return (0);
160}
161
162/* register a new callback, fp, to be called with ud as arg when fd is ready.
163 * return a unique callback id for use with rmCallback().
164 */
165int addCallback(int fd, CBF *fp, void *ud)
166{
167 CB *cp;
168
169 /* reuse first unused slot or grow */
170 for (cp = cback; cp < &cback[ncback]; cp++)
171 if (!cp->in_use)
172 break;
173 if (cp == &cback[ncback])
174 {
175 cback = realloc(cback, (ncback + 1) * sizeof(CB));
176 cp = &cback[ncback++];
177 }
178
179 /* init new entry */
180 cp->in_use = 1;
181 cp->fp = fp;
182 cp->ud = ud;
183 cp->fd = fd;
184 ncbinuse++;
185
186 /* id is index into array */
187 return (cp - cback);
188}
189
190/* remove the callback with the given id, as returned from addCallback().
191 * silently ignore if id not valid.
192 */
193void rmCallback(int cid)
194{
195 CB *cp;
196
197 /* validate id */
198 if (cid < 0 || cid >= ncback)
199 return;
200 cp = &cback[cid];
201 if (!cp->in_use)
202 return;
203
204 /* mark for reuse */
205 cp->in_use = 0;
206 ncbinuse--;
207}
208
209/* insert maintaining sort */
210static void insertTimer(TF *node)
211{
212 TF *it = timefunc;
213
214 for(; ; it = it->next)
215 {
216 if (it->next == NULL || node->tgo < it->next->tgo)
217 {
218 node->next = it->next;
219 it->next = node;
220 break;
221 }
222 }
223}
224
225/* register a new timer function, fp, to be called with ud as arg after ms
226 * milliseconds. add to list in order of increasing time from epoch, ie,
227 * first entry runs soonest. return id for use with rmTimer().
228 */
229static int addTimerImpl(int delay, int interval, TCF *fp, void *ud)
230{
231 struct timeval t;
232 TF *node;
233
234 /* get time now */
235 gettimeofday(&t, NULL);
236
237 /* create entry */
238 node = (TF*)malloc(sizeof(TF));
239
240 /* init new entry */
241 node->ud = ud;
242 node->fp = fp;
243 node->tid = ++tid; /* store new unique id */
244 node->tgo = EPOCHDT(&t) + delay;
245 node->interval = interval;
246
247 insertTimer(node);
248
249 return node->tid;
250}
251
252int addTimer(int ms, TCF *fp, void *ud)
253{
254 return addTimerImpl(ms, 0, fp, ud);
255}
256
257int addPeriodicTimer(int ms, TCF *fp, void *ud)
258{
259 return addTimerImpl(ms, ms, fp, ud);
260}
261
262/* find the timer and remove from list */
263static TF *dettachTimer(TF *node)
264{
265 TF *it = timefunc;
266 for(; it->next != NULL; it = it->next)
267 {
268 if (it->next == node)
269 {
270 it->next = node->next;
271 return node;
272 }
273 }
274 return NULL;
275}
276
277/* find the timer by id */
278static TF *findTimer(int timer_id)
279{
280 TF *it = timefunc->next;
281 for(; it != NULL; it = it->next)
282 if (it->tid == timer_id)
283 return it;
284 return NULL;
285}
286
287/* remove the timer with the given id, as returned from addTimer().
288 * silently ignore if id not found.
289 */
290void rmTimer(int timer_id)
291{
292 TF *node, *it = timefunc;
293 for(; (node = it->next) != NULL; it = it->next)
294 {
295 if (node->tid == timer_id)
296 {
297 it->next = node->next;
298 free(node);
299 break;
300 }
301 }
302}
303
304/* Returns the timer's remaining value in milliseconds left until the timeout. */
305static double remainingTimerNode(TF *node)
306{
307 struct timeval now;
308 gettimeofday(&now, NULL);
309 return (node->tgo - EPOCHDT(&now));
310}
311
312/* Returns the timer's remaining value in milliseconds left until the timeout.
313 * If the timer not exists, the returned value will be -1.
314 */
315int remainingTimer(int timer_id)
316{
317 TF *it = findTimer(timer_id);
318 return it == NULL ? -1 : remainingTimerNode(it);
319}
320
321/* Returns the timer's remaining value in nanoseconds left until the timeout.
322 * If the timer not exists, the returned value will be -1.
323 */
324int64_t nsecsRemainingTimer(int timer_id)
325{
326 TF *it = findTimer(timer_id);
327 return it == NULL ? -1 : remainingTimerNode(it) * 1000000;
328}
329
330/* add a new work procedure, fp, to be called with ud when nothing else to do.
331 * return unique id for use with rmWorkProc().
332 */
333int addWorkProc(WPF *fp, void *ud)
334{
335 WP *wp;
336
337 /* reuse first unused slot or grow */
338 for (wp = wproc; wp < &wproc[nwproc]; wp++)
339 if (!wp->in_use)
340 break;
341 if (wp == &wproc[nwproc])
342 {
343 wproc = (WP *)realloc(wproc, (nwproc + 1) * sizeof(WP));
344 wp = &wproc[nwproc++];
345 }
346
347 /* init new entry */
348 wp->in_use = 1;
349 wp->fp = fp;
350 wp->ud = ud;
351 nwpinuse++;
352
353 /* id is index into array */
354 return (wp - wproc);
355}
356
357/* remove the work proc with the given id, as returned from addWorkProc().
358 * silently ignore if id not found.
359 */
360void rmWorkProc(int wid)
361{
362 WP *wp;
363
364 /* validate id */
365 if (wid < 0 || wid >= nwproc)
366 return;
367 wp = &wproc[wid];
368 if (!wp->in_use)
369 return;
370
371 /* mark for reuse */
372 wp->in_use = 0;
373 nwpinuse--;
374}
375
376/* run next work procedure */
377static void runWorkProc()
378{
379 WP *wp;
380
381 /* skip if list is empty */
382 if (!nwpinuse)
383 return;
384
385 /* find next */
386 do
387 {
388 lastwp = (lastwp + 1) % nwproc;
389 wp = &wproc[lastwp];
390 } while (!wp->in_use);
391
392 /* run */
393 (*wp->fp)(wp->ud);
394}
395
396/* run next callback whose fd is listed as ready to go in rfdp */
397static void callCallback(fd_set *rfdp)
398{
399 CB *cp;
400
401 /* skip if list is empty */
402 if (!ncbinuse)
403 return;
404
405 /* find next */
406 do
407 {
408 lastcb = (lastcb + 1) % ncback;
409 cp = &cback[lastcb];
410 } while (!cp->in_use || !FD_ISSET(cp->fd, rfdp));
411
412 /* run */
413 (*cp->fp)(cp->fd, cp->ud);
414}
415
416/* run the next timer callback whose time has come, if any. all we have to do
417 * is is check the first entry in timefunc because it is sorted in increasing
418 * order of time from epoch to run, ie, first entry runs soonest.
419 */
420static void checkTimer()
421{
422 TF *node = timefunc->next;
423
424 if (node == NULL || remainingTimerNode(node) > 0)
425 return;
426
427 (*node->fp)(node->ud);
428
429 node = dettachTimer(node);
430
431 if (node == NULL)
432 return;
433
434 if (node->interval > 0)
435 {
436 node->tgo += node->interval;
437 insertTimer(node);
438 } else {
439 free(node);
440 }
441}
442
443/* check fd's from each active callback.
444 * if any ready, call their callbacks else call each registered work procedure.
445 */
446static void oneLoop()
447{
448 struct timeval tv, *tvp;
449 fd_set rfd;
450 CB *cp;
451 int maxfd, ns;
452
453 /* build list of callback file descriptors to check */
454 FD_ZERO(&rfd);
455 maxfd = -1;
456 for (cp = cback; cp < &cback[ncback]; cp++)
457 {
458 if (cp->in_use)
459 {
460 FD_SET(cp->fd, &rfd);
461 if (cp->fd > maxfd)
462 maxfd = cp->fd;
463 }
464 }
465
466 /* determine timeout:
467 * if there are work procs
468 * set delay = 0
469 * else if there is at least one timer func
470 * set delay = time until soonest timer func expires
471 * else
472 * set delay = forever
473 */
474 if (nwpinuse > 0)
475 {
476 tvp = &tv;
477 tvp->tv_sec = tvp->tv_usec = 0;
478 }
479 else if (timefunc->next != NULL)
480 {
481 double late = remainingTimerNode(timefunc->next); /* ms late */
482 if (late < 0)
483 late = 0;
484 late /= 1000.0; /* secs late */
485 tvp = &tv;
486 tvp->tv_sec = (long)floor(late);
487 tvp->tv_usec = (long)floor((late - tvp->tv_sec) * 1000000.0);
488 }
489 else
490 tvp = NULL;
491
492 /* check file descriptors, timeout depending on pending work */
493 ns = select(maxfd + 1, &rfd, NULL, NULL, tvp);
494 if (ns < 0)
495 {
496 perror("select");
497 return;
498 }
499
500 /* dispatch */
501 checkTimer();
502 if (ns == 0)
503 runWorkProc();
504 else
505 callCallback(&rfd);
506
507 runImmediates();
508}
509
510/* timer callback used to implement deferLoop().
511 * arg is pointer to int which we set to 1
512 */
513static void deferTO(void *p)
514{
515 *(int *)p = 1;
516}
517
518typedef struct Immediate
519{
520 void *ud;
522 struct Immediate * prev, *next;
524
525static Immediate * firstImmediate = NULL;
526static Immediate * lastImmediate = NULL;
527
528void addImmediateWork(TCF * fp, void *ud)
529{
530 Immediate * immediate = (Immediate*)malloc(sizeof(Immediate));
531 immediate->fp = fp;
532 immediate->ud = ud;
533 immediate->prev = lastImmediate;
534 immediate->next = NULL;
535 if (lastImmediate) {
536 lastImmediate->next = immediate;
537 } else {
538 firstImmediate = immediate;
539 }
540 lastImmediate = immediate;
541}
542
543static int peekImmediate(Immediate * into)
544{
545 if (!firstImmediate) {
546 return 0;
547 }
548 Immediate * o = firstImmediate;
549 into->fp = o->fp;
550 into->ud = o->ud;
551
552 firstImmediate = o->next;
553 if (o->next) {
554 o->next->prev = NULL;
555 } else {
556 lastImmediate = NULL;
557 }
558 free(o);
559
560 return 1;
561}
562
563void runImmediates() {
564 Immediate immediate;
565 while((peekImmediate(&immediate))) {
566 (*immediate.fp)(immediate.ud);
567 }
568}
569
570/* "INDI" wrappers to the more generic eventloop facility. */
571
572typedef void(IE_CBF)(int readfiledes, void *userpointer);
573typedef void(IE_TCF)(void *userpointer);
574typedef void(IE_WPF)(void *userpointer);
575
576int IEAddCallback(int readfiledes, IE_CBF *fp, void *p)
577{
578 return (addCallback(readfiledes, (CBF *)fp, p));
579}
580
581void IERmCallback(int callbackid)
582{
583 rmCallback(callbackid);
584}
585
586int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
587{
588 return (addTimer(millisecs, (TCF *)fp, p));
589}
590
591int IEAddPeriodicTimer(int millisecs, IE_TCF *fp, void *p)
592{
593 return (addPeriodicTimer(millisecs, (TCF *)fp, p));
594}
595
596int IERemainingTimer(int timerid)
597{
598 return (remainingTimer(timerid));
599}
600
601int64_t IENSecsRemainingTimer(int timerid)
602{
603 return (nsecsRemainingTimer(timerid));
604}
605
606void IERmTimer(int timerid)
607{
608 rmTimer(timerid);
609}
610
611int IEAddWorkProc(IE_WPF *fp, void *p)
612{
613 return (addWorkProc((WPF *)fp, p));
614}
615
616void IERmWorkProc(int workprocid)
617{
618 rmWorkProc(workprocid);
619}
620
621int IEDeferLoop(int maxms, int *flagp)
622{
623 return (deferLoop(maxms, flagp));
624}
625
626int IEDeferLoop0(int maxms, int *flagp)
627{
628 return (deferLoop0(maxms, flagp));
629}
630
631#if defined(MAIN_TEST)
632/* make a small stand-alone test program.
633 */
634
635#include <unistd.h>
636#include <sys/time.h>
637
638int mycid;
639int mywid;
640int mytid;
641
642int user_a;
643int user_b;
644int counter;
645
646void wp(void *ud)
647{
648 struct timeval tv;
649
650 gettimeofday(&tv, NULL);
651 printf("workproc @ %ld.%03ld %d %d\n", (long)tv.tv_sec, (long)tv.tv_usec / 1000, counter, ++(*(int *)ud));
652}
653
654void to(void *ud)
655{
656 printf("timeout %d\n", (int)ud);
657}
658
659void stdinCB(int fd, void *ud)
660{
661 char c;
662
663 if (read(fd, &c, 1) != 1)
664 {
665 perror("read");
666 return;
667 }
668
669 switch (c)
670 {
671 case '+':
672 counter++;
673 break;
674 case '-':
675 counter--;
676 break;
677
678 case 'W':
679 mywid = addWorkProc(wp, &user_b);
680 break;
681 case 'w':
682 rmWorkProc(mywid);
683 break;
684
685 case 'c':
686 rmCallback(mycid);
687 break;
688
689 case 't':
690 rmTimer(mytid);
691 break;
692 case '1':
693 mytid = addTimer(1000, to, (void *)1);
694 break;
695 case '2':
696 mytid = addTimer(2000, to, (void *)2);
697 break;
698 case '3':
699 mytid = addTimer(3000, to, (void *)3);
700 break;
701 case '4':
702 mytid = addTimer(4000, to, (void *)4);
703 break;
704 case '5':
705 mytid = addTimer(5000, to, (void *)5);
706 break;
707 default:
708 return; /* silently absorb other chars like \n */
709 }
710
711 printf("callback: %d\n", ++(*(int *)ud));
712}
713
714int main(int ac, char *av[])
715{
716 (void)addCallback(0, stdinCB, &user_a);
717 eventLoop();
718 exit(0);
719}
720
721#endif
void eventLoop()
Main calls this when ready to hand over control.
Definition eventloop.c:109
int64_t IENSecsRemainingTimer(int timerid)
Definition eventloop.c:601
int IEAddPeriodicTimer(int millisecs, IE_TCF *fp, void *p)
Register a new periodic timer function, fp, to be called with ud as argument after ms.
Definition eventloop.c:591
void IERmCallback(int callbackid)
Remove a callback function.
Definition eventloop.c:581
void IERmWorkProc(int workprocid)
Remove a work procedure.
Definition eventloop.c:616
void rmCallback(int cid)
Definition eventloop.c:193
void rmWorkProc(int wid)
Definition eventloop.c:360
void rmTimer(int timer_id)
Definition eventloop.c:290
#define EPOCHDT(tp)
Definition eventloop.c:82
int addWorkProc(WPF *fp, void *ud)
Definition eventloop.c:333
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition eventloop.c:606
int addTimer(int ms, TCF *fp, void *ud)
Definition eventloop.c:252
int IEAddWorkProc(IE_WPF *fp, void *p)
Add a new work procedure, fp, to be called with ud when nothing else to do.
Definition eventloop.c:611
int IEAddCallback(int readfiledes, IE_CBF *fp, void *p)
Register a new callback, fp, to be called with userpointer as argument when readfiledes is ready.
Definition eventloop.c:576
void() IE_TCF(void *userpointer)
Signature of a timeout caller.
Definition eventloop.c:573
int addPeriodicTimer(int ms, TCF *fp, void *ud)
Definition eventloop.c:257
int remainingTimer(int timer_id)
Definition eventloop.c:315
int64_t nsecsRemainingTimer(int timer_id)
Definition eventloop.c:324
void addImmediateWork(TCF *fp, void *ud)
Definition eventloop.c:528
int IEDeferLoop(int maxms, int *flagp)
wait in-line for a flag to set, presumably by another event function
Definition eventloop.c:621
int deferLoop0(int maxms, int *flagp)
Definition eventloop.c:145
int IERemainingTimer(int timerid)
Returns the timer's remaining value in milliseconds left until the timeout.
Definition eventloop.c:596
void() IE_CBF(int readfiledes, void *userpointer)
Signature of a callback.
Definition eventloop.c:572
int deferLoop(int maxms, int *flagp)
Definition eventloop.c:122
int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
Register a new single-shot timer function, fp, to be called with ud as argument after ms.
Definition eventloop.c:586
void() IE_WPF(void *userpointer)
Signature of a work procedure function.
Definition eventloop.c:574
int IEDeferLoop0(int maxms, int *flagp)
Definition eventloop.c:626
int addCallback(int fd, CBF *fp, void *ud)
Definition eventloop.c:165
Public interface to INDI's eventloop mechanism.
void() WPF(void *)
Signature of a work procedure function.
Definition eventloop.h:43
void() CBF(int fd, void *)
Signature of a callback function.
Definition eventloop.h:38
void() TCF(void *)
Signature of a timer function.
Definition eventloop.h:48
int main()
int fd
Namespace to encapsulate INDI client, drivers, and mediator classes.
Definition eventloop.c:55
void * ud
Definition eventloop.c:58
CBF * fp
Definition eventloop.c:59
int in_use
Definition eventloop.c:56
int fd
Definition eventloop.c:57
WPF * fp
Definition eventloop.c:521
struct Immediate * next
Definition eventloop.c:522
void * ud
Definition eventloop.c:520
struct Immediate * prev
Definition eventloop.c:522
Definition eventloop.c:71
struct TF * next
Definition eventloop.c:77
void * ud
Definition eventloop.c:74
TCF * fp
Definition eventloop.c:75
double tgo
Definition eventloop.c:72
int tid
Definition eventloop.c:76
int interval
Definition eventloop.c:73
Definition eventloop.c:89
WPF * fp
Definition eventloop.c:92
int in_use
Definition eventloop.c:90
void * ud
Definition eventloop.c:91