rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
lua.cpp
Go to the documentation of this file.
1#include "pch.h"
2
3#include "rusefi_lua.h"
4#include "thread_controller.h"
5
6#if EFI_LUA
7
8#include "lua.hpp"
9#include "lua_heap.h"
10#include "lua_hooks.h"
11#include "can_filter.h"
12
13#define TAG "LUA "
14
15static bool withErrorLoading = false;
16static int luaTickPeriodUs;
17
18#if EFI_CAN_SUPPORT
19static int recentRxCount = 0;
20static int totalRxCount = 0;
21static int rxTime;
22#endif // EFI_CAN_SUPPORT
23
24static int lua_setTickRate(lua_State* l) {
25 float userFreq = luaL_checknumber(l, 1);
26
27 // For instance BMW does 100 CAN messages per second on some IDs, let's allow at least twice that speed
28 // Limit to 1..200 hz
29 float freq = clampF(1, userFreq, 2000);
30 if (freq != userFreq) {
31 efiPrintf(TAG "clamping tickrate %f", freq);
32 }
33
34 if (freq > 150 && !engineConfiguration->luaCanRxWorkaround) {
35 efiPrintf(TAG "luaCanRxWorkaround recommended at high tick rate!");
36 }
37
38 luaTickPeriodUs = 1000000.0f / freq;
39 return 0;
40}
41
42static void loadLibraries(LuaHandle& ls) {
43 constexpr luaL_Reg libs[] = {
44 // TODO: do we even need the base lib?
45 //{ LUA_GNAME, luaopen_base },
46 { LUA_MATHLIBNAME, luaopen_math },
47 };
48
49 for (size_t i = 0; i < efi::size(libs); i++) {
50 luaL_requiref(ls, libs[i].name, libs[i].func, 1);
51 lua_pop(ls, 1);
52 }
53}
54
55static LuaHandle setupLuaState(lua_Alloc alloc) {
56 LuaHandle ls = lua_newstate(alloc, NULL);
57
58 if (!ls) {
59 criticalError("Failed to start Lua interpreter");
60
61 return nullptr;
62 }
63
64 lua_atpanic(ls, [](lua_State* l) {
65 criticalError("Lua panic: %s", lua_tostring(l, -1));
66
67 // hang the lua thread
68 while (true) ;
69
70 return 0;
71 });
72
73 // Load Lua's own libraries
74 loadLibraries(ls);
75
76 // Load rusEFI hooks
77 lua_register(ls, "setTickRate", lua_setTickRate);
79
80 // run a GC cycle
81 lua_gc(ls, LUA_GCCOLLECT, 0);
82
83 // set GC settings
84 // see https://www.lua.org/manual/5.4/manual.html#2.5.1
85 lua_gc(ls, LUA_GCINC, 50, 1000, 9);
86
87 return ls;
88}
89
90static bool loadScript(LuaHandle& ls, const char* scriptStr) {
91 efiPrintf(TAG "loading script length: %u...", std::strlen(scriptStr));
92
93 if (0 != luaL_dostring(ls, scriptStr)) {
94 withErrorLoading = true;
95 efiPrintf(TAG "ERROR loading script: %s", lua_tostring(ls, -1));
96 lua_pop(ls, 1);
97 return false;
98 }
99
100 efiPrintf(TAG "script loaded successfully!");
101
102#if EFI_PROD_CODE
104#endif // EFI_PROD_CODE
105
106 return true;
107}
108
109#if !EFI_UNIT_TEST
110static bool interactivePending = false;
111static char interactiveCmd[100];
112
113static void doInteractive(LuaHandle& ls) {
114 if (!interactivePending) {
115 // no cmd pending, return
116 return;
117 }
118
119 auto status = luaL_dostring(ls, interactiveCmd);
120
121 if (0 == status) {
122 // Function call was OK, resolve return value and print it
123 if (lua_isinteger(ls, -1)) {
124 efiPrintf(TAG "interactive returned integer: %d", lua_tointeger(ls, -1));
125 } else if (lua_isnumber(ls, -1)) {
126 efiPrintf(TAG "interactive returned number: %f", lua_tonumber(ls, -1));
127 } else if (lua_isstring(ls, -1)) {
128 efiPrintf(TAG "interactive returned string: '%s'", lua_tostring(ls, -1));
129 } else if (lua_isboolean(ls, -1)) {
130 efiPrintf(TAG "interactive returned bool: %s", lua_toboolean(ls, -1) ? "true" : "false");
131 } else if (lua_isnil(ls, -1)) {
132 efiPrintf(TAG "interactive returned nil.");
133 } else {
134 efiPrintf(TAG "interactive returned nothing.");
135 }
136 } else {
137 // error with interactive command, print it
138 efiPrintf(TAG "interactive error: %s", lua_tostring(ls, -1));
139 }
140
141 interactivePending = false;
142
143 lua_settop(ls, 0);
144}
145
146static uint32_t maxLuaDuration{};
147
148static void invokeTick(LuaHandle& ls) {
150
151 // run the tick function
152 lua_getglobal(ls, "onTick");
153 if (lua_isnil(ls, -1)) {
154 // TODO: handle missing tick function
155 lua_settop(ls, 0);
156 return;
157 }
158#if EFI_PROD_CODE
159 uint32_t before = port_rt_get_counter_value();
160#endif // EFI_PROD_CODE
161
162 int status = lua_pcall(ls, 0, 0, 0);
163
164#if EFI_PROD_CODE
165 uint32_t duration = port_rt_get_counter_value() - before;
166 maxLuaDuration = std::max(maxLuaDuration, duration);
167#endif // EFI_PROD_CODE
168
169 if (0 != status) {
170 // error calling hook function
171 auto errMsg = lua_tostring(ls, -1);
172 efiPrintf(TAG "error %s", errMsg);
173 lua_pop(ls, 1);
174 }
175
176 lua_settop(ls, 0);
177}
178
179struct LuaThread : ThreadController<4096> {
180 LuaThread() : ThreadController("lua", PRIO_LUA) { }
181
182 void ThreadTask() override;
183};
184
185static void resetLua() {
186 engine->module<AcController>().unmock().isDisabledByLua = false;
187#if EFI_CAN_SUPPORT
189#endif // EFI_CAN_SUPPORT
190
191 // De-init pins, they will reinit next start of the script.
193}
194
195
196static bool needsReset = false;
197
198// Each invocation of runOneLua will:
199// - create a new Lua instance
200// - read the script from config
201// - run the tick function until needsReset is set
202// Returns true if it should be re-called immediately,
203// or false if there was a problem setting up the interpreter
204// or parsing the script.
205static bool runOneLua(lua_Alloc alloc, const char* script) {
206 needsReset = false;
207
208 auto ls = setupLuaState(alloc);
209
210 // couldn't start Lua interpreter, bail out
211 if (!ls) {
212 return false;
213 }
214
215 // Reset default tick rate
216 luaTickPeriodUs = MS2US(5);
217
218 if (!loadScript(ls, script)) {
219 return false;
220 }
221
222 while (!needsReset && !chThdShouldTerminateX()) {
223 efitick_t beforeNt = getTimeNowNt();
224#if EFI_CAN_SUPPORT
225 // First, process any pending can RX messages
228 rxTime = getTimeNowNt() - beforeNt;
229#endif // EFI_CAN_SUPPORT
230
231 // Next, check if there is a pending interactive command entered by the user
232 doInteractive(ls);
233
234 invokeTick(ls);
235
238 chThdSleep(TIME_US2I(luaTickPeriodUs));
239
244 }
245
246 resetLua();
247
248 return true;
249}
250
251void LuaThread::ThreadTask() {
252 while (!chThdShouldTerminateX()) {
253 bool wasOk = runOneLua(luaHeapAlloc, config->luaScript);
254
255 auto usedAfterRun = luaHeapUsed();
256 if (usedAfterRun != 0) {
257 if (!withErrorLoading) {
258 efiPrintf(TAG "MEMORY LEAK DETECTED: %d bytes used after teardown", usedAfterRun);
259 }
260
261 // Lua blew up in some terrible way that left memory allocated, reset the heap
262 // so that subsequent runs don't overflow the heap
263 luaHeapReset();
264 }
265
266 // Reset any lua adjustments the script made
267 engine->resetLua();
268
269 if (!wasOk) {
270 // Something went wrong executing the script, spin
271 // until reset invoked (maybe the user fixed the script)
272 while (!needsReset) {
273 chThdSleepMilliseconds(100);
274 }
275 }
276 }
277}
278
279static LuaThread luaThread;
280
281void startLua() {
282 luaHeapInit();
283
284#if EFI_CAN_SUPPORT
285 initLuaCanRx();
286#endif // EFI_CAN_SUPPORT
287
288 addConsoleActionII("set_lua_setting", [](int index, int value) {
289 engineConfiguration->scriptSetting[index] = value;
290 });
291
292 luaThread.start();
293
294 addConsoleActionS("lua", [](const char* str){
295 if (interactivePending) {
296 return;
297 }
298
299 strncpy(interactiveCmd, str, sizeof(interactiveCmd) - 1);
300 interactiveCmd[sizeof(interactiveCmd) - 1] = '\0';
301
302 interactivePending = true;
303 });
304
305 addConsoleAction("luareset", [](){
306 needsReset = true;
307 });
308
309 addConsoleAction("luamemory", [](){
310 efiPrintf("maxLuaDuration %lu", maxLuaDuration);
311 maxLuaDuration = 0;
312 efiPrintf("rx total/recent/dropped %d %d %d", totalRxCount,
314 efiPrintf("luaCycle %luus including luaRxTime %dus", NT2US(engine->outputChannels.luaLastCycleDuration),
315 NT2US(rxTime));
316
318 });
319}
320
321#else // not EFI_UNIT_TEST
322
323void startLua() { }
324
325#include <stdexcept>
326#include <string>
327
328static LuaHandle runScript(const char* script) {
329 auto ls = setupLuaState(luaHeapAlloc);
330
331 if (!ls) {
332 throw std::logic_error("Call to setupLuaState failed, returned null");
333 }
334
335 if (!loadScript(ls, script)) {
336 throw std::logic_error("Call to loadScript failed");
337 }
338
339 lua_getglobal(ls, "testFunc");
340 if (lua_isnil(ls, -1)) {
341 throw std::logic_error("Failed to find function testFunc");
342 }
343
344 int status = lua_pcall(ls, 0, 1, 0);
345
346 if (0 != status) {
347 std::string msg = std::string("lua error while running script: ") + lua_tostring(ls, -1);
348 throw std::logic_error(msg);
349 }
350
351 return ls;
352}
353
354expected<float> testLuaReturnsNumberOrNil(const char* script) {
355 auto ls = runScript(script);
356
357 // check nil return first
358 if (lua_isnil(ls, -1)) {
359 return unexpected;
360 }
361
362 // If not nil, it should be a number
363 if (!lua_isnumber(ls, -1)) {
364 throw std::logic_error("Returned value is not a number");
365 }
366
367 // pop the return value
368 return lua_tonumber(ls, -1);
369}
370
371float testLuaReturnsNumber(const char* script) {
372 auto ls = runScript(script);
373
374 // check the return value
375 if (!lua_isnumber(ls, -1)) {
376 throw new std::logic_error("Returned value is not a number");
377 }
378
379 // pop the return value
380 return lua_tonumber(ls, -1);
381}
382
383int testLuaReturnsInteger(const char* script) {
384 auto ls = runScript(script);
385
386 // pop the return value;
387 if (!lua_isinteger(ls, -1)) {
388 throw std::logic_error("Returned value is not an integer");
389 }
390
391 return lua_tointeger(ls, -1);
392}
393
394void testLuaExecString(const char* script) {
395 auto ls = setupLuaState(luaHeapAlloc);
396
397 if (!ls) {
398 throw std::logic_error("Call to setupLuaState failed, returned null");
399 }
400
401 if (!loadScript(ls, script)) {
402 throw std::logic_error("Call to loadScript failed");
403 }
404}
405
406#endif // EFI_UNIT_TEST
407#endif // EFI_LUA
408
409// This is technically non-compliant, but it's only used for lua float parsing.
410// It doesn't properly handle very small and very large numbers, and doesn't
411// parse numbers in the format 1.3e5 at all.
412extern "C" float strtof_rusefi(const char* str, char** endPtr) {
413 bool afterDecimalPoint = false;
414 float div = 1; // Divider to place digits after the decimal point
415
416 if (endPtr) {
417 *endPtr = const_cast<char*>(str);
418 }
419
420 float integerPart = 0;
421 float fractionalPart = 0;
422
423 while (*str != '\0') {
424 char c = *str;
425 int digitVal = c - '0';
426
427 if (c >= '0' && c <= '9') {
428 if (!afterDecimalPoint) {
429 // Integer part
430 integerPart = 10 * integerPart + digitVal;
431 } else {
432 // Fractional part
433 fractionalPart = 10 * fractionalPart + digitVal;
434 div *= 10;
435 }
436 } else if (c == '.') {
437 afterDecimalPoint = true;
438 } else {
439 break;
440 }
441
442 str++;
443
444 if (endPtr) {
445 *endPtr = const_cast<char*>(str);
446 }
447 }
448
449 return integerPart + fractionalPart / div;
450}
void resetLuaCanRx()
void resetLua()
Definition engine.cpp:301
EngineState engineState
Definition engine.h:344
TunerStudioOutputChannels outputChannels
Definition engine.h:109
constexpr auto & module()
Definition engine.h:200
A base class for a controller that requires its own thread.
virtual void ThreadTask()=0
void addConsoleActionS(const char *token, VoidCharPtr callback)
void addConsoleAction(const char *token, Void callback)
Register console action without parameters.
void addConsoleActionII(const char *token, VoidIntInt callback)
Register a console command with two Integer parameters.
efitick_t getTimeNowNt()
Definition efitime.cpp:19
static EngineAccessor engine
Definition engine.h:413
static constexpr persistent_config_s * config
static constexpr engine_configuration_s * engineConfiguration
float strtof_rusefi(const char *str, char **endPtr)
Definition lua.cpp:412
static LuaThread luaThread
Definition lua.cpp:279
static void loadLibraries(LuaHandle &ls)
Definition lua.cpp:42
static int lua_setTickRate(lua_State *l)
Definition lua.cpp:24
static bool withErrorLoading
Definition lua.cpp:15
float testLuaReturnsNumber(const char *script)
Definition lua.cpp:371
static char interactiveCmd[100]
Definition lua.cpp:111
static bool loadScript(LuaHandle &ls, const char *scriptStr)
Definition lua.cpp:90
static int recentRxCount
Definition lua.cpp:19
void testLuaExecString(const char *script)
Definition lua.cpp:394
expected< float > testLuaReturnsNumberOrNil(const char *script)
Definition lua.cpp:354
static int luaTickPeriodUs
Definition lua.cpp:16
void startLua()
Definition lua.cpp:281
static void doInteractive(LuaHandle &ls)
Definition lua.cpp:113
static LuaHandle setupLuaState(lua_Alloc alloc)
Definition lua.cpp:55
static int rxTime
Definition lua.cpp:21
static int totalRxCount
Definition lua.cpp:20
static bool runOneLua(lua_Alloc alloc, const char *script)
Definition lua.cpp:205
static uint32_t maxLuaDuration
Definition lua.cpp:146
static void invokeTick(LuaHandle &ls)
Definition lua.cpp:148
static void resetLua()
Definition lua.cpp:185
static bool needsReset
Definition lua.cpp:196
static bool interactivePending
Definition lua.cpp:110
int testLuaReturnsInteger(const char *script)
Definition lua.cpp:383
static LuaHandle runScript(const char *script)
Definition lua.cpp:328
int doLuaCanRx(LuaHandle &ls)
size_t getLuaCanRxDropped()
void initLuaCanRx()
size_t luaHeapUsed()
Definition lua_heap.cpp:188
void luaHeapPrintInfo()
Definition lua_heap.cpp:213
void * luaHeapAlloc(void *, void *optr, size_t osize, size_t nsize)
Definition lua_heap.cpp:138
void luaHeapInit()
Definition lua_heap.cpp:109
void luaHeapReset()
Definition lua_heap.cpp:193
bool getAuxDigital(int index)
void configureRusefiLuaHooks(lua_State *lState)
void luaDeInitPins()
@ LuaTickFunction