Commit 809d6a6b authored by David Milligan's avatar David Milligan
Browse files

lua scripting: script unloading and autorun options

user can disable autorun
scripts don't autorun until user runs them the first time
"simple" scripts (no menus or events) don't autorun, only run on demand and unload when finished
script menu now lists all available scripts, "simple" scripts can be run from here, "complex" scripts can have autorun disabled/enabled from here
TODO: update docs
TODO: the 'extra' directory is probably no longer needed

--HG--
branch : lua_fix
parent f7b08a2dab11
......@@ -31,6 +31,7 @@
#include <shoot.h>
#include <zebra.h>
#include <focus.h>
#include <config.h>
#include "lua_common.h"
struct script_event_entry
......@@ -38,6 +39,7 @@ struct script_event_entry
struct script_event_entry * next;
int function_ref;
lua_State * L;
int mask;
};
struct script_semaphore
......@@ -225,6 +227,24 @@ set_event_script_entry(&event##_cbr_scripts, L, lua_isfunction(L, -1) ? luaL_ref
return 0;\
}\
static void update_event_cant_unload(struct script_event_entry ** root, lua_State * L)
{
//are there any event handlers for this script left?
int any_active_handlers = 0;
struct script_event_entry * current;
for(current = *root; current; current = current->next)
{
if(current->L == L && current->function_ref != LUA_NOREF)
{
any_active_handlers = 1;
break;
}
}
if(*root) lua_set_cant_unload(L, any_active_handlers, (*root)->mask);
}
static void set_event_script_entry(struct script_event_entry ** root, lua_State * L, int function_ref)
{
struct script_event_entry * current;
......@@ -237,6 +257,7 @@ static void set_event_script_entry(struct script_event_entry ** root, lua_State
luaL_unref(L, LUA_REGISTRYINDEX, current->function_ref);
}
current->function_ref = function_ref;
update_event_cant_unload(root, L);
return;
}
}
......@@ -245,6 +266,8 @@ static void set_event_script_entry(struct script_event_entry ** root, lua_State
struct script_event_entry * new_entry = malloc(sizeof(struct script_event_entry));
if(new_entry)
{
static int event_masks = LUA_EVENT_UNLOAD_MASK;
new_entry->mask = *root == NULL ? event_masks++ : (*root)->mask;
new_entry->next = *root;
new_entry->L = L;
new_entry->function_ref = function_ref;
......@@ -255,6 +278,7 @@ static void set_event_script_entry(struct script_event_entry ** root, lua_State
luaL_error(L, "malloc error creating script event");
}
}
update_event_cant_unload(root, L);
}
static int luaCB_event_index(lua_State * L)
......@@ -497,21 +521,116 @@ static lua_State * load_lua_state()
return L;
}
static void add_script(const char * filename)
#define SCRIPT_STATE_NOT_RUNNING 0
#define SCRIPT_STATE_LOADING 1
#define SCRIPT_STATE_RUNNING 2
#define SCRIPT_STATE_FAILED 3
struct lua_script
{
lua_State* L = load_lua_state();
struct semaphore * sem = new_script_semaphore(L, filename);
int menu_state;
int state;
struct lua_script * next;
char * filename;
lua_State * L;
int cant_unload;
struct menu_entry * menu_entry;
};
static struct lua_script * lua_scripts = NULL;
void lua_set_cant_unload(lua_State * L, int cant_unload, int mask)
{
struct lua_script * current;
for (current = lua_scripts; current; current = current->next)
{
if(current->L == L)
{
if(cant_unload)
{
current->cant_unload |= (1 << mask);
}
else
{
current->cant_unload &= ~(1 << mask);
}
return;
}
}
fprintf(stderr, "lua_set_cant_unload: script not found\n");
}
static void set_autorun(struct lua_script * script)
{
char full_path[MAX_PATH_LEN];
snprintf(full_path, MAX_PATH_LEN, "%s%s", get_config_dir(), script->filename);
int len = strlen(full_path);
if(len > 3)
{
//change file extension to .LEN
full_path[len-2] = 'E';
full_path[len-1] = 'N';
config_flag_file_setting_save(full_path, script->menu_state);
}
}
static int get_autorun(struct lua_script * script)
{
char full_path[MAX_PATH_LEN];
snprintf(full_path, MAX_PATH_LEN, "%s%s", get_config_dir(), script->filename);
int len = strlen(full_path);
if(len > 3)
{
//change file extension to .LEN
full_path[len-2] = 'E';
full_path[len-1] = 'N';
return config_flag_file_setting_load(full_path);
}
return 0;
}
static void load_script(struct lua_script * script)
{
if(script->L)
{
fprintf(stderr, "script is already running\n");
return;
}
script->state = SCRIPT_STATE_LOADING;
lua_State* L = script->L = load_lua_state();
script->cant_unload = 0;
struct semaphore * sem = new_script_semaphore(L, script->filename);
if(sem)
{
int error = 0;
char full_path[MAX_PATH_LEN];
snprintf(full_path, MAX_PATH_LEN, SCRIPTS_DIR "/%s", filename);
printf("Loading script: %s\n", filename);
snprintf(full_path, MAX_PATH_LEN, SCRIPTS_DIR "/%s", script->filename);
printf("Loading script: %s\n", script->filename);
if(luaL_loadfile(L, full_path) || docall(L, 0, LUA_MULTRET))
{
/* error loading script */
fprintf(stderr, "%s\n", lua_tostring(L, -1));
error = 1;
}
give_semaphore(sem);
//"complex" script that keeps running after load, set autorun
if (script->cant_unload)
{
script->state = SCRIPT_STATE_RUNNING;
//if there was an error, disable autorun, otherwise turn it on
script->menu_state = !error;
set_autorun(script);
}
//"simple" script didn't create a menu, start a task, or register any event handlers, so we can safely unload it
else
{
lua_close(L);
script->L = NULL;
script->menu_entry->icon_type = IT_ACTION;
script->state = error ? SCRIPT_STATE_FAILED : SCRIPT_STATE_NOT_RUNNING;
script->menu_state = 0;
}
}
else
{
......@@ -519,6 +638,118 @@ static void add_script(const char * filename)
}
}
static MENU_UPDATE_FUNC(lua_script_menu_update)
{
struct lua_script * script = (struct lua_script *)(entry->priv);
if(script)
{
MENU_SET_RINFO(script->state == SCRIPT_STATE_NOT_RUNNING ? "Not Running" :
script->state == SCRIPT_STATE_LOADING ? "Loading" :
script->state == SCRIPT_STATE_RUNNING ? "Running" :
"Error");
if(script->state == SCRIPT_STATE_FAILED)
{
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Script Failed");
}
else if(script->state == SCRIPT_STATE_RUNNING)
{
MENU_SET_HELP(script->menu_state ?
"Running; will autorun on next boot" :
"Running; will not autorun on next boot");
}
if(script->state == SCRIPT_STATE_NOT_RUNNING || script->state == SCRIPT_STATE_FAILED)
{
MENU_SET_HELP("Load/run this script");
if(!lua_loaded)
{
MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Please wait for Lua to finish loading...");
}
}
}
}
static struct lua_script * user_running = NULL;
static void lua_user_load_task(int unused)
{
if(user_running)
{
struct lua_script * temp = user_running;
user_running = NULL;
load_script(temp);
}
}
static MENU_SELECT_FUNC(lua_script_menu_select)
{
struct lua_script * script = (struct lua_script *)priv;
if(script)
{
if(script->state == SCRIPT_STATE_NOT_RUNNING || script->state == SCRIPT_STATE_FAILED)
{
if(lua_loaded && !user_running)
{
script->state = SCRIPT_STATE_LOADING;
user_running = script;
task_create("lua_user_load_task", 0x1c, 0x10000, lua_user_load_task, (void*) 0);
}
}
else if(script->state == SCRIPT_STATE_RUNNING)
{
//toggle auto_run
script->menu_state = !script->menu_state;
set_autorun(script);
}
}
}
static void add_script(const char * filename)
{
struct lua_script * new_script = malloc(sizeof(struct lua_script));
if(new_script)
{
new_script->filename = copy_string(filename);
if(new_script->filename)
{
new_script->menu_entry = malloc(sizeof(struct menu_entry));
new_script->L = NULL;
new_script->next = lua_scripts;
lua_scripts = new_script;
if(new_script->menu_entry)
{
memset(new_script->menu_entry, 0, sizeof(struct menu_entry));
new_script->menu_entry->name = new_script->filename;
new_script->menu_entry->min = 0;
new_script->menu_entry->max = 1;
new_script->menu_entry->icon_type = IT_BOOL;
new_script->menu_entry->priv = new_script;
new_script->menu_entry->select = lua_script_menu_select;
new_script->menu_entry->update = lua_script_menu_update;
menu_add("Scripts", new_script->menu_entry, 1);
return;
}
free(new_script->filename);
}
free(new_script);
}
fprintf(stderr, "add_script: malloc error\n");
}
static void lua_do_autoload()
{
struct lua_script * current;
for (current = lua_scripts; current; current = current->next)
{
if(get_autorun(current))
{
load_script(current);
msleep(100);
}
}
}
static void lua_load_task(int unused)
{
console_show();
......@@ -545,12 +776,13 @@ static void lua_load_task(int unused)
if (!(file.mode & ATTR_DIRECTORY) && (string_ends_with(file.name, ".LUA") || string_ends_with(file.name, ".lua")) && file.name[0] != '.' && file.name[0] != '_')
{
add_script(file.name);
msleep(100);
}
}
while(FIO_FindNextEx(dirent, &file) == 0);
}
lua_do_autoload();
if (strict_lua)
{
fio_free(strict_lua);
......
......@@ -31,6 +31,13 @@
#define CBR_RET_KEYPRESS_HANDLED 0
//#define CONFIG_VSYNC_EVENTS
#define LUA_MENU_UNLOAD_MASK 0
#define LUA_TASK_UNLOAD_MASK 1
#define LUA_PROP_UNLOAD_MASK 2
#define LUA_LVINFO_UNLOAD_MASK 3
//this needs to always be last b/c each event takes it's own mask starting at this one
#define LUA_EVENT_UNLOAD_MASK 4
#define LUA_PARAM_INT(name, index)\
if(index > lua_gettop(L) || !(lua_isinteger(L, index) || lua_isnumber(L,index))) return luaL_argerror(L, index, "expected integer for param '" #name "'");\
int name = lua_isinteger(L, index) ? lua_tointeger(L, index) : (int)lua_tonumber(L, index)
......@@ -99,6 +106,8 @@ int docall(lua_State *L, int narg, int nres);
int lua_take_semaphore(lua_State * L, int timeout, struct semaphore ** assoc_semaphore);
int lua_give_semaphore(lua_State * L, struct semaphore ** assoc_semaphore);
void lua_set_cant_unload(lua_State * L, int cant_unload, int mask);
int luaCB_next(lua_State * L);
int luaCB_pairs(lua_State * L);
......
......@@ -185,6 +185,10 @@ static int luaCB_lv_info(lua_State * L)
{
struct lvinfo_item_entry * entry = lua_newuserdata(L, sizeof(struct lvinfo_item_entry));
if(!entry) return luaL_error(L, "malloc error");
//script created a lvinfo so it can't be unloaded
lua_set_cant_unload(L, 1, LUA_LVINFO_UNLOAD_MASK);
struct lvinfo_item * item = &(entry->item);
memset(entry, 0, sizeof(struct lvinfo_item_entry));
lua_pushvalue(L, -1);
......
......@@ -297,6 +297,9 @@ static int luaCB_menu_new(lua_State * L)
{
if(!lua_istable(L, 1)) return luaL_argerror(L, 1, "expected table");
//script created a menu so it can't be unloaded
lua_set_cant_unload(L, 1, LUA_MENU_UNLOAD_MASK);
lua_pushvalue(L, 1);
const char * parent = LUA_FIELD_STRING("parent", "Scripts");
lua_pop(L, 1);
......
......@@ -600,6 +600,22 @@ static int luaCB_property_index(lua_State * L)
return 1;
}
static void update_cant_unload(lua_State * L)
{
//are there any prop handlers for this script left?
int any_active_handlers = 0;
struct lua_prop * current;
for(current = prop_handlers; current; current = current->next)
{
if(current->L == L && current->prop_handler_ref != LUA_NOREF)
{
any_active_handlers = 1;
break;
}
}
lua_set_cant_unload(L, any_active_handlers, LUA_PROP_UNLOAD_MASK);
}
static int luaCB_property_newindex(lua_State * L)
{
if(lua_isstring(L, 2))
......@@ -622,11 +638,15 @@ static int luaCB_property_newindex(lua_State * L)
}
lua_pushvalue(L, 3);
current->prop_handler_ref = lua_isfunction(L, -1) ? luaL_ref(L, LUA_REGISTRYINDEX) : LUA_NOREF;
update_cant_unload(L);
return 0;
}
}
if(!lua_isnil(L, 3))
{
//script created a prop handler so it can't be unloaded
lua_set_cant_unload(L, 1, LUA_PROP_UNLOAD_MASK);
lua_register_prop_handler(prop_id);
//create new prop handler
current = malloc(sizeof(struct lua_prop));
......@@ -640,6 +660,10 @@ static int luaCB_property_newindex(lua_State * L)
lua_pushvalue(L, 1);
current->self_ref = luaL_ref(L, LUA_REGISTRYINDEX);
}
else
{
update_cant_unload(L);
}
}
else
{
......
......@@ -68,6 +68,9 @@ static int luaCB_task_create(lua_State * L)
struct lua_task_func * func = malloc(sizeof(struct lua_task_func));
if(!func) return luaL_error(L, "malloc error\n");
//script created a task so it can't be unloaded
lua_set_cant_unload(L, 1, LUA_TASK_UNLOAD_MASK);
func->L = L;
func->function_ref = luaL_ref(L, LUA_REGISTRYINDEX);
char task_name[32];
......
......@@ -31,16 +31,6 @@ calc.left = display.width // 2 - calc.width // 2
calc.top = display.height // 2 - calc.height // 2
calc.error = false
calc.mlmenu = menu.new
{
name = "Calculator",
help = "A simple calculator in Lua",
select = function(this)
task.create(function() calc:run() end)
end,
update = function(this) return calc.value end
}
-- The main program loop
function calc:run()
local status, error = xpcall(function()
......@@ -181,3 +171,5 @@ function handle_error(error)
log:close()
keys:anykey()
end
calc:run()
......@@ -478,23 +478,6 @@ table.sort(editor.menu[4].items)
editor.lines_per_page = (display.height - 20 - FONT.LARGE.height) / editor.font.height / 2
editor.scrollbar = scrollbar.create(editor.font.height,1,1,display.width - 2,20 + FONT.LARGE.height,2)
editor.mlmenu = menu.new
{
name = "Text Editor",
help = "Edit text files or debug Lua scripts",
icon_type = ICON_TYPE.ACTION,
select = function(this)
task.create(function() editor:run() end)
end,
update = function(this)
if editor.filename ~= nil then
return editor.filename
else
return ""
end
end
}
-- The main program loop
function editor:run()
local status, error = xpcall(function()
......@@ -1219,3 +1202,5 @@ function handle_error(error)
log:close()
keys:anykey()
end
editor:run()
-- A very simple script.
function main()
menu.close()
console.show()
print "Hello, World!"
print "Press any key to exit."
key.wait()
console.hide()
end
keymenu = menu.new
{
name = "Hello, World!",
help = "Prints 'Hello World' on the console",
select = function(this) task.create(main) end,
}
menu.close()
console.show()
print "Hello, World!"
print "Press any key to exit."
key.wait()
console.hide()
......@@ -116,7 +116,8 @@ function main()
msleep(500)
display.clear()
game_speed = pong_menu.submenu["Game speed"].value
game_speed = 3
counter = 0
ball_dx = game_speed
ball_dy = math.random(-game_speed, game_speed)
......@@ -124,6 +125,13 @@ function main()
AI_maxspeed = game_speed
while menu.visible do
--gradually increase speed
counter = counter + 1
if counter == 1000 then
counter = 0
game_speed = game_speed + 1
AI_maxspeed = game_speed
end
local key = keys:getkey()
if key == KEY.UP then
-- up, down: move right paddle (smooth movement)
......@@ -228,22 +236,4 @@ function main()
keys:stop()
end
pong_menu = menu.new
{
parent = "Games",
name = "Pong",
help = "A simple game in Lua",
submenu =
{
{
name = "Play",
select = function(this) task.create(main) end,
},
{
name = "Game speed",
value = 5,
min = 1,
max = 20
}
}
}
main()
......@@ -281,11 +281,6 @@ function victory()
beep();
draw_maze();
display.print("YOU WIN :)", 300, 200, FONT.LARGE );
local level_menu = sokoban_menu.submenu["Level"]
if level_menu.value < level_menu.max then
level_menu.value = level_menu.value + 1
end
end
function split_target()
......@@ -370,38 +365,42 @@ function check_solution()
end
function main()
setup(sokoban_menu.submenu["Level"].value)
menu.block(true);
local status,error = xpcall(function()
split_target();
keys:start()
draw_maze();
while true do
if menu.visible == false then return end
-- print_maze();
local key = keys:getkey();
if key == KEY.LEFT or key == KEY.WHEEL_LEFT then
move(0, -1);
elseif key == KEY.RIGHT or key == KEY.WHEEL_RIGHT then
move(0, 1);
elseif key == KEY.UP or key == KEY.WHEEL_UP then
move(-1, 0);
elseif key == KEY.DOWN or key == KEY.WHEEL_DOWN then
move(1, 0);
elseif key == KEY.SET or key == KEY.UNPRESS_SET then
elseif key == KEY.Q or key == KEY.TRASH or key == KEY.MENU then
printf("Exiting...\n");
menu.block(false);
keys:stop()
return;
for i = 1,6,1 do
setup(i)
split_target();
keys:start()
draw_maze();
while true do
if menu.visible == false then return end
-- print_maze();
local key = keys:getkey();
if key == KEY.LEFT or key == KEY.WHEEL_LEFT then
move(0, -1);
elseif key == KEY.RIGHT or key == KEY.WHEEL_RIGHT then
move(0, 1);
elseif key == KEY.UP or key == KEY.WHEEL_UP then
move(-1, 0);
elseif key == KEY.DOWN or key == KEY.WHEEL_DOWN then
move(1, 0);
elseif key == KEY.SET or key == KEY.UNPRESS_SET then
elseif key == KEY.Q or key == KEY.TRASH or key == KEY.MENU then
printf("Exiting...\n");
menu.block(false);
keys:stop()
return;
end
display.draw(draw_maze)
if check_solution() ~=