From 52d1ded7dfde6adf77047c4b9a50e323d3fc86dd Mon Sep 17 00:00:00 2001 From: Přemysl Janouch Date: Fri, 4 Nov 2016 20:38:09 +0100 Subject: degesch: move the Lua async code within the file --- degesch.c | 800 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 400 insertions(+), 400 deletions(-) diff --git a/degesch.c b/degesch.c index 511fdae..a761dee 100644 --- a/degesch.c +++ b/degesch.c @@ -8507,454 +8507,151 @@ lua_plugin_parse (lua_State *L) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// The script can create as many wait channels as wanted. They only actually -// do anything once they get yielded to the main lua_resume() call. - -/// Identifier for the Lua metatable -#define XLUA_WCHANNEL_METATABLE "wchannel" - -struct lua_wait_channel -{ - LIST_HEADER (struct lua_wait_channel) - - struct lua_task *task; ///< The task we're active in - - /// Check if the event is ready and eventually push values to the thread; - /// the channel then may release any resources - bool (*check) (struct lua_wait_channel *self); +// Lua code can use weakly referenced wrappers for internal objects. - /// Release all resources held by the subclass - void (*cleanup) (struct lua_wait_channel *self); -}; +typedef struct weak_ref_link * + (*lua_weak_ref_fn) (void *object, destroy_cb_fn cb, void *user_data); +typedef void (*lua_weak_unref_fn) (void *object, struct weak_ref_link **link); -static int -lua_wchannel_gc (lua_State *L) +struct lua_weak_info { - struct lua_wait_channel *self = - luaL_checkudata (L, 1, XLUA_WCHANNEL_METATABLE); - if (self->cleanup) - self->cleanup (self); - return 0; -} + const char *name; ///< Metatable name + struct ispect_field *ispect; ///< Introspection data -static luaL_Reg lua_wchannel_table[] = -{ - { "__gc", lua_wchannel_gc }, - { NULL, NULL } + lua_weak_ref_fn ref; ///< Weak link invalidator + lua_weak_unref_fn unref; ///< Weak link generator }; -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// A task encapsulates a thread so that wait channels yielded from its main -// function get waited upon by the event loop - -#define XLUA_TASK_METATABLE "task" ///< Identifier for the Lua metatable - -struct lua_task +struct lua_weak { - LIST_HEADER (struct lua_task) - struct lua_plugin *plugin; ///< The plugin we belong to - lua_State *thread; ///< Lua thread - struct lua_wait_channel *active; ///< Channels we're waiting on - struct poller_idle idle; ///< Idle job + struct lua_weak_info *info; ///< Introspection data + void *object; ///< The object + struct weak_ref_link *weak_ref; ///< A weak reference link }; static void -lua_task_unregister_channels (struct lua_task *self) +lua_weak_invalidate (void *object, void *user_data) { - LIST_FOR_EACH (struct lua_wait_channel, iter, self->active) - { - iter->task = NULL; - LIST_UNLINK (self->active, iter); - lua_cache_invalidate (self->plugin->L, iter); - } + struct lua_weak *wrapper = user_data; + wrapper->object = NULL; + wrapper->weak_ref = NULL; + // This can in theory call the GC, order isn't arbitrary here + lua_cache_invalidate (wrapper->plugin->L, object); } static void -lua_task_cancel_internal (struct lua_task *self) +lua_weak_push (lua_State *L, struct lua_plugin *plugin, void *object, + struct lua_weak_info *info) { - if (self->thread) + if (!object) { - lua_cache_invalidate (self->plugin->L, self->thread); - self->thread = NULL; + lua_pushnil (L); + return; } - lua_task_unregister_channels (self); - poller_idle_reset (&self->idle); + if (lua_cache_get (L, object)) + return; - // The task no longer has to stay alive - lua_cache_invalidate (self->plugin->L, self); + struct lua_weak *wrapper = lua_newuserdata (L, sizeof *wrapper); + luaL_setmetatable (L, info->name); + wrapper->plugin = plugin; + wrapper->info = info; + wrapper->object = object; + wrapper->weak_ref = NULL; + if (info->ref) + wrapper->weak_ref = info->ref (object, lua_weak_invalidate, wrapper); + lua_cache_store (L, object, -1); } static int -lua_task_cancel (lua_State *L) +lua_weak_gc (lua_State *L, const struct lua_weak_info *info) { - struct lua_task *self = luaL_checkudata (L, 1, XLUA_TASK_METATABLE); - // We could also yield and make lua_task_resume() check "self->thread", - // however the main issue here is that the script should just return - luaL_argcheck (L, L != self->thread, 1, - "cannot cancel task from within itself"); - lua_task_cancel_internal (self); + struct lua_weak *wrapper = luaL_checkudata (L, 1, info->name); + if (wrapper->object) + { + lua_cache_invalidate (L, wrapper->object); + if (info->unref) + info->unref (wrapper->object, &wrapper->weak_ref); + wrapper->object = NULL; + } return 0; } -#define lua_task_wakeup(self) poller_idle_set (&(self)->idle) - -static bool -lua_task_schedule (struct lua_task *self, int n, struct error **e) +static struct lua_weak * +lua_weak_deref (lua_State *L, const struct lua_weak_info *info) { - lua_State *L = self->thread; - for (int i = -1; -i <= n; i--) - { - struct lua_wait_channel *channel = - luaL_testudata (L, i, XLUA_WCHANNEL_METATABLE); - if (!channel) - return error_set (e, "bad argument #%d to yield: %s", -i + n + 1, - "tasks can only yield wait channels"); - if (channel->task) - return error_set (e, "bad argument #%d to yield: %s", -i + n + 1, - "wait channels can only be active in one task at most"); - } - for (int i = -1; -i <= n; i--) - { - // Quietly ignore duplicate channels - struct lua_wait_channel *channel = lua_touserdata (L, i); - if (channel->task) - continue; + struct lua_weak *weak = luaL_checkudata (L, 1, info->name); + luaL_argcheck (L, weak->object, 1, "dead reference used"); + return weak; +} - // By going in reverse the list ends up in the right order - channel->task = self; - LIST_PREPEND (self->active, channel); - lua_cache_store (self->plugin->L, channel, i); - } - lua_pop (L, n); +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // There doesn't have to be a single channel - // We can also be waiting on a channel that is already ready - lua_task_wakeup (self); - return true; -} +#define LUA_WEAK_DECLARE(id, metatable_id) \ + static struct lua_weak_info lua_ ## id ## _info = \ + { \ + .name = metatable_id, \ + .ispect = g_ ## id ## _ispect, \ + .ref = (lua_weak_ref_fn) id ## _weak_ref, \ + .unref = (lua_weak_unref_fn) id ## _weak_unref, \ + }; -static void -lua_task_resume (struct lua_task *self, int index) -{ - lua_State *L = self->thread; - bool waiting_on_multiple = self->active && self->active->next; +#define XLUA_USER_METATABLE "user" ///< Identifier for Lua metatable +#define XLUA_CHANNEL_METATABLE "channel" ///< Identifier for Lua metatable +#define XLUA_BUFFER_METATABLE "buffer" ///< Identifier for Lua metatable +#define XLUA_SERVER_METATABLE "server" ///< Identifier for Lua metatable - // Since we've ended the wait, we don't need to hold on to them anymore - lua_task_unregister_channels (self); +LUA_WEAK_DECLARE (user, XLUA_USER_METATABLE) +LUA_WEAK_DECLARE (channel, XLUA_CHANNEL_METATABLE) +LUA_WEAK_DECLARE (buffer, XLUA_BUFFER_METATABLE) +LUA_WEAK_DECLARE (server, XLUA_SERVER_METATABLE) - // On the first run we also have the main function on the stack, - // before any initial arguments - int n = lua_gettop (L) - (lua_status (L) == LUA_OK); +// The global context is kind of fake and don't have any ref-counting, +// however it's still very much an object +static struct lua_weak_info lua_ctx_info = +{ + .name = PROGRAM_NAME, + .ispect = g_ctx_ispect, +}; - // Pack the values in a table and prepend the index of the channel, so that - // the caller doesn't need to care about the number of return values - if (waiting_on_multiple) - { - lua_plugin_pack (L, n); - lua_pushinteger (L, index); - lua_insert (L, -2); - n = 2; - } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int res = lua_resume (L, NULL, n); - struct error *error = NULL; - if (res == LUA_YIELD) - { - // AFAIK we don't get any good error context information from here - if (lua_task_schedule (self, lua_gettop (L), &error)) - return; - } - // For simplicity ignore any results from successful returns - else if (res != LUA_OK) - { - luaL_traceback (L, L, lua_tostring (L, -1), 0 /* or 1? */); - lua_plugin_process_error (self->plugin, lua_tostring (L, -1), &error); - lua_pop (L, 2); - } - if (error) - lua_plugin_log_error (self->plugin, "task", error); - lua_task_cancel_internal (self); +static int +lua_user_gc (lua_State *L) +{ + return lua_weak_gc (L, &lua_user_info); } -static void -lua_task_check (struct lua_task *self) +static int +lua_user_get_channels (lua_State *L) { - poller_idle_reset (&self->idle); + struct lua_weak *wrapper = lua_weak_deref (L, &lua_user_info); + struct user *user = wrapper->object; - lua_Integer i = 0; - LIST_FOR_EACH (struct lua_wait_channel, iter, self->active) + int i = 1; + lua_newtable (L); + LIST_FOR_EACH (struct user_channel, iter, user->channels) { - i++; - if (iter->check (iter)) - { - lua_task_resume (self, i); - return; - } + lua_weak_push (L, wrapper->plugin, iter->channel, &lua_channel_info); + lua_rawseti (L, -2, i++); } - if (!self->active) - lua_task_resume (self, i); + return 1; } -// The task dies either when it finishes, it is cancelled, or at plugin unload -static luaL_Reg lua_task_table[] = +static luaL_Reg lua_user_table[] = { - { "cancel", lua_task_cancel }, - { "__gc", lua_task_cancel }, - { NULL, NULL } + { "__gc", lua_user_gc }, + { "get_channels", lua_user_get_channels }, + { NULL, NULL } }; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -struct lua_wait_timer +static int +lua_channel_gc (lua_State *L) { - struct lua_wait_channel super; ///< The structure we're deriving - struct poller_timer timer; ///< Timer event - bool expired; ///< Whether the timer has expired -}; - -static bool -lua_wait_timer_check (struct lua_wait_channel *wchannel) -{ - struct lua_wait_timer *self = - CONTAINER_OF (wchannel, struct lua_wait_timer, super); - return self->super.task && self->expired; -} - -static void -lua_wait_timer_cleanup (struct lua_wait_channel *wchannel) -{ - struct lua_wait_timer *self = - CONTAINER_OF (wchannel, struct lua_wait_timer, super); - poller_timer_reset (&self->timer); -} - -static void -lua_wait_timer_dispatch (struct lua_wait_timer *self) -{ - self->expired = true; - if (self->super.task) - lua_task_wakeup (self->super.task); -} - -static int -lua_plugin_push_wait_timer (struct lua_plugin *plugin, lua_State *L, - lua_Integer timeout) -{ - struct lua_wait_timer *self = lua_newuserdata (L, sizeof *self); - luaL_setmetatable (L, XLUA_WCHANNEL_METATABLE); - memset (self, 0, sizeof *self); - - self->super.check = lua_wait_timer_check; - self->super.cleanup = lua_wait_timer_cleanup; - - poller_timer_init (&self->timer, &plugin->ctx->poller); - self->timer.dispatcher = (poller_timer_fn) lua_wait_timer_dispatch; - self->timer.user_data = self; - - if (timeout) - poller_timer_set (&self->timer, timeout); - else - self->expired = true; - return 1; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static int -lua_async_go (lua_State *L) -{ - struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1)); - luaL_checktype (L, 1, LUA_TFUNCTION); - - lua_State *thread = lua_newthread (L); - lua_cache_store (L, thread, -1); - lua_pop (L, 1); - - // Move the main function w/ arguments to the thread - lua_xmove (L, thread, lua_gettop (L)); - - struct lua_task *task = lua_newuserdata (L, sizeof *task); - luaL_setmetatable (L, XLUA_TASK_METATABLE); - memset (task, 0, sizeof *task); - task->plugin = plugin; - task->thread = thread; - - poller_idle_init (&task->idle, &plugin->ctx->poller); - task->idle.dispatcher = (poller_idle_fn) lua_task_check; - task->idle.user_data = task; - poller_idle_set (&task->idle); - - // Make sure the task doesn't get garbage collected and return it - lua_cache_store (L, task, -1); - return 1; -} - -static int -lua_async_timer_ms (lua_State *L) -{ - struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1)); - lua_Integer timeout = luaL_checkinteger (L, 1); - if (timeout < 0) - luaL_argerror (L, 1, "timeout mustn't be negative"); - return lua_plugin_push_wait_timer (plugin, L, timeout); -} - -static luaL_Reg lua_async_library[] = -{ - { "go", lua_async_go }, - { "timer_ms", lua_async_timer_ms }, - { NULL, NULL }, -}; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -// Lua code can use weakly referenced wrappers for internal objects. - -typedef struct weak_ref_link * - (*lua_weak_ref_fn) (void *object, destroy_cb_fn cb, void *user_data); -typedef void (*lua_weak_unref_fn) (void *object, struct weak_ref_link **link); - -struct lua_weak_info -{ - const char *name; ///< Metatable name - struct ispect_field *ispect; ///< Introspection data - - lua_weak_ref_fn ref; ///< Weak link invalidator - lua_weak_unref_fn unref; ///< Weak link generator -}; - -struct lua_weak -{ - struct lua_plugin *plugin; ///< The plugin we belong to - struct lua_weak_info *info; ///< Introspection data - void *object; ///< The object - struct weak_ref_link *weak_ref; ///< A weak reference link -}; - -static void -lua_weak_invalidate (void *object, void *user_data) -{ - struct lua_weak *wrapper = user_data; - wrapper->object = NULL; - wrapper->weak_ref = NULL; - // This can in theory call the GC, order isn't arbitrary here - lua_cache_invalidate (wrapper->plugin->L, object); -} - -static void -lua_weak_push (lua_State *L, struct lua_plugin *plugin, void *object, - struct lua_weak_info *info) -{ - if (!object) - { - lua_pushnil (L); - return; - } - if (lua_cache_get (L, object)) - return; - - struct lua_weak *wrapper = lua_newuserdata (L, sizeof *wrapper); - luaL_setmetatable (L, info->name); - wrapper->plugin = plugin; - wrapper->info = info; - wrapper->object = object; - wrapper->weak_ref = NULL; - if (info->ref) - wrapper->weak_ref = info->ref (object, lua_weak_invalidate, wrapper); - lua_cache_store (L, object, -1); -} - -static int -lua_weak_gc (lua_State *L, const struct lua_weak_info *info) -{ - struct lua_weak *wrapper = luaL_checkudata (L, 1, info->name); - if (wrapper->object) - { - lua_cache_invalidate (L, wrapper->object); - if (info->unref) - info->unref (wrapper->object, &wrapper->weak_ref); - wrapper->object = NULL; - } - return 0; -} - -static struct lua_weak * -lua_weak_deref (lua_State *L, const struct lua_weak_info *info) -{ - struct lua_weak *weak = luaL_checkudata (L, 1, info->name); - luaL_argcheck (L, weak->object, 1, "dead reference used"); - return weak; -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#define LUA_WEAK_DECLARE(id, metatable_id) \ - static struct lua_weak_info lua_ ## id ## _info = \ - { \ - .name = metatable_id, \ - .ispect = g_ ## id ## _ispect, \ - .ref = (lua_weak_ref_fn) id ## _weak_ref, \ - .unref = (lua_weak_unref_fn) id ## _weak_unref, \ - }; - -#define XLUA_USER_METATABLE "user" ///< Identifier for Lua metatable -#define XLUA_CHANNEL_METATABLE "channel" ///< Identifier for Lua metatable -#define XLUA_BUFFER_METATABLE "buffer" ///< Identifier for Lua metatable -#define XLUA_SERVER_METATABLE "server" ///< Identifier for Lua metatable - -LUA_WEAK_DECLARE (user, XLUA_USER_METATABLE) -LUA_WEAK_DECLARE (channel, XLUA_CHANNEL_METATABLE) -LUA_WEAK_DECLARE (buffer, XLUA_BUFFER_METATABLE) -LUA_WEAK_DECLARE (server, XLUA_SERVER_METATABLE) - -// The global context is kind of fake and don't have any ref-counting, -// however it's still very much an object -static struct lua_weak_info lua_ctx_info = -{ - .name = PROGRAM_NAME, - .ispect = g_ctx_ispect, -}; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static int -lua_user_gc (lua_State *L) -{ - return lua_weak_gc (L, &lua_user_info); -} - -static int -lua_user_get_channels (lua_State *L) -{ - struct lua_weak *wrapper = lua_weak_deref (L, &lua_user_info); - struct user *user = wrapper->object; - - int i = 1; - lua_newtable (L); - LIST_FOR_EACH (struct user_channel, iter, user->channels) - { - lua_weak_push (L, wrapper->plugin, iter->channel, &lua_channel_info); - lua_rawseti (L, -2, i++); - } - return 1; -} - -static luaL_Reg lua_user_table[] = -{ - { "__gc", lua_user_gc }, - { "get_channels", lua_user_get_channels }, - { NULL, NULL } -}; - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static int -lua_channel_gc (lua_State *L) -{ - return lua_weak_gc (L, &lua_channel_info); -} + return lua_weak_gc (L, &lua_channel_info); +} static int lua_channel_get_users (lua_State *L) @@ -10026,6 +9723,309 @@ lua_plugin_connect (lua_State *L) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// The script can create as many wait channels as wanted. They only actually +// do anything once they get yielded to the main lua_resume() call. + +/// Identifier for the Lua metatable +#define XLUA_WCHANNEL_METATABLE "wchannel" + +struct lua_wait_channel +{ + LIST_HEADER (struct lua_wait_channel) + + struct lua_task *task; ///< The task we're active in + + /// Check if the event is ready and eventually push values to the thread; + /// the channel then may release any resources + bool (*check) (struct lua_wait_channel *self); + + /// Release all resources held by the subclass + void (*cleanup) (struct lua_wait_channel *self); +}; + +static int +lua_wchannel_gc (lua_State *L) +{ + struct lua_wait_channel *self = + luaL_checkudata (L, 1, XLUA_WCHANNEL_METATABLE); + if (self->cleanup) + self->cleanup (self); + return 0; +} + +static luaL_Reg lua_wchannel_table[] = +{ + { "__gc", lua_wchannel_gc }, + { NULL, NULL } +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// A task encapsulates a thread so that wait channels yielded from its main +// function get waited upon by the event loop + +#define XLUA_TASK_METATABLE "task" ///< Identifier for the Lua metatable + +struct lua_task +{ + LIST_HEADER (struct lua_task) + + struct lua_plugin *plugin; ///< The plugin we belong to + lua_State *thread; ///< Lua thread + struct lua_wait_channel *active; ///< Channels we're waiting on + struct poller_idle idle; ///< Idle job +}; + +static void +lua_task_unregister_channels (struct lua_task *self) +{ + LIST_FOR_EACH (struct lua_wait_channel, iter, self->active) + { + iter->task = NULL; + LIST_UNLINK (self->active, iter); + lua_cache_invalidate (self->plugin->L, iter); + } +} + +static void +lua_task_cancel_internal (struct lua_task *self) +{ + if (self->thread) + { + lua_cache_invalidate (self->plugin->L, self->thread); + self->thread = NULL; + } + lua_task_unregister_channels (self); + poller_idle_reset (&self->idle); + + // The task no longer has to stay alive + lua_cache_invalidate (self->plugin->L, self); +} + +static int +lua_task_cancel (lua_State *L) +{ + struct lua_task *self = luaL_checkudata (L, 1, XLUA_TASK_METATABLE); + // We could also yield and make lua_task_resume() check "self->thread", + // however the main issue here is that the script should just return + luaL_argcheck (L, L != self->thread, 1, + "cannot cancel task from within itself"); + lua_task_cancel_internal (self); + return 0; +} + +#define lua_task_wakeup(self) poller_idle_set (&(self)->idle) + +static bool +lua_task_schedule (struct lua_task *self, int n, struct error **e) +{ + lua_State *L = self->thread; + for (int i = -1; -i <= n; i--) + { + struct lua_wait_channel *channel = + luaL_testudata (L, i, XLUA_WCHANNEL_METATABLE); + if (!channel) + return error_set (e, "bad argument #%d to yield: %s", -i + n + 1, + "tasks can only yield wait channels"); + if (channel->task) + return error_set (e, "bad argument #%d to yield: %s", -i + n + 1, + "wait channels can only be active in one task at most"); + } + for (int i = -1; -i <= n; i--) + { + // Quietly ignore duplicate channels + struct lua_wait_channel *channel = lua_touserdata (L, i); + if (channel->task) + continue; + + // By going in reverse the list ends up in the right order + channel->task = self; + LIST_PREPEND (self->active, channel); + lua_cache_store (self->plugin->L, channel, i); + } + lua_pop (L, n); + + // There doesn't have to be a single channel + // We can also be waiting on a channel that is already ready + lua_task_wakeup (self); + return true; +} + +static void +lua_task_resume (struct lua_task *self, int index) +{ + lua_State *L = self->thread; + bool waiting_on_multiple = self->active && self->active->next; + + // Since we've ended the wait, we don't need to hold on to them anymore + lua_task_unregister_channels (self); + + // On the first run we also have the main function on the stack, + // before any initial arguments + int n = lua_gettop (L) - (lua_status (L) == LUA_OK); + + // Pack the values in a table and prepend the index of the channel, so that + // the caller doesn't need to care about the number of return values + if (waiting_on_multiple) + { + lua_plugin_pack (L, n); + lua_pushinteger (L, index); + lua_insert (L, -2); + n = 2; + } + + int res = lua_resume (L, NULL, n); + struct error *error = NULL; + if (res == LUA_YIELD) + { + // AFAIK we don't get any good error context information from here + if (lua_task_schedule (self, lua_gettop (L), &error)) + return; + } + // For simplicity ignore any results from successful returns + else if (res != LUA_OK) + { + luaL_traceback (L, L, lua_tostring (L, -1), 0 /* or 1? */); + lua_plugin_process_error (self->plugin, lua_tostring (L, -1), &error); + lua_pop (L, 2); + } + if (error) + lua_plugin_log_error (self->plugin, "task", error); + lua_task_cancel_internal (self); +} + +static void +lua_task_check (struct lua_task *self) +{ + poller_idle_reset (&self->idle); + + lua_Integer i = 0; + LIST_FOR_EACH (struct lua_wait_channel, iter, self->active) + { + i++; + if (iter->check (iter)) + { + lua_task_resume (self, i); + return; + } + } + if (!self->active) + lua_task_resume (self, i); +} + +// The task dies either when it finishes, it is cancelled, or at plugin unload +static luaL_Reg lua_task_table[] = +{ + { "cancel", lua_task_cancel }, + { "__gc", lua_task_cancel }, + { NULL, NULL } +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +struct lua_wait_timer +{ + struct lua_wait_channel super; ///< The structure we're deriving + struct poller_timer timer; ///< Timer event + bool expired; ///< Whether the timer has expired +}; + +static bool +lua_wait_timer_check (struct lua_wait_channel *wchannel) +{ + struct lua_wait_timer *self = + CONTAINER_OF (wchannel, struct lua_wait_timer, super); + return self->super.task && self->expired; +} + +static void +lua_wait_timer_cleanup (struct lua_wait_channel *wchannel) +{ + struct lua_wait_timer *self = + CONTAINER_OF (wchannel, struct lua_wait_timer, super); + poller_timer_reset (&self->timer); +} + +static void +lua_wait_timer_dispatch (struct lua_wait_timer *self) +{ + self->expired = true; + if (self->super.task) + lua_task_wakeup (self->super.task); +} + +static int +lua_plugin_push_wait_timer (struct lua_plugin *plugin, lua_State *L, + lua_Integer timeout) +{ + struct lua_wait_timer *self = lua_newuserdata (L, sizeof *self); + luaL_setmetatable (L, XLUA_WCHANNEL_METATABLE); + memset (self, 0, sizeof *self); + + self->super.check = lua_wait_timer_check; + self->super.cleanup = lua_wait_timer_cleanup; + + poller_timer_init (&self->timer, &plugin->ctx->poller); + self->timer.dispatcher = (poller_timer_fn) lua_wait_timer_dispatch; + self->timer.user_data = self; + + if (timeout) + poller_timer_set (&self->timer, timeout); + else + self->expired = true; + return 1; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static int +lua_async_go (lua_State *L) +{ + struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1)); + luaL_checktype (L, 1, LUA_TFUNCTION); + + lua_State *thread = lua_newthread (L); + lua_cache_store (L, thread, -1); + lua_pop (L, 1); + + // Move the main function w/ arguments to the thread + lua_xmove (L, thread, lua_gettop (L)); + + struct lua_task *task = lua_newuserdata (L, sizeof *task); + luaL_setmetatable (L, XLUA_TASK_METATABLE); + memset (task, 0, sizeof *task); + task->plugin = plugin; + task->thread = thread; + + poller_idle_init (&task->idle, &plugin->ctx->poller); + task->idle.dispatcher = (poller_idle_fn) lua_task_check; + task->idle.user_data = task; + poller_idle_set (&task->idle); + + // Make sure the task doesn't get garbage collected and return it + lua_cache_store (L, task, -1); + return 1; +} + +static int +lua_async_timer_ms (lua_State *L) +{ + struct lua_plugin *plugin = lua_touserdata (L, lua_upvalueindex (1)); + lua_Integer timeout = luaL_checkinteger (L, 1); + if (timeout < 0) + luaL_argerror (L, 1, "timeout mustn't be negative"); + return lua_plugin_push_wait_timer (plugin, L, timeout); +} + +static luaL_Reg lua_async_library[] = +{ + { "go", lua_async_go }, + { "timer_ms", lua_async_timer_ms }, + { NULL, NULL }, +}; + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + static int lua_plugin_get_screen_size (lua_State *L) { -- cgit v1.2.3-70-g09d2