From c4924f756da14cc4c3e47ebe5e11b70fdeccf8b4 Mon Sep 17 00:00:00 2001 From: Hirek193 Date: Tue, 10 Nov 2026 15:29:25 +0100 Subject: [PATCH] Lua script screens beside the WebAssembly ones --- CMakeLists.txt | 16 +- CMake_modules/wamr/CMakeLists.txt | 2 +- application/application.cpp | 9 +- application/application.h | 12 +- model/Texture.cpp | 4 +- script_screens_doc.md | 199 ++++ scripting/luaprogram.cpp | 847 ++++++++++++++++ scripting/scriptscreendrawing.cpp | 294 ++++++ scripting/scriptscreendrawing.h | 126 +++ ...eenkernels.cpp => scriptscreenkernels.cpp} | 6 +- ...mscreenkernels.h => scriptscreenkernels.h} | 6 +- scripting/scriptscreenprogram.h | 92 ++ ...mscreenrules.cpp => scriptscreenrules.cpp} | 6 +- ...{wasmscreenrules.h => scriptscreenrules.h} | 17 +- scripting/scriptscreens.cpp | 511 ++++++++++ scripting/scriptscreens.h | 98 ++ scripting/wasmprogram.cpp | 630 ++++++++++++ scripting/wasmscreens.cpp | 942 ------------------ scripting/wasmscreens.h | 72 -- scripting/wasmscreensstub.cpp | 51 - tests/unit/CMakeLists.txt | 12 +- tests/unit/run_tests.cmake | 8 + tests/unit/test_script_screen_drawing.cpp | 120 +++ tests/unit/test_script_screen_host.cpp | 596 +++++++++++ ...els.cpp => test_script_screen_kernels.cpp} | 36 +- ...sm_screens.cpp => test_script_screens.cpp} | 46 +- tests/unit/test_wasm_host.cpp | 288 ------ utilities/Globals.cpp | 28 +- utilities/Globals.h | 7 +- vehicle/DynObj.cpp | 116 ++- vehicle/DynObj.h | 25 +- vehicle/Train.cpp | 45 +- vehicle/Train.h | 16 +- wasm_screens_doc.md | 129 --- 34 files changed, 3777 insertions(+), 1635 deletions(-) create mode 100644 script_screens_doc.md create mode 100644 scripting/luaprogram.cpp create mode 100644 scripting/scriptscreendrawing.cpp create mode 100644 scripting/scriptscreendrawing.h rename scripting/{wasmscreenkernels.cpp => scriptscreenkernels.cpp} (98%) rename scripting/{wasmscreenkernels.h => scriptscreenkernels.h} (88%) create mode 100644 scripting/scriptscreenprogram.h rename scripting/{wasmscreenrules.cpp => scriptscreenrules.cpp} (96%) rename scripting/{wasmscreenrules.h => scriptscreenrules.h} (75%) create mode 100644 scripting/scriptscreens.cpp create mode 100644 scripting/scriptscreens.h create mode 100644 scripting/wasmprogram.cpp delete mode 100644 scripting/wasmscreens.cpp delete mode 100644 scripting/wasmscreens.h delete mode 100644 scripting/wasmscreensstub.cpp create mode 100644 tests/unit/test_script_screen_drawing.cpp create mode 100644 tests/unit/test_script_screen_host.cpp rename tests/unit/{test_wasm_kernels.cpp => test_script_screen_kernels.cpp} (82%) rename tests/unit/{test_wasm_screens.cpp => test_script_screens.cpp} (59%) delete mode 100644 tests/unit/test_wasm_host.cpp delete mode 100644 wasm_screens_doc.md diff --git a/CMakeLists.txt b/CMakeLists.txt index bfa580a3..cbcf185d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,7 +72,7 @@ option(WITH_OPENGL_LEGACY "Compile with OpenGL legacy renderer" ON) option(WITH_PYTHON "Python 3.14 embedded scripting" ON) option(WITH_UART "Compile with libserialport" ON) option(WITH_LUA "Compile with lua scripting support" ON) -option(WITH_WASM "WebAssembly screens (wasmscreen:, wasmdestinationsign:), run by the WAMR interpreter in ref/wamr" ON) +option(WITH_WASM "WebAssembly script screens (wasmscreen:, wasmdestinationsign:), run by the WAMR interpreter in ref/wamr" ON) option(WITH_OPENVR "Compile with OpenVR" OFF) option(WITH_ZMQ "Compile with cppzmq" OFF) option(WITH_CRASHPAD "Compile with crashpad" OFF) @@ -322,11 +322,14 @@ if (WITH_LUA) set(SOURCES ${SOURCES} "scripting/lua.cpp") endif() +# the script screens (scripting/scriptscreens.cpp), with the Lua and the WebAssembly runtime when they're built +set(SOURCES ${SOURCES} "scripting/scriptscreens.cpp") +if (WITH_LUA) + set(SOURCES ${SOURCES} "scripting/luaprogram.cpp") +endif() if (WITH_WASM) set(DEFINITIONS ${DEFINITIONS} "WITH_WASM") - set(SOURCES ${SOURCES} "scripting/wasmscreens.cpp") -else() - set(SOURCES ${SOURCES} "scripting/wasmscreensstub.cpp") + set(SOURCES ${SOURCES} "scripting/wasmprogram.cpp") endif() if (WITHDUMPGEN) @@ -471,8 +474,9 @@ set(CORE_SOURCES "utilities/parser.cpp" "utilities/utilities.cpp" "scripting/ladderlogic.cpp" -"scripting/wasmscreenrules.cpp" -"scripting/wasmscreenkernels.cpp" +"scripting/scriptscreenrules.cpp" +"scripting/scriptscreenkernels.cpp" +"scripting/scriptscreendrawing.cpp" "simulation/simulationtestrules.cpp" "vehicle/enginestart.cpp" "vehicle/handover.cpp" diff --git a/CMake_modules/wamr/CMakeLists.txt b/CMake_modules/wamr/CMakeLists.txt index 74e82358..d12d48cd 100644 --- a/CMake_modules/wamr/CMakeLists.txt +++ b/CMake_modules/wamr/CMakeLists.txt @@ -1,4 +1,4 @@ -# The WebAssembly Micro Runtime (ref/wamr) for the wasm screens (scripting/wasmscreens.cpp). +# The WebAssembly Micro Runtime (ref/wamr) for the WebAssembly script screens (scripting/wasmprogram.cpp). # # Built here, in a directory of its own, because the WAMR build scripts set their configuration with # add_definitions() and include_directories(): kept in this directory, none of it reaches the game's sources. diff --git a/application/application.cpp b/application/application.cpp index 98f004a9..136aa060 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -425,10 +425,11 @@ int eu07_application::init(int Argc, char *Argv[]) { m_taskqueue.init(); } + crashreport_add_info("luascreens_enabled", Global.luascreens_enabled ? "yes" : "no"); crashreport_add_info("wasm_enabled", Global.wasm_enabled ? "yes" : "no"); - if (Global.wasm_enabled) + if (Global.luascreens_enabled || Global.wasm_enabled) { - m_wasmqueue.init(); + m_screenqueue.init(); } if ((result = init_modes()) != 0) { @@ -640,7 +641,7 @@ int eu07_application::run() EditorTerrain.update(Global.pCamera.Pos); m_taskqueue.update(); - m_wasmqueue.update(); + m_screenqueue.update(); opengl_texture::reset_unit_cache(); if (!GfxRenderer->Render()) @@ -738,7 +739,7 @@ void eu07_application::exit() // the python workers detach their GL context on the way out, so they have to be done before the windows they use are destroyed m_taskqueue.exit(); - m_wasmqueue.exit(); + m_screenqueue.exit(); step("the windows"); for (auto *window : m_windows) { diff --git a/application/application.h b/application/application.h index 4a1d1ad1..850b66e1 100644 --- a/application/application.h +++ b/application/application.h @@ -11,7 +11,7 @@ http://mozilla.org/MPL/2.0/. #include "application/applicationmode.h" #include "scripting/PyInt.h" -#include "scripting/wasmscreens.h" +#include "scripting/scriptscreens.h" #include "network/manager.h" #include "utilities/headtrack.h" #ifdef WITH_UART @@ -50,10 +50,10 @@ public: // frees the python gil and swaps out the main thread void release_python_lock(); - // the screens drawn by WebAssembly modules - wasm_screen_queue & - wasm_screens() { - return m_wasmqueue; } + // the screens drawn by scripts (Lua, WebAssembly) + script_screen_queue & + screens() { + return m_screenqueue; } void exit(); void @@ -127,7 +127,7 @@ private: modeptr_array m_modes { nullptr }; // collection of available application behaviour modes mode_stack m_modestack; // current behaviour mode python_taskqueue m_taskqueue; - wasm_screen_queue m_wasmqueue; + script_screen_queue m_screenqueue; std::vector m_windows; int m_glfwversion; diff --git a/model/Texture.cpp b/model/Texture.cpp index e9dc063d..67a33f44 100644 --- a/model/Texture.cpp +++ b/model/Texture.cpp @@ -722,8 +722,8 @@ void opengl_texture::make_request() { auto const components { Split( name, '?' ) }; - if( components.front().ends_with( ".wasm" ) ) { - // wasm destination signs are drawn by the vehicles showing them (wasm_screen_queue) + if( components.front().ends_with( ".lua" ) || components.front().ends_with( ".wasm" ) ) { + // script destination signs are drawn by the vehicles showing them (script_screen_queue) return; } diff --git a/script_screens_doc.md b/script_screens_doc.md new file mode 100644 index 00000000..d8a6811b --- /dev/null +++ b/script_screens_doc.md @@ -0,0 +1,199 @@ +# Script screens + +Screens drawn by scripts, alongside the Python screens (`pyscreen:`, `pydestinationsign:`): Lua scripts, run by +LuaJIT, and WebAssembly modules, run by the WAMR interpreter (`ref/wamr`). A script gets the same input a Python +screen gets (the table in `python_var_doc.md`), returns an RGBA image and, in the cab, may send commands. + +A Lua script is text, kept with the vehicles and the `scripts/` folder like the Python screens, and changed with any +editor. A WebAssembly module is compiled from another language (Rust, C, Zig and the like). + +Both run sandboxed: a script reaches only the functions of `eu07` below, and reads files only from its own folder and +below. A Lua script gets no `io`, `os`, `ffi`, `debug` or `require`, and no precompiled code is loaded. + +Unlike a Python screen, which refreshes after a time (`python.updatetime`, `updatetime:`), a script screen refreshes +every so many rendered frames, set in `eu07.ini`. A call into a script may run at most 200 million instructions +(Lua runs without the JIT compiler so that the limit holds). A script that fails, or runs over, is switched off, and +the reason goes to the log. + +## eu07.ini + +| key | default | meaning | +|--------------------------|---------|--------------------------------------------------------------------------------| +| `luascreens.enabled` | 1 | 0: no Lua screens; the vehicles show what they did before | +| `wasm.enabled` | 1 | 0: no WebAssembly screens; the vehicles show what they did before | +| `screens.cabframes` | 4 | a screen in the cab is refreshed every so many frames (1-1000) | +| `screens.externalframes` | 30 | a screen outside the cab (destination sign) is refreshed every so many frames | + +`wasm.cabframes` and `wasm.externalframes`, the first names of the last two, are read as well. + +A screen may ask for a longer interval of its own (`frames:` below); the longer of the two counts. Screens sharing +an interval are spread over its frames, so they don't all refresh on the same one. A screen in the cab is refreshed +only while the player sits in that cab, a screen outside only while its vehicle is drawn. + +## .mmd + +In the cab section, next to `pyscreen:`: + +``` +luascreen: { ekran.lua target: screen01 frames: 6 parameters: id=1&mode=full } +wasmscreen: { ekran.wasm target: screen02 } +``` + +| key | meaning | +|---------------|---------------------------------------------------------------------------------------------------| +| (first token) | the script, relative to the vehicle folder when it has no path of its own | +| `target:` | the submodel the screen is drawn on (its texture is replaced); `none` for a screen that only sends commands | +| `frames:` | the screen's own refresh interval, used when longer than `screens.cabframes` | +| `parameters:` | extra `key=value` pairs, `&`-separated, added to the input | + +A touch on the screen's submodel comes in the input as the list of points `touches`, as for the Python screens. + +In `internaldata:`, next to `pydestinationsign:`: + +``` +luadestinationsign: { scripts/led_111a20.lua parameters: kod_e=IC&kod_p=TLK&kod_o=R instance: name } +destinationsignbackground: make:scripts/led_111a20.lua?dummy=1 +``` + +`luadestinationsign:` and `wasmdestinationsign:` take the keys of `pydestinationsign:` (`instance:`, `parameters:`, +`background:`) and `frames:`. The input is what a Python sign gets: the timetable (`$timetable`), `_id1` (the +train), `_id2` (the instance) and the parameters, read anew on every refresh, so the sign can follow the journey. The +vehicles of one train showing the same request share one screen and one texture. A static destination texture, when +there is one, still comes first. + +A blank sign (`background:`, or the older `destinationsignbackground:`) named `make: