2 /* Jim - A small embeddable Tcl interpreter
4 * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
5 * Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
6 * Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
7 * Copyright 2008 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
8 * Copyright 2008 Andrew Lunn <andrew@lunn.ch>
9 * Copyright 2008 Duane Ellis <openocd@duaneellis.com>
10 * Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials
23 * provided with the distribution.
25 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
26 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
27 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
28 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
36 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 * The views and conclusions contained in the software and documentation
39 * are those of the authors and should not be interpreted as representing
40 * official policies, either expressed or implied, of the Jim Tcl Project.
45 * - to really use flags in Jim_ProcessEvents()
46 * - more complete [after] command with [after info] and other subcommands.
51 #include "jim-eventloop.h"
55 #include <sys/types.h>
58 #include <sys/select.h>
63 /* File event structure */
64 typedef struct Jim_FileEvent
67 int mask
; /* one of JIM_EVENT_(READABLE|WRITABLE|EXCEPTION) */
68 Jim_FileProc
*fileProc
;
69 Jim_EventFinalizerProc
*finalizerProc
;
71 struct Jim_FileEvent
*next
;
74 /* Time event structure */
75 typedef struct Jim_TimeEvent
77 jim_wide id
; /* time event identifier. */
78 int mode
; /* restart, repetitive .. UK */
79 long initialms
; /* initial relativ timer value UK */
80 long when_sec
; /* seconds */
81 long when_ms
; /* milliseconds */
82 Jim_TimeProc
*timeProc
;
83 Jim_EventFinalizerProc
*finalizerProc
;
85 struct Jim_TimeEvent
*next
;
88 /* Per-interp stucture containing the state of the event loop */
89 typedef struct Jim_EventLoop
91 jim_wide timeEventNextId
;
92 Jim_FileEvent
*fileEventHead
;
93 Jim_TimeEvent
*timeEventHead
;
96 void Jim_CreateFileHandler(Jim_Interp
*interp
, FILE * handle
, int mask
,
97 Jim_FileProc
* proc
, void *clientData
, Jim_EventFinalizerProc
* finalizerProc
)
100 Jim_EventLoop
*eventLoop
= Jim_GetAssocData(interp
, "eventloop");
102 fe
= Jim_Alloc(sizeof(*fe
));
106 fe
->finalizerProc
= finalizerProc
;
107 fe
->clientData
= clientData
;
108 fe
->next
= eventLoop
->fileEventHead
;
109 eventLoop
->fileEventHead
= fe
;
112 void Jim_DeleteFileHandler(Jim_Interp
*interp
, FILE * handle
)
114 Jim_FileEvent
*fe
, *prev
= NULL
;
115 Jim_EventLoop
*eventLoop
= Jim_GetAssocData(interp
, "eventloop");
117 fe
= eventLoop
->fileEventHead
;
119 if (fe
->handle
== handle
) {
121 eventLoop
->fileEventHead
= fe
->next
;
123 prev
->next
= fe
->next
;
124 if (fe
->finalizerProc
)
125 fe
->finalizerProc(interp
, fe
->clientData
);
134 /* That's another part of this extension that needs to be ported
136 static void JimGetTime(long *seconds
, long *milliseconds
)
140 gettimeofday(&tv
, NULL
);
141 *seconds
= tv
.tv_sec
;
142 *milliseconds
= tv
.tv_usec
/ 1000;
145 jim_wide
Jim_CreateTimeHandler(Jim_Interp
*interp
, jim_wide milliseconds
,
146 Jim_TimeProc
* proc
, void *clientData
, Jim_EventFinalizerProc
* finalizerProc
)
148 Jim_EventLoop
*eventLoop
= Jim_GetAssocData(interp
, "eventloop");
149 jim_wide id
= eventLoop
->timeEventNextId
++;
151 long cur_sec
, cur_ms
;
153 JimGetTime(&cur_sec
, &cur_ms
);
155 te
= Jim_Alloc(sizeof(*te
));
158 te
->initialms
= milliseconds
;
159 te
->when_sec
= cur_sec
+ milliseconds
/ 1000;
160 te
->when_ms
= cur_ms
+ milliseconds
% 1000;
161 if (te
->when_ms
>= 1000) {
166 te
->finalizerProc
= finalizerProc
;
167 te
->clientData
= clientData
;
168 te
->next
= eventLoop
->timeEventHead
;
169 eventLoop
->timeEventHead
= te
;
173 jim_wide
Jim_DeleteTimeHandler(Jim_Interp
*interp
, jim_wide id
)
175 Jim_TimeEvent
*te
, *prev
= NULL
;
176 Jim_EventLoop
*eventLoop
= Jim_GetAssocData(interp
, "eventloop");
177 long cur_sec
, cur_ms
;
180 JimGetTime(&cur_sec
, &cur_ms
);
182 te
= eventLoop
->timeEventHead
;
183 if (id
>= eventLoop
->timeEventNextId
) {
184 return -2; /* wrong event ID */
188 remain
= (te
->when_sec
- cur_sec
) * 1000;
189 remain
+= (te
->when_ms
- cur_ms
);
190 remain
= (remain
< 0) ? 0 : remain
;
193 eventLoop
->timeEventHead
= te
->next
;
195 prev
->next
= te
->next
;
196 if (te
->finalizerProc
)
197 te
->finalizerProc(interp
, te
->clientData
);
204 return -1; /* NO event with the specified ID found */
207 /* Search the first timer to fire.
208 * This operation is useful to know how many time the select can be
209 * put in sleep without to delay any event.
210 * If there are no timers NULL is returned. */
211 static Jim_TimeEvent
*JimSearchNearestTimer(Jim_EventLoop
* eventLoop
)
213 Jim_TimeEvent
*te
= eventLoop
->timeEventHead
;
214 Jim_TimeEvent
*nearest
= NULL
;
217 if (!nearest
|| te
->when_sec
< nearest
->when_sec
||
218 (te
->when_sec
== nearest
->when_sec
&& te
->when_ms
< nearest
->when_ms
))
225 /* --- POSIX version of Jim_ProcessEvents, for now the only available --- */
227 /* Process every pending time event, then every pending file event
228 * (that may be registered by time event callbacks just processed).
229 * Without special flags the function sleeps until some file event
230 * fires, or when the next time event occurrs (if any).
232 * If flags is 0, the function does nothing and returns.
233 * if flags has JIM_ALL_EVENTS set, all the kind of events are processed.
234 * if flags has JIM_FILE_EVENTS set, file events are processed.
235 * if flags has JIM_TIME_EVENTS set, time events are processed.
236 * if flags has JIM_DONT_WAIT set the function returns ASAP until all
237 * the events that's possible to process without to wait are processed.
239 * The function returns the number of events processed. */
240 int Jim_ProcessEvents(Jim_Interp
*interp
, int flags
)
242 int maxfd
= 0, numfd
= 0, processed
= 0;
243 fd_set rfds
, wfds
, efds
;
244 Jim_EventLoop
*eventLoop
= Jim_GetAssocData(interp
, "eventloop");
245 Jim_FileEvent
*fe
= eventLoop
->fileEventHead
;
255 /* Check file events */
257 int fd
= fileno(fe
->handle
);
259 if (fe
->mask
& JIM_EVENT_READABLE
)
261 if (fe
->mask
& JIM_EVENT_WRITABLE
)
263 if (fe
->mask
& JIM_EVENT_EXCEPTION
)
271 /* Note that we want call select() even if there are no
272 * file events to process as long as we want to process time
273 * events, in order to sleep until the next time event is ready
275 if (numfd
|| ((flags
& JIM_TIME_EVENTS
) && !(flags
& JIM_DONT_WAIT
))) {
277 Jim_TimeEvent
*shortest
;
278 struct timeval tv
, *tvp
;
281 shortest
= JimSearchNearestTimer(eventLoop
);
283 long now_sec
, now_ms
;
285 /* Calculate the time missing for the nearest
287 JimGetTime(&now_sec
, &now_ms
);
289 dt
= 1000 * (shortest
->when_sec
- now_sec
);
290 dt
+= (shortest
->when_ms
- now_ms
);
294 tvp
->tv_sec
= dt
/ 1000;
295 tvp
->tv_usec
= dt
% 1000;
298 tvp
= NULL
; /* wait forever */
301 retval
= select(maxfd
+ 1, &rfds
, &wfds
, &efds
, tvp
);
303 /* XXX: Consider errno? EINTR? */
305 else if (retval
> 0) {
306 fe
= eventLoop
->fileEventHead
;
308 int fd
= fileno(fe
->handle
);
310 if ((fe
->mask
& JIM_EVENT_READABLE
&& FD_ISSET(fd
, &rfds
)) ||
311 (fe
->mask
& JIM_EVENT_WRITABLE
&& FD_ISSET(fd
, &wfds
)) ||
312 (fe
->mask
& JIM_EVENT_EXCEPTION
&& FD_ISSET(fd
, &efds
))) {
315 if ((fe
->mask
& JIM_EVENT_READABLE
) && FD_ISSET(fd
, &rfds
)) {
316 mask
|= JIM_EVENT_READABLE
;
317 if ((fe
->mask
& JIM_EVENT_FEOF
) && feof(fe
->handle
))
318 mask
|= JIM_EVENT_FEOF
;
320 if (fe
->mask
& JIM_EVENT_WRITABLE
&& FD_ISSET(fd
, &wfds
))
321 mask
|= JIM_EVENT_WRITABLE
;
322 if (fe
->mask
& JIM_EVENT_EXCEPTION
&& FD_ISSET(fd
, &efds
))
323 mask
|= JIM_EVENT_EXCEPTION
;
324 if (fe
->fileProc(interp
, fe
->clientData
, mask
) == JIM_ERR
) {
325 /* Remove the element on handler error */
326 Jim_DeleteFileHandler(interp
, fe
->handle
);
329 /* After an event is processed our file event list
330 * may no longer be the same, so what we do
331 * is to clear the bit for this file descriptor and
332 * restart again from the head. */
333 fe
= eventLoop
->fileEventHead
;
345 /* Check time events */
346 te
= eventLoop
->timeEventHead
;
347 maxId
= eventLoop
->timeEventNextId
- 1;
349 long now_sec
, now_ms
;
352 if (te
->id
> maxId
) {
356 JimGetTime(&now_sec
, &now_ms
);
357 if (now_sec
> te
->when_sec
|| (now_sec
== te
->when_sec
&& now_ms
>= te
->when_ms
)) {
359 te
->timeProc(interp
, te
->clientData
);
360 /* After an event is processed our time event list may
361 * no longer be the same, so we restart from head.
362 * Still we make sure to don't process events registered
363 * by event handlers itself in order to don't loop forever
364 * even in case an [after 0] that continuously register
365 * itself. To do so we saved the max ID we want to handle. */
366 Jim_DeleteTimeHandler(interp
, id
);
367 te
= eventLoop
->timeEventHead
;
377 /* ---------------------------------------------------------------------- */
379 void JimELAssocDataDeleProc(Jim_Interp
*interp
, void *data
)
384 Jim_EventLoop
*eventLoop
= data
;
386 fe
= eventLoop
->fileEventHead
;
389 if (fe
->finalizerProc
)
390 fe
->finalizerProc(interp
, fe
->clientData
);
395 te
= eventLoop
->timeEventHead
;
398 if (te
->finalizerProc
)
399 te
->finalizerProc(interp
, te
->clientData
);
406 static int JimELVwaitCommand(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
411 Jim_WrongNumArgs(interp
, 1, argv
, "name");
414 oldValue
= Jim_GetGlobalVariable(interp
, argv
[1], JIM_NONE
);
416 Jim_IncrRefCount(oldValue
);
420 Jim_ProcessEvents(interp
, JIM_ALL_EVENTS
);
421 currValue
= Jim_GetGlobalVariable(interp
, argv
[1], JIM_NONE
);
422 /* Stop the loop if the vwait-ed variable changed value,
423 * or if was unset and now is set (or the contrary). */
424 if ((oldValue
&& !currValue
) ||
425 (!oldValue
&& currValue
) ||
426 (oldValue
&& currValue
&& !Jim_StringEqObj(oldValue
, currValue
, JIM_CASESENS
)))
430 Jim_DecrRefCount(interp
, oldValue
);
434 void JimAfterTimeHandler(Jim_Interp
*interp
, void *clientData
)
436 Jim_Obj
*objPtr
= clientData
;
438 Jim_EvalObjBackground(interp
, objPtr
);
441 void JimAfterTimeEventFinalizer(Jim_Interp
*interp
, void *clientData
)
443 Jim_Obj
*objPtr
= clientData
;
445 Jim_DecrRefCount(interp
, objPtr
);
448 static int JimELAfterCommand(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
451 Jim_Obj
*objPtr
, *idObjPtr
;
452 const char *options
[] = {
453 "info", "cancel", NULL
456 { INFO
, CANCEL
, RESTART
, EXPIRE
, CREATE
};
460 Jim_WrongNumArgs(interp
, 1, argv
, "<after milliseconds> script|cancel <id>");
463 if (Jim_GetWide(interp
, argv
[1], &ms
) != JIM_OK
) {
464 if (Jim_GetEnum(interp
, argv
[1], options
, &option
, "after options", JIM_ERRMSG
) != JIM_OK
) {
470 Jim_IncrRefCount(argv
[2]);
471 id
= Jim_CreateTimeHandler(interp
, ms
, JimAfterTimeHandler
, argv
[2],
472 JimAfterTimeEventFinalizer
);
473 objPtr
= Jim_NewStringObj(interp
, NULL
, 0);
474 Jim_AppendString(interp
, objPtr
, "after#", -1);
475 idObjPtr
= Jim_NewIntObj(interp
, id
);
476 Jim_IncrRefCount(idObjPtr
);
477 Jim_AppendObj(interp
, objPtr
, idObjPtr
);
478 Jim_DecrRefCount(interp
, idObjPtr
);
479 Jim_SetResult(interp
, objPtr
);
484 const char *tok
= Jim_GetString(argv
[2], &tlen
);
486 if (strncmp(tok
, "after#", 6) == 0 && Jim_StringToWide(tok
+ 6, &id
, 10) == JIM_OK
) {
487 remain
= Jim_DeleteTimeHandler(interp
, id
);
489 Jim_SetResult(interp
, Jim_NewIntObj(interp
, remain
));
493 Jim_SetResultString(interp
, "invalid event", -1);
497 fprintf(stderr
, "unserviced option to after %d\n", option
);
502 int Jim_eventloopInit(Jim_Interp
*interp
)
504 Jim_EventLoop
*eventLoop
;
506 eventLoop
= Jim_Alloc(sizeof(*eventLoop
));
507 eventLoop
->fileEventHead
= NULL
;
508 eventLoop
->timeEventHead
= NULL
;
509 eventLoop
->timeEventNextId
= 1;
510 Jim_SetAssocData(interp
, "eventloop", JimELAssocDataDeleProc
, eventLoop
);
512 Jim_CreateCommand(interp
, "vwait", JimELVwaitCommand
, NULL
, NULL
);
513 Jim_CreateCommand(interp
, "after", JimELAfterCommand
, NULL
, NULL
);