From 5c817c20ed2269cb860f40da2f866ce2274ba90a Mon Sep 17 00:00:00 2001 From: fm Date: Wed, 14 Mar 2012 18:12:16 +0100 Subject: [PATCH] Lua binding, done coding --- Makefile | 11 +- README | 3 +- libuevent.c | 193 ++++++++++++++------------ libuevent.h | 2 +- luevent.c | 393 ++++++++++++++++++++++++++++++++++++++++++++++++++++ test.c | 2 +- 6 files changed, 507 insertions(+), 97 deletions(-) create mode 100644 luevent.c diff --git a/Makefile b/Makefile index 26be861..ea0a87f 100644 --- a/Makefile +++ b/Makefile @@ -12,13 +12,14 @@ CROSS_COMPILE= CC=$(CROSS_COMPILE)gcc AR=$(CROSS_COMPILE)ar +INCLUDE += -I /usr/include/lua5.1/ LDFLAGS=-s -CFLAGS=-fPIC -g -Os +CFLAGS=-fPIC -g -Os $(INCLUDE) MAJOR = 1 all: libuevent.a libuevent.so.$(MAJOR) -lua: luauevent.so +lua: luevent.so libuevent.a: libuevent.o $(AR) r $@ $^ @@ -27,8 +28,8 @@ libuevent.a: libuevent.o libuevent.so.$(MAJOR): libuevent.o $(CC) $(LDFLAGS) -shared -o $@ $^ -Wl,-soname=libuevent -luauevent.so: luauevent.o all - $(CC) $(LDFLAGS) -shared -o $@ $^ -L . -l uevent -Wl,-soname=uevent +luevent.so: luevent.o all + $(CC) $(LDFLAGS) -shared -o $@ $^ -L . -l uevent -Wl,-soname=luevent test: all $(CC) -g -o test -D NO_DEBUG test.c libuevent.c && ./test @@ -37,7 +38,7 @@ luatest: lua lua test.lua clean: - rm -f libuevent.o luauevent.o libuevent.so.$(MAJOR) luauevent.so test + rm -f libuevent.o luauevent.o libuevent.so.$(MAJOR) luevent.so test .PHONY: all test clean luatest lua diff --git a/README b/README index cd4cad9..e6a3ed0 100644 --- a/README +++ b/README @@ -33,7 +33,6 @@ Installation ------------ Is purely manual, but would be something along the lines of: sudo install -m 444 libuevent.so.1 /usr/lib/libuevent.so.1 - sudo install -m 444 lualue.so /usr/lib/lua/5.1/lue.so - + sudo install -m 444 luevent.so /usr/lib/lua/5.1/luevent.so diff --git a/libuevent.c b/libuevent.c index 9f2f62e..8005940 100644 --- a/libuevent.c +++ b/libuevent.c @@ -310,121 +310,138 @@ void lueTimeAdvance(struct LUECtxt *ctxt, int mSecs) } /** - * Run the application + * Process pending events */ -void lueRun(struct LUECtxt *ctxt) +static int _lueProcessPending(struct LUECtxt *ctxt, int immediate) { struct LUETimerH *timer; int pollRet; + clock_t curTime; + int curPeriod = 0; - do + // Do due timers + if (lListHead(&ctxt->timeoutHandlers)) { - clock_t curTime; - int curPeriod = 0; - - // Do due timers - if (lListHead(&ctxt->timeoutHandlers)) + curTime = ctxt->timeSet ? ctxt->setTime : times(NULL); + curPeriod = (curTime - ctxt->baseTime) / clock_tick * 1000 + + ((curTime - ctxt->baseTime) % clock_tick) * 1000 / clock_tick; + Debug("Period for timer execution %d", curPeriod); + while((timer = lListHead(&ctxt->timeoutHandlers)) && + timer->dueTime <= curPeriod) { - curTime = ctxt->timeSet ? ctxt->setTime : times(NULL); + timer->handler(ctxt, timer, timer->data); + lueRemTimer(ctxt, timer); + + // baseTime changes for every lueAddTimer curPeriod = (curTime - ctxt->baseTime) / clock_tick * 1000 - + ((curTime - ctxt->baseTime) % clock_tick) * 1000 / clock_tick; - Debug("Period for timer execution %d", curPeriod); - while((timer = lListHead(&ctxt->timeoutHandlers)) && - timer->dueTime <= curPeriod) - { - timer->handler(ctxt, timer, timer->data); - lueRemTimer(ctxt, timer); - - // baseTime changes for every lueAddTimer - curPeriod = (curTime - ctxt->baseTime) / clock_tick * 1000 - + ((curTime - ctxt->baseTime) % clock_tick) * 1000 / clock_tick; - } - lListPurge(&ctxt->timeRem); // Reap timer zombies + + ((curTime - ctxt->baseTime) % clock_tick) * 1000 / clock_tick; } + lListPurge(&ctxt->timeRem); // Reap timer zombies + } - // Calculate waiting time - if (ctxt->exiting) - curPeriod = 0; - else if ((timer = lListHead(&ctxt->timeoutHandlers)) && - timer->dueTime > curPeriod) + // Calculate waiting time + if (ctxt->exiting) + curPeriod = 0; + else if ((timer = lListHead(&ctxt->timeoutHandlers)) && + timer->dueTime > curPeriod) + { + Debug("Period from timer head %d - %d -> %d", + timer->dueTime, curPeriod, timer->dueTime - curPeriod); + curPeriod = timer->dueTime - curPeriod; + } + else if (NULL != timer) + curPeriod = 0; + else + curPeriod = -1; + + // Do pipe handlers + if (lListHead(&ctxt->fileHandlers) && !ctxt->exiting) + { + struct pollfd polls[lListLength(&ctxt->fileHandlers)]; + struct LUEFileEventH *ent; + + int ix = 0; + lListForeachIn(ent, &ctxt->fileHandlers) { - Debug("Period from timer head %d - %d -> %d", - timer->dueTime, curPeriod, timer->dueTime - curPeriod); - curPeriod = timer->dueTime - curPeriod; + ent->pollIx = ix; + polls[ix].fd = ent->fd; + polls[ix].events = LUE_FHIN == ent->direction ? POLLIN : POLLOUT; + polls[ix].revents = 0; + ix++; } - else if (NULL != timer) - curPeriod = 0; - else - curPeriod = -1; - - // Do pipe handlers - pollRet = 0; - if (lListHead(&ctxt->fileHandlers) && !ctxt->exiting) + Debug("Polling %d fds in %d ms", ix, curPeriod); + pollRet = poll(polls, ix, immediate ? 0 : curPeriod); + if (pollRet < 0) { - struct pollfd polls[lListLength(&ctxt->fileHandlers)]; - struct LUEFileEventH *ent; - - int ix = 0; - lListForeachIn(ent, &ctxt->fileHandlers) - { - ent->pollIx = ix; - polls[ix].fd = ent->fd; - polls[ix].events = LUE_FHIN == ent->direction ? POLLIN : POLLOUT; - polls[ix].revents = 0; - ix++; - } - Debug("Polling %d fds in %d ms", ix, curPeriod); - pollRet = poll(polls, ix, curPeriod); - if (pollRet < 0) - { - if (EINTR == errno) - continue; - else if (EBADF == errno) - { - lListForeachIn(ent, &ctxt->fileHandlers) - { - if (fcntl(ent->fd, F_GETFD) == -1 && EBADF == errno) - { - ent->handler(ctxt, ent, ent->fd, ent->data); - } - } - } - else - { - // Error in poll. Cannot continue - assert(false); - return; - } - } - else if (pollRet > 0) + if (EINTR == errno) + return 0; + else if (EBADF == errno) { lListForeachIn(ent, &ctxt->fileHandlers) { - if (ent->handler && ent->pollIx >= 0 && polls[ent->pollIx].revents) + if (fcntl(ent->fd, F_GETFD) == -1 && EBADF == errno) { ent->handler(ctxt, ent, ent->fd, ent->data); } } } - if (lListHead(&ctxt->fileRem)) + else { - struct LUEFileEventH *ent; - - while (NULL != (ent = lListHead(&ctxt->fileRem))) + // Error in poll. Cannot continue + assert(false); + return; + } + } + else if (pollRet > 0) + { + lListForeachIn(ent, &ctxt->fileHandlers) + { + if (ent->handler && ent->pollIx >= 0 && polls[ent->pollIx].revents) { - lListRemove(&ctxt->fileHandlers, ent); - free(ent); + ent->handler(ctxt, ent, ent->fd, ent->data); } } } - else if (curPeriod > 0) + if (lListHead(&ctxt->fileRem)) + { + struct LUEFileEventH *ent; + + while (NULL != (ent = lListHead(&ctxt->fileRem))) + { + lListRemove(&ctxt->fileHandlers, ent); + free(ent); + } + } + } + else if (curPeriod > 0) + { + if (!immediate) poll(NULL, 0, curPeriod); - else - break; // LUEarently were out of work! - } while (!ctxt->exiting); - // Wont exit while there is due timers or files - //((timer = lListHead(&ctxt->timeoutHandlers)) && - //timer->dueTime <= 0) || pollRet > 0); + } + else if (curPeriod == -1) + curPeriod = -2; // Nothing more to do + + return curPeriod; +} + +/** + * Process pending events + */ +int lueProcessPending(struct LUECtxt *ctxt) +{ + return _lueProcessPending(ctxt, 1); +} + +/** + * Run the application + */ +void lueRun(struct LUECtxt *ctxt) +{ + int ret; + + do { + ret = _lueProcessPending(ctxt, 0); + } while (!ctxt->exiting && ret != -2); Debug("LUE exiting - bye"); } diff --git a/libuevent.h b/libuevent.h index a92d09e..7c75e8d 100644 --- a/libuevent.h +++ b/libuevent.h @@ -45,7 +45,7 @@ struct LUECtxt *lueCtxtCreate(); void lueCtxtDestroy(struct LUECtxt *lue); void lueTimeAdvance(struct LUECtxt *ctxt, int mSecs); void lueRun(struct LUECtxt *ctxt); -void lueProcessEvents(struct LUECtxt *ctxt); +int lueProcessPending(struct LUECtxt *ctxt); void lueExit(struct LUECtxt *ctxt); #endif diff --git a/luevent.c b/luevent.c new file mode 100644 index 0000000..a8fb011 --- /dev/null +++ b/luevent.c @@ -0,0 +1,393 @@ +/mple** + * @file + * Lua binding to libuevent + * $Id:$ + * + * (C) Copyright 2012 flemming.madsen at madsensoft.dk. See libuevent.h + */ +#include +#include +#include +#include +#include +#include +#include "libuevent.h" + +static char * LUA_LTIMER_MT = "libuevent-timer"; +static char * LUA_LFILEH_MT = "libuevent-fileh"; +static char * LUA_LUEOBJ = "libuevent-objects"; + +typedef struct lue_data +{ + lua_State *L; // Index to our world + struct LUETimerH *timer; + struct LUEFileEventH *file; +} lue_data; +#define toptimer(L) ((struct lue_data *)luaL_checkudata(L, 1, LUA_LTIMER_MT)) +#define topfileh(L) ((struct lue_data *)luaL_checkudata(L, 1, LUA_LFILEH_MT)) + +static struct LUECtxt *getCtxt(lua_State *L) +{ + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LUEOBJ); + lua_rawgeti(L, -1, 1); + struct LUECtxt *ret = lua_touserdata(L, -1); + lua_pop(L, 2); + return ret; +} + +static int l_traceback (lua_State *L) +{ + lua_getfield(L, LUA_GLOBALSINDEX, "debug"); + if (!lua_istable(L, -1)) { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); /* pass error message */ + lua_pushinteger(L, 2); /* skip this function and traceback */ + lua_call(L, 2, 1); /* call debug.traceback */ + return 1; +} + +// Top half +static int _lueHandler(lua_State *L, void *data, bool remObj) +{ + lua_pushcfunction(L, l_traceback); /* push traceback function */ + + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LUEOBJ); + lua_pushlightuserdata(L, data); + lua_rawget(L, -2); // objects[object_p] => The object + lua_pushvalue(L, -1); + lua_rawget(L, -3); // object[object] => closure + + if (remObj) + { + lua_pushlightuserdata(L, data); + lua_pushnil(L); + lua_rawset(L, -5); // objects[object_p] = nil -> GC + lua_pushvalue(L, -2); + lua_pushnil(L); + lua_rawset(L, -5); // objects[object] = nil -> GC + } + lua_remove(L, -3); // objects table + + int ret; + lua_insert(L, -2); // exch: closure <-> obj + if ((ret = lua_pcall(L, 1, 0, -3))) // Call closure(object) + { + fprintf(stderr, "luevent: Error: %d:\n%s\n", + ret, lua_isnil(L, -1) ? "(nil)" : lua_tostring(L, -1)); + exit(1); + } + lua_pop(L, 1); // Error handler, metatable + + return 0; +} + +static int _ltimerHandler(struct LUECtxt *ctxt, struct LUETimerH *handle, void *data) +{ + lue_data *self = (lue_data *) data; + if (!self || !self->timer) + return 0; + + int ret = _lueHandler(self->L, data, true); + self->timer = NULL; + return ret; +} + +static int _lfileHandler(struct LUECtxt *ctxt, struct LUEFileEventH *handle, int fd, void *data) +{ + lue_data *self = (lue_data *) data; + bool remObj = false; + if (!self || !self->file) + return 0; + + if (fcntl(fd, F_GETFD) == -1 && EBADF == errno) + { + remObj = true; + lueRemInput(getCtxt(self->L), self->file); // Works on both types + self->file = NULL; + } + + return _lueHandler(self->L, data, remObj); +} + +// Bottom half +static void lueAddObj(lua_State *L, lue_data *self, int cbix) +{ + self->L = L; + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LUEOBJ); + + lua_pushlightuserdata(L, self); + lua_pushvalue(L, -3); + lua_rawset(L, -3); // objects[self_p] = obj + + lua_pushvalue(L, -2); + lua_pushvalue(L, cbix); + lua_rawset(L, -3); // objects[obj] = callback + lua_pop(L, 1); // objects +} + + /* LUADOC + * Add a handler on a stream. + * The handler will sustain itself GC wise. Use <:object>:clear() to unregister the handler + * @param fdesc The posix file descriptor "small integer". + *
Use the ":getfd()" method on a "normal" lua stream or socket to obtain fdesc. + * @param handler A function object. No paramaeter supplied when called. + * @param mode [Optional] Input ("r") or output ("w") event. Default is input + * @return A new filehandler object + function luevent.addFileIO(fdesc, handler, mode) + */ +static int lfileh_add(lua_State *L) +{ + int fd = luaL_checkint(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + const char *mode = luaL_optstring(L, 3, "r"); + + lue_data *self = (lue_data *) lua_newuserdata(L, sizeof(lue_data)); + struct LUEFileEventH *h = *mode == 'r' ? + lueAddInput(getCtxt(L), fd, _lfileHandler, self) : + lueAddOutput(getCtxt(L), fd, _lfileHandler, self); + if (!h) luaL_error(L, "lueAddFileIO error"); + self->file = h; + + luaL_getmetatable(L, LUA_LFILEH_MT); + lua_setmetatable(L, -2); + lueAddObj(L, self, 2); + + return 1; +} + + /* LUADOC + * Create a new timer object. Implicitly cleared when handler is called. + * @usage
+  *  local hdl
+ * hdl = luevent.addTimer(50, function() hdl = nil xxx end)
+ * ......
+ * if hdl then hdl:clear() hdl = nil end
+ *
+ * Here hdl is used to keep track of whether the timer is active. + * @param msecs The number of milliseconds until the timer expires + * @param handler A function object + * @return A new timer object + function luevent.addTimer(msecs, handler) + */ +static int ltimer_add(lua_State *L) +{ + int mSecs = luaL_checkint(L, 1); + luaL_checktype(L, 2, LUA_TFUNCTION); + + lue_data *self = (lue_data *) lua_newuserdata(L, sizeof(lue_data)); + struct LUETimerH *h = self ? lueAddTimer(getCtxt(L), mSecs, _ltimerHandler, self) : NULL; + if (!h) + luaL_error(L, "lueAddTimer error"); + self->timer = h; + + luaL_getmetatable(L, LUA_LTIMER_MT); + lua_setmetatable(L, 3); + lueAddObj(L, self, 2); + + return 1; +} + +static void lobj_clear(lua_State *L, lue_data *self) +{ + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LUEOBJ); + lua_pushlightuserdata(L, self); + lua_rawget(L, -2); // objects[object_p] => The object + + lua_pushnil(L); + lua_rawset(L, -3); // objects[object] = nil -> GC + lua_pushlightuserdata(L, self); + lua_pushnil(L); + lua_rawset(L, -3); // objects[object_p] = nil -> GC + lua_pop(L, 1); // pop objects +} + + /* LUADOC + * Cancel a timer callback. Renders the object ready for GC. + function timer:clear() + */ +static int ltimer_clear(lua_State *L) +{ + lue_data *self = toptimer(L); + + if (self->timer) + { + lueRemTimer(getCtxt(L), self->timer); + self->timer = NULL; + } + lobj_clear(L, self); + + return 0; +} + + /* LUADOC + * Cancel a fileio callback. Renders the object ready for GC. + function fileio:clear() + */ +static int lfileh_clear(lua_State *L) +{ + lue_data *self = topfileh(L); + + if (self->file) + { + lueRemInput(getCtxt(L), self->file); // Works on both types + self->file = NULL; + } + lobj_clear(L, self); + + return 0; +} + + /* LUADOC + * Obtain the registered handler on the object + * @return The handler + function luevent_object:handler() + */ +static int luevent_handler(lua_State *L) +{ + lue_data *self = lua_touserdata(L, 1); + + if (self) + { + lua_getfield(L, LUA_REGISTRYINDEX, LUA_LUEOBJ); + + lua_getfield(L, -1, "_objects"); + lua_getfield(L, -2, "_callbacks"); + if (lua_istable(L, -1) && lua_istable(L, -2)) + { + lua_pushlightuserdata(L, self); + lua_rawget(L, -2); // The object + lua_rawget(L, -2); // callbacks[obj] -> closure + return 1; + } + } + return 0; +} + +static int ltimer_gc(lua_State *L) +{ + //STACKDUMP(L); + lue_data *self = toptimer(L); + + if (self->timer) + { + lueRemTimer(getCtxt(L), self->timer); + self->timer = NULL; + } + return 0; +} + +static int lfileh_gc(lua_State *L) +{ + lue_data *self = topfileh(L); + + if (self->file) + { + lueRemInput(getCtxt(L), self->file); + self->file = NULL; + } + return 0; +} + +static int luevent_tostring(lua_State *L) +{ + lue_data *self = lua_touserdata(L, 1); + + lua_getmetatable(L, 1); + lua_getfield(L, -1, "_typename"); + const char *t = lua_tostring(L, -1); + + if (!self->timer && !self->file) + lua_pushfstring(L, "%s (cleared)", t); + else + lua_pushfstring(L, "%s (%p)", t, self); + return 1; +} + +/* LUADOC + * Get the system time in hight resolution + * @return two numbers: seconds and milliseconds since the epoch + function luevent.systemTime() + */ +static int l_systemTime (lua_State *L) +{ + struct timeval v; + + gettimeofday(&v, (struct timezone *) NULL); + lua_pushinteger(L, t.tv_sec); + lua_pushinteger(L, v.tv_usec / 1000); + return 2; // num of results +} + +static int l_cleanup(lua_State * L) +{ + struct LUECtxt *ctxt = getCtxt(L); + + lua_pushnil(L); + lua_setfield(L, LUA_REGISTRYINDEX, LUA_LUEOBJ); + lua_gc(L, LUA_GCCOLLECT, 0); // Make sure all timers & schedules & callbacks are gone before context + + lueCtxtDestroy(ctxt); + return 0; +} + +static const luaL_Reg lueventlib[] = { + {"addFileIO", lfileh_add}, + {"clearFileIO", lfileh_clear}, + {"addTimer", ltimer_add}, + {"clearTimer", ltimer_clear}, + {"systemTime", l_systemTime}, + {"__gc", l_cleanup}, + { NULL, NULL} +}; + +static const luaL_Reg ltimermeta[] = { + {"clear", ltimer_clear}, + {"handler", luevent_handler}, + {"__gc", ltimer_gc}, + {"__tostring", luevent_tostring}, + { NULL, NULL} +}; + +static const luaL_Reg lfilehmeta[] = { + {"clear", lfileh_clear}, + {"handler", luevent_handler}, + {"__gc", lfileh_gc}, + {"__tostring", luevent_tostring}, + { NULL, NULL} +}; + +int luaopen_luevent(lua_State * L) +{ + struct LUECtxt *lueContext = lueCtxtCreate(); + assert(ctxt); + + luaL_register(L, lua_tostring(L, 1), lueventlib); // calls + lua_setmetatable(L, -1); // Call GC at module cleanup. Pops it too + + l_newType(L, LUA_LTIMER_MT); // create metatable for timers + luaL_register(L, NULL, ltimermeta); // methods + lua_pop(L, 1); + + l_newType(L, LUA_LFILEH_MT); // create metatable for file handlers + luaL_register(L, NULL, lfilehmeta); // methods + lua_pop(L, 1); + + lua_newtable(L); // Table[obj] -> Callback-closure + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, LUA_LUEOBJ); + lua_pushlightuserdata(L, lueContext);// Table[1] = lueContext + lua_rawseti(L, -2, 1); + lua_pop(L, 1); + + return 0; +} + + +// vim: set sw=3 sts=3 et: diff --git a/test.c b/test.c index 55ebc45..9d43bee 100644 --- a/test.c +++ b/test.c @@ -73,7 +73,7 @@ main() lueRun(ctxt); lueCtxtDestroy(ctxt); - printf("Period is %d must be 10\n", period); + printf("Period is %d must be 10: %s\n", period, 10 == period ? "OK" : "ERROR!"); exit(10 == period ? 0 : 1); }