/*** Task functions @author Magic Lantern Team @copyright 2014 @license GPL @module task */ #include #include #include #include "lua_common.h" struct lua_task_func { lua_State * L; int function_ref; }; static int luaCB_card_index(lua_State * L); static int luaCB_card_newindex(lua_State * L); static void lua_run_task(struct lua_task_func * lua_task_func) { if(lua_task_func) { lua_State * L = lua_task_func->L; struct semaphore * sem = NULL; if(!lua_take_semaphore(L, 0, &sem) && sem) { if (lua_get_cant_yield(L) == -1) { /* main task was unloaded? continuing would be use after free */ fprintf(stderr, "[%s] will not start new tasks.\n", lua_get_script_filename(L)); goto skip; } if(lua_rawgeti(L, LUA_REGISTRYINDEX, lua_task_func->function_ref) == LUA_TFUNCTION) { /* script created a task; * it can't be unloaded while this task is running */ lua_set_cant_unload(L, 1, LUA_TASK_UNLOAD_MASK); printf("[%s] task starting.\n", lua_get_script_filename(L)); if(docall(L, 0, 0)) { fprintf(stderr, "[%s] task error:\n%s\n", lua_get_script_filename(L), lua_tostring(L, -1)); lua_save_last_error(L); } luaL_unref(L, LUA_REGISTRYINDEX, lua_task_func->function_ref); printf("[%s] task exiting.\n", lua_get_script_filename(L)); /* If all tasks started by the script are finished * _before_ the main task ends, the script can be unloaded. * Note: lua_set_cant_unload keeps a counter of tasks * (number of calls LUA_TASK_UNLOAD_MASK) */ lua_set_cant_unload(L, 0, LUA_TASK_UNLOAD_MASK); } else { /* should be covered in luaCB_task_create */ ASSERT(0); } skip: give_semaphore(sem); } else { printf("[%s] semaphore error: run task\n", lua_get_script_filename(L)); } free(lua_task_func); } } /*** Creates a new task. It will begin executing when you return or call task.yield() @tparam function f the function to run @tparam[opt] int priority @tparam[opt] int stack_size @function create */ static int luaCB_task_create(lua_State * L) { if(!lua_isfunction(L, 1)) return luaL_argerror(L, 1, "function expected"); LUA_PARAM_INT_OPTIONAL(priority, 2, 0x1c); LUA_PARAM_INT_OPTIONAL(stack_size, 3, 0x10000); struct lua_task_func * func = malloc(sizeof(struct lua_task_func)); if(!func) return luaL_error(L, "malloc error\n"); func->L = L; func->function_ref = luaL_ref(L, LUA_REGISTRYINDEX); char task_name[32]; static int lua_task_id = 0; snprintf(task_name,32,"lua_run_task[%d]",lua_task_id++); uint32_t ret = (uint32_t) task_create(task_name, priority, stack_size, lua_run_task, func); if (ret & 1) return luaL_error(L, "task not started\n"); return 0; } /*** Yields execution of this script to other tasks and event handlers (also from the same script). FIXME: once a task executed yield(), other tasks or event handlers from the same script, that might interrupt the former, **must not** execute yield(). Otherwise, an error will be thrown to prevent memory corruption, camera lockup or worse. The above limitation is a very dirty hack - a proper fix should be implemented by somebody familiar with multitasking in Lua. Help is more than welcome, as this topic is [not exactly our cup of tea](http://www.magiclantern.fm/forum/index.php?topic=14828.msg179227#msg179227). TODO: replace with msleep? @tparam int duration how long to sleep for in ms. @function yield */ static int luaCB_task_yield(lua_State * L) { LUA_PARAM_INT(duration, 1); /* hack: figure out whether we might be interrupting * somebody else who called task.yield() */ if (lua_get_cant_yield(L)) { return luaL_error(L, "FIXME: cannot use task.yield() from two tasks"); } lua_set_cant_yield(L, 1); lua_give_semaphore(L, NULL); msleep(duration); lua_take_semaphore(L, 0, NULL); lua_set_cant_yield(L, 0); return 0; } static int luaCB_task_index(lua_State * L) { lua_rawget(L, 1); return 1; } static int luaCB_task_newindex(lua_State * L) { lua_rawset(L, 1); return 0; } static const char * lua_task_fields[] = { NULL }; const luaL_Reg tasklib[] = { {"create", luaCB_task_create}, {"yield", luaCB_task_yield}, {NULL, NULL} }; LUA_LIB(task)