diff --git a/radio/src/gui/128x64/model_custom_scripts.cpp b/radio/src/gui/128x64/model_custom_scripts.cpp index 8bb90859a..a09b5ec37 100644 --- a/radio/src/gui/128x64/model_custom_scripts.cpp +++ b/radio/src/gui/128x64/model_custom_scripts.cpp @@ -157,9 +157,6 @@ void menuModelCustomScripts(event_t event) case SCRIPT_SYNTAX_ERROR: lcdDrawText(30*FW+2, y, "(error)"); break; - case SCRIPT_KILLED: - lcdDrawText(29*FW+2, y, "(killed)"); - break; default: lcdDrawNumber(34*FW, y, luaGetCpuUsed(scriptIndex), RIGHT); lcdDrawChar(34*FW, y, '%'); diff --git a/radio/src/gui/128x64/view_telemetry.cpp b/radio/src/gui/128x64/view_telemetry.cpp index 3b6835f64..a3d6afee0 100644 --- a/radio/src/gui/128x64/view_telemetry.cpp +++ b/radio/src/gui/128x64/view_telemetry.cpp @@ -177,7 +177,6 @@ bool displayTelemetryScreen() return false; // requested lua telemetry screen not available case SCRIPT_SYNTAX_ERROR: case SCRIPT_PANIC: - case SCRIPT_KILLED: luaError(lsScripts, state, false); return true; } diff --git a/radio/src/gui/212x64/model_custom_scripts.cpp b/radio/src/gui/212x64/model_custom_scripts.cpp index 12b904e04..d79ae9f88 100644 --- a/radio/src/gui/212x64/model_custom_scripts.cpp +++ b/radio/src/gui/212x64/model_custom_scripts.cpp @@ -150,9 +150,6 @@ void menuModelCustomScripts(event_t event) case SCRIPT_SYNTAX_ERROR: lcdDrawText(30*FW+2, y, "(error)"); break; - case SCRIPT_KILLED: - lcdDrawText(29*FW+2, y, "(killed)"); - break; default: lcdDrawNumber(34*FW, y, luaGetCpuUsed(scriptIndex), RIGHT); lcdDrawChar(34*FW, y, '%'); diff --git a/radio/src/gui/212x64/view_telemetry.cpp b/radio/src/gui/212x64/view_telemetry.cpp index e15fc5781..2fe96d768 100644 --- a/radio/src/gui/212x64/view_telemetry.cpp +++ b/radio/src/gui/212x64/view_telemetry.cpp @@ -179,7 +179,6 @@ bool displayTelemetryScreen() return false; // requested lua telemetry screen not available case SCRIPT_SYNTAX_ERROR: case SCRIPT_PANIC: - case SCRIPT_KILLED: luaError(lsScripts, state, false); return true; } diff --git a/radio/src/gui/colorlcd/model_mixer_scripts.cpp b/radio/src/gui/colorlcd/model_mixer_scripts.cpp index 0e0fd012a..dec877b7b 100644 --- a/radio/src/gui/colorlcd/model_mixer_scripts.cpp +++ b/radio/src/gui/colorlcd/model_mixer_scripts.cpp @@ -167,7 +167,6 @@ class ScriptEditWindow : public Page { }; constexpr char SCRIPT_STATUS_ERROR[] = "(error)"; -constexpr char SCRIPT_STATUS_KILLED[] = "(killed)"; class ScriptLineButton : public Button { @@ -204,10 +203,6 @@ class ScriptLineButton : public Button dc->drawSizedText(x, y, SCRIPT_STATUS_ERROR, sizeof(SCRIPT_STATUS_ERROR), textColor); break; - case SCRIPT_KILLED: - dc->drawSizedText(x, y, SCRIPT_STATUS_KILLED, - sizeof(SCRIPT_STATUS_KILLED), textColor); - break; default: dc->drawNumber(x, y, runtimeData->instructions, textColor, 0, nullptr, "%"); break; diff --git a/radio/src/gui/colorlcd/standalone_lua.cpp b/radio/src/gui/colorlcd/standalone_lua.cpp index abe74cf81..95bd504d0 100644 --- a/radio/src/gui/colorlcd/standalone_lua.cpp +++ b/radio/src/gui/colorlcd/standalone_lua.cpp @@ -155,20 +155,23 @@ void StandaloneLuaWindow::runLua(event_t evt) // Set global LUA LCD buffer luaLcdBuffer = &lcdBuffer; - TRACE("evt = 0x%x", evt); - - bool hasRun = luaTask(evt, RUN_STNDAL_SCRIPT, true); - if (hasRun) { + if (luaState != INTERPRETER_RELOAD_PERMANENT_SCRIPTS) { + if (luaTask(evt, true)) { #if defined(DEBUG_WINDOWS) - TRACE("# StandaloneLuaWindow::invalidate()"); + TRACE("# StandaloneLuaWindow::invalidate()"); #endif - invalidate(); - } else { + invalidate(); + } else { + // The script was preempted, and the LCD should not be updated yet + } + } + + if (luaState == INTERPRETER_RELOAD_PERMANENT_SCRIPTS) { // Script does not run anymore... TRACE("LUA standalone script exited: deleting window!"); deleteLater(); } - + // Kill global LUA LCD buffer luaLcdBuffer = nullptr; } diff --git a/radio/src/lua/api_general.cpp b/radio/src/lua/api_general.cpp index 65322ae05..41d88a631 100644 --- a/radio/src/lua/api_general.cpp +++ b/radio/src/lua/api_general.cpp @@ -1081,12 +1081,13 @@ Stops key state machine. See [Key Events](../key_events.md) for the detailed des */ static int luaKillEvents(lua_State * L) { - uint8_t key = EVT_KEY_MASK(luaL_checkinteger(L, 1)); + event_t key = EVT_KEY_MASK(luaL_checkinteger(L, 1)); // prevent killing maskable keys (only in telemetry scripts) - // TODO add which tpye of script is running before p_call() + // TODO add which type of script is running before lua_resume() if (IS_MASKABLE(key)) { killEvents(key); - } + luaEmptyEventBuffer(); + } return 0; } diff --git a/radio/src/lua/interface.cpp b/radio/src/lua/interface.cpp index 755c67150..4f2fea594 100644 --- a/radio/src/lua/interface.cpp +++ b/radio/src/lua/interface.cpp @@ -39,20 +39,25 @@ extern "C" { #include } -#define PERMANENT_SCRIPTS_MAX_INSTRUCTIONS (10000/100) -#define MANUAL_SCRIPTS_MAX_INSTRUCTIONS (20000/100) -#define LUA_WARNING_INFO_LEN 64 +#define PERMANENT_SCRIPTS_MAX_INSTRUCTIONS 100 +#define LUA_WARNING_INFO_LEN 64 +#define LUA_TASK_PERIOD_TICKS 5 // 50 ms +// Since we may not run FG every time, keep the first two events in a buffer +event_t events[2] = {0, 0}; +// The main thread - lsScripts is now a coroutine +lua_State * L = nullptr; lua_State *lsScripts = nullptr; uint8_t luaState = 0; uint8_t luaScriptsCount = 0; bool luaLcdAllowed = false; ScriptInternalData scriptInternalData[MAX_SCRIPTS]; ScriptInputsOutputs scriptInputsOutputs[MAX_SCRIPTS]; -ScriptInternalData standaloneScript; + uint16_t maxLuaInterval = 0; uint16_t maxLuaDuration = 0; uint8_t instructionsPercent = 0; +tmr10ms_t luaCycleStart; char lua_warning_info[LUA_WARNING_INFO_LEN+1]; struct our_longjmp * global_lj = 0; #if defined(COLORLCD) @@ -95,7 +100,7 @@ void *tracer_alloc(void * ud, void * ptr, size_t osize, size_t nsize) /* custom panic handler */ int custom_lua_atpanic(lua_State * L) { - TRACE("PANIC: unprotected error in call to Lua API (%s)", lua_tostring(L, -1)); + TRACE_ERROR("PANIC: unprotected error in call to Lua API (%s)\n", lua_tostring(L, -1)); if (global_lj) { longjmp(global_lj->b, 1); /* will never return */ @@ -103,32 +108,14 @@ int custom_lua_atpanic(lua_State * L) return 0; } -void luaHook(lua_State * L, lua_Debug *ar) +static void luaHook(lua_State * L, lua_Debug *ar) { if (ar->event == LUA_HOOKCOUNT) { - instructionsPercent++; -#if defined(DEBUG) - // Disable Lua script instructions limit in DEBUG mode, - // just report max value reached - static uint16_t max = 0; - if (instructionsPercent > 100) { - if (max + 10 < instructionsPercent) { - max = instructionsPercent; - TRACE("LUA instructionsPercent %u%%", (uint32_t)max); + if (get_tmr10ms() - luaCycleStart >= LUA_TASK_PERIOD_TICKS) { + lua_yield(lsScripts, 0); } } - else if (instructionsPercent < 10) { - max = 0; - } -#else - if (instructionsPercent > 100) { - // From now on, as soon as a line is executed, error - // keep erroring until you're script reaches the top - lua_sethook(L, luaHook, LUA_MASKLINE, 0); - luaL_error(L, "CPU limit"); - } -#endif - } + #if defined(LUA_ALLOCATOR_TRACER) else if (ar->event == LUA_HOOKLINE) { lua_getinfo(L, "nSl", ar); @@ -144,41 +131,41 @@ void luaHook(lua_State * L, lua_Debug *ar) #endif // #if defined(LUA_ALLOCATOR_TRACER) } -void luaSetInstructionsLimit(lua_State * L, int count) +void luaEmptyEventBuffer() { - instructionsPercent = 0; -#if defined(LUA_ALLOCATOR_TRACER) - lua_sethook(L, luaHook, LUA_MASKCOUNT|LUA_MASKLINE, count); -#else - lua_sethook(L, luaHook, LUA_MASKCOUNT, count); -#endif + events[0] = 0; + events[1] = 0; } -int luaGetInputs(lua_State * L, ScriptInputsOutputs & sid) +#if defined(LUA_MODEL_SCRIPTS) +void luaGetInputs(ScriptInputsOutputs & sid) { - if (!lua_istable(L, -1)) - return -1; + if (!lua_istable(lsScripts, -1)) + return; memclear(sid.inputs, sizeof(sid.inputs)); sid.inputsCount = 0; - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - luaL_checktype(L, -2, LUA_TNUMBER); // key is number - luaL_checktype(L, -1, LUA_TTABLE); // value is table + for (lua_pushnil(lsScripts); lua_next(lsScripts, -2); lua_pop(lsScripts, 1)) { + luaL_checktype(lsScripts, -2, LUA_TNUMBER); // key is number + luaL_checktype(lsScripts, -1, LUA_TTABLE); // value is table if (sid.inputsCountname = lua_tostring(L, -1); + luaL_checktype(lsScripts, -2, LUA_TNUMBER); // key is number + luaL_checktype(lsScripts, -1, LUA_TSTRING); // value is string + lua_xmove(lsScripts, L, 1); // To preserve the string value, move it to the main stack + lua_pushnil(lsScripts); // Keep the stack balanced + lua_insert(L, -2); // Keep the coroutine at the top of the main stack + si->name = lua_tostring(L, -2); break; case 1: - luaL_checktype(L, -2, LUA_TNUMBER); // key is number - luaL_checktype(L, -1, LUA_TNUMBER); // value is number - type = lua_tointeger(L, -1); + luaL_checktype(lsScripts, -2, LUA_TNUMBER); // key is number + luaL_checktype(lsScripts, -1, LUA_TNUMBER); // value is number + type = lua_tointeger(lsScripts, -1); if (type >= INPUT_TYPE_FIRST && type <= INPUT_TYPE_LAST) { si->type = type; } @@ -191,24 +178,24 @@ int luaGetInputs(lua_State * L, ScriptInputsOutputs & sid) } break; case 2: - luaL_checktype(L, -2, LUA_TNUMBER); // key is number - luaL_checktype(L, -1, LUA_TNUMBER); // value is number + luaL_checktype(lsScripts, -2, LUA_TNUMBER); // key is number + luaL_checktype(lsScripts, -1, LUA_TNUMBER); // value is number if (si->type == INPUT_TYPE_VALUE) { - si->min = lua_tointeger(L, -1); + si->min = lua_tointeger(lsScripts, -1); } break; case 3: - luaL_checktype(L, -2, LUA_TNUMBER); // key is number - luaL_checktype(L, -1, LUA_TNUMBER); // value is number + luaL_checktype(lsScripts, -2, LUA_TNUMBER); // key is number + luaL_checktype(lsScripts, -1, LUA_TNUMBER); // value is number if (si->type == INPUT_TYPE_VALUE) { - si->max = lua_tointeger(L, -1); + si->max = lua_tointeger(lsScripts, -1); } break; case 4: - luaL_checktype(L, -2, LUA_TNUMBER); // key is number - luaL_checktype(L, -1, LUA_TNUMBER); // value is number + luaL_checktype(lsScripts, -2, LUA_TNUMBER); // key is number + luaL_checktype(lsScripts, -1, LUA_TNUMBER); // value is number if (si->type == INPUT_TYPE_VALUE) { - si->def = lua_tointeger(L, -1); + si->def = lua_tointeger(lsScripts, -1); } break; } @@ -216,26 +203,26 @@ int luaGetInputs(lua_State * L, ScriptInputsOutputs & sid) sid.inputsCount++; } } - - return 0; } -int luaGetOutputs(lua_State * L, ScriptInputsOutputs & sid) +void luaGetOutputs(ScriptInputsOutputs & sid) { - if (!lua_istable(L, -1)) - return -1; + if (!lua_istable(lsScripts, -1)) + return; sid.outputsCount = 0; - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - luaL_checktype(L, -2, LUA_TNUMBER); // key is number - luaL_checktype(L, -1, LUA_TSTRING); // value is string - if (sid.outputsCountfdate/ftime @@ -374,20 +359,16 @@ static void luaDumpState(lua_State * L, const char * filename, const FILINFO * f TRACE("luaDumpState(%s): Saved bytecode to file.", filename); } } else - TRACE_ERROR("luaDumpState(%s): Error: Could not open output file.", filename); + TRACE_ERROR("luaDumpState(%s): Error: Could not open output file\n", filename); } #endif // LUA_COMPILER /** @fn luaLoadScriptFileToState(lua_State * L, const char * filename, const char * mode) - Load a Lua script file into a given lua_State (stack). May use OpenTx's optional pre-compilation feature to save memory and time during load. - @param L (lua_State) the Lua stack to load into. - @param filename (string) full path and file name of script. - @param mode (string) controls whether the file can be text or binary (that is, a pre-compiled file). Possible values are: "b" only binary. @@ -400,7 +381,6 @@ static void luaDumpState(lua_State * L, const char * filename, const FILINFO * f Eg: "tc" or "btc" (forces "t", overrides "x"). Add "d" to keep extra debug info in the compiled binary. Eg: "td", "btd", or "tcd" (no effect with just "b" or with "x"). - @retval (int) SCRIPT_OK on success (LUA_OK) SCRIPT_NOFILE if file wasn't found for specified mode or Lua could not open file (LUA_ERRFILE) @@ -552,196 +532,145 @@ int luaLoadScriptFileToState(lua_State * L, const char * filename, const char * return ret; } -static int luaLoad(lua_State * L, const char * filename, ScriptInternalData & sid, ScriptInputsOutputs * sio=nullptr) +#if defined(SIMU) || defined(DEBUG) +// Get the name of a script for error reporting etc. +static const char * getScriptName(uint8_t idx) { - int init = 0; - int lstatus = 0; + int ref = scriptInternalData[idx].reference; - sid.instructions = 0; - sid.state = SCRIPT_OK; - - if (luaState == INTERPRETER_PANIC) { - return SCRIPT_PANIC; +#if defined(LUA_MODEL_SCRIPTS) + if (ref <= SCRIPT_MIX_LAST) { + return g_model.scriptsData[ref - SCRIPT_MIX_FIRST].file; + } else +#endif + if (ref <= SCRIPT_FUNC_LAST) { + return g_model.customFn[ref - SCRIPT_FUNC_FIRST].play.name; } - - luaSetInstructionsLimit(L, MANUAL_SCRIPTS_MAX_INSTRUCTIONS); - - PROTECT_LUA() { - sid.state = luaLoadScriptFileToState(L, filename, LUA_SCRIPT_LOAD_MODE); - if (sid.state == SCRIPT_OK && (lstatus = lua_pcall(L, 0, 1, 0)) == LUA_OK && lua_istable(L, -1)) { - for (lua_pushnil(L); lua_next(L, -2); lua_pop(L, 1)) { - const char * key = lua_tostring(L, -2); - if (!strcmp(key, "init")) { - init = luaL_ref(L, LUA_REGISTRYINDEX); - lua_pushnil(L); - } - else if (!strcmp(key, "run")) { - sid.run = luaL_ref(L, LUA_REGISTRYINDEX); - lua_pushnil(L); - } - else if (!strcmp(key, "background")) { - sid.background = luaL_ref(L, LUA_REGISTRYINDEX); - lua_pushnil(L); - } - else if (sio && !strcmp(key, "input")) { - luaGetInputs(L, *sio); - } - else if (sio && !strcmp(key, "output")) { - luaGetOutputs(L, *sio); - } - } - - if (init) { - lua_rawgeti(L, LUA_REGISTRYINDEX, init); - if (lua_pcall(L, 0, 0, 0) != 0) { - TRACE_ERROR("luaLoad(%s): Error in script init(): %s\n", filename, lua_tostring(L, -1)); - sid.state = SCRIPT_SYNTAX_ERROR; - } - luaL_unref(L, LUA_REGISTRYINDEX, init); - lua_gc(L, LUA_GCCOLLECT, 0); - } - } - else if (sid.state == SCRIPT_OK) { - TRACE_ERROR("luaLoad(%s): Error parsing script (%d): %s\n", filename, lstatus, lua_tostring(L, -1)); - sid.state = SCRIPT_SYNTAX_ERROR; - } + else if (ref <= SCRIPT_GFUNC_LAST) { + return g_eeGeneral.customFn[ref - SCRIPT_GFUNC_FIRST].play.name; } +#if defined(PCBTARANIS) + else if (ref <= SCRIPT_TELEMETRY_LAST) { + return g_model.screens[ref - SCRIPT_TELEMETRY_FIRST].script.file; + } +#endif else { - luaDisable(); - return SCRIPT_PANIC; + return "standalone"; } - UNPROTECT_LUA(); +} +#endif + +static bool luaLoad(const char * filename, ScriptInternalData & sid) +{ + sid.state = luaLoadScriptFileToState(lsScripts, filename, LUA_SCRIPT_LOAD_MODE); if (sid.state != SCRIPT_OK) { - luaFree(L, sid); + luaFree(lsScripts, sid); + return true; } - - luaDoGc(L, true); - - return sid.state; + return false; } -bool luaLoadMixScript(uint8_t index) +#if defined(LUA_MODEL_SCRIPTS) +static bool luaLoadMixScript(uint8_t ref) { - ScriptData & sd = g_model.scriptsData[index]; + uint8_t idx = ref - SCRIPT_MIX_FIRST; + ScriptData & sd = g_model.scriptsData[idx]; if (ZEXIST(sd.file)) { ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; - ScriptInputsOutputs * sio = &scriptInputsOutputs[index]; - sid.reference = SCRIPT_MIX_FIRST+index; - sid.state = SCRIPT_NOFILE; + sid.reference = ref; + char filename[sizeof(SCRIPTS_MIXES_PATH) + LEN_SCRIPT_FILENAME + sizeof(SCRIPT_EXT)] = SCRIPTS_MIXES_PATH "/"; strncpy(filename + sizeof(SCRIPTS_MIXES_PATH), sd.file, LEN_SCRIPT_FILENAME); filename[sizeof(SCRIPTS_MIXES_PATH) + LEN_SCRIPT_FILENAME] = '\0'; strcat(filename + sizeof(SCRIPTS_MIXES_PATH), SCRIPT_EXT); - if (luaLoad(lsScripts, filename, sid, sio) == SCRIPT_PANIC) { - return false; - } - } - return true; -} -bool luaLoadFunctionScript(uint8_t index, uint8_t ref) + return luaLoad(filename, sid); + } + return false; +} +#endif + +static bool luaLoadFunctionScript(uint8_t ref) { + uint8_t idx; CustomFunctionData * fn; - if (ref < SCRIPT_GFUNC_FIRST) - fn = &g_model.customFn[index]; - else if (!g_model.noGlobalFunctions) - fn = &g_eeGeneral.customFn[index]; + if (ref <= SCRIPT_FUNC_LAST) { + idx = ref - SCRIPT_FUNC_FIRST; + fn = &g_model.customFn[idx]; + } + else if (!g_model.noGlobalFunctions) { + idx = ref - SCRIPT_GFUNC_FIRST; + fn = &g_eeGeneral.customFn[idx]; + } else - return true; + return false; - if (fn->func == FUNC_PLAY_SCRIPT && ZEXIST(fn->play.name)) { + if (fn -> func == FUNC_PLAY_SCRIPT && ZEXIST(fn -> play.name)) { if (luaScriptsCount < MAX_SCRIPTS) { ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; - sid.reference = ref + index; - sid.state = SCRIPT_NOFILE; + sid.reference = ref; + char filename[sizeof(SCRIPTS_FUNCS_PATH) + LEN_FUNCTION_NAME + sizeof(SCRIPT_EXT)] = SCRIPTS_FUNCS_PATH "/"; strncpy(filename + sizeof(SCRIPTS_FUNCS_PATH), fn->play.name, LEN_FUNCTION_NAME); filename[sizeof(SCRIPTS_FUNCS_PATH) + LEN_FUNCTION_NAME] = '\0'; strcat(filename + sizeof(SCRIPTS_FUNCS_PATH), SCRIPT_EXT); - if (luaLoad(lsScripts, filename, sid) == SCRIPT_PANIC) { - return false; - } + + return luaLoad(filename, sid); } else { POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS); - return false; + return true; } } - return true; + return false; } #if defined(PCBTARANIS) -bool luaLoadTelemetryScript(uint8_t index) +static bool luaLoadTelemetryScript(uint8_t ref) { - TelemetryScreenType screenType = TELEMETRY_SCREEN_TYPE(index); + uint8_t idx = ref - SCRIPT_TELEMETRY_FIRST; + TelemetryScreenType screenType = TELEMETRY_SCREEN_TYPE(idx); if (screenType == TELEMETRY_SCREEN_TYPE_SCRIPT) { - TelemetryScriptData & script = g_model.screens[index].script; + TelemetryScriptData & script = g_model.screens[idx].script; + if (ZEXIST(script.file)) { if (luaScriptsCount < MAX_SCRIPTS) { ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; - sid.reference = SCRIPT_TELEMETRY_FIRST+index; - sid.state = SCRIPT_NOFILE; + sid.reference = ref; + char filename[sizeof(SCRIPTS_TELEM_PATH) + LEN_SCRIPT_FILENAME + sizeof(SCRIPT_EXT)] = SCRIPTS_TELEM_PATH "/"; strncpy(filename + sizeof(SCRIPTS_TELEM_PATH), script.file, LEN_SCRIPT_FILENAME); filename[sizeof(SCRIPTS_TELEM_PATH) + LEN_SCRIPT_FILENAME] = '\0'; strcat(filename + sizeof(SCRIPTS_TELEM_PATH), SCRIPT_EXT); - if (luaLoad(lsScripts, filename, sid) == SCRIPT_PANIC) { - return false; - } + + return luaLoad(filename, sid); } else { POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS); - return false; + return true; } } } - return true; + return false; } #endif -uint8_t isTelemetryScriptAvailable(uint8_t index) +uint8_t isTelemetryScriptAvailable(uint8_t idx) { - for (int i=0; i 100) { - TRACE("Script killed"); - standaloneScript.state = SCRIPT_KILLED; + if (init) { + luaInit(); + if (luaState == INTERPRETER_PANIC) return; + + luaLcdAllowed = false; + initFunction = LUA_NOREF; + luaEmptyEventBuffer(); + + // Initialize loop over references + if (filename) { + ref = SCRIPT_STANDALONE; + } + else { +#if defined(LUA_MODEL_SCRIPTS) + ref = SCRIPT_MIX_FIRST; +#else + ref = SCRIPT_FUNC_FIRST; +#endif + } + } + + // Continue loop with existing reference value + do { + uint8_t countBefore = luaScriptsCount; + int luaStatus = lua_status(lsScripts); + + // If Lua is not yielded, then find the next script to load + if (luaStatus == LUA_OK) { +#if defined(LUA_MODEL_SCRIPTS) + if (ref <= SCRIPT_MIX_LAST) { + if (luaLoadMixScript(ref)) continue; // If error then skip the rest of the loop + } else +#endif + if (ref <= SCRIPT_GFUNC_LAST) { + if (luaLoadFunctionScript(ref)) continue; + } +#if defined(PCBTARANIS) + else if (ref <= SCRIPT_TELEMETRY_LAST) { + if (luaLoadTelemetryScript(ref)) continue; + } +#endif + else { + // Standalone script + ScriptInternalData & sid = scriptInternalData[luaScriptsCount++]; + sid.reference = SCRIPT_STANDALONE; + if (luaLoad(filename, sid)) { luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; + luaError(lsScripts, sid.state, true); + return; + } + } + // Skip the rest of the loop if we did not get a new script + if (countBefore == luaScriptsCount) continue; + } + + int idx = luaScriptsCount - 1; + ScriptInternalData & sid = scriptInternalData[idx]; + + // 1. run chunk() 2. run init(), if available: + do { + // Resume running the coroutine + luaStatus = lua_resume(lsScripts, 0, 0); + + if (luaStatus == LUA_YIELD) { + // Coroutine yielded - wait for the next cycle + return; + } + else if (luaStatus == LUA_OK) { + // Coroutine returned + if (initFunction != LUA_NOREF) { + // init() returned - clean up + luaL_unref(lsScripts, LUA_REGISTRYINDEX, initFunction); + lua_settop(lsScripts, 0); + initFunction = LUA_NOREF; + } + else { + // chunk() returned + lua_settop(lsScripts, 1); // Only one return value, please + + if (lua_istable(lsScripts, -1)) { + // Register functions from the table + sid.run = luaRegisterFunction("run"); + sid.background = luaRegisterFunction("background"); + initFunction = luaRegisterFunction("init"); + if (sid.run == LUA_NOREF) { + TRACE_ERROR("luaLoadScripts(%s): No run function\n", getScriptName(idx)); + sid.state = SCRIPT_SYNTAX_ERROR; + } +#if defined(LUA_MODEL_SCRIPTS) + // Get input/output tables for mixer scripts + if (ref <= SCRIPT_MIX_LAST) { + ScriptInputsOutputs * sio = & scriptInputsOutputs[idx]; + lua_getfield(lsScripts, -1, "input"); + luaGetInputs(*sio); + lua_pop(lsScripts, 1); + lua_getfield(lsScripts, -1, "output"); + luaGetOutputs(*sio); + lua_pop(lsScripts, 1); + } +#endif + } + else { + TRACE_ERROR("luaLoadScripts(%s): The script did not return a table\n", getScriptName(idx)); + sid.state = SCRIPT_SYNTAX_ERROR; + } + + // Pop the table off the stack + lua_pop(lsScripts, 1); + + // If init(), push it on the stack + if (initFunction != LUA_NOREF) { + lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, initFunction); + if (ref == SCRIPT_STANDALONE) luaLcdAllowed = true; + } + } + } + else { + // Error + sid.state = SCRIPT_SYNTAX_ERROR; + + if (initFunction != LUA_NOREF) + TRACE_ERROR("luaLoadScripts(%s): init function: %s\n", getScriptName(idx), lua_tostring(lsScripts, -1)); + else + TRACE_ERROR("luaLoadScripts(%s): %s\n", getScriptName(idx), lua_tostring(lsScripts, -1)); + + if (ref == SCRIPT_STANDALONE) { + luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; + luaError(lsScripts, sid.state, true); + return; + } + + // Replace the dead coroutine with a new one + lua_pop(L, 1); // Pop the dead coroutine off the main stack + lsScripts = lua_newthread(L); // Push the new coroutine + } + + luaDoGc(lsScripts, true); + + } while(initFunction != LUA_NOREF); + + } while(++ref < SCRIPT_STANDALONE); + + // Loading has finished - start running scripts + luaState = INTERPRETER_START_RUNNING; +} // luaLoadScripts + +void luaExec(const char * filename) +{ + luaState = INTERPRETER_LOADING; + luaLoadScripts(true, filename); +} + +static bool resumeLua(bool init, bool allowLcdUsage) +{ + static uint8_t idx; + static event_t evt = 0; + if (init) idx = 0; + + bool scriptWasRun = false; + bool fullGC = !allowLcdUsage; + static uint8_t luaDisplayStatistics = false; + + // Run in the right interactive mode + if (allowLcdUsage != luaLcdAllowed) { +#if defined(PCBTARANIS) + if (luaLcdAllowed && scriptInternalData[0].reference != SCRIPT_STANDALONE && menuHandlers[menuLevel] != menuViewTelemetry) { + // Telemetry screen was exited while foreground function was preempted - finish in the background + luaLcdAllowed = false; + } + else +#endif + return scriptWasRun; + } + + do { + ScriptInternalData & sid = scriptInternalData[idx]; + if (sid.state != SCRIPT_OK) continue; + + uint8_t ref = sid.reference; + int inputsCount = 0; + int luaStatus = lua_status(lsScripts); + + if (luaStatus == LUA_OK) { + // Not preempted - setup another function call + lua_settop(lsScripts, 0); + + if (allowLcdUsage) { +#if defined(PCBTARANIS) + if ((menuHandlers[menuLevel] == menuViewTelemetry && ref == SCRIPT_TELEMETRY_FIRST + s_frsky_view) || ref == SCRIPT_STANDALONE) { +#else + if (ref == SCRIPT_STANDALONE) { +#endif + // Pull a new event from the buffer + evt = events[0]; + events[0] = events[1]; + events[1] = 0; + + lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run); + lua_pushunsigned(lsScripts, evt); + inputsCount = 1; + } + else continue; + } + else { +#if defined(LUA_MODEL_SCRIPTS) + if (ref <= SCRIPT_MIX_LAST) { + lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run); + + ScriptData & sd = g_model.scriptsData[ref - SCRIPT_MIX_FIRST]; + ScriptInputsOutputs * sio = & scriptInputsOutputs[ref - SCRIPT_MIX_FIRST]; + inputsCount = sio -> inputsCount; + + for (int j = 0; j < inputsCount; j++) { + if (sio -> inputs[j].type == INPUT_TYPE_SOURCE) + luaGetValueAndPush(lsScripts, sd.inputs[j].source); + else + lua_pushinteger(lsScripts, sd.inputs[j].value + sio -> inputs[j].def); + } + } else +#endif + if (ref <= SCRIPT_GFUNC_LAST) { + CustomFunctionData * fn; + + if (ref <= SCRIPT_FUNC_LAST) + fn = &g_model.customFn[ref - SCRIPT_FUNC_FIRST]; + else + fn = &g_eeGeneral.customFn[ref - SCRIPT_GFUNC_FIRST]; + + if (getSwitch(fn -> swtch)) { + lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run); + } + else { + if (sid.background == LUA_NOREF) continue; + lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.background); + } + } +#if defined(PCBTARANIS) + else if (ref <= SCRIPT_TELEMETRY_LAST) { + if (sid.background == LUA_NOREF) continue; + lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.background); + } +#endif + else continue; + } + } + + // Full garbage collection at the start of every cycle + luaDoGc(lsScripts, fullGC); + fullGC = false; + + // Resume running the coroutine + luaStatus = lua_resume(lsScripts, 0, inputsCount); + + if (luaStatus == LUA_YIELD) { + // Coroutine yielded - wait for the next cycle + return scriptWasRun; + } + else if (luaStatus == LUA_OK) { + // Coroutine returned + scriptWasRun = true; + +#if defined(LUA_MODEL_SCRIPTS) + if (ref <= SCRIPT_MIX_LAST) { + ScriptInputsOutputs * sio = & scriptInputsOutputs[ref - SCRIPT_MIX_FIRST]; + lua_settop(lsScripts, sio -> outputsCount); + + for (int j = sio -> outputsCount - 1; j >= 0; j--) { + if (!lua_isnumber(lsScripts, -1)) { + TRACE_ERROR("Script %s: run function did not return a number\n", getScriptName(idx)); + sid.state = SCRIPT_SYNTAX_ERROR; + break; + } + sio -> outputs[j].value = lua_tointeger(lsScripts, -1); + lua_pop(lsScripts, 1); + } + } else +#endif + if (ref == SCRIPT_STANDALONE) { + lua_settop(lsScripts, 1); + if (lua_isnumber(lsScripts, -1)) { + int scriptResult = lua_tointeger(lsScripts, -1); + lua_pop(lsScripts, 1); /* pop returned value */ + + if (scriptResult != 0) { + TRACE("Script finished with status %d", scriptResult); + sid.state = SCRIPT_FINISHED; + } + else if (luaDisplayStatistics) { + #if defined(COLORLCD) + #else + lcdDrawSolidHorizontalLine(0, 7*FH-1, lcdLastRightPos+6, ERASE); + lcdDrawText(0, 7*FH, "GV Use: "); + lcdDrawNumber(lcdLastRightPos, 7*FH, luaGetMemUsed(lsScripts), LEFT); + lcdDrawChar(lcdLastRightPos, 7*FH, 'b'); + lcdDrawSolidHorizontalLine(0, 7*FH-2, lcdLastRightPos+6, FORCE); + lcdDrawVerticalLine(lcdLastRightPos+6, 7*FH-2, FH+2, SOLID, FORCE); + #endif + } } else if (lua_isstring(lsScripts, -1)) { char nextScript[FF_MAX_LFN+1]; strncpy(nextScript, lua_tostring(lsScripts, -1), FF_MAX_LFN); nextScript[FF_MAX_LFN] = '\0'; luaExec(nextScript); + return scriptWasRun; } else { - TRACE("Script run function returned unexpected value"); - standaloneScript.state = SCRIPT_SYNTAX_ERROR; - luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; + TRACE_ERROR("Script run function returned unexpected value\n"); + sid.state = SCRIPT_SYNTAX_ERROR; } - } - else { - int scriptResult = lua_tointeger(lsScripts, -1); - lua_pop(lsScripts, 1); /* pop returned value */ - if (scriptResult != 0) { - TRACE("Script finished with status %d", scriptResult); - standaloneScript.state = SCRIPT_NOFILE; - luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; - return; + + if (evt == EVT_KEY_LONG(KEY_EXIT)) { + TRACE("Script force exit"); + killEvents(evt); + luaEmptyEventBuffer(); + sid.state = SCRIPT_FINISHED; } - else if (luaDisplayStatistics) { -#if !defined(COLORLCD) - lcdDrawSolidHorizontalLine(0, 7*FH-1, lcdLastRightPos+6, ERASE); - lcdDrawText(0, 7*FH, "GV Use: "); - lcdDrawNumber(lcdLastRightPos, 7*FH, luaGetMemUsed(lsScripts), LEFT); - lcdDrawChar(lcdLastRightPos, 7*FH, 'b'); - lcdDrawSolidHorizontalLine(0, 7*FH-2, lcdLastRightPos+6, FORCE); - lcdDrawVerticalLine(lcdLastRightPos+6, 7*FH-2, FH+2, SOLID, FORCE); -#endif - } - } - } - else { - TRACE("Script error: %s", lua_tostring(lsScripts, -1)); - standaloneScript.state = (instructionsPercent > 100 ? SCRIPT_KILLED : SCRIPT_SYNTAX_ERROR); - luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; - } - - if (standaloneScript.state != SCRIPT_OK) { - luaError(lsScripts, standaloneScript.state); - luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; - } - - if (evt == EVT_KEY_LONG(KEY_EXIT)) { - TRACE("Script force exit"); - killEvents(evt); - standaloneScript.state = SCRIPT_NOFILE; - luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; - } #if defined(KEYS_GPIO_REG_MENU) - // TODO find another key and add a #define - else if (evt == EVT_KEY_LONG(KEY_MENU)) { - killEvents(evt); - luaDisplayStatistics = !luaDisplayStatistics; - } -#endif - } - else { - TRACE("Script run method missing"); - standaloneScript.state = SCRIPT_SYNTAX_ERROR; - luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS; - } -} - -bool luaDoOneRunPermanentScript(event_t evt, int i, uint32_t scriptType) -{ - ScriptInternalData & sid = scriptInternalData[i]; - if (sid.state != SCRIPT_OK) return false; - - luaSetInstructionsLimit(lsScripts, PERMANENT_SCRIPTS_MAX_INSTRUCTIONS); - int inputsCount = 0; -#if defined(SIMU) || defined(DEBUG) - const char *filename; -#endif - ScriptInputsOutputs * sio = nullptr; -#if SCRIPT_MIX_FIRST > 0 - if ((scriptType & RUN_MIX_SCRIPT) && (sid.reference >= SCRIPT_MIX_FIRST && sid.reference <= SCRIPT_MIX_LAST)) { -#else - if ((scriptType & RUN_MIX_SCRIPT) && (sid.reference <= SCRIPT_MIX_LAST)) { -#endif - ScriptData & sd = g_model.scriptsData[sid.reference-SCRIPT_MIX_FIRST]; - sio = &scriptInputsOutputs[sid.reference-SCRIPT_MIX_FIRST]; - inputsCount = sio->inputsCount; -#if defined(SIMU) || defined(DEBUG) - filename = sd.file; -#endif - lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run); - for (int j=0; jinputsCount; j++) { - if (sio->inputs[j].type == INPUT_TYPE_SOURCE) - luaGetValueAndPush(lsScripts, sd.inputs[j].source); - else - lua_pushinteger(lsScripts, sd.inputs[j].value + sio->inputs[j].def); - } - } - else if ((scriptType & RUN_FUNC_SCRIPT) && (sid.reference >= SCRIPT_FUNC_FIRST && sid.reference <= SCRIPT_GFUNC_LAST)) { - CustomFunctionData & fn = (sid.reference < SCRIPT_GFUNC_FIRST ? g_model.customFn[sid.reference-SCRIPT_FUNC_FIRST] : g_eeGeneral.customFn[sid.reference-SCRIPT_GFUNC_FIRST]); -#if defined(SIMU) || defined(DEBUG) - filename = fn.play.name; -#endif - if (getSwitch(fn.swtch)) - lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run); - else if (sid.background) - lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.background); - else - return false; - } - else { -#if defined(PCBTARANIS) -#if defined(SIMU) || defined(DEBUG) - TelemetryScriptData & script = g_model.screens[sid.reference-SCRIPT_TELEMETRY_FIRST].script; - filename = script.file; -#endif - if ((scriptType & RUN_TELEM_FG_SCRIPT) && (menuHandlers[0]==menuViewTelemetry && sid.reference==SCRIPT_TELEMETRY_FIRST+s_frsky_view)) { - lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run); - lua_pushunsigned(lsScripts, evt); - inputsCount = 1; - } - else if ((scriptType & RUN_TELEM_BG_SCRIPT) && (sid.background)) { - lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.background); - } - else { - return false; - } -#else // !PCBTARANIS - // COLORLCD - return false; -#endif - } - - if (lua_pcall(lsScripts, inputsCount, sio ? sio->outputsCount : 0, 0) == 0) { - if (sio) { - for (int j=sio->outputsCount-1; j>=0; j--) { - if (!lua_isnumber(lsScripts, -1)) { - sid.state = (instructionsPercent > 100 ? SCRIPT_KILLED : SCRIPT_SYNTAX_ERROR); - TRACE("Script %8s disabled", filename); - break; + // TODO find another key and add a #define + else if (evt == EVT_KEY_LONG(KEY_MENU)) { + killEvents(evt); + luaEmptyEventBuffer(); + luaDisplayStatistics = !luaDisplayStatistics; } - sio->outputs[j].value = lua_tointeger(lsScripts, -1); - lua_pop(lsScripts, 1); - } - } - } - else { - if (instructionsPercent > 100) { - TRACE("Script %8s killed", filename); - sid.state = SCRIPT_KILLED; - } - else { - TRACE("Script %8s error: %s", filename, lua_tostring(lsScripts, -1)); - sid.state = SCRIPT_SYNTAX_ERROR; - } - } - - if (sid.state != SCRIPT_OK) { - luaFree(lsScripts, sid); - } - else { - if (instructionsPercent > sid.instructions) { - sid.instructions = instructionsPercent; - } - } - return true; -} - - -// This loop-pass should run in checkEvents(). -// However, libopenui has 2 main separated routines: -// - checkEvents(): runs the event loop and can effect an async redraw (if visible) -// - paint(): (if visible) the redraw itself -// -bool luaTask(event_t evt, uint8_t scriptType, bool allowLcdUsage) -{ - if (luaState == INTERPRETER_PANIC) return false; - luaLcdAllowed = allowLcdUsage; - bool scriptWasRun = false; - - // TRACE("luaTask(0x%x,0x%x,%s)", - // evt, scriptType, - // allowLcdUsage ? "true" : "false"); - - // we run either standalone script or permanent scripts - if (luaState & INTERPRETER_RUNNING_STANDALONE_SCRIPT) { - // run standalone script - if ((scriptType & RUN_STNDAL_SCRIPT) == 0) return false; - PROTECT_LUA() { - luaDoOneRunStandalone(evt); - scriptWasRun = true; - } - else { - luaDisable(); - return false; - } - UNPROTECT_LUA(); - } - else { - // run permanent scripts - if (luaState & INTERPRETER_RELOAD_PERMANENT_SCRIPTS) { - luaState = 0; - luaInit(); - if (luaState == INTERPRETER_PANIC) return false; - luaLoadPermanentScripts(); - if (luaState == INTERPRETER_PANIC) return false; - } - - for (int i=0; i LUA_MEM_MAX) { - TRACE("checkLuaMemoryUsage(): max limit reached (%u), killing Lua", totalMemUsed); + TRACE_ERROR("checkLuaMemoryUsage(): max limit reached (%u), killing Lua\n", totalMemUsed); // disable Lua scripts luaClose(&lsScripts); luaDisable(); @@ -1127,6 +1223,7 @@ void checkLuaMemoryUsage() #endif } + uint32_t luaGetMemUsed(lua_State * L) { return L ? (lua_gc(L, LUA_GCCOUNT, 0) << 10) + lua_gc(L, LUA_GCCOUNTB, 0) : 0; @@ -1137,25 +1234,36 @@ void luaInit() TRACE("luaInit"); luaClose(&lsScripts); + L = nullptr; if (luaState != INTERPRETER_PANIC) { #if defined(USE_BIN_ALLOCATOR) - lsScripts = lua_newstate(bin_l_alloc, nullptr); //we use our own allocator! + L = lua_newstate(bin_l_alloc, nullptr); //we use our own allocator! #elif defined(LUA_ALLOCATOR_TRACER) - memset(&lsScriptsTrace, 0 , sizeof(lsScriptsTrace)); + memset(&lsScriptsTrace, 0 , sizeof(lsScriptsTrace); lsScriptsTrace.script = "lua_newstate(scripts)"; - lsScripts = lua_newstate(tracer_alloc, &lsScriptsTrace); //we use tracer allocator + L = lua_newstate(tracer_alloc, &lsScriptsTrace); //we use tracer allocator #else - lsScripts = lua_newstate(l_alloc, nullptr); //we use Lua default allocator + L = lua_newstate(l_alloc, nullptr); //we use Lua default allocator #endif - if (lsScripts) { + if (L) { // install our panic handler - lua_atpanic(lsScripts, &custom_lua_atpanic); + lua_atpanic(L, &custom_lua_atpanic); #if defined(LUA_ALLOCATOR_TRACER) - lua_sethook(lsScripts, luaHook, LUA_MASKLINE, 0); + lua_sethook(L, luaHook, LUA_MASKCOUNT|LUA_MASKLINE, PERMANENT_SCRIPTS_MAX_INSTRUCTIONS); +#else + lua_sethook(L, luaHook, LUA_MASKCOUNT, PERMANENT_SCRIPTS_MAX_INSTRUCTIONS); #endif + // lsScripts is now a coroutine in lieu of the main thread to support preemption + lsScripts = lua_newthread(L); + + // Clear loaded scripts + memset(scriptInternalData, 0, sizeof(scriptInternalData)); + memset(scriptInputsOutputs, 0, sizeof(scriptInputsOutputs)); + luaScriptsCount = 0; + // protect libs and constants registration PROTECT_LUA() { luaRegisterLibraries(lsScripts); diff --git a/radio/src/lua/lua_api.h b/radio/src/lua/lua_api.h index 36f658d1a..ca0d9c294 100644 --- a/radio/src/lua/lua_api.h +++ b/radio/src/lua/lua_api.h @@ -47,15 +47,19 @@ extern Fifo * luaRxFifo; #endif extern lua_State * lsScripts; -extern lua_State * lsWidgets; + extern bool luaLcdAllowed; + #if defined(COLORLCD) +extern lua_State * lsWidgets; extern uint32_t luaExtraMemoryUsage; +void luaInitThemesAndWidgets(); #endif void luaInit(); -void luaInitThemesAndWidgets(); +void luaEmptyEventBuffer(); +#define LUA_INIT_THEMES_AND_WIDGETS() luaInitThemesAndWidgets() #define lua_registernumber(L, n, i) (lua_pushnumber(L, (i)), lua_setglobal(L, (n))) #define lua_registerint(L, n, i) (lua_pushinteger(L, (i)), lua_setglobal(L, (n))) #define lua_pushtablenil(L, k) (lua_pushstring(L, (k)), lua_pushnil(L), lua_settable(L, -3)) @@ -78,12 +82,6 @@ void luaInitThemesAndWidgets(); #define lua_registerlib(L, name, tab) (luaL_newmetatable(L, name), luaL_setfuncs(L, tab, 0), lua_setglobal(L, name)) -#define RUN_MIX_SCRIPT (1 << 0) -#define RUN_FUNC_SCRIPT (1 << 1) -#define RUN_TELEM_BG_SCRIPT (1 << 2) -#define RUN_TELEM_FG_SCRIPT (1 << 3) -#define RUN_STNDAL_SCRIPT (1 << 4) - enum luaScriptInputType { INPUT_TYPE_FIRST = 0, INPUT_TYPE_VALUE = INPUT_TYPE_FIRST, @@ -98,27 +96,36 @@ struct ScriptInput { int16_t max; int16_t def; }; + struct ScriptOutput { const char *name; int16_t value; }; + enum ScriptState { SCRIPT_OK, SCRIPT_NOFILE, SCRIPT_SYNTAX_ERROR, SCRIPT_PANIC, - SCRIPT_KILLED + SCRIPT_FINISHED }; + enum ScriptReference { +#if defined(LUA_MODEL_SCRIPTS) SCRIPT_MIX_FIRST, SCRIPT_MIX_LAST=SCRIPT_MIX_FIRST+MAX_SCRIPTS-1, +#endif SCRIPT_FUNC_FIRST, SCRIPT_FUNC_LAST=SCRIPT_FUNC_FIRST+MAX_SPECIAL_FUNCTIONS-1, // model functions SCRIPT_GFUNC_FIRST, SCRIPT_GFUNC_LAST=SCRIPT_GFUNC_FIRST+MAX_SPECIAL_FUNCTIONS-1, // global functions +#if defined(PCBTARANIS) SCRIPT_TELEMETRY_FIRST, - SCRIPT_TELEMETRY_LAST=SCRIPT_TELEMETRY_FIRST+MAX_SCRIPTS, // telem0 and telem1 .. telem7 + SCRIPT_TELEMETRY_LAST=SCRIPT_TELEMETRY_FIRST+MAX_SCRIPTS, // telem0 and telem1 .. telem7 +#endif + SCRIPT_STANDALONE // Standalone script }; + struct ScriptInternalData { uint8_t reference; uint8_t state; @@ -126,41 +133,55 @@ struct ScriptInternalData { int background; uint8_t instructions; }; + struct ScriptInputsOutputs { uint8_t inputsCount; ScriptInput inputs[MAX_SCRIPT_INPUTS]; uint8_t outputsCount; ScriptOutput outputs[MAX_SCRIPT_OUTPUTS]; }; -#define INTERPRETER_RUNNING_STANDALONE_SCRIPT 1 -#define INTERPRETER_RELOAD_PERMANENT_SCRIPTS 2 -#define INTERPRETER_PANIC 255 + + +enum InterpreterState { + INTERPRETER_RELOAD_PERMANENT_SCRIPTS = 1, + INTERPRETER_LOADING, + INTERPRETER_START_RUNNING, + INTERPRETER_RUNNING, + INTERPRETER_PANIC = 255 +}; + extern uint8_t luaState; extern uint8_t luaScriptsCount; extern bool luaLcdAllowed; -extern ScriptInternalData standaloneScript; + extern ScriptInternalData scriptInternalData[MAX_SCRIPTS]; extern ScriptInputsOutputs scriptInputsOutputs[MAX_SCRIPTS]; + void luaClose(lua_State ** L); -bool luaTask(event_t evt, uint8_t scriptType, bool allowLcdUsage); +bool luaTask(event_t evt, bool allowLcdUsage); void checkLuaMemoryUsage(); void luaExec(const char * filename); void luaDoGc(lua_State * L, bool full); void luaError(lua_State * L, uint8_t error, bool acknowledge=true); uint32_t luaGetMemUsed(lua_State * L); void luaGetValueAndPush(lua_State * L, int src); -#define luaGetCpuUsed(idx) scriptInternalData[idx].instructions uint8_t isTelemetryScriptAvailable(uint8_t index); -#define LUA_LOAD_MODEL_SCRIPTS() luaState |= INTERPRETER_RELOAD_PERMANENT_SCRIPTS -#define LUA_LOAD_MODEL_SCRIPT(idx) luaState |= INTERPRETER_RELOAD_PERMANENT_SCRIPTS + +#define luaGetCpuUsed(idx) scriptInternalData[idx].instructions +#define LUA_LOAD_MODEL_SCRIPTS() luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS +#define LUA_LOAD_MODEL_SCRIPT(idx) luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS + // Lua PROTECT/UNPROTECT #include + struct our_longjmp { struct our_longjmp *previous; jmp_buf b; volatile int status; /* error code */ }; + extern struct our_longjmp * global_lj; + #define PROTECT_LUA() { struct our_longjmp lj; \ lj.previous = global_lj; /* chain new error handler */ \ global_lj = &lj; \ @@ -172,14 +193,16 @@ extern uint16_t maxLuaDuration; extern uint8_t instructionsPercent; #if defined(KEYS_GPIO_REG_PAGE) - #define IS_MASKABLE(key) ((key) != KEY_EXIT && (key) != KEY_ENTER && ((luaState & INTERPRETER_RUNNING_STANDALONE_SCRIPT) || (key) != KEY_PAGE)) + #define IS_MASKABLE(key) ((key) != KEY_EXIT && (key) != KEY_ENTER && ((scriptInternalData[0].reference == SCRIPT_STANDALONE) || (key) != KEY_PAGE)) #else #define IS_MASKABLE(key) ((key) != KEY_EXIT && (key) != KEY_ENTER) #endif + struct LuaField { uint16_t id; char desc[50]; }; + bool luaFindFieldByName(const char * name, LuaField & field, unsigned int flags=0); void luaLoadThemes(); void luaRegisterLibraries(lua_State * L); @@ -207,8 +230,6 @@ struct LuaMemTracer { }; void * tracer_alloc(void * ud, void * ptr, size_t osize, size_t nsize); -void luaHook(lua_State * L, lua_Debug *ar); - #else // defined(LUA) diff --git a/radio/src/lua/widgets.cpp b/radio/src/lua/widgets.cpp index 9483bc16a..585603ff3 100644 --- a/radio/src/lua/widgets.cpp +++ b/radio/src/lua/widgets.cpp @@ -47,6 +47,57 @@ extern int custom_lua_atpanic(lua_State *L); #define LUA_WIDGET_FILENAME "/main.lua" #define LUA_FULLPATH_MAXLEN (LEN_FILE_PATH_MAX + LEN_SCRIPT_FILENAME + LEN_FILE_EXTENSION_MAX) // max length (example: /SCRIPTS/THEMES/mytheme.lua) +static void luaHook(lua_State * L, lua_Debug *ar) +{ + if (ar->event == LUA_HOOKCOUNT) { + instructionsPercent++; +#if defined(DEBUG) + // Disable Lua script instructions limit in DEBUG mode, + // just report max value reached + static uint16_t max = 0; + if (instructionsPercent > 100) { + if (max + 10 < instructionsPercent) { + max = instructionsPercent; + TRACE("LUA instructionsPercent %u%%", (uint32_t)max); + } + } + else if (instructionsPercent < 10) { + max = 0; + } +#else + if (instructionsPercent > 100) { + // From now on, as soon as a line is executed, error + // keep erroring until you're script reaches the top + lua_sethook(L, luaHook, LUA_MASKLINE, 0); + luaL_error(L, "CPU limit"); + } +#endif + } +#if defined(LUA_ALLOCATOR_TRACER) + else if (ar->event == LUA_HOOKLINE) { + lua_getinfo(L, "nSl", ar); + LuaMemTracer * tracer = GET_TRACER(L); + if (tracer->alloc || tracer->free) { + TRACE("LT: [+%u,-%u] %s:%d", tracer->alloc, tracer->free, tracer->script, tracer->lineno); + } + tracer->script = ar->source; + tracer->lineno = ar->currentline; + tracer->alloc = 0; + tracer->free = 0; + } +#endif // #if defined(LUA_ALLOCATOR_TRACER) +} + +void luaSetInstructionsLimit(lua_State * L, int count) +{ + instructionsPercent = 0; +#if defined(LUA_ALLOCATOR_TRACER) + lua_sethook(L, luaHook, LUA_MASKCOUNT|LUA_MASKLINE, count); +#else + lua_sethook(L, luaHook, LUA_MASKCOUNT, count); +#endif +} + void exec(int function, int nresults=0) { if (lsWidgets == 0) return; @@ -440,12 +491,16 @@ void LuaWidget::refresh(BitmapBuffer* dc) // Enable drawing into the current LCD buffer luaLcdBuffer = dc; + + // This little hack is needed to not interfere with the LCD usage of preempted scripts + bool lla = luaLcdAllowed; luaLcdAllowed = true; if (lua_pcall(lsWidgets, 3, 0, 0) != 0) { setErrorMessage("refresh()"); } // Remove LCD + luaLcdAllowed = lla; luaLcdBuffer = nullptr; // mark as refreshed diff --git a/radio/src/main.cpp b/radio/src/main.cpp index 24dd94066..5c927db79 100644 --- a/radio/src/main.cpp +++ b/radio/src/main.cpp @@ -356,7 +356,7 @@ void guiMain(event_t evt) DEBUG_TIMER_START(debugTimerLua); // Run Lua scripts first that don't use LCD - luaTask( 0, RUN_MIX_SCRIPT | RUN_FUNC_SCRIPT, false); + luaTask( 0, false); // This is run from StandaloneLuaWindow::checkEvents() // luaTask(evt, RUN_STNDAL_SCRIPT, true); @@ -382,29 +382,28 @@ void guiMain(event_t evt) } #elif defined(GUI) -void handleGui(event_t event) { - // if Lua standalone, run it and don't clear the screen (Lua will do it) - // else if Lua telemetry view, run it and don't clear the screen - // else clear scren and show normal menus +bool handleGui(event_t event) { + bool refreshNeeded; #if defined(LUA) - if (luaTask(event, RUN_STNDAL_SCRIPT, true)) { - // standalone script is active + refreshNeeded = luaTask(event, true); + if (menuHandlers[menuLevel] == menuViewTelemetry && TELEMETRY_SCREEN_TYPE(s_frsky_view) == TELEMETRY_SCREEN_TYPE_SCRIPT) { + menuHandlers[menuLevel](event); } - else if (luaTask(event, RUN_TELEM_FG_SCRIPT, true)) { - // the telemetry screen is active - menuHandlers[menuLevel](event); - } - else + else if (scriptInternalData[0].reference != SCRIPT_STANDALONE) #endif +// No foreground Lua script is running - clear the screen show normal menu { lcdClear(); menuHandlers[menuLevel](event); drawStatusLine(); + refreshNeeded = true; } + return refreshNeeded; } void guiMain(event_t evt) { + bool refreshNeeded = menuEvent || warningText || (popupMenuItemsCount > 0); #if defined(LUA) // TODO better lua stopwatch uint32_t t0 = get_tmr10ms(); @@ -416,7 +415,7 @@ void guiMain(event_t evt) } // run Lua scripts that don't use LCD (to use CPU time while LCD DMA is running) - luaTask(0, RUN_MIX_SCRIPT | RUN_FUNC_SCRIPT | RUN_TELEM_BG_SCRIPT, false); + luaTask(0, false); t0 = get_tmr10ms() - t0; if (t0 > maxLuaDuration) { @@ -440,10 +439,10 @@ void guiMain(event_t evt) } if (isEventCaughtByPopup()) { - handleGui(0); + refreshNeeded |= handleGui(0); } else { - handleGui(evt); + refreshNeeded |= handleGui(evt); evt = 0; } @@ -463,8 +462,8 @@ void guiMain(event_t evt) } } - lcdRefresh(); - + if (refreshNeeded) lcdRefresh(); + if (mainRequestFlags & (1u << REQUEST_SCREENSHOT)) { writeScreenshot(); mainRequestFlags &= ~(1u << REQUEST_SCREENSHOT);