diff --git a/src/gui/lua/Makefile.am b/src/gui/lua/Makefile.am index c5471750f..f4052960f 100644 --- a/src/gui/lua/Makefile.am +++ b/src/gui/lua/Makefile.am @@ -41,4 +41,7 @@ libneutrino_gui_lua_a_SOURCES = \ lua_menue.cpp \ lua_messagebox.cpp \ lua_misc.cpp \ + lua_threads.cpp \ + lua_threads_copy.cpp \ + lua_threads_functions.cpp \ lua_video.cpp diff --git a/src/gui/lua/lua_threads.cpp b/src/gui/lua/lua_threads.cpp new file mode 100644 index 000000000..3bc13da75 --- /dev/null +++ b/src/gui/lua/lua_threads.cpp @@ -0,0 +1,391 @@ +/****************************************************************************** +* Copyright (c) 2011 by Robert G. Jakabosky +* Copyright (c) 2015 M. Liebmann (micha-bbg) +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include "lua_threads.h" + +int __strerror_r(int err, char* buf, size_t len) +{ + memset(buf, '\0', len); + snprintf(buf, len-1, "%s", strerror(err)); + return 0; +} + +CLLThread* CLLThread::getInstance() +{ + static CLLThread* llthreadInst = NULL; + + if (!llthreadInst) + llthreadInst = new CLLThread(); + return llthreadInst; +} + +void CLLThread::llthread_log(lua_State *L, const char *hdr, const char *msg) +{ + int top = lua_gettop(L); + lua_rawgetp(L, LUA_REGISTRYINDEX, LLTHREAD_LOGGER_HOLDER); + if (lua_isnil(L, -1)) { + lua_pop(L, 1); + fputs(hdr, stderr); + fputs(msg, stderr); + fputc('\n', stderr); + fflush(stderr); + return; + } + lua_pushstring(L, hdr); + lua_pushstring(L, msg); + lua_concat(L, 2); + lua_pcall(L, 1, 0, 0); + lua_settop(L, top); +} + +int CLLThread::fail(lua_State *L, const char *msg) +{ + lua_pushnil(L); + lua_pushstring(L, msg); + return 2; +} + +int CLLThread::traceback (lua_State *L) +{ + const char *msg = lua_tostring(L, 1); + if (msg) + luaL_traceback(L, L, msg, 1); + else if (!lua_isnoneornil(L, 1)) { /* is there an error object? */ + if (!luaL_callmeta(L, 1, "__tostring")) /* try its 'tostring' metamethod */ + lua_pushliteral(L, "(no error message)"); + } + return 1; +} + +void CLLThread::open_thread_libs(lua_State *L) +{ + /* Not yet tested calling thread from thread out */ + /* LuaThreadsRegister(L); */ + + /* luainstance.cpp */ + LuaInstRegisterFunctions(L, true); +} + +llthread_child_t *CLLThread::llthread_child_new() +{ + llthread_child_t *_this = ALLOC_STRUCT(llthread_child_t); + if (!_this) return NULL; + + memset(_this, 0, sizeof(llthread_child_t)); + + /* create new lua_State for the thread. */ + /* open standard libraries. */ + _this->L = luaL_newstate(); + open_thread_libs(_this->L); + + return _this; +} + +llthread_t *CLLThread::llthread_new() +{ + llthread_t *_this = ALLOC_STRUCT(llthread_t); + if (!_this) return NULL; + + _this->flags = FLAG_NONE; + _this->child = llthread_child_new(); + if (!_this->child) { + FREE_STRUCT(_this); + return NULL; + } + return _this; +} + +int CLLThread::llthread_push_args(lua_State *L, llthread_child_t *child, int idx, int top) +{ + return llthread_copy_values(L, child->L, idx, top, 1 /* is_arg */); +} + +int CLLThread::llthread_push_results(lua_State *L, llthread_child_t *child, int idx, int top) +{ + return llthread_copy_values(child->L, L, idx, top, 0 /* is_arg */); +} + +void CLLThread::llthread_validate(llthread_t *_this) +{ + /* describe valid state of llthread_t object + * from after create and before destroy + */ + if (!IS(_this, STARTED)) { + assert(!IS(_this, DETACHED)); + assert(!IS(_this, JOINED)); + assert(!IS(_this, JOINABLE)); + return; + } + + if (IS(_this, DETACHED)) { + if (!IS(_this, JOINABLE)) assert(_this->child == NULL); + else assert(_this->child != NULL); + } +} + +llthread_t *CLLThread::llthread_create(lua_State *L, const char *code, size_t code_len) +{ + llthread_t *_this = llthread_new(); + llthread_child_t *child = _this->child; + + /* load Lua code into child state. */ + int rc = luaL_loadbuffer(child->L, code, code_len, code); + if (rc != 0) { + /* copy error message to parent state. */ + size_t len; const char *str = lua_tolstring(child->L, -1, &len); + if (str != NULL) { + lua_pushlstring(L, str, len); + } else { + /* non-string error message. */ + lua_pushfstring(L, "luaL_loadbuffer() failed to load Lua code: rc=%d", rc); + } + /* llthread_destroy(_this); */ + lua_error(L); + return NULL; + } + + /* copy extra args from main state to child state. */ + /* Push all args after the Lua code. */ + llthread_push_args(L, child, 3, lua_gettop(L)); + + llthread_validate(_this); + + return _this; +} + +llthread_t *CLLThread::l_llthread_at (lua_State *L, int i) +{ + llthread_t **_this = (llthread_t **) luaL_checkudata(L, i, LLTHREAD_TAG); + return *_this; +} + +void CLLThread::llthread_child_destroy(llthread_child_t *_this) +{ + lua_close(_this->L); + FREE_STRUCT(_this); +} + +void CLLThread::llthread_cleanup_child(llthread_t *_this) +{ + if (_this->child) { + llthread_child_destroy(_this->child); + _this->child = NULL; + } +} + +int CLLThread::llthread_detach(llthread_t *_this) +{ + int rc = 0; + + assert(IS(_this, STARTED)); + assert(_this->child != NULL); + + _this->child = NULL; + + /*we can not detach joined thread*/ + if (IS(_this, JOINED)) + return 0; + + rc = pthread_detach(_this->thread); + return rc; +} + +void* CLLThread::llthread_child_thread_run(void *arg) +{ + llthread_child_t *_this = (llthread_child_t *)arg; + lua_State *L = _this->L; + int nargs = lua_gettop(L) - 1; + + /* push traceback function as first value on stack. */ + lua_pushcfunction(_this->L, traceback); + lua_insert(L, 1); + + _this->status = lua_pcall(L, nargs, LUA_MULTRET, 1); + + /* alwasy print errors here, helps with debugging bad code. */ + if (_this->status != 0) { + llthread_log(L, "Error from thread: ", lua_tostring(L, -1)); + } + + if (IS(_this, DETACHED) || !IS(_this, JOINABLE)) { + /* thread is detached, so it must clean-up the child state. */ + llthread_child_destroy(_this); + _this = NULL; + } + + return _this; +} + +int CLLThread::llthread_start(llthread_t *_this, int start_detached, int joinable) +{ + llthread_child_t *child = _this->child; + int rc = 0; + + llthread_validate(_this); + + if (joinable) SET(child, JOINABLE); + if (start_detached) SET(child, DETACHED); + + rc = pthread_create(&(_this->thread), NULL, llthread_child_thread_run, child); + + if (rc == 0) { + SET(_this, STARTED); + if (joinable) SET(_this, JOINABLE); + if (start_detached) SET(_this, DETACHED); + if ((start_detached)&&(!joinable)) { + rc = llthread_detach(_this); + } + } + + llthread_validate(_this); + + return rc; +} + +int CLLThread::llthread_cancel(llthread_t *_this) +{ + llthread_validate(_this); + + if (IS(_this, JOINED)) { + return JOIN_OK; + } else { + int rc = pthread_cancel(_this->thread); + if (rc == 0) { + return JOIN_ETIMEDOUT; + } + + if (rc != ESRCH) { + /*@fixme what else it can be ?*/ + return rc; + } + + return JOIN_OK; + } +} + +int CLLThread::llthread_join(llthread_t *_this, join_timeout_t timeout) +{ + llthread_validate(_this); + + if (IS(_this, JOINED)) { + return JOIN_OK; + } else { + int rc; + if (timeout == 0) { + rc = pthread_kill(_this->thread, 0); + if (rc == 0) { /* still alive */ + return JOIN_ETIMEDOUT; + } + + if (rc != ESRCH) { + /*@fixme what else it can be ?*/ + return rc; + } + + /*thread dead so we call join to free pthread_t struct */ + } + + /* @todo use pthread_tryjoin_np/pthread_timedjoin_np to support timeout */ + + /* then join the thread. */ + rc = pthread_join(_this->thread, NULL); + if ((rc == 0) || (rc == ESRCH)) { + SET(_this, JOINED); + rc = JOIN_OK; + } + + llthread_validate(_this); + + return rc; + } +} + +void CLLThread::llthread_destroy(llthread_t *_this) +{ + do { + /* thread not started */ + if (!IS(_this, STARTED)) { + llthread_cleanup_child(_this); + break; + } + + /* DETACHED */ + if (IS(_this, DETACHED)) { + if (IS(_this, JOINABLE)) { + llthread_detach(_this); + } + break; + } + + /* ATTACHED */ + if (!IS(_this, JOINED)) { + llthread_join(_this, INFINITE_JOIN_TIMEOUT); + if (!IS(_this, JOINED)) { + /* @todo use current lua state to logging */ + /* + * char buf[ERROR_LEN]; + * strerror_r(errno, buf, ERROR_LEN); + * llthread_log(L, "Error can not join thread on gc: ", buf); + */ + } + } + if (IS(_this, JOINABLE)) { + llthread_cleanup_child(_this); + } + + } while (0); + + FREE_STRUCT(_this); +} + +int CLLThread::llthread_alive(llthread_t *_this) +{ + llthread_validate(_this); + + if (IS(_this, JOINED)) { + return JOIN_OK; + } else { + int rc = pthread_kill(_this->thread, 0); + if (rc == 0) { /* still alive */ + return JOIN_ETIMEDOUT; + } + + if (rc != ESRCH) { + /*@fixme what else it can be ?*/ + return rc; + } + return JOIN_OK; + } +} diff --git a/src/gui/lua/lua_threads.h b/src/gui/lua/lua_threads.h new file mode 100644 index 000000000..3d1304982 --- /dev/null +++ b/src/gui/lua/lua_threads.h @@ -0,0 +1,152 @@ +/****************************************************************************** +* Copyright (c) 2011 by Robert G. Jakabosky +* Copyright (c) 2015 M. Liebmann (micha-bbg) +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#ifndef _LLTHREAD_H_ +#define _LLTHREAD_H_ + +extern "C" +{ +#include +#include +#include +} + +/* wrap strerror_r(). */ +#ifndef strerror_r +#define strerror_r __strerror_r +#endif + +#define OS_THREAD_RETURN void * +#define INFINITE_JOIN_TIMEOUT -1 +#define JOIN_OK 0 +#define JOIN_ETIMEDOUT ETIMEDOUT +typedef int join_timeout_t; +typedef pthread_t os_thread_t; + +#define ERROR_LEN 1024 + +#define flags_t unsigned char + +#define FLAG_NONE (flags_t)0 +#define FLAG_STARTED (flags_t)1<<0 +#define FLAG_DETACHED (flags_t)1<<1 +#define FLAG_JOINED (flags_t)1<<2 +#define FLAG_JOINABLE (flags_t)1<<3 + +/*At least one flag*/ +#define FLAG_IS_SET(O, F) (O->flags & (flags_t)(F)) +#define FLAG_SET(O, F) O->flags |= (flags_t)(F) +#define FLAG_UNSET(O, F) O->flags &= ~((flags_t)(F)) +#define IS(O, F) FLAG_IS_SET(O, FLAG_##F) +#define SET(O, F) FLAG_SET(O, FLAG_##F) + +#define ALLOC_STRUCT(S) (S*)calloc(1, sizeof(S)) +#define FREE_STRUCT(O) free(O) + +#define LLTHREAD_NAME "threads" +#define LLTHREAD_TAG LLTHREAD_NAME +#define LLTHREAD_LOGGER_HOLDER LLTHREAD_NAME " logger holder" + +typedef struct llthread_child_t { + lua_State* L; + int status; + flags_t flags; +} llthread_child_t; + +typedef struct llthread_t { + llthread_child_t* child; + os_thread_t thread; + flags_t flags; +} llthread_t; + +typedef struct { + lua_State* from_L; + lua_State* to_L; + int has_cache; + int cache_idx; + int is_arg; +} llthread_copy_state; + +#if LUA_VERSION_NUM >= 503 /* Lua 5.3 */ + +#ifndef luaL_optint +# define luaL_optint luaL_optinteger +#endif + +#ifndef luaL_checkint +# define luaL_checkint luaL_checkinteger +#endif + +#endif /* Lua 5.3 */ + +int __strerror_r(int err, char* buf, size_t len); + +class CLLThread +{ + public: + CLLThread() {}; +// ~CLLThread() {}; + static CLLThread* getInstance(); + static void LuaThreadsRegister(lua_State *L); + + private: + static void llthread_log(lua_State *L, const char *hdr, const char *msg); + static int fail(lua_State *L, const char *msg); + static int traceback (lua_State *L); + static void open_thread_libs(lua_State *L); + static llthread_child_t *llthread_child_new(); + static llthread_t *llthread_new(); + static int llthread_push_args(lua_State *L, llthread_child_t *child, int idx, int top); + static int llthread_push_results(lua_State *L, llthread_child_t *child, int idx, int top); + static void llthread_validate(llthread_t *_this); + static llthread_t *llthread_create(lua_State *L, const char *code, size_t code_len); + static llthread_t *l_llthread_at (lua_State *L, int i); + static void llthread_child_destroy(llthread_child_t *_this); + static void llthread_cleanup_child(llthread_t *_this); + static int llthread_detach(llthread_t *_this); + static void* llthread_child_thread_run(void *arg); + static int llthread_start(llthread_t *_this, int start_detached, int joinable); + static int llthread_cancel(llthread_t *_this); + static int llthread_join(llthread_t *_this, join_timeout_t timeout); + static void llthread_destroy(llthread_t *_this); + static int llthread_alive(llthread_t *_this); + + /* copy.cpp */ + static int llthread_copy_table_from_cache(llthread_copy_state *state, int idx); + static int llthread_copy_value(llthread_copy_state *state, int depth, int idx); + static int llthread_copy_values(lua_State *from_L, lua_State *to_L, int idx, int top, int is_arg); + + /* lua_functions.cpp */ + static int l_llthread_new(lua_State *L); + static int l_llthread_start(lua_State *L); + static int l_llthread_cancel(lua_State *L); + static int l_llthread_join(lua_State *L); + static int l_llthread_alive(lua_State *L); + static int l_llthread_set_logger(lua_State *L); + static int l_llthread_started(lua_State *L); + static int l_llthread_detached(lua_State *L); + static int l_llthread_joinable(lua_State *L); + static int l_llthread_delete(lua_State *L); +}; + +#endif // _LLTHREAD_H_ diff --git a/src/gui/lua/lua_threads_copy.cpp b/src/gui/lua/lua_threads_copy.cpp new file mode 100644 index 000000000..a7d140018 --- /dev/null +++ b/src/gui/lua/lua_threads_copy.cpp @@ -0,0 +1,167 @@ +/****************************************************************************** +* Copyright (c) 2011 by Robert G. Jakabosky +* Copyright (c) 2015 M. Liebmann (micha-bbg) +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include "lua_threads.h" + +/* maximum recursive depth of table copies. */ +#define MAX_COPY_DEPTH 30 + +int CLLThread::llthread_copy_table_from_cache(llthread_copy_state *state, int idx) +{ + void *ptr; + + /* convert table to pointer for lookup in cache. */ + ptr = (void *)lua_topointer(state->from_L, idx); + if (ptr == NULL) return 0; /* can't convert to pointer. */ + + /* check if we need to create the cache. */ + if (!state->has_cache) { + lua_newtable(state->to_L); + lua_replace(state->to_L, state->cache_idx); + state->has_cache = 1; + } + + lua_pushlightuserdata(state->to_L, ptr); + lua_rawget(state->to_L, state->cache_idx); + if (lua_isnil(state->to_L, -1)) { + /* not in cache. */ + lua_pop(state->to_L, 1); + /* create new table and add to cache. */ + lua_newtable(state->to_L); + lua_pushlightuserdata(state->to_L, ptr); + lua_pushvalue(state->to_L, -2); + lua_rawset(state->to_L, state->cache_idx); + return 0; + } + /* found table in cache. */ + return 1; +} + +int CLLThread::llthread_copy_value(llthread_copy_state *state, int depth, int idx) +{ + const char *str; + size_t str_len; + + /* Maximum recursive depth */ + if (++depth > MAX_COPY_DEPTH) { + return luaL_error(state->from_L, "Hit maximum copy depth (%d > %d).", depth, MAX_COPY_DEPTH); + } + + /* only support string/number/boolean/nil/table/lightuserdata. */ + switch (lua_type(state->from_L, idx)) { + case LUA_TNIL: + lua_pushnil(state->to_L); + break; + case LUA_TNUMBER: + lua_pushnumber(state->to_L, lua_tonumber(state->from_L, idx)); + break; + case LUA_TBOOLEAN: + lua_pushboolean(state->to_L, lua_toboolean(state->from_L, idx)); + break; + case LUA_TSTRING: + str = lua_tolstring(state->from_L, idx, &(str_len)); + lua_pushlstring(state->to_L, str, str_len); + break; + case LUA_TLIGHTUSERDATA: + lua_pushlightuserdata(state->to_L, lua_touserdata(state->from_L, idx)); + break; + case LUA_TTABLE: + /* make sure there is room on the new state for 3 values (table,key,value) */ + if (!lua_checkstack(state->to_L, 3)) { + return luaL_error(state->from_L, "To stack overflow!"); + } + /* make room on from stack for key/value pairs. */ + luaL_checkstack(state->from_L, 2, "From stack overflow!"); + + /* check cache for table. */ + if (llthread_copy_table_from_cache(state, idx)) { + /* found in cache don't need to copy table. */ + break; + } + lua_pushnil(state->from_L); + while (lua_next(state->from_L, idx) != 0) { + /* key is at (top - 1), value at (top), but we need to normalize these + * to positive indices */ + int kv_pos = lua_gettop(state->from_L); + /* copy key */ + llthread_copy_value(state, depth, kv_pos - 1); + /* copy value */ + llthread_copy_value(state, depth, kv_pos); + /* Copied key and value are now at -2 and -1 in state->to_L. */ + lua_settable(state->to_L, -3); + /* Pop value for next iteration */ + lua_pop(state->from_L, 1); + } + break; + case LUA_TFUNCTION: + if (lua_iscfunction(state->from_L, idx)) { + lua_CFunction fn = lua_tocfunction(state->from_L, idx); + lua_pushcfunction(state->to_L, fn); + break; + } + case LUA_TUSERDATA: + case LUA_TTHREAD: + default: + if (state->is_arg) { + return luaL_argerror(state->from_L, idx, "function/userdata/thread types un-supported."); + } else { + /* convert un-supported types to an error string. */ + lua_pushfstring(state->to_L, "Un-supported value: %s: %p", + lua_typename(state->from_L, lua_type(state->from_L, idx)), lua_topointer(state->from_L, idx)); + } + } + + return 1; +} + +int CLLThread::llthread_copy_values(lua_State *from_L, lua_State *to_L, int idx, int top, int is_arg) +{ + llthread_copy_state state; + int nvalues = 0; + int n; + + nvalues = (top - idx) + 1; + /* make sure there is room on the new state for the values. */ + if (!lua_checkstack(to_L, nvalues + 1)) { + return luaL_error(from_L, "To stack overflow!"); + } + + /* setup copy state. */ + state.from_L = from_L; + state.to_L = to_L; + state.is_arg = is_arg; + state.has_cache = 0; /* don't create cache table unless it is needed. */ + lua_pushnil(to_L); + state.cache_idx = lua_gettop(to_L); + + nvalues = 0; + for (n = idx; n <= top; n++) { + llthread_copy_value(&state, 0, n); + ++nvalues; + } + + /* remove cache table. */ + lua_remove(to_L, state.cache_idx); + + return nvalues; +} diff --git a/src/gui/lua/lua_threads_functions.cpp b/src/gui/lua/lua_threads_functions.cpp new file mode 100644 index 000000000..57b4744d5 --- /dev/null +++ b/src/gui/lua/lua_threads_functions.cpp @@ -0,0 +1,264 @@ +/****************************************************************************** +* Copyright (c) 2011 by Robert G. Jakabosky +* Copyright (c) 2015 M. Liebmann (micha-bbg) +* +* Permission is hereby granted, free of charge, to any person obtaining a copy +* of this software and associated documentation files (the "Software"), to deal +* in the Software without restriction, including without limitation the rights +* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +* copies of the Software, and to permit persons to whom the Software is +* furnished to do so, subject to the following conditions: +* +* The above copyright notice and this permission notice shall be included in +* all copies or substantial portions of the Software. +* +* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +* THE SOFTWARE. +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "lua_threads.h" + +void CLLThread::LuaThreadsRegister(lua_State *L) +{ + luaL_Reg meth[] = { + { "new", CLLThread::l_llthread_new }, + { "start", CLLThread::l_llthread_start }, + { "cancel", CLLThread::l_llthread_cancel }, + { "join", CLLThread::l_llthread_join }, +#if 0 + /* Fix me */ + { "set_logger", CLLThread::l_llthread_set_logger }, +#endif + { "started", CLLThread::l_llthread_started }, + { "detached", CLLThread::l_llthread_detached }, + { "joinable", CLLThread::l_llthread_joinable }, + { "__gc", CLLThread::l_llthread_delete }, + { NULL, NULL } + }; + + luaL_newmetatable(L, LLTHREAD_TAG); + luaL_setfuncs(L, meth, 0); + lua_pushvalue(L, -1); + lua_setfield(L, -1, "__index"); + lua_setglobal(L, LLTHREAD_TAG); +} + +int CLLThread::l_llthread_new(lua_State *L) +{ + size_t lua_code_len; const char *lua_code = luaL_checklstring(L, 1, &lua_code_len); + + llthread_t **_this = (llthread_t **) lua_newuserdata(L, sizeof(llthread_t *)); + luaL_getmetatable(L, LLTHREAD_TAG); + lua_setmetatable(L, -2); + lua_insert(L, 2); /*move self prior args*/ + *_this = llthread_create(L, lua_code, lua_code_len); + + lua_settop(L, 2); + return 1; +} + +int CLLThread::l_llthread_start(lua_State *L) +{ + llthread_t *_this = l_llthread_at(L, 1); + int start_detached = lua_toboolean(L, 2); + int joinable, rc; + + if (!lua_isnone(L, 3)) + joinable = lua_toboolean(L, 3); + else + joinable = start_detached ? 0 : 1; + + if (IS(_this, STARTED)) { + return fail(L, "Thread already started."); + } + + rc = llthread_start(_this, start_detached, joinable); + if (rc != 0) { + char buf[ERROR_LEN]; + strerror_r(errno, buf, ERROR_LEN); + return fail(L, buf); + } + + lua_settop(L, 1); // return _this + return 1; +} + +int CLLThread::l_llthread_cancel(lua_State *L) +{ + llthread_t *_this = l_llthread_at(L, 1); + /* llthread_child_t *child = _this->child; */ + int rc; + + if (!IS(_this, STARTED )) { + return fail(L, "Can't cancel a thread that hasn't be started."); + } + if ( IS(_this, DETACHED)) { + return fail(L, "Can't cancel a thread that has been detached."); + } + if ( IS(_this, JOINED )) { + return fail(L, "Can't cancel a thread that has already been joined."); + } + + /* cancel the thread. */ + rc = llthread_cancel(_this); + + if ( rc == JOIN_ETIMEDOUT ) { + lua_pushboolean(L, 1); + return 1; + } + + if (rc == JOIN_OK) { + lua_pushboolean(L, 0); + return 1; + } + + char buf[ERROR_LEN]; + strerror_r(errno, buf, ERROR_LEN); + /* llthread_cleanup_child(_this); */ + return fail(L, buf); +} + +int CLLThread::l_llthread_join(lua_State *L) +{ + llthread_t *_this = l_llthread_at(L, 1); + llthread_child_t *child = _this->child; + int rc; + + if (!IS(_this, STARTED )) { + return fail(L, "Can't join a thread that hasn't be started."); + } + if ( IS(_this, DETACHED) && !IS(_this, JOINABLE)) { + return fail(L, "Can't join a thread that has been detached."); + } + if ( IS(_this, JOINED )) { + return fail(L, "Can't join a thread that has already been joined."); + } + + /* join the thread. */ + rc = llthread_join(_this, luaL_optint(L, 2, INFINITE_JOIN_TIMEOUT)); + + if (child && IS(_this, JOINED)) { + int top; + + if (IS(_this, DETACHED) || !IS(_this, JOINABLE)) { + /*child lua state has been destroyed by child thread*/ + /*@todo return thread exit code*/ + lua_pushboolean(L, 1); + lua_pushnumber(L, 0); + return 2; + } + + /* copy values from child lua state */ + if (child->status != 0) { + const char *err_msg = lua_tostring(child->L, -1); + lua_pushboolean(L, 0); + lua_pushfstring(L, "Error from child thread: %s", err_msg); + top = 2; + } else { + lua_pushboolean(L, 1); + top = lua_gettop(child->L); + /* return results to parent thread. */ + llthread_push_results(L, child, 2, top); + } + +// llthread_cleanup_child(_this); + return top; + } + + if ( rc == JOIN_ETIMEDOUT ) { + return fail(L, "timeout"); + } + + char buf[ERROR_LEN]; + strerror_r(errno, buf, ERROR_LEN); + /* llthread_cleanup_child(_this); */ + return fail(L, buf); +} + +int CLLThread::l_llthread_alive(lua_State *L) +{ + llthread_t *_this = l_llthread_at(L, 1); + /* llthread_child_t *child = _this->child; */ + int rc; + + if (!IS(_this, STARTED )) { + return fail(L, "Can't join a thread that hasn't be started."); + } + if ( IS(_this, DETACHED) && !IS(_this, JOINABLE)) { + return fail(L, "Can't join a thread that has been detached."); + } + if ( IS(_this, JOINED )) { + return fail(L, "Can't join a thread that has already been joined."); + } + + /* join the thread. */ + rc = llthread_alive(_this); + + if ( rc == JOIN_ETIMEDOUT ) { + lua_pushboolean(L, 1); + return 1; + } + + if (rc == JOIN_OK) { + lua_pushboolean(L, 0); + return 1; + } + + char buf[ERROR_LEN]; + strerror_r(errno, buf, ERROR_LEN); + /* llthread_cleanup_child(_this); */ + return fail(L, buf); +} + +int CLLThread::l_llthread_set_logger(lua_State *L) +{ + lua_settop(L, 1); + luaL_argcheck(L, lua_isfunction(L, 1), 1, "function expected"); + lua_rawsetp(L, LUA_REGISTRYINDEX, LLTHREAD_LOGGER_HOLDER); + return 0; +} + +int CLLThread::l_llthread_started(lua_State *L) +{ + llthread_t *_this = l_llthread_at(L, 1); + lua_pushboolean(L, IS(_this, STARTED)?1:0); + return 1; +} + +int CLLThread::l_llthread_detached(lua_State *L) +{ + llthread_t *_this = l_llthread_at(L, 1); + lua_pushboolean(L, IS(_this, DETACHED)?1:0); + return 1; +} + +int CLLThread::l_llthread_joinable(lua_State *L) +{ + llthread_t *_this = l_llthread_at(L, 1); + lua_pushboolean(L, IS(_this, JOINABLE)?1:0); + return 1; +} + +int CLLThread::l_llthread_delete(lua_State *L) +{ + llthread_t **pthis = (llthread_t **)luaL_checkudata(L, 1, LLTHREAD_TAG); + luaL_argcheck (L, pthis != NULL, 1, "thread expected"); + if (*pthis == NULL) return 0; + llthread_destroy(*pthis); + *pthis = NULL; + + return 0; +} diff --git a/src/gui/lua/luainstance.cpp b/src/gui/lua/luainstance.cpp index f85805621..759b4c738 100644 --- a/src/gui/lua/luainstance.cpp +++ b/src/gui/lua/luainstance.cpp @@ -46,6 +46,7 @@ #include "lua_menue.h" #include "lua_messagebox.h" #include "lua_misc.h" +#include "lua_threads.h" #include "lua_video.h" extern CPictureViewer * g_PicViewer; @@ -402,7 +403,7 @@ CLuaInstance::CLuaInstance() lua = luaL_newstate(); /* register standard + custom functions. */ - registerFunctions(); + LuaInstRegisterFunctions(lua); } CLuaInstance::~CLuaInstance() @@ -508,50 +509,6 @@ void CLuaInstance::abortScript() lua_sethook(lua, &abortHook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKCOUNT, 1); } -const luaL_Reg CLuaInstance::methods[] = -{ - { "GetInput", CLuaInstance::GetInput }, - { "Blit", CLuaInstance::Blit }, - { "GetLanguage", CLuaInstance::GetLanguage }, - { "PaintBox", CLuaInstance::PaintBox }, - { "paintHLine", CLuaInstance::paintHLineRel }, - { "paintVLine", CLuaInstance::paintVLineRel }, - { "RenderString", CLuaInstance::RenderString }, - { "getRenderWidth", CLuaInstance::getRenderWidth }, - { "FontHeight", CLuaInstance::FontHeight }, - { "getDynFont", CLuaInstance::getDynFont }, - { "PaintIcon", CLuaInstance::PaintIcon }, - { "DisplayImage", CLuaInstance::DisplayImage }, - { "GetSize", CLuaInstance::GetSize }, - { "saveScreen", CLuaInstance::saveScreen }, - { "restoreScreen", CLuaInstance::restoreScreen }, - { "deleteSavedScreen", CLuaInstance::deleteSavedScreen }, - - /* - lua_misc.cpp - Deprecated, for the future separate class for misc functions - */ - { "strFind", CLuaInstMisc::getInstance()->strFind_old }, - { "strSub", CLuaInstMisc::getInstance()->strSub_old }, - { "enableInfoClock", CLuaInstMisc::getInstance()->enableInfoClock_old }, - { "runScript", CLuaInstMisc::getInstance()->runScriptExt_old }, - { "GetRevision", CLuaInstMisc::getInstance()->GetRevision_old }, - { "checkVersion", CLuaInstMisc::getInstance()->checkVersion_old }, - - /* - lua_video.cpp - Deprecated, for the future separate class for video - */ - { "setBlank", CLuaInstVideo::getInstance()->setBlank_old }, - { "ShowPicture", CLuaInstVideo::getInstance()->ShowPicture_old }, - { "StopPicture", CLuaInstVideo::getInstance()->StopPicture_old }, - { "PlayFile", CLuaInstVideo::getInstance()->PlayFile_old }, - { "zapitStopPlayBack", CLuaInstVideo::getInstance()->zapitStopPlayBack_old }, - { "channelRezap", CLuaInstVideo::getInstance()->channelRezap_old }, - { "createChannelIDfromUrl", CLuaInstVideo::getInstance()->createChannelIDfromUrl_old }, - { NULL, NULL } -}; - #ifdef STATIC_LUAPOSIX /* hack: we link against luaposix, which is included in our * custom built lualib */ @@ -559,47 +516,101 @@ extern "C" { LUAMOD_API int (luaopen_posix_c) (lua_State *L); } #endif /* load basic functions and register our own C callbacks */ -void CLuaInstance::registerFunctions() +void LuaInstRegisterFunctions(lua_State *L, bool fromThreads/*=false*/) { - luaL_openlibs(lua); - luaopen_table(lua); - luaopen_io(lua); - luaopen_string(lua); - luaopen_math(lua); - lua_newtable(lua); - int methodtable = lua_gettop(lua); - luaL_newmetatable(lua, className); - int metatable = lua_gettop(lua); - lua_pushliteral(lua, "__metatable"); - lua_pushvalue(lua, methodtable); - lua_settable(lua, metatable); +// ------------------------------------------ + const luaL_Reg methods[] = + { + { "GetInput", CLuaInstance::GetInput }, + { "Blit", CLuaInstance::Blit }, + { "GetLanguage", CLuaInstance::GetLanguage }, + { "PaintBox", CLuaInstance::PaintBox }, + { "paintHLine", CLuaInstance::paintHLineRel }, + { "paintVLine", CLuaInstance::paintVLineRel }, + { "RenderString", CLuaInstance::RenderString }, + { "getRenderWidth", CLuaInstance::getRenderWidth }, + { "FontHeight", CLuaInstance::FontHeight }, + { "getDynFont", CLuaInstance::getDynFont }, + { "PaintIcon", CLuaInstance::PaintIcon }, + { "DisplayImage", CLuaInstance::DisplayImage }, + { "GetSize", CLuaInstance::GetSize }, + { "saveScreen", CLuaInstance::saveScreen }, + { "restoreScreen", CLuaInstance::restoreScreen }, + { "deleteSavedScreen", CLuaInstance::deleteSavedScreen }, - lua_pushliteral(lua, "__index"); - lua_pushvalue(lua, methodtable); - lua_settable(lua, metatable); + /* + lua_misc.cpp + Deprecated, for the future using separate class for misc functions + */ + { "strFind", CLuaInstMisc::getInstance()->strFind_old }, + { "strSub", CLuaInstMisc::getInstance()->strSub_old }, + { "enableInfoClock", CLuaInstMisc::getInstance()->enableInfoClock_old }, + { "runScript", CLuaInstMisc::getInstance()->runScriptExt_old }, + { "GetRevision", CLuaInstMisc::getInstance()->GetRevision_old }, + { "checkVersion", CLuaInstMisc::getInstance()->checkVersion_old }, - lua_pushliteral(lua, "__gc"); - lua_pushcfunction(lua, GCWindow); - lua_settable(lua, metatable); + /* + lua_video.cpp + Deprecated, for the future using separate class for video + */ + { "setBlank", CLuaInstVideo::getInstance()->setBlank_old }, + { "ShowPicture", CLuaInstVideo::getInstance()->ShowPicture_old }, + { "StopPicture", CLuaInstVideo::getInstance()->StopPicture_old }, + { "PlayFile", CLuaInstVideo::getInstance()->PlayFile_old }, + { "zapitStopPlayBack", CLuaInstVideo::getInstance()->zapitStopPlayBack_old }, + { "channelRezap", CLuaInstVideo::getInstance()->channelRezap_old }, + { "createChannelIDfromUrl", CLuaInstVideo::getInstance()->createChannelIDfromUrl_old }, + { NULL, NULL } + }; +// ------------------------------------------ + int top; + if (fromThreads) + top = lua_gettop(L); - lua_pop(lua, 1); + luaL_openlibs(L); + luaopen_table(L); + luaopen_io(L); + luaopen_string(L); + luaopen_math(L); + lua_newtable(L); + int methodtable = lua_gettop(L); + luaL_newmetatable(L, LUA_CLASSNAME); + int metatable = lua_gettop(L); + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); - luaL_setfuncs(lua, methods, 0); - lua_pop(lua, 1); + lua_pushliteral(L, "__index"); + lua_pushvalue(L, methodtable); + lua_settable(L, metatable); - lua_register(lua, className, NewWindow); + lua_pushliteral(L, "__gc"); + lua_pushcfunction(L, CLuaInstance::GCWindow); + lua_settable(L, metatable); - CLuaInstCCPicture::getInstance()->CCPictureRegister(lua); - CLuaInstCCSignalbox::getInstance()->CCSignalBoxRegister(lua); - CLuaInstCCText::getInstance()->CCTextRegister(lua); - CLuaInstCCWindow::getInstance()->CCWindowRegister(lua); - CLuaInstConfigFile::getInstance()->LuaConfigFileRegister(lua); - CLuaInstCurl::getInstance()->LuaCurlRegister(lua); - CLuaInstHintbox::getInstance()->HintboxRegister(lua); - CLuaInstMenu::getInstance()->MenuRegister(lua); - CLuaInstMessagebox::getInstance()->MessageboxRegister(lua); - CLuaInstMisc::getInstance()->LuaMiscRegister(lua); - CLuaInstVideo::getInstance()->LuaVideoRegister(lua); + lua_pop(L, 1); + + luaL_setfuncs(L, methods, 0); + lua_pop(L, 1); + + lua_register(L, LUA_CLASSNAME, CLuaInstance::NewWindow); + + if (fromThreads) + lua_settop(L, top); +// ------------------------------------------ + CLuaInstCCPicture::getInstance()->CCPictureRegister(L); + CLuaInstCCSignalbox::getInstance()->CCSignalBoxRegister(L); + CLuaInstCCText::getInstance()->CCTextRegister(L); + CLuaInstCCWindow::getInstance()->CCWindowRegister(L); + CLuaInstConfigFile::getInstance()->LuaConfigFileRegister(L); + CLuaInstCurl::getInstance()->LuaCurlRegister(L); + CLuaInstHintbox::getInstance()->HintboxRegister(L); + CLuaInstMenu::getInstance()->MenuRegister(L); + CLuaInstMessagebox::getInstance()->MessageboxRegister(L); + CLuaInstMisc::getInstance()->LuaMiscRegister(L); + CLuaInstVideo::getInstance()->LuaVideoRegister(L); + if (!fromThreads) + CLLThread::getInstance()->LuaThreadsRegister(L); } CLuaData *CLuaInstance::CheckData(lua_State *L, int narg) @@ -653,10 +664,10 @@ int CLuaInstance::GCWindow(lua_State *L) CNeutrinoFonts::getInstance()->deleteDynFontExtAll(); /* restoreNeutrino at plugin closing, when blocked from plugin */ - LUA_DEBUG(">>>>[%s:%d] (restoreNeutrino()) BlockedFromPlugin: %d, Playing: %d\n", __func__, __LINE__, - CMoviePlayerGui::getInstance().getBlockedFromPlugin, CMoviePlayerGui::getInstance().Playing()); - if (CMoviePlayerGui::getInstance().getBlockedFromPlugin() && - CMoviePlayerGui::getInstance().Playing()) { + bool block = CMoviePlayerGui::getInstance().getBlockedFromPlugin(); + bool play = CMoviePlayerGui::getInstance().Playing(); + LUA_DEBUG(">>>>[%s:%d] (restoreNeutrino()) BlockedFromPlugin: %d, Playing: %d\n", __func__, __LINE__, block, play); + if (block && play) { CMoviePlayerGui::getInstance().setBlockedFromPlugin(false); CMoviePlayerGui::getInstance().restoreNeutrino(); } diff --git a/src/gui/lua/luainstance.h b/src/gui/lua/luainstance.h index 9a8ec0ab9..425a2fdbe 100644 --- a/src/gui/lua/luainstance.h +++ b/src/gui/lua/luainstance.h @@ -31,14 +31,14 @@ extern "C" { #include "luainstance_helpers.h" #define LUA_API_VERSION_MAJOR 1 -#define LUA_API_VERSION_MINOR 33 +#define LUA_API_VERSION_MINOR 34 + +void LuaInstRegisterFunctions(lua_State *L, bool fromThreads=false); /* inspired by Steve Kemp http://www.steve.org.uk/ */ class CLuaInstance { static const char className[]; - static const luaL_Reg methods[]; - static const luaL_Reg menu_methods[]; static CLuaData *CheckData(lua_State *L, int narg); public: CLuaInstance(); @@ -58,10 +58,6 @@ public: // The last parameter to NULL is imperative. void runScript(const char *fileName, const char *arg0, ...); -private: - lua_State* lua; - void registerFunctions(); - static int NewWindow(lua_State *L); static int GCWindow(lua_State *L); static int GetInput(lua_State *L); @@ -80,6 +76,10 @@ private: static int saveScreen(lua_State *L); static int restoreScreen(lua_State *L); static int deleteSavedScreen(lua_State *L); + +private: + lua_State* lua; + }; #endif /* _LUAINSTANCE_H */