Instrument Neutral Distributed Interface INDI  1.9.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 #include "indidevapi.h"
46 
47 /* info about one registered callback.
48  * the malloced array cback is never shrunk, entries are reused. new id's are
49  * the index of first unused slot in array (and thus reused like unix' open(2)).
50  */
51 typedef struct
52 {
53  int in_use; /* flag to mark this record is active */
54  int fd; /* fd descriptor to watch for read */
55  void *ud; /* user's data handle */
56  CBF *fp; /* callback function */
57 } CB;
58 static CB *cback; /* malloced list of callbacks */
59 static int ncback; /* n entries in cback[] */
60 static int ncbinuse; /* n entries in cback[] marked in_use */
61 static int lastcb; /* cback index of last cb called */
62 
63 /* info about one registered timer function.
64  * the entries are kept sorted by increasing time from epoch, ie,
65  * the next entry to fire is at the begin of the list.
66  */
67 typedef struct TF
68 {
69  double tgo; /* trigger time, ms from epoch */
70  int interval; /* repeat timer if interval > 0, ms */
71  void *ud; /* user's data handle */
72  TCF *fp; /* timer function */
73  int tid; /* unique id for this timer */
74  struct TF *next; /* pointer to next item */
75 } TF;
76 static TF timefunc_null = {0, 0, NULL, NULL, 0, NULL};
77 static TF *timefunc = &timefunc_null; /* list of timer functions */
78 static int tid = 0; /* source of unique timer ids */
79 #define EPOCHDT(tp) /* ms from epoch to timeval *tp */ (((tp)->tv_usec) / 1000.0 + ((tp)->tv_sec) * 1000.0)
80 
81 /* info about one registered work procedure.
82  * the malloced array wproc is never shrunk, entries are reused. new id's are
83  * the index of first unused slot in array (and thus reused like unix' open(2)).
84  */
85 typedef struct
86 {
87  int in_use; /* flag to mark this record is active */
88  void *ud; /* user's data handle */
89  WPF *fp; /* work proc function function */
90 } WP;
91 static WP *wproc; /* malloced list of work procedures */
92 static int nwproc; /* n entries in wproc[] */
93 static int nwpinuse; /* n entries in wproc[] marked in-use */
94 static int lastwp; /* wproc index of last workproc called*/
95 
96 static void runWorkProc(void);
97 static void callCallback(fd_set *rfdp);
98 static void checkTimer();
99 static void oneLoop(void);
100 static void deferTO(void *p);
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 
504 /* timer callback used to implement deferLoop().
505  * arg is pointer to int which we set to 1
506  */
507 static void deferTO(void *p)
508 {
509  *(int *)p = 1;
510 }
511 
512 
513 /* "INDI" wrappers to the more generic eventloop facility. */
514 
515 int IEAddCallback(int readfiledes, IE_CBF *fp, void *p)
516 {
517  return (addCallback(readfiledes, (CBF *)fp, p));
518 }
519 
520 void IERmCallback(int callbackid)
521 {
522  rmCallback(callbackid);
523 }
524 
525 int IEAddTimer(int millisecs, IE_TCF *fp, void *p)
526 {
527  return (addTimer(millisecs, (TCF *)fp, p));
528 }
529 
530 int IEAddPeriodicTimer(int millisecs, IE_TCF *fp, void *p)
531 {
532  return (addPeriodicTimer(millisecs, (TCF *)fp, p));
533 }
534 
535 int IERemainingTimer(int timerid)
536 {
537  return (remainingTimer(timerid));
538 }
539 
540 int64_t IENSecsRemainingTimer(int timerid)
541 {
542  return (nsecsRemainingTimer(timerid));
543 }
544 
545 void IERmTimer(int timerid)
546 {
547  rmTimer(timerid);
548 }
549 
550 int IEAddWorkProc(IE_WPF *fp, void *p)
551 {
552  return (addWorkProc((WPF *)fp, p));
553 }
554 
555 void IERmWorkProc(int workprocid)
556 {
557  rmWorkProc(workprocid);
558 }
559 
560 int IEDeferLoop(int maxms, int *flagp)
561 {
562  return (deferLoop(maxms, flagp));
563 }
564 
565 int IEDeferLoop0(int maxms, int *flagp)
566 {
567  return (deferLoop0(maxms, flagp));
568 }
569 
570 #if defined(MAIN_TEST)
571 /* make a small stand-alone test program.
572  */
573 
574 #include <unistd.h>
575 #include <sys/time.h>
576 
577 int mycid;
578 int mywid;
579 int mytid;
580 
581 int user_a;
582 int user_b;
583 int counter;
584 
585 void wp(void *ud)
586 {
587  struct timeval tv;
588 
589  gettimeofday(&tv, NULL);
590  printf("workproc @ %ld.%03ld %d %d\n", (long)tv.tv_sec, (long)tv.tv_usec / 1000, counter, ++(*(int *)ud));
591 }
592 
593 void to(void *ud)
594 {
595  printf("timeout %d\n", (int)ud);
596 }
597 
598 void stdinCB(int fd, void *ud)
599 {
600  char c;
601 
602  if (read(fd, &c, 1) != 1)
603  {
604  perror("read");
605  return;
606  }
607 
608  switch (c)
609  {
610  case '+':
611  counter++;
612  break;
613  case '-':
614  counter--;
615  break;
616 
617  case 'W':
618  mywid = addWorkProc(wp, &user_b);
619  break;
620  case 'w':
621  rmWorkProc(mywid);
622  break;
623 
624  case 'c':
625  rmCallback(mycid);
626  break;
627 
628  case 't':
629  rmTimer(mytid);
630  break;
631  case '1':
632  mytid = addTimer(1000, to, (void *)1);
633  break;
634  case '2':
635  mytid = addTimer(2000, to, (void *)2);
636  break;
637  case '3':
638  mytid = addTimer(3000, to, (void *)3);
639  break;
640  case '4':
641  mytid = addTimer(4000, to, (void *)4);
642  break;
643  case '5':
644  mytid = addTimer(5000, to, (void *)5);
645  break;
646  default:
647  return; /* silently absorb other chars like \n */
648  }
649 
650  printf("callback: %d\n", ++(*(int *)ud));
651 }
652 
653 int main(int ac, char *av[])
654 {
655  (void)addCallback(0, stdinCB, &user_a);
656  eventLoop();
657  exit(0);
658 }
659 
660 #endif
WP::ud
void * ud
Definition: eventloop.c:88
IEAddCallback
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:515
TF::next
struct TF * next
Definition: eventloop.c:74
Aux::ANY
@ ANY
Definition: celestronauxpacket.h:86
rmCallback
void rmCallback(int cid)
Definition: eventloop.c:189
TF::tid
int tid
Definition: eventloop.c:73
CB::ud
void * ud
Definition: eventloop.c:55
CBF
void() CBF(int fd, void *)
Signature of a callback function.
Definition: eventloop.h:37
TF
Definition: eventloop.c:67
IE_WPF
void() IE_WPF(void *userpointer)
Signature of a work procedure function.
Definition: indidevapi.h:309
deferLoop
int deferLoop(int maxms, int *flagp)
Definition: eventloop.c:118
TF
struct TF TF
TF::tgo
double tgo
Definition: eventloop.c:69
eventloop.h
Public interface to INDI's eventloop mechanism.
WPF
void() WPF(void *)
Signature of a work procedure function.
Definition: eventloop.h:42
CB::fd
int fd
Definition: eventloop.c:54
addWorkProc
int addWorkProc(WPF *fp, void *ud)
Definition: eventloop.c:329
IEAddTimer
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:525
rmWorkProc
void rmWorkProc(int wid)
Definition: eventloop.c:356
TF::interval
int interval
Definition: eventloop.c:70
WP
Definition: eventloop.c:85
indidevapi.h
Interface to the reference INDI C API device implementation on the Device Driver side.
TCF
void() TCF(void *)
Signature of a timer function.
Definition: eventloop.h:47
EPOCHDT
#define EPOCHDT(tp)
Definition: eventloop.c:79
addPeriodicTimer
int addPeriodicTimer(int ms, TCF *fp, void *ud)
Definition: eventloop.c:253
IEDeferLoop0
int IEDeferLoop0(int maxms, int *flagp)
Definition: eventloop.c:565
IE_CBF
void() IE_CBF(int readfiledes, void *userpointer)
Signature of a callback.
Definition: indidevapi.h:301
WP::in_use
int in_use
Definition: eventloop.c:87
IENSecsRemainingTimer
int64_t IENSecsRemainingTimer(int timerid)
Definition: eventloop.c:540
CB::fp
CBF * fp
Definition: eventloop.c:56
nsecsRemainingTimer
int64_t nsecsRemainingTimer(int timer_id)
Definition: eventloop.c:320
WP::fp
WPF * fp
Definition: eventloop.c:89
fd
int fd
Definition: intelliscope.c:43
TF::fp
TCF * fp
Definition: eventloop.c:72
IERmWorkProc
void IERmWorkProc(int workprocid)
Remove a work procedure.
Definition: eventloop.c:555
deferLoop0
int deferLoop0(int maxms, int *flagp)
Definition: eventloop.c:141
IERemainingTimer
int IERemainingTimer(int timerid)
Returns the timer's remaining value in milliseconds left until the timeout.
Definition: eventloop.c:535
CB
Definition: eventloop.c:51
main
int main(int, char **)
Definition: tutorial_client.cpp:53
INDI
Namespace to encapsulate INDI client, drivers, and mediator classes.
Definition: AlignmentSubsystemForClients.cpp:11
IEAddWorkProc
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:550
IEDeferLoop
int IEDeferLoop(int maxms, int *flagp)
Definition: eventloop.c:560
IEAddPeriodicTimer
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:530
CB::in_use
int in_use
Definition: eventloop.c:53
remainingTimer
int remainingTimer(int timer_id)
Definition: eventloop.c:311
rmTimer
void rmTimer(int timer_id)
Definition: eventloop.c:286
addTimer
int addTimer(int ms, TCF *fp, void *ud)
Definition: eventloop.c:248
addCallback
int addCallback(int fd, CBF *fp, void *ud)
Definition: eventloop.c:161
TF::ud
void * ud
Definition: eventloop.c:71
IE_TCF
void() IE_TCF(void *userpointer)
Signature of a timeout caller.
Definition: indidevapi.h:305
eventLoop
void eventLoop()
Main calls this when ready to hand over control.
Definition: eventloop.c:105
IERmTimer
void IERmTimer(int timerid)
Remove the timer with the given timerid, as returned from IEAddTimer() or IEAddPeriodicTimer().
Definition: eventloop.c:545
IERmCallback
void IERmCallback(int callbackid)
Remove a callback function.
Definition: eventloop.c:520