diff --git a/Orbitersdk/include/OrbiterAPI.h b/Orbitersdk/include/OrbiterAPI.h index 9d83b0dc1..a688b3a29 100644 --- a/Orbitersdk/include/OrbiterAPI.h +++ b/Orbitersdk/include/OrbiterAPI.h @@ -4492,21 +4492,6 @@ OAPIFUNC int oapiDelInterpreter (INTERPRETERHANDLE hInterp); */ OAPIFUNC bool oapiExecScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd); - /** - * \brief Passes a command to an interpreter instance for execution. - * \param hInterp interpreter handle - * \param cmd Lua command to be executed - * \return \e false on error (interpreter library not found, or command error) - * \note This function returns immediately. The command is executed during the - * next postStep cycle. If more asynchronous commands are issued before - * execution starts, they are appended to the execution list. If the - * interpreter receives a synchronous request (oapiExecScriptCmd) before the - * asynchrounous commands are executed, the synchronous command is executed - * immediately, while the asynchronous requests continue waiting. - * \sa oapiExecScriptCmd, oapiCreateInterpreter, oapiDelInterpreter - */ -OAPIFUNC bool oapiAsyncScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd); - //typedef struct lua_State lua_State; OAPIFUNC lua_State *oapiGetLua (INTERPRETERHANDLE hInterp); //@} diff --git a/Script/oapi_init.lua b/Script/oapi_init.lua index 0ead06b3e..31f179544 100644 --- a/Script/oapi_init.lua +++ b/Script/oapi_init.lua @@ -33,195 +33,7 @@ function run_global (script) dofile(script) end --- ------------------------------------------------- --- Branch management --- Branch threads are stored in table 'branch' with --- counter 'branch.count' --- ------------------------------------------------- - -branch = {} -branch.count = 0 -branch.nslot = 0 - --- Create a new branch coroutine, store it in the branch --- table, and execute its first cycle - -function proc.bg (func,...) - -- first, collect all dead branches - for i=1,branch.nslot do - if branch[i] ~= nil then - if coroutine.status(branch[i]) == 'dead' then - branch[i] = nil - branch.count = branch.count-1 - end - end - end - - -- check if there is a free slot - local slot = 0 - for i=1,branch.nslot do - if branch[i] == nil then - slot = i - break - end - end - - -- no free slot: increase branch counter - if slot == 0 then - branch.nslot = branch.nslot+1 - slot = branch.nslot - end - - -- create the new branch - local th = coroutine.create (func) - branch[slot] = th - branch.count = branch.count+1 - coroutine.resume (th,...) - term.out ('job id='..slot..' ('..branch.count..' jobs)') - return slot -end - --- Remove a branch from the branch table (and keep fingers --- crossed that the coroutine will be garbage-collected) - -function proc.kill (n) - if branch[n] ~= nil then - branch[n] = nil - branch.count = branch.count-1 - while (branch.nslot > 0) and (branch[branch.nslot] == nil) do - branch.nslot = branch.nslot-1 - end - term.out ('job '..n..' killed ('..branch.count..' jobs left)') - end -end - --- Time skip: branches yield, the main trunk resumes all --- coroutines for a single cycle, then calls proc.Frameskip --- to pass control back to orbiter for a new simulation cycle - -function proc.skip () - if coroutine.running() == nil then -- we are in the main trunk - for i=1,branch.nslot do - if branch[i] ~= nil then - coroutine.resume (branch[i]) - end - end - proc.Frameskip() -- hand control to orbiter for one cycle - if wait_exit ~= nil then - error("Lua thread terminated") -- return to caller immediately - -- WARNING: then string must be matched in Interpreter::LuaCall - end - else - coroutine.yield() - end -end - --- ------------------------------------------------- --- A few waiting functions --- ------------------------------------------------- - --- wait for simulation time t. --- Optionally execute a function at each frame while waiting -function proc.wait_simtime (t, f, ...) - while oapi.get_simtime() < t do - if f then - f(unpack(arg)) - end - proc.skip() - end -end - --- wait for simulation interval dt --- Optionally execute a function at each frame while waiting -function proc.wait_simdt (dt, f, ...) - local t1 = oapi.get_simtime()+dt - while oapi.get_simtime() < t1 do - if f then - f(unpack(arg)) - end - proc.skip() - end -end - --- wait for system time t --- Optionally execute a function at each frame while waiting -function proc.wait_systime (t, f, ...) - while oapi.get_systime() < t do - if f then - f(unpack(arg)) - end - proc.skip() - end -end - --- wait for system interval dt --- Optionally execute a function at each frame while waiting -function proc.wait_sysdt (dt, f, ...) - local t1 = oapi.get_systime()+dt - while oapi.get_systime() < t1 do - if f then - f(unpack(arg)) - end - proc.skip() - end -end - --- wait for a given number of frames --- Optionally execute a function at each frame while waiting -function proc.wait_frames(n, f, ...) - local frame = 0 - while frame < n do - if f then - f(unpack(arg)) - end - frame = frame+1 - proc.skip() - end -end - --- wait for f() >= tgt -function proc.wait_ge (f, tgt, ...) - while f(unpack(arg)) < tgt do proc.skip() end -end - --- wait for f() <= tgt -function proc.wait_le (f, tgt, ...) - while f(unpack(arg)) > tgt do proc.skip() end -end - --- wait for input -function proc.wait_input (title) - oapi.open_inputbox (title) - local ans = oapi.receive_input () - while ans == nil do - proc.skip() - ans = oapi.receive_input () - end - return ans -end - --- ------------------------------------------------- --- Private functions (should not be directly called) --- ------------------------------------------------- - --- Idle loop (called after command returns, to allow --- background jobs to continue executing) - -function _idle () - for i=1,branch.nslot do - if branch[i] ~= nil then - coroutine.resume (branch[i]) - end - end - return branch.count -end - --- Returns the number of background jobs - -function _nbranch () - return branch.count -end - +proc = require("proc") -- helpers for basic types function _V(x,y,z) diff --git a/Script/proc.lua b/Script/proc.lua new file mode 100644 index 000000000..8133556ab --- /dev/null +++ b/Script/proc.lua @@ -0,0 +1,184 @@ +local proc = { + tasks = {}, + nextId = 1, + active = false +} + +-- spawn a new task and run immediately +function proc.bg(func, ...) + local co = coroutine.create(func) + + -- immediately run until first yield + local status, res = coroutine.resume(co, ...) + if not status then + print("Coroutine error:", res) + error(res) + return nil + end + + if coroutine.status(co) ~= "dead" then + local id = proc.nextId + proc.nextId = proc.nextId + 1 + table.insert(proc.tasks, { id = id, co = co }) + proc.active = true + return id + end + + return nil +end + +-- spawn a Lua file as a task +function proc.bgFile(filename) + local chunk, err = loadfile(filename) + if not chunk then + print("Failed to load file:", err) + error(err) + return nil + end + + -- spawn the chunk like a normal function + return proc.bg(chunk) +end + +-- spawn a Lua string as a task +function proc.bgString(code) + local chunk, err = loadstring(code) + if not chunk then + print("Failed to load string:", err) + error(err) + return nil + end + + -- spawn the chunk like a normal function + return proc.bg(chunk) +end + +-- update all tasks per frame +function proc.update() + local anyLeft = false + local i = 1 + while i <= #proc.tasks do + local task = proc.tasks[i] + + local status, res = coroutine.resume(task.co) + if not status then + print("Coroutine error:", res) + error(res) + table.remove(proc.tasks, i) + elseif coroutine.status(task.co) == "dead" then + table.remove(proc.tasks, i) + else + anyLeft = true + i = i + 1 + end + end + + proc.active = anyLeft +end + +-- kill a task by ID +function proc.kill(taskId) + for i, task in ipairs(proc.tasks) do + if task.id == taskId then + table.remove(proc.tasks, i) + break + end + end + + proc.active = #proc.tasks > 0 +end + +-- check if there are any active tasks +function proc.hasTasks() + return proc.active +end + +-- abstraction for coroutine.yield() +function proc.skip() + return coroutine.yield() +end +-- ------------------------------------------------- +-- A few waiting functions +-- ------------------------------------------------- + +-- wait for simulation time t. +-- Optionally execute a function at each frame while waiting +function proc.wait_simtime (t, f, ...) + while oapi.get_simtime() < t do + if f then + f(unpack(arg)) + end + proc.skip() + end +end + +-- wait for simulation interval dt +-- Optionally execute a function at each frame while waiting +function proc.wait_simdt (dt, f, ...) + local t1 = oapi.get_simtime()+dt + while oapi.get_simtime() < t1 do + if f then + f(unpack(arg)) + end + proc.skip() + end +end + +-- wait for system time t +-- Optionally execute a function at each frame while waiting +function proc.wait_systime (t, f, ...) + while oapi.get_systime() < t do + if f then + f(unpack(arg)) + end + proc.skip() + end +end + +-- wait for system interval dt +-- Optionally execute a function at each frame while waiting +function proc.wait_sysdt (dt, f, ...) + local t1 = oapi.get_systime()+dt + while oapi.get_systime() < t1 do + if f then + f(unpack(arg)) + end + proc.skip() + end +end + +-- wait for a given number of frames +-- Optionally execute a function at each frame while waiting +function proc.wait_frames(n, f, ...) + local frame = 0 + while frame < n do + if f then + f(unpack(arg)) + end + frame = frame+1 + proc.skip() + end +end + +-- wait for f() >= tgt +function proc.wait_ge (f, tgt, ...) + while f(unpack(arg)) < tgt do proc.skip() end +end + +-- wait for f() <= tgt +function proc.wait_le (f, tgt, ...) + while f(unpack(arg)) > tgt do proc.skip() end +end + +-- wait for input +function proc.wait_input (title) + oapi.open_inputbox (title) + local ans = oapi.receive_input () + while ans == nil do + proc.skip() + ans = oapi.receive_input () + end + return ans +end + +return proc diff --git a/Src/Module/LuaScript/LuaInline/LuaInline.cpp b/Src/Module/LuaScript/LuaInline/LuaInline.cpp index f30eac185..f901e8d93 100644 --- a/Src/Module/LuaScript/LuaInline/LuaInline.cpp +++ b/Src/Module/LuaScript/LuaInline/LuaInline.cpp @@ -32,28 +32,12 @@ InterpreterList::Environment::Environment() { - cmd = NULL; - singleCmd = false; - hThread = NULL; interp = CreateInterpreter (); } InterpreterList::Environment::~Environment() { - if (interp) { - if (hThread) { - termInterp = true; - interp->Terminate(); - interp->EndExec(); // give the thread opportunity to close - - if (WaitForSingleObject (hThread, 1000) != 0) { - oapiWriteLog((char*)"LuaInline: timeout while waiting for interpreter thread"); - TerminateThread (hThread, 0); - } - CloseHandle (hThread); - } - delete interp; - } + delete interp; } Interpreter *InterpreterList::Environment::CreateInterpreter () @@ -62,35 +46,9 @@ Interpreter *InterpreterList::Environment::CreateInterpreter () termInterp = false; interp = new Interpreter (); interp->Initialise(); - hThread = (HANDLE)_beginthreadex (NULL, 4096, &InterpreterThreadProc, this, 0, &id); return interp; } -unsigned int WINAPI InterpreterList::Environment::InterpreterThreadProc (LPVOID context) -{ - InterpreterList::Environment *env = (InterpreterList::Environment*)context; - Interpreter *interp = env->interp; - // interpreter loop - for (;;) { - interp->WaitExec(); // wait for execution permission - if (env->termInterp) break; // close thread requested - if (env->cmd) { - interp->RunChunk (env->cmd, strlen (env->cmd)); // run command from buffer - delete []env->cmd; - env->cmd = 0; - if (env->singleCmd) break; - } else { - interp->RunChunk ("", 0); // idle loop - } - if (interp->Status() == 1) break; - interp->EndExec(); // return control - } - interp->EndExec(); // return mutex (is this necessary?) - _endthreadex(0); - return 0; -} - - // ============================================================== // class InterpreterList: implementation @@ -120,10 +78,7 @@ void InterpreterList::clbkPostStep (double simt, double simdt, double mjd) if (!list[i]->interp) DelInterpreter (list[i--]); for (i = 0; i < nlist; i++) { // let the interpreter do some work - if (list[i]->interp->IsBusy() || list[i]->cmd || list[i]->interp->nJobs()) { - list[i]->interp->EndExec(); - list[i]->interp->WaitExec(); - } + list[i]->interp->PostStep(simt, simdt, mjd); } } @@ -177,10 +132,8 @@ DLLCLBK void InitModule (HINSTANCE hDLL) DLLCLBK void ExitModule (HINSTANCE hDLL) { - if (g_IList) { - delete g_IList; - g_IList = nullptr; - } + delete g_IList; + g_IList = nullptr; } // interpreter-specific callback functions @@ -206,50 +159,21 @@ DLLCLBK INTERPRETERHANDLE opcRunInterpreter (const char *cmd) { if (g_IList) { InterpreterList::Environment *env = g_IList->AddInterpreter(); - env->cmd = new char[strlen(cmd)+10]; - sprintf (env->cmd, "run('%s')", cmd); + char *runcmd = new char[strlen(cmd)+32]; + sprintf (runcmd, "proc.bgFile('Script/%s.lua')", cmd); + env->interp->RunChunk(runcmd, strlen(runcmd)); + delete[] runcmd; return (INTERPRETERHANDLE)env; } else { return NULL; } } -DLLCLBK bool opcAsyncScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd) -{ - InterpreterList::Environment *env = (InterpreterList::Environment*)hInterp; - char *str; - if (env->cmd) { // command still waiting: append new command - str = new char[strlen(env->cmd)+strlen(cmd)+2]; - strcpy(str,env->cmd); - strcat(str,";"); - strcat(str,cmd); - char *tmp = env->cmd; - env->cmd = str; - delete []tmp; - } else { - str = new char[strlen(cmd)+1]; - strcpy (str, cmd); - env->cmd = str; - } - return true; -} - DLLCLBK bool opcExecScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd) { InterpreterList::Environment *env = (InterpreterList::Environment*)hInterp; - char *str = new char[strlen(cmd)+1]; - char *cmd_async = 0; - strcpy (str, cmd); - if (env->cmd) // asynchronous request is waiting - cmd_async = env->cmd; - env->cmd = str; - while (env->cmd) { - // wait until command has been executed - env->interp->EndExec(); - env->interp->WaitExec(); - } - if (cmd_async) // restore the asynchronous request - env->cmd = cmd_async; + + env->interp->RunChunk (cmd, strlen (cmd)); return true; } diff --git a/Src/Module/LuaScript/LuaInline/LuaInline.h b/Src/Module/LuaScript/LuaInline/LuaInline.h index 566c2a379..d4567423d 100644 --- a/Src/Module/LuaScript/LuaInline/LuaInline.h +++ b/Src/Module/LuaScript/LuaInline/LuaInline.h @@ -35,11 +35,7 @@ class InterpreterList: public oapi::Module { ~Environment(); Interpreter *CreateInterpreter (); Interpreter *interp; // interpreter instance - HANDLE hThread; // interpreter thread bool termInterp; // interpreter kill flag - bool singleCmd; // terminate after single command - char *cmd; // interpreter command - static unsigned int WINAPI InterpreterThreadProc (LPVOID context); }; InterpreterList (HINSTANCE hDLL); diff --git a/Src/Module/LuaScript/LuaInterpreter/Interpreter.cpp b/Src/Module/LuaScript/LuaInterpreter/Interpreter.cpp index dce6823a6..91e18a145 100644 --- a/Src/Module/LuaScript/LuaInterpreter/Interpreter.cpp +++ b/Src/Module/LuaScript/LuaInterpreter/Interpreter.cpp @@ -54,22 +54,13 @@ VECTOR3 lua_tovector (lua_State *L, int idx) Interpreter::Interpreter () { L = luaL_newstate(); // create new Lua context - is_busy = false; // waiting for input is_term = false; // no attached terminal by default - bExecLocal = false; // flag for locally created mutexes - bWaitLocal = false; - jobs = 0; // background jobs - status = 0; // normal term_verbose = 0; // verbosity level postfunc = 0; postcontext = 0; // store interpreter context in the registry lua_pushlightuserdata (L, this); lua_setfield (L, LUA_REGISTRYINDEX, "interp"); - - hExecMutex = CreateMutex (NULL, TRUE, NULL); - hWaitMutex = CreateMutex (NULL, FALSE, NULL); - } void Interpreter::LazyInitGCCore() { @@ -87,6 +78,12 @@ static int traceback(lua_State *L) { return 1; } +void Interpreter::OnError(const char *msg) +{ + oapiWriteLogError("%s", msg); + oapiAddNotification(OAPINOTIF_ERROR, "Lua error", msg); +} + int Interpreter::LuaCall(lua_State *L, int narg, int nres) { int base = lua_gettop(L) - narg; @@ -95,25 +92,40 @@ int Interpreter::LuaCall(lua_State *L, int narg, int nres) int res = lua_pcall(L, narg, nres, base); lua_remove(L, base); if(res != 0) { + Interpreter *interp = GetInterpreter (L); const char *msg = lua_tostring(L, -1); - // Lua "threads" that are terminated when the scenario ends generate "Lua thread terminated" errors - // This is expected and should not generate logs/notifications - // Warning: the string must match with the one in oapi_init.lua: proc.skip () - // strstr may be heavy but it's only an error path - if(strstr(msg, "Lua thread terminated") == NULL) { - oapiWriteLogError("%s", msg); - oapiAddNotification(OAPINOTIF_ERROR, "Lua error", msg); - } + interp->OnError(msg); } return res; } +void Interpreter::hookTimeout(lua_State* L, lua_Debug* ar) +{ + Interpreter *interp = GetInterpreter (L); + auto now = std::chrono::steady_clock::now(); + int elapsed = std::chrono::duration_cast(now - interp->startTime).count(); + static int i; + i++; + if (elapsed > 100) { // milliseconds + luaL_error(L, "Script execution time exceeded"); + } +} + +int Interpreter::LuaCallTimeout(lua_State *L, int nargs, int nres) +{ + startTime = std::chrono::steady_clock::now(); + + // Call hook every 100000 instructions + lua_sethook(L, hookTimeout, LUA_MASKCOUNT, 100000); + int res = LuaCall(L, nargs, nres); + lua_sethook(L, nullptr, 0, 0); + + return res; +} + Interpreter::~Interpreter () { lua_close (L); - - if (hExecMutex) CloseHandle (hExecMutex); - if (hWaitMutex) CloseHandle (hWaitMutex); } void Interpreter::Initialise () @@ -134,21 +146,6 @@ void Interpreter::Initialise () LoadStartupScript (); // load default initialisation script } -int Interpreter::Status () const -{ - return status; -} - -bool Interpreter::IsBusy () const -{ - return is_busy; -} - -void Interpreter::Terminate () -{ - status = 1; -} - void Interpreter::PostStep (double simt, double simdt, double mjd) { if (postfunc) { @@ -156,6 +153,19 @@ void Interpreter::PostStep (double simt, double simdt, double mjd) postfunc = 0; postcontext = 0; } + + // check if proc.active is set and call proc.update if necessary + lua_getglobal(L, "proc"); + lua_getfield(L, -1, "active"); + bool hasTasks = lua_toboolean(L, -1); + lua_pop(L, 1); + + if (hasTasks) { + lua_getfield(L, -1, "update"); + LuaCallTimeout(L, 0, 0); + } + + lua_pop(L, 1); } int Interpreter::lua_tointeger_safe (lua_State *L, int idx, int prmno, const char *funcname) @@ -673,72 +683,13 @@ void Interpreter::lua_pushsketchpad (lua_State *L, oapi::Sketchpad *skp) #endif } -void Interpreter::WaitExec (DWORD timeout) -{ - // Called by orbiter thread or interpreter thread to wait its turn - // Orbiter waits for the script for 1 second to return - WaitForSingleObject (hWaitMutex, timeout); // wait for synchronisation mutex - WaitForSingleObject (hExecMutex, timeout); // wait for execution mutex - ReleaseMutex (hWaitMutex); // release synchronisation mutex -} - -void Interpreter::EndExec () -{ - // called by orbiter thread or interpreter thread to hand over control - ReleaseMutex (hExecMutex); -} - -void Interpreter::frameskip (lua_State *L) -{ - if (status == 1) { // termination request - lua_pushboolean(L, 1); - lua_setfield (L, LUA_GLOBALSINDEX, "wait_exit"); - } else { - EndExec(); - WaitExec(); - } -} - -int Interpreter::ProcessChunk (const char *chunk, int n) -{ - WaitExec(); - int res = RunChunk (chunk, n); - EndExec(); - return res; -} - int Interpreter::RunChunk (const char *chunk, int n) { int res = 0; if (chunk[0]) { - is_busy = true; // run command luaL_loadbuffer (L, chunk, n, "line"); - res = LuaCall (L, 0, 0); - if (res) { - auto error = lua_tostring(L, -1); - if (error) { // can be nullptr - if (is_term) { - // term_strout ("Execution error."); - term_strout(error, true); - } - is_busy = false; - return res; - } - } - // check for leftover background jobs - lua_getfield (L, LUA_GLOBALSINDEX, "_nbranch"); - LuaCall (L, 0, 1); - jobs = lua_tointeger (L, -1); - lua_pop (L, 1); - is_busy = false; - } else { - // idle loop: execute background jobs - lua_getfield (L, LUA_GLOBALSINDEX, "_idle"); - LuaCall (L, 0, 1); - jobs = lua_tointeger (L, -1); - lua_pop (L, 1); - res = -1; + LuaCallTimeout (L, 0, 0); } return res; } @@ -788,18 +739,10 @@ void Interpreter::LoadAPI () }; luaL_openlib (L, "mat", matLib, 0); - // Load the process library - static const struct luaL_reg procLib[] = { - {"Frameskip", procFrameskip}, - {NULL, NULL} - }; - luaL_openlib (L, "proc", procLib, 0); - // Load the oapi library static const struct luaL_reg oapiLib[] = { {"get_orbiter_version", oapi_get_orbiter_version}, {"get_viewport_size", oapi_get_viewport_size}, - {"get_objhandle", oapiGetObjectHandle}, {"get_objcount", oapiGetObjectCount}, {"get_objname", oapiGetObjectName}, @@ -1660,7 +1603,13 @@ void Interpreter::LoadVesselStatusAPI() void Interpreter::LoadStartupScript () { - luaL_dofile (L, "./Script/oapi_init.lua"); + luaL_dostring(L, "package.path = package.path .. ';Script/?.lua'"); + int res = luaL_dofile (L, "./Script/oapi_init.lua"); + if(res != 0) { + const char *msg = lua_tostring(L, -1); + oapiWriteLogError("%s", msg); + oapiAddNotification(OAPINOTIF_ERROR, "Lua error", msg); + } } bool Interpreter::InitialiseVessel (lua_State *L, VESSEL *v) @@ -2567,19 +2516,6 @@ int Interpreter::mat_rotm (lua_State *L) { return 1; } -// ============================================================================ -// process library functions - -int Interpreter::procFrameskip (lua_State *L) -{ - // return control to the orbiter core for execution of one time step - // This should be called in the loop of any "wait"-type function - - Interpreter *interp = GetInterpreter(L); - interp->frameskip (L); - return 0; -} - // ============================================================================ // oapi library functions diff --git a/Src/Module/LuaScript/LuaInterpreter/Interpreter.h b/Src/Module/LuaScript/LuaInterpreter/Interpreter.h index 3e64babdf..148815468 100644 --- a/Src/Module/LuaScript/LuaInterpreter/Interpreter.h +++ b/Src/Module/LuaScript/LuaInterpreter/Interpreter.h @@ -13,6 +13,7 @@ extern "C" { #include "OrbiterAPI.h" #include "VesselAPI.h" // for TOUCHDOWNVTX #include +#include class gcCore; @@ -103,56 +104,8 @@ class INTERPRETERLIB Interpreter { */ lua_State *GetState() { return L; } - /** - * \brief Returns interpreter status. - * \return 0=normal, 1=kill pending - */ - int Status () const; - - /** - * \brief Returns interpreter execution status. - * \return \e true if interpreter is busy (in the process of running a - * command or script), \e false if it is waiting for intput. - */ - bool IsBusy () const; - - /** - * \brief Returns the number of background jobs active during idle phase. - * \return number of background jobs - * \note A command may create background jobs that are still active after - * the command returns. The interpreter idle loop continues processing - * the remaining jobs until all are finished, or until a new command - * is entered which takes over control of the background jobs. - */ - inline int nJobs () const { return jobs; } - - /** - * \brief Request interpreter termination. - * \note This sets the interpreter Status() to 1 (kill pending). It is - * up to the client to delete the interpreter instance and clean up - * (terminate interpreter thread etc.) - */ - void Terminate (); - void PostStep (double simt, double simdt, double mjd); - /** - * \brief Wait for thread execution. - * \note This is called by either the orbiter thread or the interpreter - * thread when they are waiting to regain execution control. - */ - virtual void WaitExec (DWORD timeout = INFINITE); - - /** - * \brief Release thread execution. - * \param timeout time [ms] to wait for the mutex. Default is infinite - * (wait does not time out). - * \note This is called by either the orbiter thread or the interpreter - * thread after finishing a cycle to hand control over to the other - * thread. - */ - virtual void EndExec (); - /** * \brief Define functions for interfacing with Orbiter API */ @@ -178,8 +131,6 @@ class INTERPRETERLIB Interpreter { */ virtual void LoadStartupScript (); - virtual int ProcessChunk (const char *chunk, int n); - /** * \brief Executes a command or script. * \param chunk command line string @@ -188,6 +139,12 @@ class INTERPRETERLIB Interpreter { */ virtual int RunChunk (const char *chunk, int n); + /** + * \brief Callback for displaying error + * \param Error message + */ + virtual void OnError(const char *msg); + /** * \brief Copies a string to the terminal. * \param str string to be displayed. @@ -266,9 +223,6 @@ class INTERPRETERLIB Interpreter { static int AssertMtdNumber(lua_State *L, int idx, const char *funcname); static int AssertMtdHandle(lua_State *L, int idx, const char *funcname); - // suspend script execution for one cycle - void frameskip (lua_State *L); - // extract interpreter pointer from lua state static Interpreter *GetInterpreter (lua_State *L); @@ -380,9 +334,6 @@ class INTERPRETERLIB Interpreter { static int bit_rol(lua_State* L); static int bit_ror(lua_State* L); - // process library functions - static int procFrameskip (lua_State *L); - // ------------------------------------------- // oapi library functions // ------------------------------------------- @@ -1146,24 +1097,19 @@ class INTERPRETERLIB Interpreter { static int xrsound_collect(lua_State *L); private: - HANDLE hExecMutex; // flow control synchronisation - HANDLE hWaitMutex; static inline gcCore *pCore; static inline bool gcCoreInitialized = false; static void LazyInitGCCore(); - bool bExecLocal; // flag for locally created mutexes - bool bWaitLocal; - - int status; // interpreter status - bool is_busy; // interpreter busy (running a script) - int jobs; // number of background jobs left over after command terminates int (*postfunc)(void*); void *postcontext; static inline std::unordered_setknownVessels; // for lua_isvessel + std::chrono::steady_clock::time_point startTime; + static void hookTimeout(lua_State* L, lua_Debug* ar); + int LuaCallTimeout(lua_State *L, int nargs, int nres); static int lua_tointeger_safe (lua_State *L, int idx, int prmno, const char *funcname); static double lua_tonumber_safe (lua_State *L, int idx, int prmno, const char *funcname); diff --git a/Src/Orbiter/OrbiterAPI.cpp b/Src/Orbiter/OrbiterAPI.cpp index 240038f29..266998ad6 100644 --- a/Src/Orbiter/OrbiterAPI.cpp +++ b/Src/Orbiter/OrbiterAPI.cpp @@ -1362,11 +1362,6 @@ DLLEXPORT bool oapiExecScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd) return g_pOrbiter->Script()->ExecScriptCmd (hInterp, cmd); } -DLLEXPORT bool oapiAsyncScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd) -{ - return g_pOrbiter->Script()->AsyncScriptCmd (hInterp, cmd); -} - DLLEXPORT lua_State *oapiGetLua (INTERPRETERHANDLE hInterp) { return g_pOrbiter->Script()->GetLua (hInterp); diff --git a/Src/Orbiter/Script.cpp b/Src/Orbiter/Script.cpp index 2e690423b..276beb705 100644 --- a/Src/Orbiter/Script.cpp +++ b/Src/Orbiter/Script.cpp @@ -56,14 +56,6 @@ bool ScriptInterface::ExecScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd) else return false; } -bool ScriptInterface::AsyncScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd) -{ - if (!hLib && !LoadInterpreterLib()) return false; - bool(*proc)(INTERPRETERHANDLE,const char*) = (bool(*)(INTERPRETERHANDLE,const char*))GetProcAddress (hLib, "opcAsyncScriptCmd"); - if (proc) return proc(hInterp, cmd); - else return false; -} - lua_State *ScriptInterface::GetLua (INTERPRETERHANDLE hInterp) { if (!hLib && !LoadInterpreterLib()) return NULL; diff --git a/Src/Orbiter/Script.h b/Src/Orbiter/Script.h index 3254ce625..cfedfa4b4 100644 --- a/Src/Orbiter/Script.h +++ b/Src/Orbiter/Script.h @@ -13,7 +13,6 @@ class ScriptInterface { int DelInterpreter (INTERPRETERHANDLE); INTERPRETERHANDLE RunInterpreter (const char *cmd); bool ExecScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd); - bool AsyncScriptCmd (INTERPRETERHANDLE hInterp, const char *cmd); lua_State *GetLua (INTERPRETERHANDLE hInterp); protected: diff --git a/Src/Plugin/LuaConsole/ConsoleInterpreter.h b/Src/Plugin/LuaConsole/ConsoleInterpreter.h index 5871efb08..a171242db 100644 --- a/Src/Plugin/LuaConsole/ConsoleInterpreter.h +++ b/Src/Plugin/LuaConsole/ConsoleInterpreter.h @@ -19,6 +19,7 @@ class ConsoleInterpreter: public Interpreter { void term_clear (); protected: + void OnError(const char *msg) override { term_strout(msg, true); } static int termOut (lua_State *L); static int termLineUp (lua_State *L); static int termSetVerbosity (lua_State *L); diff --git a/Src/Plugin/LuaConsole/LuaConsole.cpp b/Src/Plugin/LuaConsole/LuaConsole.cpp index 1901a1077..6c53486a9 100644 --- a/Src/Plugin/LuaConsole/LuaConsole.cpp +++ b/Src/Plugin/LuaConsole/LuaConsole.cpp @@ -203,7 +203,6 @@ ConsoleConfig *g_Config = NULL; LuaConsole::LuaConsole (HINSTANCE hDLL): Module (hDLL) { - hThread = NULL; interp = NULL; cConsoleCmd[0]=0; @@ -244,21 +243,8 @@ void LuaConsole::clbkSimulationStart (RenderMode mode) void LuaConsole::clbkSimulationEnd () { // Kill the interpreter thread - if (interp) { - if (hThread) { - termInterp = true; - interp->Terminate(); - interp->EndExec(); // give the thread opportunity to close - if (WaitForSingleObject (hThread, 1000) != 0) { - oapiWriteLog ((char*)"LuaConsole: timeout while waiting for interpreter thread"); - TerminateThread (hThread, 0); - } - CloseHandle (hThread); - hThread = NULL; - } - delete interp; - interp = NULL; - } + delete interp; + interp = NULL; } // ============================================================== @@ -266,10 +252,9 @@ void LuaConsole::clbkSimulationEnd () void LuaConsole::clbkPreStep (double simt, double simdt, double mjd) { if (interp) { - if (interp->IsBusy() || cConsoleCmd[0] || interp->nJobs()) { // let the interpreter do some work - interp->EndExec(); // orbiter hands over control - // At this point the interpreter is performing one cycle - interp->WaitExec(); // orbiter waits to get back control + if(cConsoleCmd[0]) { + interp->RunChunk(cConsoleCmd, strlen(cConsoleCmd)); + cConsoleCmd[0] = '\0'; } interp->PostStep (simt, simdt, mjd); } @@ -277,15 +262,13 @@ void LuaConsole::clbkPreStep (double simt, double simdt, double mjd) // ============================================================== -HWND LuaConsole::Open () +void LuaConsole::Open () { oapiOpenDialog(hDlg); - // create the interpreter and execution thread + // create the interpreter if (!interp) interp = CreateInterpreter (); - - return NULL; } // ============================================================== @@ -340,26 +323,5 @@ Interpreter *LuaConsole::CreateInterpreter () termInterp = false; interp = new ConsoleInterpreter (this); interp->Initialise(); - hThread = (HANDLE)_beginthreadex (NULL, 4096, &InterpreterThreadProc, this, 0, &id); return interp; } -// Interpreter thread function -unsigned int WINAPI LuaConsole::InterpreterThreadProc (LPVOID context) -{ - int res; - LuaConsole *console = (LuaConsole*)context; - ConsoleInterpreter *interp = (ConsoleInterpreter*)console->interp; - - // interpreter loop - for (;;) { - interp->WaitExec(); // wait for execution permission - if (console->termInterp) break; // close thread requested - res = interp->RunChunk (console->cConsoleCmd, strlen (console->cConsoleCmd)); // run command from buffer - if (interp->Status() == 1) break; // close thread requested - console->cConsoleCmd[0] = '\0'; // free buffer - interp->EndExec(); // return control - } - interp->EndExec(); // release mutex (is this necessary?) - _endthreadex(0); - return 0; -} diff --git a/Src/Plugin/LuaConsole/LuaConsole.h b/Src/Plugin/LuaConsole/LuaConsole.h index 26f49894b..148c3bc92 100644 --- a/Src/Plugin/LuaConsole/LuaConsole.h +++ b/Src/Plugin/LuaConsole/LuaConsole.h @@ -26,21 +26,19 @@ class LuaConsole: public oapi::Module { LuaConsole (HINSTANCE hDLL); ~LuaConsole (); - void clbkSimulationStart (RenderMode mode); - void clbkSimulationEnd (); - void clbkPreStep (double simt, double simdt, double mjd); + void clbkSimulationStart (RenderMode mode) override; + void clbkSimulationEnd () override; + void clbkPreStep (double simt, double simdt, double mjd) override; - HWND Open (); + void Open (); void Close (); void AddLine(const char *str, LineType type = LineType::LUA_OUT); void Clear(); private: - static unsigned int WINAPI InterpreterThreadProc (LPVOID context); static void OpenDlgClbk (void *context); // called when user requests console window Interpreter *CreateInterpreter (); - HANDLE hThread; // interpreter thread handle bool termInterp; Interpreter *interp; // interpreter instance diff --git a/Src/Plugin/LuaMFD/LuaMFD.cpp b/Src/Plugin/LuaMFD/LuaMFD.cpp index 960016887..bcb9b2fce 100644 --- a/Src/Plugin/LuaMFD/LuaMFD.cpp +++ b/Src/Plugin/LuaMFD/LuaMFD.cpp @@ -110,16 +110,14 @@ bool ScriptMFD::Input (const char *line) { InterpreterList::Environment *env = vi->env[pg]; env->interp->AddLine (line, 0xFFFFFF); - if (env->cmd[0]) return false; - strncpy (env->cmd, line, 1024); + env->interp->RunChunk(line, strlen(line)); return true; } void ScriptMFD::QueryCommand () { InterpreterList::Environment *env = vi->env[pg]; - if (!env->interp->IsBusy()) - oapiOpenInputBox ((char*)"Input script command:", ScriptInput, 0, 40, (void*)this); + oapiOpenInputBox ((char*)"Input script command:", ScriptInput, 0, 40, (void*)this); } void ScriptMFD::CreateInterpreter () @@ -157,8 +155,6 @@ bool ScriptMFD::Update (oapi::Sketchpad *skp) char cbuf[256]; sprintf (cbuf, "Term %d/%d", pg+1, npg); Title (skp, cbuf); - if (env->interp->IsBusy()) - skp->Text (W-cw*5, 1, "busy", 4); oapi::Pen *pen = GetDefaultPen(0); skp->SetPen(pen); diff --git a/Src/Plugin/LuaMFD/MfdInterpreter.cpp b/Src/Plugin/LuaMFD/MfdInterpreter.cpp index 114548f64..6f1522249 100644 --- a/Src/Plugin/LuaMFD/MfdInterpreter.cpp +++ b/Src/Plugin/LuaMFD/MfdInterpreter.cpp @@ -134,19 +134,12 @@ void MFDInterpreter::AddLine (const char *line, COLORREF col) InterpreterList::Environment::Environment (OBJHANDLE hV) { - cmd[0] = '\0'; interp = CreateInterpreter (hV); } InterpreterList::Environment::~Environment() { - if (interp) { - if (hThread) { - TerminateThread (hThread, 0); - CloseHandle (hThread); - } - delete interp; - } + delete interp; } MFDInterpreter *InterpreterList::Environment::CreateInterpreter (OBJHANDLE hV) @@ -155,30 +148,9 @@ MFDInterpreter *InterpreterList::Environment::CreateInterpreter (OBJHANDLE hV) interp = new MFDInterpreter (); interp->Initialise(); interp->SetSelf (hV); - hThread = (HANDLE)_beginthreadex (NULL, 4096, &InterpreterThreadProc, this, 0, &id); return interp; } -// Interpreter thread function -unsigned int WINAPI InterpreterList::Environment::InterpreterThreadProc (LPVOID context) -{ - InterpreterList::Environment *env = (InterpreterList::Environment*)context; - MFDInterpreter *interp = (MFDInterpreter*)env->interp; - - // interpreter loop - for (;;) { - interp->WaitExec(); // wait for execution permission - if (interp->Status() == 1) break; // close thread requested - interp->RunChunk (env->cmd, strlen (env->cmd)); // run command from buffer - if (interp->Status() == 1) break; - env->cmd[0] = '\0'; // free buffer - interp->EndExec(); // return control - } - interp->EndExec(); // release mutex (is this necessary?) - _endthreadex(0); - return 0; -} - // ============================================================== // Interpreter repository implementation @@ -198,10 +170,6 @@ void InterpreterList::Update (double simt, double simdt, double mjd) for (i = 0; i < nlist; i++) { for (j = 0; j < list[i].nenv; j++) { Environment *env = list[i].env[j]; - if (env->interp->IsBusy() || env->cmd[0] || env->interp->nJobs()) { // let the interpreter do some work - env->interp->EndExec(); - env->interp->WaitExec(); - } env->interp->PostStep (simt, simdt, mjd); } } diff --git a/Src/Plugin/LuaMFD/MfdInterpreter.h b/Src/Plugin/LuaMFD/MfdInterpreter.h index 2c1305571..e922ad53d 100644 --- a/Src/Plugin/LuaMFD/MfdInterpreter.h +++ b/Src/Plugin/LuaMFD/MfdInterpreter.h @@ -33,6 +33,7 @@ class MFDInterpreter: public Interpreter { void term_clear (); protected: + void OnError(const char *msg) override { term_strout(msg, true); } static int termOut (lua_State *L); static int termLineUp (lua_State *L); static int termSetVerbosity (lua_State *L); @@ -53,9 +54,6 @@ class InterpreterList { ~Environment(); MFDInterpreter *CreateInterpreter (OBJHANDLE hV); MFDInterpreter *interp; - HANDLE hThread; - char cmd[1024]; - static unsigned int WINAPI InterpreterThreadProc (LPVOID context); }; struct VesselInterp { OBJHANDLE hVessel; diff --git a/Src/Vessel/DeltaGlider/AAPSubsys.cpp b/Src/Vessel/DeltaGlider/AAPSubsys.cpp index 2663ea1bb..6c8c28f0a 100644 --- a/Src/Vessel/DeltaGlider/AAPSubsys.cpp +++ b/Src/Vessel/DeltaGlider/AAPSubsys.cpp @@ -89,8 +89,8 @@ AAP::AAP (AAPSubsystem *_subsys) oapiExecScriptCmd (hAAP, "run('dg/aap')"); // load the autopilot code char setVesselCmd[256]; - sprintf_s(setVesselCmd,256,"setvessel(vessel.get_interface('%s'))",vessel->GetName()); - oapiAsyncScriptCmd (hAAP, setVesselCmd); // set autopilot vessel + sprintf_s(setVesselCmd,256,"setvessel('%s')",vessel->GetName()); + oapiExecScriptCmd (hAAP, setVesselCmd); // set autopilot vessel active_block = -1; for (i = 0; i < 3; i++) { @@ -225,15 +225,15 @@ void AAP::SetValue (int block, double val) switch (block) { case 0: // altitude sprintf (cbuf, "aap.alt(%e)", val); - oapiAsyncScriptCmd (hAAP, cbuf); + oapiExecScriptCmd (hAAP, cbuf); break; case 1: // airspeed sprintf (cbuf, "aap.spd(%e)", val); - oapiAsyncScriptCmd (hAAP, cbuf); + oapiExecScriptCmd (hAAP, cbuf); break; case 2: // heading/course sprintf (cbuf, "aap.hdg(%e)", val*DEG); - oapiAsyncScriptCmd (hAAP, cbuf); + oapiExecScriptCmd (hAAP, cbuf); break; } } @@ -254,17 +254,17 @@ void AAP::SetActive (int block, bool activate) case 0: // altitude if (activate) sprintf (cbuf, "aap.alt(%e)", tgt[block]); else strcpy (cbuf, "aap.alt()"); - oapiAsyncScriptCmd (hAAP, cbuf); + oapiExecScriptCmd (hAAP, cbuf); break; case 1: // airspeed if (activate) sprintf (cbuf, "aap.spd(%e)", tgt[block]); else strcpy (cbuf, "aap.spd()"); - oapiAsyncScriptCmd (hAAP, cbuf); + oapiExecScriptCmd (hAAP, cbuf); break; case 2: // heading if (activate) sprintf (cbuf, "aap.hdg(%e)", tgt[block]*DEG); else strcpy (cbuf, "aap.hdg()"); - oapiAsyncScriptCmd (hAAP, cbuf); + oapiExecScriptCmd (hAAP, cbuf); break; } } diff --git a/Src/Vessel/DeltaGlider/Script/aap.lua b/Src/Vessel/DeltaGlider/Script/aap.lua index fcf4f834c..ef9db8789 100644 --- a/Src/Vessel/DeltaGlider/Script/aap.lua +++ b/Src/Vessel/DeltaGlider/Script/aap.lua @@ -14,16 +14,24 @@ aap.maxdsc = -20 -- Define help page dg_aap = {file='Html/Script/Stockvessels/DG/aap.chm'} -function setvessel (_v) - if _v then - v = _v - class = _v:get_classname() - if (class ~= 'DeltaGlider') and (class ~= 'DG-S') then - term.out('Warning: Autopilot is designed for use with DeltaGlider.') - end +function setvesselbg(name) + while vessel.get_interface(name) == nil do + proc.skip() + end + local _v = vessel.get_interface(name) + v = _v + local class = _v:get_classname() + if (class ~= 'DeltaGlider') and (class ~= 'DG-S') then + term.out('Warning: Autopilot is designed for use with DeltaGlider.') end end +-- Called *during* the creation of the vessel, so before vessel.get_interface can be used +-- We spawn a background task to initialize 'v' whenever possible +function setvessel (name) + proc.bg(setvesselbg, name) +end + function altitude () alt = v:get_altitude() return alt @@ -250,12 +258,7 @@ function aap.hdg (hdg) end end --- ----------------------------------------------------------- --- Initialisation code - -v = {} - -setvessel(V) +v = V term.out('DG: Atmospheric autopilot loaded.') term.out('For help, type: help(dg_aap)') \ No newline at end of file diff --git a/Src/Vessel/ScriptVessel/ScriptVessel.cpp b/Src/Vessel/ScriptVessel/ScriptVessel.cpp index 67a770dab..0d3c743d8 100644 --- a/Src/Vessel/ScriptVessel/ScriptVessel.cpp +++ b/Src/Vessel/ScriptVessel/ScriptVessel.cpp @@ -386,7 +386,6 @@ void ScriptVessel::clbkPostStep (double simt, double simdt, double mjd) lua_pushnumber(L,mjd); LuaCall (L, 3, 0); } - oapiExecScriptCmd (hInterp, "--"); // update background threads count } void ScriptVessel::clbkSaveState(FILEHANDLE scn)