Lua memory (de)allocation tracer: (#5191)
[opentx.git] / radio / src / lua / widgets.cpp
blobccf2b347e8943f28d517b6e6164cf178eee6ea4c
1 /*
2 * Copyright (C) OpenTX
4 * Based on code named
5 * th9x - http://code.google.com/p/th9x
6 * er9x - http://code.google.com/p/er9x
7 * gruvin9x - http://code.google.com/p/gruvin9x
9 * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License version 2 as
13 * published by the Free Software Foundation.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
21 #include <ctype.h>
22 #include <stdio.h>
23 #include "opentx.h"
24 #include "bin_allocator.h"
25 #include "lua_api.h"
27 #define WIDGET_SCRIPTS_MAX_INSTRUCTIONS (10000/100)
28 #define MANUAL_SCRIPTS_MAX_INSTRUCTIONS (20000/100)
29 #define LUA_WARNING_INFO_LEN 64
31 lua_State *lsWidgets = NULL;
32 extern int custom_lua_atpanic(lua_State *L);
34 #define LUA_WIDGET_FILENAME "/main.lua"
35 #define LUA_FULLPATH_MAXLEN (LEN_FILE_PATH_MAX + LEN_SCRIPT_FILENAME + LEN_FILE_EXTENSION_MAX) // max length (example: /SCRIPTS/THEMES/mytheme.lua)
37 void exec(int function, int nresults=0)
39 if (lsWidgets == 0) return;
41 if (function) {
42 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
43 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, function);
44 if (lua_pcall(lsWidgets, 0, nresults, 0) != 0) {
45 TRACE("Error in theme %s", lua_tostring(lsWidgets, -1));
46 // TODO disable theme - revert back to default theme???
51 ZoneOption * createOptionsArray(int reference, uint8_t maxOptions)
53 if (reference == 0) {
54 // TRACE("createOptionsArray() no options");
55 return NULL;
58 int count = 0;
59 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference);
60 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
61 count++;
64 // TRACE("we have %d options", count);
65 if (count > maxOptions) {
66 count = maxOptions;
67 // TRACE("limited to %d options", count);
70 ZoneOption * options = (ZoneOption *)malloc(sizeof(ZoneOption) * (count+1));
71 if (!options) {
72 return NULL;
75 PROTECT_LUA() {
76 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, reference);
77 ZoneOption * option = options;
78 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2), count-- > 0; lua_pop(lsWidgets, 1)) {
79 // TRACE("parsing option %d", count);
80 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
81 luaL_checktype(lsWidgets, -1, LUA_TTABLE); // value is table
82 uint8_t field = 0;
83 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2) && field<5; lua_pop(lsWidgets, 1), field++) {
84 switch (field) {
85 case 0:
86 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
87 luaL_checktype(lsWidgets, -1, LUA_TSTRING); // value is string
88 option->name = lua_tostring(lsWidgets, -1);
89 // TRACE("name = %s", option->name);
90 break;
91 case 1:
92 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
93 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
94 option->type = (ZoneOption::Type)lua_tointeger(lsWidgets, -1);
95 // TRACE("type = %d", option->type);
96 if (option->type > ZoneOption::Color) {
97 // wrong type
98 option->type = ZoneOption::Integer;
100 if (option->type == ZoneOption::Integer) {
101 // set some sensible defaults (only Integer actually uses them)
102 option->deflt.signedValue = 0;
103 option->min.signedValue = -100;
104 option->max.signedValue = 100;
106 break;
107 case 2:
108 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
109 if (option->type == ZoneOption::Integer) {
110 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
111 option->deflt.signedValue = lua_tointeger(lsWidgets, -1);
112 // TRACE("default signed = %d", option->deflt.signedValue);
114 else if (option->type == ZoneOption::Source ||
115 option->type == ZoneOption::TextSize ||
116 option->type == ZoneOption::Source ||
117 option->type == ZoneOption::Color) {
118 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
119 option->deflt.unsignedValue = lua_tounsigned(lsWidgets, -1);
120 // TRACE("default unsigned = %u", option->deflt.unsignedValue);
122 else if (option->type == ZoneOption::Bool) {
123 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
124 option->deflt.boolValue = (lua_tounsigned(lsWidgets, -1) != 0);
125 // TRACE("default bool = %d", (int)(option->deflt.boolValue));
127 else if (option->type == ZoneOption::String) {
128 str2zchar(option->deflt.stringValue, lua_tostring(lsWidgets, -1), sizeof(option->deflt.stringValue)); // stringValue is ZCHAR
129 // TRACE("default string = %s", lua_tostring(lsWidgets, -1));
131 break;
132 case 3:
133 if (option->type == ZoneOption::Integer) {
134 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
135 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
136 option->min.signedValue = lua_tointeger(lsWidgets, -1);
138 break;
139 case 4:
140 if (option->type == ZoneOption::Integer) {
141 luaL_checktype(lsWidgets, -2, LUA_TNUMBER); // key is number
142 luaL_checktype(lsWidgets, -1, LUA_TNUMBER); // value is number
143 option->max.signedValue = lua_tointeger(lsWidgets, -1);
145 break;
148 option++;
150 option->name = NULL; // sentinel
152 else {
153 TRACE("error in theme/widget options");
154 free(options);
155 return NULL;
157 UNPROTECT_LUA();
158 return options;
161 class LuaTheme: public Theme
163 friend void luaLoadThemeCallback();
165 public:
166 LuaTheme(const char * name, ZoneOption * options):
167 Theme(name, options),
168 loadFunction(0),
169 drawBackgroundFunction(0),
170 drawTopbarBackgroundFunction(0),
171 drawAlertBoxFunction(0)
175 virtual void load() const
177 luaLcdAllowed = true;
178 exec(loadFunction);
181 virtual void drawBackground() const
183 exec(drawBackgroundFunction);
186 virtual void drawTopbarBackground(uint8_t icon) const
188 exec(drawTopbarBackgroundFunction);
191 #if 0
192 virtual void drawAlertBox(const char * title, const char * text, const char * action) const
194 exec(drawAlertBoxFunction);
196 #endif
198 protected:
199 int loadFunction;
200 int drawBackgroundFunction;
201 int drawTopbarBackgroundFunction;
202 int drawAlertBoxFunction;
205 void luaLoadThemeCallback()
207 TRACE("luaLoadThemeCallback()");
208 const char * name=NULL;
209 int themeOptions=0, loadFunction=0, drawBackgroundFunction=0, drawTopbarBackgroundFunction=0;
211 luaL_checktype(lsWidgets, -1, LUA_TTABLE);
213 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
214 const char * key = lua_tostring(lsWidgets, -2);
215 if (!strcmp(key, "name")) {
216 name = luaL_checkstring(lsWidgets, -1);
218 else if (!strcmp(key, "options")) {
219 themeOptions = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
220 lua_pushnil(lsWidgets);
222 else if (!strcmp(key, "load")) {
223 loadFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
224 lua_pushnil(lsWidgets);
226 else if (!strcmp(key, "drawBackground")) {
227 drawBackgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
228 lua_pushnil(lsWidgets);
230 else if (!strcmp(key, "drawTopbarBackground")) {
231 drawTopbarBackgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
232 lua_pushnil(lsWidgets);
236 if (name) {
237 ZoneOption * options = createOptionsArray(themeOptions, MAX_THEME_OPTIONS);
238 if (options) {
239 LuaTheme * theme = new LuaTheme(name, options);
240 theme->loadFunction = loadFunction;
241 theme->drawBackgroundFunction = drawBackgroundFunction;
242 theme->drawTopbarBackgroundFunction = drawTopbarBackgroundFunction;
243 TRACE("Loaded Lua theme %s", name);
248 class LuaWidget: public Widget
250 public:
251 LuaWidget(const WidgetFactory * factory, const Zone & zone, Widget::PersistentData * persistentData, int widgetData):
252 Widget(factory, zone, persistentData),
253 widgetData(widgetData),
254 errorMessage(0)
258 virtual ~LuaWidget()
260 luaL_unref(lsWidgets, LUA_REGISTRYINDEX, widgetData);
261 if (errorMessage) free(errorMessage);
264 virtual void update();
266 virtual void refresh();
268 virtual void background();
270 virtual const char * getErrorMessage() const;
272 protected:
273 int widgetData;
274 char * errorMessage;
276 void setErrorMessage(const char * funcName);
279 void l_pushtableint(const char * key, int value)
281 lua_pushstring(lsWidgets, key);
282 lua_pushinteger(lsWidgets, value);
283 lua_settable(lsWidgets, -3);
286 class LuaWidgetFactory: public WidgetFactory
288 friend void luaLoadWidgetCallback();
289 friend class LuaWidget;
291 public:
292 LuaWidgetFactory(const char * name, ZoneOption * widgetOptions, int createFunction):
293 WidgetFactory(name, widgetOptions),
294 createFunction(createFunction),
295 updateFunction(0),
296 refreshFunction(0),
297 backgroundFunction(0)
301 virtual Widget * create(const Zone & zone, Widget::PersistentData * persistentData, bool init=true) const
303 if (lsWidgets == 0) return 0;
304 if (init) {
305 initPersistentData(persistentData);
308 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
309 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, createFunction);
311 lua_newtable(lsWidgets);
312 l_pushtableint("x", zone.x);
313 l_pushtableint("y", zone.y);
314 l_pushtableint("w", zone.w);
315 l_pushtableint("h", zone.h);
317 lua_newtable(lsWidgets);
318 int i = 0;
319 for (const ZoneOption * option = options; option->name; option++, i++) {
320 l_pushtableint(option->name, persistentData->options[i].signedValue);
323 if (lua_pcall(lsWidgets, 2, 1, 0) != 0) {
324 TRACE("Error in widget %s create() function: %s", getName(), lua_tostring(lsWidgets, -1));
326 int widgetData = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
327 Widget * widget = new LuaWidget(this, zone, persistentData, widgetData);
328 return widget;
331 protected:
332 int createFunction;
333 int updateFunction;
334 int refreshFunction;
335 int backgroundFunction;
338 void LuaWidget::update()
340 if (lsWidgets == 0 || errorMessage) return;
342 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
343 LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
344 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->updateFunction);
345 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
347 lua_newtable(lsWidgets);
348 int i = 0;
349 for (const ZoneOption * option = getOptions(); option->name; option++, i++) {
350 l_pushtableint(option->name, persistentData->options[i].signedValue);
353 if (lua_pcall(lsWidgets, 2, 0, 0) != 0) {
354 setErrorMessage("update()");
358 void LuaWidget::setErrorMessage(const char * funcName)
360 TRACE("Error in widget %s %s function: %s", factory->getName(), funcName, lua_tostring(lsWidgets, -1));
361 TRACE("Widget disabled");
362 size_t needed = snprintf(NULL, 0, "%s: %s", funcName, lua_tostring(lsWidgets, -1)) + 1;
363 errorMessage = (char *)malloc(needed);
364 if (errorMessage) {
365 snprintf(errorMessage, needed, "%s: %s", funcName, lua_tostring(lsWidgets, -1));
369 const char * LuaWidget::getErrorMessage() const
371 return errorMessage;
374 void LuaWidget::refresh()
376 if (lsWidgets == 0) return;
378 if (errorMessage) {
379 lcdSetColor(RED);
380 lcdDrawText(zone.x, zone.y, "Disabled", SMLSIZE|CUSTOM_COLOR);
381 return;
384 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
385 LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
386 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->refreshFunction);
387 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
388 if (lua_pcall(lsWidgets, 1, 0, 0) != 0) {
389 setErrorMessage("refresh()");
393 void LuaWidget::background()
395 if (lsWidgets == 0 || errorMessage) return;
397 luaSetInstructionsLimit(lsWidgets, WIDGET_SCRIPTS_MAX_INSTRUCTIONS);
398 LuaWidgetFactory * factory = (LuaWidgetFactory *)this->factory;
399 if (factory->backgroundFunction) {
400 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, factory->backgroundFunction);
401 lua_rawgeti(lsWidgets, LUA_REGISTRYINDEX, widgetData);
402 if (lua_pcall(lsWidgets, 1, 0, 0) != 0) {
403 setErrorMessage("background()");
408 void luaLoadWidgetCallback()
410 TRACE("luaLoadWidgetCallback()");
411 const char * name=NULL;
412 int widgetOptions=0, createFunction=0, updateFunction=0, refreshFunction=0, backgroundFunction=0;
414 luaL_checktype(lsWidgets, -1, LUA_TTABLE);
416 for (lua_pushnil(lsWidgets); lua_next(lsWidgets, -2); lua_pop(lsWidgets, 1)) {
417 const char * key = lua_tostring(lsWidgets, -2);
418 if (!strcmp(key, "name")) {
419 name = luaL_checkstring(lsWidgets, -1);
421 else if (!strcmp(key, "options")) {
422 widgetOptions = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
423 lua_pushnil(lsWidgets);
425 else if (!strcmp(key, "create")) {
426 createFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
427 lua_pushnil(lsWidgets);
429 else if (!strcmp(key, "update")) {
430 updateFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
431 lua_pushnil(lsWidgets);
433 else if (!strcmp(key, "refresh")) {
434 refreshFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
435 lua_pushnil(lsWidgets);
437 else if (!strcmp(key, "background")) {
438 backgroundFunction = luaL_ref(lsWidgets, LUA_REGISTRYINDEX);
439 lua_pushnil(lsWidgets);
443 if (name && createFunction) {
444 ZoneOption * options = createOptionsArray(widgetOptions, MAX_WIDGET_OPTIONS);
445 if (options) {
446 LuaWidgetFactory * factory = new LuaWidgetFactory(name, options, createFunction);
447 factory->updateFunction = updateFunction;
448 factory->refreshFunction = refreshFunction;
449 factory->backgroundFunction = backgroundFunction;
450 TRACE("Loaded Lua widget %s", name);
455 void luaLoadFile(const char * filename, void (*callback)())
457 if (lsWidgets == NULL || callback == NULL)
458 return;
460 TRACE("luaLoadFile(%s)", filename);
462 luaSetInstructionsLimit(lsWidgets, MANUAL_SCRIPTS_MAX_INSTRUCTIONS);
464 PROTECT_LUA() {
465 if (luaLoadScriptFileToState(lsWidgets, filename, LUA_SCRIPT_LOAD_MODE) == SCRIPT_OK) {
466 if (lua_pcall(lsWidgets, 0, 1, 0) == LUA_OK && lua_istable(lsWidgets, -1)) {
467 (*callback)();
469 else {
470 TRACE("luaLoadFile(%s): Error parsing script: %s", filename, lua_tostring(lsWidgets, -1));
474 else {
475 // error while loading Lua widget/theme,
476 // do not disable whole Lua state, just ingnore bad widget/theme
477 return;
479 UNPROTECT_LUA();
482 void luaLoadFiles(const char * directory, void (*callback)())
484 char path[LUA_FULLPATH_MAXLEN+1];
485 FILINFO fno;
486 DIR dir;
488 strcpy(path, directory);
489 TRACE("luaLoadFiles() %s", path);
491 FRESULT res = f_opendir(&dir, path); /* Open the directory */
493 if (res == FR_OK) {
494 int pathlen = strlen(path);
495 path[pathlen++] = '/';
496 for (;;) {
497 res = f_readdir(&dir, &fno); /* Read a directory item */
498 if (res != FR_OK || fno.fname[0] == 0) break; /* Break on error or end of dir */
499 uint8_t len = strlen(fno.fname);
500 if (len > 0 && (unsigned int)(len + pathlen + sizeof(LUA_WIDGET_FILENAME)) <= sizeof(path) &&
501 fno.fname[0]!='.' && (fno.fattrib & AM_DIR)) {
502 strcpy(&path[pathlen], fno.fname);
503 strcat(&path[pathlen], LUA_WIDGET_FILENAME);
504 if (isFileAvailable(path)) {
505 luaLoadFile(path, callback);
510 else {
511 TRACE("f_opendir(%s) failed, code=%d", path, res);
514 f_closedir(&dir);
517 #if defined(LUA_ALLOCATOR_TRACER)
518 LuaMemTracer lsWidgetsTrace;
519 #endif
521 void luaInitThemesAndWidgets()
523 TRACE("luaInitThemesAndWidgets");
525 #if defined(USE_BIN_ALLOCATOR)
526 lsWidgets = lua_newstate(bin_l_alloc, NULL); //we use our own allocator!
527 #elif defined(LUA_ALLOCATOR_TRACER)
528 memset(&lsWidgetsTrace, 0 , sizeof(lsWidgetsTrace));
529 lsWidgetsTrace.script = "lua_newstate(widgets)";
530 lsWidgets = lua_newstate(tracer_alloc, &lsWidgetsTrace); //we use tracer allocator
531 #else
532 lsWidgets = lua_newstate(l_alloc, NULL); //we use Lua default allocator
533 #endif
534 if (lsWidgets) {
535 // install our panic handler
536 lua_atpanic(lsWidgets, &custom_lua_atpanic);
538 #if defined(LUA_ALLOCATOR_TRACER)
539 lua_sethook(lsWidgets, luaHook, LUA_MASKLINE, 0);
540 #endif
542 // protect libs and constants registration
543 PROTECT_LUA() {
544 luaRegisterLibraries(lsWidgets);
546 else {
547 // if we got panic during registration
548 // we disable Lua for this session
549 // luaDisable();
550 luaClose(&lsWidgets);
551 lsWidgets = 0;
553 UNPROTECT_LUA();
554 TRACE("lsWidgets %p", lsWidgets);
555 luaLoadFiles(THEMES_PATH, luaLoadThemeCallback);
556 luaLoadFiles(WIDGETS_PATH, luaLoadWidgetCallback);
557 luaDoGc(lsWidgets, true);