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