1427 lines
43 KiB
C++
1427 lines
43 KiB
C++
/*
|
|
* Copyright (C) EdgeTX
|
|
*
|
|
* Based on code named
|
|
* opentx - https://github.com/opentx/opentx
|
|
* th9x - http://code.google.com/p/th9x
|
|
* er9x - http://code.google.com/p/er9x
|
|
* gruvin9x - http://code.google.com/p/gruvin9x
|
|
*
|
|
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
/** @file Main interface layer handler for Lua API. */
|
|
|
|
#include <ctype.h>
|
|
#include <stdio.h>
|
|
#include <algorithm>
|
|
|
|
#include "opentx.h"
|
|
#include "bin_allocator.h"
|
|
|
|
#include "lua_api.h"
|
|
#include "lua_event.h"
|
|
|
|
#include "sdcard.h"
|
|
#include "api_filesystem.h"
|
|
#include "switches.h"
|
|
|
|
#if defined(LIBOPENUI)
|
|
#include "libopenui.h"
|
|
#else
|
|
#include "libopenui/src/libopenui_file.h"
|
|
#endif
|
|
|
|
extern "C" {
|
|
#include <lundump.h>
|
|
}
|
|
|
|
#if defined(COLORLCD)
|
|
#define LUA_WARNING_INFO_LEN 256
|
|
#else
|
|
#define LUA_WARNING_INFO_LEN 64
|
|
#endif
|
|
#define PERMANENT_SCRIPTS_MAX_INSTRUCTIONS 100
|
|
#define LUA_TASK_PERIOD_TICKS 5 // 50 ms
|
|
|
|
// #if defined(HARDWARE_TOUCH)
|
|
// #include "touch.h"
|
|
// #endif
|
|
|
|
// Since we may not run FG every time, keep the events in a buffer
|
|
event_t events[EVENT_BUFFER_SIZE] = { 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];
|
|
|
|
uint16_t maxLuaInterval = 0;
|
|
uint16_t maxLuaDuration = 0;
|
|
uint8_t instructionsPercent = 0;
|
|
tmr10ms_t luaCycleStart;
|
|
char lua_warning_info[LUA_WARNING_INFO_LEN+1];
|
|
uint8_t errorState;
|
|
struct our_longjmp * global_lj = 0;
|
|
#if defined(COLORLCD)
|
|
uint32_t luaExtraMemoryUsage = 0;
|
|
#endif
|
|
|
|
#if defined(USE_HATS_AS_KEYS)
|
|
static bool _is_standalone_script()
|
|
{
|
|
return scriptInternalData[0].reference == SCRIPT_STANDALONE;
|
|
}
|
|
#endif
|
|
|
|
#if defined(LUA_ALLOCATOR_TRACER)
|
|
|
|
LuaMemTracer lsScriptsTrace;
|
|
|
|
#if defined(PCBHORUS)
|
|
extern LuaMemTracer lsWidgetsTrace;
|
|
#define GET_TRACER(L) (L == lsScripts) ? &lsScriptsTrace : &lsWidgetsTrace
|
|
#else
|
|
#define GET_TRACER(L) &lsScriptsTrace
|
|
#endif
|
|
|
|
void *tracer_alloc(void * ud, void * ptr, size_t osize, size_t nsize)
|
|
{
|
|
LuaMemTracer * tracer = (LuaMemTracer *)ud;
|
|
if (ptr) {
|
|
if (osize < nsize) {
|
|
// TRACE("Lua alloc %u", nsize - osize);
|
|
tracer->alloc += nsize - osize;
|
|
}
|
|
else {
|
|
// TRACE("Lua free %u", osize - nsize);
|
|
tracer->free += osize - nsize;
|
|
}
|
|
}
|
|
else {
|
|
// TRACE("Lua alloc %u (type %s)", nsize, osize < LUA_TOTALTAGS ? lua_typename(0, osize) : "unk");
|
|
tracer->alloc += nsize;
|
|
}
|
|
return l_alloc(ud, ptr, osize, nsize);
|
|
}
|
|
|
|
#endif // #if defined(LUA_ALLOCATOR_TRACER)
|
|
|
|
/* custom panic handler */
|
|
int custom_lua_atpanic(lua_State * L)
|
|
{
|
|
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 */
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void luaHook(lua_State * L, lua_Debug *ar)
|
|
{
|
|
if (ar->event == LUA_HOOKCOUNT) {
|
|
if (get_tmr10ms() - luaCycleStart >= LUA_TASK_PERIOD_TICKS) {
|
|
lua_yield(lsScripts, 0);
|
|
}
|
|
}
|
|
|
|
#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)
|
|
}
|
|
|
|
#if defined(LUA_MODEL_SCRIPTS)
|
|
void luaGetInputs(ScriptInputsOutputs & sid)
|
|
{
|
|
if (!lua_istable(lsScripts, -1))
|
|
return;
|
|
|
|
memclear(sid.inputs, sizeof(sid.inputs));
|
|
sid.inputsCount = 0;
|
|
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.inputsCount<MAX_SCRIPT_INPUTS) {
|
|
uint8_t field = 0;
|
|
int type = 0;
|
|
ScriptInput * si = &sid.inputs[sid.inputsCount];
|
|
for (lua_pushnil(lsScripts); lua_next(lsScripts, -2) && field<5; lua_pop(lsScripts, 1), field++) {
|
|
switch (field) {
|
|
case 0:
|
|
luaL_checktype(lsScripts, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsScripts, -1, LUA_TSTRING); // value is string
|
|
{ // To preserve the string value, truncate to 6 chars and move it to the main stack
|
|
char str[7] = {0};
|
|
strncpy(str, lua_tostring(lsScripts, -1), 6);
|
|
lua_pushstring(L, &str[0]);
|
|
}
|
|
lua_pop(lsScripts, 1);
|
|
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(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;
|
|
}
|
|
if (si->type == INPUT_TYPE_VALUE) {
|
|
si->min = -100;
|
|
si->max = 100;
|
|
}
|
|
else {
|
|
si->max = MIXSRC_LAST_TELEM;
|
|
}
|
|
break;
|
|
case 2:
|
|
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(lsScripts, -1);
|
|
}
|
|
break;
|
|
case 3:
|
|
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(lsScripts, -1);
|
|
}
|
|
break;
|
|
case 4:
|
|
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(lsScripts, -1);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
sid.inputsCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void luaGetOutputs(ScriptInputsOutputs & sid)
|
|
{
|
|
if (!lua_istable(lsScripts, -1))
|
|
return;
|
|
|
|
sid.outputsCount = 0;
|
|
for (lua_pushnil(lsScripts); lua_next(lsScripts, -2); ) {
|
|
luaL_checktype(lsScripts, -2, LUA_TNUMBER); // key is number
|
|
luaL_checktype(lsScripts, -1, LUA_TSTRING); // value is string
|
|
if (sid.outputsCount < MAX_SCRIPT_OUTPUTS) {
|
|
// To preserve the string value, truncate to 6 chars and move it to the main stack
|
|
char str[7] = {0};
|
|
strncpy(str, lua_tostring(lsScripts, -1), 6);
|
|
lua_pushstring(L, &str[0]);
|
|
// Keep the coroutine at the top of the main stack
|
|
lua_insert(L, -2);
|
|
sid.outputs[sid.outputsCount++].name = lua_tostring(L, -2);
|
|
}
|
|
lua_pop(lsScripts, 1);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void luaDisable()
|
|
{
|
|
POPUP_WARNING("Lua disabled!");
|
|
luaState = INTERPRETER_PANIC;
|
|
|
|
#if defined(USE_HATS_AS_KEYS)
|
|
if (_is_standalone_script()) setTransposeHatsForLUA(false);
|
|
#endif
|
|
}
|
|
|
|
void luaClose(lua_State ** L)
|
|
{
|
|
if (*L) {
|
|
PROTECT_LUA() {
|
|
TRACE("luaClose %p", *L);
|
|
lua_close(*L); // this should not panic, but we make sure anyway
|
|
#if defined(LUA_ALLOCATOR_TRACER)
|
|
LuaMemTracer * tracer = GET_TRACER(*L);
|
|
if (tracer->alloc || tracer->free) {
|
|
TRACE("LT: [+%u,-%u] luaClose(%s)", tracer->alloc, tracer->free, (*L == lsScripts) ? "scipts" : "widgets");
|
|
}
|
|
tracer->alloc = 0;
|
|
tracer->free = 0;
|
|
#endif // #if defined(LUA_ALLOCATOR_TRACER)
|
|
}
|
|
else {
|
|
// we can only disable Lua for the rest of the session
|
|
if (*L == lsScripts) luaDisable();
|
|
}
|
|
UNPROTECT_LUA();
|
|
*L = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
void luaRegisterLibraries(lua_State * L)
|
|
{
|
|
luaL_openlibs(L);
|
|
lua_settop(L, 0);
|
|
}
|
|
|
|
#define GC_REPORT_TRESHOLD (2*1024)
|
|
|
|
void luaDoGc(lua_State * L, bool full)
|
|
{
|
|
if (L) {
|
|
PROTECT_LUA() {
|
|
if (full) {
|
|
lua_gc(L, LUA_GCCOLLECT, 0);
|
|
}
|
|
else {
|
|
lua_gc(L, LUA_GCSTEP, 10);
|
|
}
|
|
#if defined(DEBUG)
|
|
if (L == lsScripts) {
|
|
static uint32_t lastgcSctipts = 0;
|
|
uint32_t gc = luaGetMemUsed(L);
|
|
if (gc > (lastgcSctipts + GC_REPORT_TRESHOLD) || (gc + GC_REPORT_TRESHOLD) < lastgcSctipts) {
|
|
lastgcSctipts = gc;
|
|
TRACE("GC Use Scripts: %u bytes", gc);
|
|
}
|
|
}
|
|
#if defined(COLORLCD)
|
|
if (L == lsWidgets) {
|
|
static uint32_t lastgcWidgets = 0;
|
|
uint32_t gc = luaGetMemUsed(L);
|
|
if (gc > (lastgcWidgets + GC_REPORT_TRESHOLD) || (gc + GC_REPORT_TRESHOLD) < lastgcWidgets) {
|
|
lastgcWidgets = gc;
|
|
TRACE("GC Use Widgets: %u bytes + Extra %u", gc, luaExtraMemoryUsage);
|
|
}
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
else {
|
|
// we disable Lua for the rest of the session
|
|
if (L == lsScripts) luaDisable();
|
|
#if defined(COLORLCD)
|
|
if (L == lsWidgets) lsWidgets = 0;
|
|
#endif
|
|
}
|
|
UNPROTECT_LUA();
|
|
}
|
|
}
|
|
|
|
void luaFree(lua_State * L, ScriptInternalData & sid)
|
|
{
|
|
PROTECT_LUA() {
|
|
if (sid.run) {
|
|
luaL_unref(L, LUA_REGISTRYINDEX, sid.run);
|
|
sid.run = 0;
|
|
}
|
|
if (sid.background) {
|
|
luaL_unref(L, LUA_REGISTRYINDEX, sid.background);
|
|
sid.background = 0;
|
|
}
|
|
}
|
|
else {
|
|
luaDisable();
|
|
}
|
|
UNPROTECT_LUA();
|
|
|
|
luaDoGc(L, true);
|
|
}
|
|
|
|
#if defined(LUA_COMPILER)
|
|
/// callback for luaU_dump()
|
|
static int luaDumpWriter(lua_State * L, const void* p, size_t size, void* u)
|
|
{
|
|
UNUSED(L);
|
|
UINT written;
|
|
FRESULT result = f_write((FIL *)u, p, size, &written);
|
|
return (result != FR_OK && !written);
|
|
}
|
|
|
|
/*
|
|
@fn luaDumpState(lua_State * L, const char * filename, const FILINFO * finfo, int stripDebug)
|
|
Save compiled bytecode from a given Lua stack to a file.
|
|
@param L The Lua stack to dump.
|
|
@param filename Full path and name of file to save to (typically with .luac extension).
|
|
@param finfo Can be NULL. If not NULL, sets timestamp of created file to match the one in finfo->fdate/ftime
|
|
@param stripDebug This is passed directly to luaU_dump()
|
|
1 = remove debug info from bytecode (smaller but errors are less informative)
|
|
0 = keep debug info
|
|
*/
|
|
static void luaDumpState(lua_State * L, const char * filename, const FILINFO * finfo, int stripDebug)
|
|
{
|
|
FIL D;
|
|
if (f_open(&D, filename, FA_WRITE | FA_CREATE_ALWAYS) == FR_OK) {
|
|
lua_lock(L);
|
|
luaU_dump(L, getproto(L->top - 1), luaDumpWriter, &D, stripDebug);
|
|
lua_unlock(L);
|
|
if (f_close(&D) == FR_OK) {
|
|
if (finfo != nullptr)
|
|
f_utime(filename, finfo); // set the file mod time
|
|
TRACE("luaDumpState(%s): Saved bytecode to file.", filename);
|
|
}
|
|
} else
|
|
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.
|
|
"t" only text.
|
|
"T" (default on simulator) prefer text but load binary if that is the only version available.
|
|
"bt" (default on radio) either binary or text, whichever is newer (binary preferred when timestamps are equal).
|
|
Add "x" to avoid automatic compilation of source file to .luac version.
|
|
Eg: "tx", "bx", or "btx".
|
|
Add "c" to force compilation of source file to .luac version (even if existing version is newer than source file).
|
|
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)
|
|
SCRIPT_SYNTAX_ERROR if Lua returned a syntax error during pre/de-compilation (LUA_ERRSYNTAX)
|
|
SCRIPT_PANIC for Lua memory errors (LUA_ERRMEM or LUA_ERRGCMM)
|
|
*/
|
|
int luaLoadScriptFileToState(lua_State * L, const char * filename, const char * mode)
|
|
{
|
|
if (luaState == INTERPRETER_PANIC) {
|
|
return SCRIPT_PANIC;
|
|
} else if (filename == nullptr) {
|
|
return SCRIPT_NOFILE;
|
|
}
|
|
|
|
int lstatus;
|
|
char lmode[6] = "bt";
|
|
uint8_t ret = SCRIPT_NOFILE;
|
|
|
|
if (mode != nullptr) {
|
|
strncpy(lmode, mode, sizeof(lmode)-1);
|
|
lmode[sizeof(lmode)-1] = '\0';
|
|
}
|
|
|
|
#if defined(LUA_COMPILER)
|
|
uint16_t fnamelen;
|
|
uint8_t extlen;
|
|
char filenameFull[LEN_FILE_PATH_MAX + FF_MAX_LFN + 1] = "\0";
|
|
FILINFO fnoLuaS, fnoLuaC;
|
|
FRESULT frLuaS, frLuaC;
|
|
|
|
bool scriptNeedsCompile = false;
|
|
uint8_t loadFileType = 0; // 1=text, 2=binary
|
|
|
|
memclear(&fnoLuaS, sizeof(FILINFO));
|
|
memclear(&fnoLuaC, sizeof(FILINFO));
|
|
|
|
fnamelen = strlen(filename);
|
|
// check if file extension is already in the file name and strip it
|
|
getFileExtension(filename, fnamelen, 0, nullptr, &extlen);
|
|
fnamelen -= extlen;
|
|
if (fnamelen > sizeof(filenameFull) - sizeof(SCRIPT_BIN_EXT)) {
|
|
TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: filename buffer overflow.\n", filename, lmode);
|
|
return ret;
|
|
}
|
|
strncat(filenameFull, filename, fnamelen);
|
|
|
|
// check if binary version exists
|
|
strcpy(filenameFull + fnamelen, SCRIPT_BIN_EXT);
|
|
frLuaC = f_stat(filenameFull, &fnoLuaC);
|
|
|
|
// check if text version exists
|
|
strcpy(filenameFull + fnamelen, SCRIPT_EXT);
|
|
frLuaS = f_stat(filenameFull, &fnoLuaS);
|
|
|
|
// decide which version to load, text or binary
|
|
if (frLuaC != FR_OK && frLuaS == FR_OK) {
|
|
// only text version exists
|
|
loadFileType = 1;
|
|
scriptNeedsCompile = true;
|
|
}
|
|
else if (frLuaC == FR_OK && frLuaS != FR_OK) {
|
|
// only binary version exists
|
|
loadFileType = 2;
|
|
}
|
|
else if (frLuaS == FR_OK) {
|
|
// both versions exist, compare them
|
|
if (strchr(lmode, 'c') || (uint32_t)((fnoLuaC.fdate << 16) + fnoLuaC.ftime) < (uint32_t)((fnoLuaS.fdate << 16) + fnoLuaS.ftime)) {
|
|
// text version is newer than binary or forced by "c" mode flag, rebuild it
|
|
scriptNeedsCompile = true;
|
|
}
|
|
if (scriptNeedsCompile || !strchr(lmode, 'b')) {
|
|
// text version needs compilation or forced by mode
|
|
loadFileType = 1;
|
|
} else {
|
|
// use binary file
|
|
loadFileType = 2;
|
|
}
|
|
}
|
|
// else both versions are missing
|
|
|
|
// skip compilation based on mode flags? ("c" overrides "x")
|
|
if (scriptNeedsCompile && strchr(lmode, 'x') && !strchr(lmode, 'c')) {
|
|
scriptNeedsCompile = false;
|
|
}
|
|
|
|
if (loadFileType == 2) {
|
|
// change file extension to binary version
|
|
strcpy(filenameFull + fnamelen, SCRIPT_BIN_EXT);
|
|
}
|
|
|
|
// TRACE_DEBUG("luaLoadScriptFileToState(%s, %s):\n", filename, lmode);
|
|
// TRACE_DEBUG("\tldfile='%s'; ldtype=%u; compile=%u;\n", filenameFull, loadFileType, scriptNeedsCompile);
|
|
// TRACE_DEBUG("\t%-5s: %s; mtime: %04X%04X = %u/%02u/%02u %02u:%02u:%02u;\n", SCRIPT_EXT, (frLuaS == FR_OK ? "ok" : "nf"), fnoLuaS.fdate, fnoLuaS.ftime,
|
|
// (fnoLuaS.fdate >> 9) + 1980, (fnoLuaS.fdate >> 5) & 15, fnoLuaS.fdate & 31, fnoLuaS.ftime >> 11, (fnoLuaS.ftime >> 5) & 63, (fnoLuaS.ftime & 31) * 2);
|
|
// TRACE_DEBUG("\t%-5s: %s; mtime: %04X%04X = %u/%02u/%02u %02u:%02u:%02u;\n", SCRIPT_BIN_EXT, (frLuaC == FR_OK ? "ok" : "nf"), fnoLuaC.fdate, fnoLuaC.ftime,
|
|
// (fnoLuaC.fdate >> 9) + 1980, (fnoLuaC.fdate >> 5) & 15, fnoLuaC.fdate & 31, fnoLuaC.ftime >> 11, (fnoLuaC.ftime >> 5) & 63, (fnoLuaC.ftime & 31) * 2);
|
|
|
|
// final check that file exists and is allowed by mode flags
|
|
if (!loadFileType || (loadFileType == 1 && !strpbrk(lmode, "tTc")) || (loadFileType == 2 && !strpbrk(lmode, "bT"))) {
|
|
TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: file not found.\n", filename, lmode);
|
|
return SCRIPT_NOFILE;
|
|
}
|
|
|
|
#else // !defined(LUA_COMPILER)
|
|
|
|
// use passed file name as-is
|
|
const char *filenameFull = filename;
|
|
|
|
#endif
|
|
|
|
TRACE("luaLoadScriptFileToState(%s, %s): loading %s", filename, lmode, filenameFull);
|
|
|
|
// we don't pass <mode> on to loadfilex() because we want lua to load whatever file we specify, regardless of content
|
|
lstatus = luaL_loadfilex(L, filenameFull, nullptr);
|
|
#if defined(LUA_COMPILER)
|
|
// Check for bytecode encoding problem, eg. compiled for x64. Unfortunately Lua doesn't provide a unique error code for this. See Lua/src/lundump.c.
|
|
if (lstatus == LUA_ERRSYNTAX && loadFileType == 2 && frLuaS == FR_OK && strstr(lua_tostring(L, -1), "precompiled")) {
|
|
loadFileType = 1;
|
|
scriptNeedsCompile = true;
|
|
strcpy(filenameFull + fnamelen, SCRIPT_EXT);
|
|
TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: %s\n\tRetrying with %s\n", filename, lmode, lua_tostring(L, -1), filenameFull);
|
|
lstatus = luaL_loadfilex(L, filenameFull, nullptr);
|
|
}
|
|
if (lstatus == LUA_OK) {
|
|
if (scriptNeedsCompile && loadFileType == 1) {
|
|
strcpy(filenameFull + fnamelen, SCRIPT_BIN_EXT);
|
|
luaDumpState(L, filenameFull, &fnoLuaS, (strchr(lmode, 'd') ? 0 : 1));
|
|
}
|
|
ret = SCRIPT_OK;
|
|
}
|
|
#else
|
|
if (lstatus == LUA_OK) {
|
|
ret = SCRIPT_OK;
|
|
}
|
|
#endif
|
|
else {
|
|
TRACE_ERROR("luaLoadScriptFileToState(%s, %s): Error loading script: %s\n", filename, lmode, lua_tostring(L, -1));
|
|
if (lstatus == LUA_ERRFILE) {
|
|
ret = SCRIPT_NOFILE;
|
|
}
|
|
else if (lstatus == LUA_ERRSYNTAX) {
|
|
ret = SCRIPT_SYNTAX_ERROR;
|
|
}
|
|
else { // LUA_ERRMEM or LUA_ERRGCMM
|
|
ret = SCRIPT_PANIC;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Get the name of a script for error reporting etc.
|
|
static const char * getScriptName(uint8_t idx)
|
|
{
|
|
int ref = scriptInternalData[idx].reference;
|
|
|
|
#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;
|
|
}
|
|
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 {
|
|
return "standalone";
|
|
}
|
|
}
|
|
|
|
static bool luaLoad(const char * pathname, ScriptInternalData & sid)
|
|
{
|
|
sid.state = luaLoadScriptFileToState(lsScripts, pathname, LUA_SCRIPT_LOAD_MODE);
|
|
|
|
if (sid.state != SCRIPT_OK) {
|
|
luaFree(lsScripts, sid);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template<unsigned int LD, unsigned int LF>
|
|
static bool luaLoadFile(const char (&dirname)[LD], const char (&filename)[LF], ScriptInternalData & sid) {
|
|
constexpr size_t maxlen{LD + LF + (sizeof(SCRIPT_EXT) - 1) + 1 + 1}; // iff dirname is string-literal (LD includes '\0') this is one byte too large, but with C++11 there is no chance to check if dirname is a literal or a (maybe-unterminated) char-array
|
|
char pathname[maxlen];
|
|
snprintf(pathname, maxlen, "%.*s/%.*s%s", LD, dirname, LF, filename, SCRIPT_EXT);
|
|
return luaLoad(pathname, sid);
|
|
}
|
|
|
|
#if defined(LUA_MODEL_SCRIPTS)
|
|
static bool luaLoadMixScript(uint8_t ref)
|
|
{
|
|
#ifdef DEBUG
|
|
if (ref < SCRIPT_MIX_FIRST) {
|
|
return false;
|
|
}
|
|
#endif
|
|
uint8_t idx = ref - SCRIPT_MIX_FIRST;
|
|
ScriptData & sd = g_model.scriptsData[idx];
|
|
|
|
if (ZEXIST(sd.file)) {
|
|
ScriptInternalData & sid = scriptInternalData[luaScriptsCount++];
|
|
sid.reference = ref;
|
|
return luaLoadFile(SCRIPTS_MIXES_PATH, sd.file, sid);
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static bool luaLoadFunctionScript(uint8_t ref)
|
|
{
|
|
uint8_t idx;
|
|
CustomFunctionData * fn;
|
|
|
|
if (ref <= SCRIPT_FUNC_LAST) {
|
|
if (modelSFEnabled()) {
|
|
idx = ref - SCRIPT_FUNC_FIRST;
|
|
fn = &g_model.customFn[idx];
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
else if (radioGFEnabled()) {
|
|
idx = ref - SCRIPT_GFUNC_FIRST;
|
|
fn = &g_eeGeneral.customFn[idx];
|
|
}
|
|
else
|
|
return false;
|
|
|
|
if (fn -> func == FUNC_PLAY_SCRIPT && ZEXIST(fn -> play.name)) {
|
|
if (luaScriptsCount < MAX_SCRIPTS) {
|
|
ScriptInternalData & sid = scriptInternalData[luaScriptsCount++];
|
|
sid.reference = ref;
|
|
return luaLoadFile(SCRIPTS_FUNCS_PATH, fn->play.name, sid);
|
|
}
|
|
else {
|
|
POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (fn -> func == FUNC_RGB_LED && ZEXIST(fn -> play.name)) {
|
|
if (luaScriptsCount < MAX_SCRIPTS) {
|
|
ScriptInternalData & sid = scriptInternalData[luaScriptsCount++];
|
|
sid.reference = ref;
|
|
return luaLoadFile(SCRIPTS_RGB_PATH, fn->play.name, sid);
|
|
}
|
|
else {
|
|
POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
#if defined(PCBTARANIS)
|
|
static bool luaLoadTelemetryScript(uint8_t ref)
|
|
{
|
|
uint8_t idx = ref - SCRIPT_TELEMETRY_FIRST;
|
|
TelemetryScreenType screenType = TELEMETRY_SCREEN_TYPE(idx);
|
|
|
|
if (screenType == TELEMETRY_SCREEN_TYPE_SCRIPT) {
|
|
TelemetryScriptData & script = g_model.screens[idx].script;
|
|
|
|
if (ZEXIST(script.file)) {
|
|
if (luaScriptsCount < MAX_SCRIPTS) {
|
|
ScriptInternalData & sid = scriptInternalData[luaScriptsCount++];
|
|
sid.reference = ref;
|
|
return luaLoadFile(SCRIPTS_TELEM_PATH, script.file, sid);
|
|
}
|
|
else {
|
|
POPUP_WARNING(STR_TOO_MANY_LUA_SCRIPTS);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
bool isTelemetryScriptAvailable()
|
|
{
|
|
#if defined(PCBTARANIS)
|
|
for (int i = 0; i < luaScriptsCount; i++) {
|
|
ScriptInternalData & sid = scriptInternalData[i];
|
|
if (sid.reference == SCRIPT_TELEMETRY_FIRST + s_frsky_view) {
|
|
return true;
|
|
}
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void displayLuaError(bool firstCall = false)
|
|
{
|
|
const char * title;
|
|
switch (errorState) {
|
|
case SCRIPT_SYNTAX_ERROR:
|
|
title = STR_SCRIPT_SYNTAX_ERROR;
|
|
break;
|
|
case SCRIPT_PANIC:
|
|
title = STR_SCRIPT_PANIC;
|
|
break;
|
|
default:
|
|
title = STR_SCRIPT_ERROR;
|
|
}
|
|
|
|
#if defined(COLORLCD)
|
|
static bool drewBackground = false;
|
|
|
|
if (firstCall)
|
|
drewBackground = false;
|
|
|
|
if (!luaLcdAllowed || luaLcdBuffer == nullptr)
|
|
return;
|
|
|
|
coord_t w = 0.75 * LCD_W;
|
|
coord_t left = (LCD_W - w) / 2;
|
|
#if (LCD_W > LCD_H)
|
|
coord_t hh = getFontHeight(FONT(XL)) + 4;
|
|
#else
|
|
coord_t hh = getFontHeight(FONT(L)) + 4;
|
|
#endif
|
|
coord_t h = 0.75 * LCD_H - hh;
|
|
coord_t top = (LCD_H - h + hh / 2) / 2;
|
|
|
|
if (!drewBackground) {
|
|
drewBackground = true;
|
|
luaLcdBuffer->drawFilledRect(0, 0, LCD_W, LCD_H, SOLID, BLACK, OPACITY(6));
|
|
}
|
|
|
|
luaLcdBuffer->drawSolidFilledRect(left, top - hh, w, hh, COLOR_THEME_SECONDARY1);
|
|
luaLcdBuffer->drawSolidFilledRect(left, top, w, h, COLOR_THEME_SECONDARY3);
|
|
#if (LCD_W > LCD_H)
|
|
luaLcdBuffer->drawText(left + 10, top - hh + 2, title, FONT(XL) | COLOR_THEME_PRIMARY2);
|
|
drawTextLines(luaLcdBuffer, left + 10, top + 5, w - 20, h - 10, lua_warning_info, FONT(L) | COLOR_THEME_PRIMARY1);
|
|
#else
|
|
luaLcdBuffer->drawText(left + 10, top - hh + 2, title, FONT(L) | COLOR_THEME_PRIMARY2);
|
|
drawTextLines(luaLcdBuffer, left + 10, top + 5, w - 20, h - 10, lua_warning_info, FONT(STD) | COLOR_THEME_PRIMARY1);
|
|
#endif
|
|
#else
|
|
if (!luaLcdAllowed)
|
|
return;
|
|
|
|
drawMessageBox(title);
|
|
coord_t y = WARNING_LINE_Y + FH + 4;
|
|
|
|
char *str = lua_warning_info;
|
|
char *split = strstr(str, ": ");
|
|
|
|
if (split) {
|
|
lcdDrawSizedText(WARNING_LINE_X, y, str, split - str, SMLSIZE);
|
|
y += FH;
|
|
str = split + 2;
|
|
}
|
|
|
|
while(strlen(str)) {
|
|
split = strlen(str) > WARNING_LINE_LEN ? str + WARNING_LINE_LEN : str + strlen(str);
|
|
lcdDrawSizedText(WARNING_LINE_X, y, str, split - str, SMLSIZE);
|
|
y += FH;
|
|
str = split;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void luaError(lua_State * L, uint8_t error)
|
|
{
|
|
errorState = error;
|
|
const char* msg = lua_tostring(L, -1);
|
|
|
|
if (msg) {
|
|
#if defined(SIMU)
|
|
if (!strncmp(msg, ".", 2)) msg += 1;
|
|
#endif
|
|
#if LCD_W == 128
|
|
const char * tmp = strrchr(msg, '/');
|
|
if (tmp) msg = tmp + 1;
|
|
#else
|
|
if (!strncmp(msg, "/SCRIPTS/", 9)) msg += 9;
|
|
#endif
|
|
strncpy(lua_warning_info, msg, LUA_WARNING_INFO_LEN);
|
|
lua_warning_info[LUA_WARNING_INFO_LEN] = '\0';
|
|
}
|
|
|
|
displayLuaError(true);
|
|
TRACE_ERROR("%s\n", lua_warning_info);
|
|
}
|
|
|
|
// static void luaDumpStack (lua_State *L) {
|
|
// int top=lua_gettop(L);
|
|
// for (int i=1; i <= top; i++) {
|
|
// printf("%d\t%s\t", i, luaL_typename(L,i));
|
|
// switch (lua_type(L, i)) {
|
|
// case LUA_TNUMBER:
|
|
// printf("%g\n",lua_tonumber(L,i));
|
|
// break;
|
|
// case LUA_TSTRING:
|
|
// printf("%s\n",lua_tostring(L,i));
|
|
// break;
|
|
// case LUA_TBOOLEAN:
|
|
// printf("%s\n", (lua_toboolean(L, i) ? "true" : "false"));
|
|
// break;
|
|
// case LUA_TNIL:
|
|
// printf("%s\n", "nil");
|
|
// break;
|
|
// case LUA_TTABLE: {
|
|
// lua_pushnil(L);
|
|
// while(lua_next(L,i)) {
|
|
// const char* key = lua_tostring(L,-2);
|
|
// const char* val = lua_tostring(L,-1);
|
|
// printf("\t%s = %s\n", key, val);
|
|
// lua_pop(L,1);
|
|
// }
|
|
// } break;
|
|
// default:
|
|
// printf("%p\n",lua_topointer(L,i));
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
// Register a function from a table on the top of the stack
|
|
static int luaRegisterFunction(const char * key)
|
|
{
|
|
lua_getfield(lsScripts, -1, key);
|
|
int typ = lua_type(lsScripts, -1);
|
|
|
|
if (typ == LUA_TFUNCTION) {
|
|
return luaL_ref(lsScripts, LUA_REGISTRYINDEX);
|
|
}
|
|
else {
|
|
if (typ != LUA_TNIL) {
|
|
TRACE_ERROR("luaRegisterFunction(%s): Error: '%.*s' is not a function\n", LEN_SCRIPT_FILENAME, getScriptName(luaScriptsCount - 1), key);
|
|
}
|
|
lua_pop(lsScripts, 1);
|
|
return LUA_NOREF;
|
|
}
|
|
}
|
|
|
|
// Load Lua scripts. If filename is given, then load a standalone script
|
|
static void luaLoadScripts(bool init, const char * filename = nullptr)
|
|
{
|
|
// Static variables for keeping state when Lua is preempted
|
|
static uint8_t ref;
|
|
static int initFunction;
|
|
|
|
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)) {
|
|
luaError(lsScripts, scriptInternalData[luaScriptsCount - 1].state);
|
|
continue; // If error then skip the rest of the loop
|
|
}
|
|
} else
|
|
#endif
|
|
if (ref <= SCRIPT_GFUNC_LAST) {
|
|
if (luaLoadFunctionScript(ref)) {
|
|
luaError(lsScripts, scriptInternalData[luaScriptsCount - 1].state);
|
|
continue;
|
|
}
|
|
}
|
|
#if defined(PCBTARANIS)
|
|
else if (ref <= SCRIPT_TELEMETRY_LAST) {
|
|
if (luaLoadTelemetryScript(ref)) {
|
|
luaError(lsScripts, scriptInternalData[luaScriptsCount - 1].state);
|
|
continue;
|
|
}
|
|
}
|
|
#endif
|
|
else {
|
|
// Standalone script
|
|
ScriptInternalData & sid = scriptInternalData[luaScriptsCount++];
|
|
sid.reference = SCRIPT_STANDALONE;
|
|
if (luaLoad(filename, sid)) {
|
|
luaError(lsScripts, sid.state);
|
|
continue;
|
|
}
|
|
}
|
|
// 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) {
|
|
snprintf(lua_warning_info, LUA_WARNING_INFO_LEN, "luaLoadScripts(%.*s): No run function\n", LEN_SCRIPT_FILENAME, getScriptName(idx));
|
|
sid.state = SCRIPT_SYNTAX_ERROR;
|
|
initFunction = LUA_NOREF;
|
|
}
|
|
#if defined(LUA_MODEL_SCRIPTS)
|
|
// Get input/output tables for mixer scripts
|
|
if (ref <= SCRIPT_MIX_LAST) {
|
|
ScriptInputsOutputs * sio = & scriptInputsOutputs[ref - SCRIPT_MIX_FIRST];
|
|
lua_getfield(lsScripts, -1, "input");
|
|
luaGetInputs(*sio);
|
|
lua_pop(lsScripts, 1);
|
|
lua_getfield(lsScripts, -1, "output");
|
|
luaGetOutputs(*sio);
|
|
lua_pop(lsScripts, 1);
|
|
}
|
|
#endif
|
|
}
|
|
else {
|
|
snprintf(lua_warning_info, LUA_WARNING_INFO_LEN, "luaLoadScripts(%.*s): The script did not return a table\n", LEN_SCRIPT_FILENAME, getScriptName(idx));
|
|
sid.state = SCRIPT_SYNTAX_ERROR;
|
|
initFunction = LUA_NOREF;
|
|
}
|
|
|
|
// 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;
|
|
initFunction = LUA_NOREF;
|
|
}
|
|
} while(initFunction != LUA_NOREF);
|
|
|
|
if (sid.state != SCRIPT_OK) {
|
|
luaError(lsScripts, sid.state);
|
|
|
|
// 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(++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 LuaEventData evt;
|
|
if (init) idx = 0;
|
|
|
|
bool scriptWasRun = false;
|
|
bool fullGC = !allowLcdUsage;
|
|
static uint8_t luaDisplayStatistics = false;
|
|
|
|
// Run in the right interactive mode
|
|
if (lua_status(lsScripts) == LUA_YIELD && allowLcdUsage != luaLcdAllowed) {
|
|
#if defined(PCBTARANIS)
|
|
uint8_t ref = scriptInternalData[idx].reference;
|
|
if (luaLcdAllowed && menuHandlers[menuLevel] != menuViewTelemetry && ref >= SCRIPT_TELEMETRY_FIRST && ref <= SCRIPT_TELEMETRY_LAST) {
|
|
// Telemetry screen was exited while foreground function was preempted - finish in the background
|
|
luaLcdAllowed = false;
|
|
} else
|
|
#endif
|
|
{
|
|
return scriptWasRun;
|
|
}
|
|
} else {
|
|
luaLcdAllowed = allowLcdUsage;
|
|
}
|
|
|
|
for (; idx < luaScriptsCount; idx++) {
|
|
ScriptInternalData & sid = scriptInternalData[idx];
|
|
uint8_t ref = sid.reference;
|
|
|
|
if (sid.state != SCRIPT_OK) {
|
|
displayLuaError();
|
|
scriptWasRun = true;
|
|
|
|
if (ref == SCRIPT_STANDALONE) {
|
|
// Pull a new event from the buffer
|
|
luaNextEvent(&evt);
|
|
if (evt.event == EVT_KEY_LONG(KEY_EXIT)) {
|
|
luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
|
|
}
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
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
|
|
luaNextEvent(&evt);
|
|
|
|
lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run);
|
|
lua_pushunsigned(lsScripts, evt.event);
|
|
inputsCount = 1;
|
|
|
|
#if defined(HARDWARE_TOUCH)
|
|
if (IS_TOUCH_EVENT(evt.event)) {
|
|
luaPushTouchEventTable(lsScripts, &evt);
|
|
inputsCount = 2;
|
|
}
|
|
#endif
|
|
}
|
|
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) {
|
|
uint8_t idx;
|
|
CustomFunctionData * fn;
|
|
CustomFunctionsContext * functionsContext;
|
|
|
|
if (ref <= SCRIPT_FUNC_LAST) {
|
|
if (!modelSFEnabled()) continue;
|
|
idx = ref - SCRIPT_FUNC_FIRST;
|
|
fn = &g_model.customFn[idx];
|
|
functionsContext = &modelFunctionsContext;
|
|
} else {
|
|
if (!radioGFEnabled()) continue;
|
|
idx = ref - SCRIPT_GFUNC_FIRST;
|
|
fn = &g_eeGeneral.customFn[idx];
|
|
functionsContext = &globalFunctionsContext;
|
|
}
|
|
|
|
tmr10ms_t tmr10ms = get_tmr10ms();
|
|
|
|
if (getSwitch(fn->swtch) && (functionsContext->lastFunctionTime[idx] == 0 || CFN_PLAY_REPEAT(fn) == 0)) {
|
|
lua_rawgeti(lsScripts, LUA_REGISTRYINDEX, sid.run);
|
|
functionsContext->lastFunctionTime[idx] = tmr10ms;
|
|
}
|
|
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)) {
|
|
sid.state = SCRIPT_SYNTAX_ERROR;
|
|
snprintf(lua_warning_info, LUA_WARNING_INFO_LEN, "Script %.*s: run function did not return a number\n", LEN_SCRIPT_FILENAME, getScriptName(idx));
|
|
luaError(lsScripts, sid.state);
|
|
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);
|
|
luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
|
|
}
|
|
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 {
|
|
sid.state = SCRIPT_SYNTAX_ERROR;
|
|
snprintf(lua_warning_info, LUA_WARNING_INFO_LEN, "Script run function returned unexpected value\n");
|
|
luaError(lsScripts, sid.state);
|
|
}
|
|
|
|
if (evt.event == EVT_KEY_LONG(KEY_EXIT)) {
|
|
TRACE("Script force exit");
|
|
// killEvents(evt);
|
|
luaEmptyEventBuffer();
|
|
luaState = INTERPRETER_RELOAD_PERMANENT_SCRIPTS;
|
|
}
|
|
#if defined(KEYS_GPIO_REG_MENU)
|
|
// TODO find another key and add a #define
|
|
else if (evt.event == EVT_KEY_LONG(KEY_MENU)) {
|
|
killEvents(evt.event);
|
|
luaEmptyEventBuffer();
|
|
luaDisplayStatistics = !luaDisplayStatistics;
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
else {
|
|
// Error
|
|
sid.state = SCRIPT_SYNTAX_ERROR;
|
|
luaError(lsScripts, sid.state);
|
|
|
|
// 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
|
|
luaFree(lsScripts, sid);
|
|
luaDoGc(lsScripts, true);
|
|
}
|
|
|
|
scriptWasRun = true;
|
|
} // for
|
|
|
|
// Start a new cycle
|
|
idx = 0;
|
|
|
|
return scriptWasRun;
|
|
} //resumeLua(...)
|
|
|
|
|
|
bool luaTask(bool allowLcdUsage)
|
|
{
|
|
bool init = false;
|
|
bool scriptWasRun = false;
|
|
|
|
// For preemption
|
|
luaCycleStart = get_tmr10ms();
|
|
|
|
// Trying to replace CPU usage measure
|
|
instructionsPercent = 100 * maxLuaDuration / LUA_TASK_PERIOD_TICKS;
|
|
|
|
switch (luaState) {
|
|
case INTERPRETER_RELOAD_PERMANENT_SCRIPTS:
|
|
init = true;
|
|
luaState = INTERPRETER_LOADING;
|
|
|
|
#if defined(USE_HATS_AS_KEYS)
|
|
if (_is_standalone_script()) setTransposeHatsForLUA(false);
|
|
#endif
|
|
|
|
case INTERPRETER_LOADING:
|
|
PROTECT_LUA() {
|
|
luaLoadScripts(init);
|
|
}
|
|
else luaDisable();
|
|
UNPROTECT_LUA();
|
|
break;
|
|
|
|
case INTERPRETER_START_RUNNING:
|
|
init = true;
|
|
luaState = INTERPRETER_RUNNING;
|
|
|
|
#if defined(USE_HATS_AS_KEYS)
|
|
if (_is_standalone_script()) setTransposeHatsForLUA(true);
|
|
#endif
|
|
|
|
case INTERPRETER_RUNNING:
|
|
PROTECT_LUA() {
|
|
scriptWasRun = resumeLua(init, allowLcdUsage);
|
|
}
|
|
else luaDisable();
|
|
UNPROTECT_LUA();
|
|
}
|
|
return scriptWasRun;
|
|
}
|
|
|
|
void checkLuaMemoryUsage()
|
|
{
|
|
#if (LUA_MEM_MAX > 0)
|
|
uint32_t totalMemUsed = luaGetMemUsed(lsScripts);
|
|
#if defined(COLORLCD)
|
|
totalMemUsed += luaGetMemUsed(lsWidgets);
|
|
totalMemUsed += luaExtraMemoryUsage;
|
|
#endif
|
|
if (totalMemUsed > LUA_MEM_MAX) {
|
|
TRACE_ERROR("checkLuaMemoryUsage(): max limit reached (%u), killing Lua\n", totalMemUsed);
|
|
// disable Lua scripts
|
|
luaClose(&lsScripts);
|
|
luaDisable();
|
|
#if defined(COLORLCD)
|
|
// disable widgets
|
|
luaClose(&lsWidgets);
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
uint32_t luaGetMemUsed(lua_State * L)
|
|
{
|
|
return L ? (lua_gc(L, LUA_GCCOUNT, 0) << 10) + lua_gc(L, LUA_GCCOUNTB, 0) : 0;
|
|
}
|
|
|
|
void luaInit()
|
|
{
|
|
TRACE("luaInit");
|
|
|
|
luaClose(&lsScripts);
|
|
L = nullptr;
|
|
|
|
if (luaState != INTERPRETER_PANIC) {
|
|
#if defined(USE_BIN_ALLOCATOR)
|
|
L = lua_newstate(bin_l_alloc, nullptr); //we use our own allocator!
|
|
#elif defined(LUA_ALLOCATOR_TRACER)
|
|
memclear(&lsScriptsTrace, sizeof(lsScriptsTrace);
|
|
lsScriptsTrace.script = "lua_newstate(scripts)";
|
|
L = lua_newstate(tracer_alloc, &lsScriptsTrace); //we use tracer allocator
|
|
#else
|
|
L = lua_newstate(l_alloc, nullptr); //we use Lua default allocator
|
|
#endif
|
|
if (L) {
|
|
// install our panic handler
|
|
lua_atpanic(L, &custom_lua_atpanic);
|
|
|
|
#if defined(LUA_ALLOCATOR_TRACER)
|
|
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
|
|
memclear(scriptInternalData, sizeof(scriptInternalData));
|
|
memclear(scriptInputsOutputs, sizeof(scriptInputsOutputs));
|
|
luaScriptsCount = 0;
|
|
|
|
// protect libs and constants registration
|
|
PROTECT_LUA() {
|
|
luaRegisterLibraries(lsScripts);
|
|
}
|
|
else {
|
|
// if we got panic during registration
|
|
// we disable Lua for this session
|
|
luaDisable();
|
|
}
|
|
UNPROTECT_LUA();
|
|
TRACE("lsScripts %p", lsScripts);
|
|
}
|
|
else {
|
|
/* log error and return */
|
|
luaDisable();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool readToolName(char * toolName, const char * filename)
|
|
{
|
|
FIL file;
|
|
char buffer[1024];
|
|
UINT count;
|
|
|
|
if (f_open(&file, filename, FA_READ) != FR_OK) {
|
|
return "Error opening file";
|
|
}
|
|
|
|
FRESULT res = f_read(&file, &buffer, sizeof(buffer), &count);
|
|
f_close(&file);
|
|
|
|
if (res != FR_OK)
|
|
return false;
|
|
|
|
const char * tns = "TNS|";
|
|
auto * start = std::search(buffer, buffer + sizeof(buffer), tns, tns + 4);
|
|
if (start >= buffer + sizeof(buffer))
|
|
return false;
|
|
|
|
start += 4;
|
|
|
|
const char * tne = "|TNE";
|
|
auto * end = std::search(buffer, buffer + sizeof(buffer), tne, tne + 4);
|
|
if (end >= buffer + sizeof(buffer) || end <= start)
|
|
return false;
|
|
|
|
uint8_t len = end - start;
|
|
if (len > RADIO_TOOL_NAME_MAXLEN)
|
|
return false;
|
|
|
|
strncpy(toolName, start, len);
|
|
toolName[len] = '\0';
|
|
|
|
return true;
|
|
}
|
|
|
|
bool isRadioScriptTool(const char * filename)
|
|
{
|
|
const char * ext = getFileExtension(filename);
|
|
return ext && !strcasecmp(ext, SCRIPT_EXT);
|
|
}
|