feat(runtime): hand the whole app contract to /.lua/main.lua
The firmware knew four paths and called four globals, so the card could not change its own layout or put anything around an app. It now loads one file, and the table that file returns owns the rest: start() mounts the route, home and data name the tree, and every callback is a field on it rather than a global the app and its chrome would have to share.
This commit is contained in:
@@ -178,35 +178,6 @@ int main() {
|
||||
"node.draw(root)\n"
|
||||
"assert(painted == 320)");
|
||||
|
||||
// Callbacks: only init failing stops an app, and a release fires the tap
|
||||
// alias after the up.
|
||||
run(state,
|
||||
"events = {}\n"
|
||||
"local function note(name) return function(a) events[#events + 1] = name "
|
||||
".. ':' .. tostring(a) end end\n"
|
||||
"function init(arg) events[#events + 1] = 'init:' .. tostring(arg) end\n"
|
||||
"function draw(delta) events[#events + 1] = 'draw:' .. delta end\n"
|
||||
"on_touch_down = note('down')\n"
|
||||
"on_touch_up = note('up')\n"
|
||||
"on_touch = note('tap')\n"
|
||||
"on_button_up = note('bup')\n"
|
||||
"on_button = note('btap')");
|
||||
bench.gui.commits = 0;
|
||||
assert(runtime.callInit("book.epub"));
|
||||
runtime.callDraw(33);
|
||||
runtime.callTouch(esp32lua::TouchPhase::Down, 5, 6);
|
||||
runtime.callTouch(esp32lua::TouchPhase::Move, 5,
|
||||
7); // the app defines no on_touch_move
|
||||
runtime.callTouch(esp32lua::TouchPhase::Up, 5, 8);
|
||||
runtime.callButton("confirm", false);
|
||||
run(state,
|
||||
"assert(table.concat(events, ' ') == "
|
||||
"'init:book.epub draw:33 down:5 up:5 tap:5 bup:confirm btap:confirm')");
|
||||
// One commit per visit to the app, so six calls and not seven: the release
|
||||
// and its tap alias are one visible change, and the move nobody handled still
|
||||
// ends a batch.
|
||||
assert(bench.gui.commits == 6);
|
||||
|
||||
// A timer firing inside draw is still one batch.
|
||||
run(state, "function draw() timer.after(1, function() end) end\n"
|
||||
"nested = timer.after(1, function() draw() end)");
|
||||
@@ -214,12 +185,57 @@ int main() {
|
||||
runtime.callTimer(bench.timer.scheduled.back());
|
||||
assert(bench.gui.commits == 1);
|
||||
|
||||
run(state, "function init() error('boom') end");
|
||||
assert(!runtime.callInit(""));
|
||||
assert(bench.log.message.find("init: ") == 0);
|
||||
run(state, "function draw() error('kaboom') end");
|
||||
runtime.callDraw(1); // a failed frame logs and the app keeps running
|
||||
assert(bench.log.message.find("draw: ") == 0);
|
||||
// Callbacks land on the table main.lua returns, only start() failing stops an
|
||||
// app, and a release fires the tap alias after the up.
|
||||
{
|
||||
fake::Bench chrome;
|
||||
chrome.fs.files["/.lua/main.lua"] =
|
||||
"events = {}\n"
|
||||
"local function note(name) return function(a) events[#events + 1] = "
|
||||
"name .. ':' .. tostring(a) end end\n"
|
||||
"return {\n"
|
||||
" start = function(route, arg) events[#events + 1] = 'start:' .. "
|
||||
"route .. ':' .. arg end,\n"
|
||||
" draw = function(delta) if delta < 0 then error('kaboom') end\n"
|
||||
" events[#events + 1] = 'draw:' .. delta end,\n"
|
||||
" on_touch_down = note('down'),\n"
|
||||
" on_touch_up = note('up'),\n"
|
||||
" on_touch = note('tap'),\n"
|
||||
" on_button_up = note('bup'),\n"
|
||||
" on_button = note('btap'),\n"
|
||||
"}\n";
|
||||
esp32lua::Runtime hosted(chrome.providers());
|
||||
assert(hosted.startApp("Reader", "book.epub"));
|
||||
chrome.gui.commits = 0;
|
||||
hosted.callDraw(33);
|
||||
hosted.callTouch(esp32lua::TouchPhase::Down, 5, 6);
|
||||
hosted.callTouch(esp32lua::TouchPhase::Move, 5,
|
||||
7); // main.lua defines no on_touch_move
|
||||
hosted.callTouch(esp32lua::TouchPhase::Up, 5, 8);
|
||||
hosted.callButton("confirm", false);
|
||||
run(hosted.state(),
|
||||
"assert(table.concat(events, ' ') == "
|
||||
"'start:Reader:book.epub draw:33 down:5 up:5 tap:5 bup:confirm "
|
||||
"btap:confirm')");
|
||||
// One commit per visit, so five calls and not six: the release and its tap
|
||||
// alias are one visible change, and the move nobody handled still ends a
|
||||
// batch.
|
||||
assert(chrome.gui.commits == 5);
|
||||
|
||||
hosted.callDraw(-1); // a failed frame logs and the app keeps running
|
||||
assert(chrome.log.message.find("draw: ") == 0);
|
||||
assert(hosted.hasApp());
|
||||
|
||||
chrome.fs.files["/.lua/main.lua"] =
|
||||
"return { start = function() error('boom') end }";
|
||||
assert(!hosted.startApp("Reader"));
|
||||
assert(chrome.log.message.find("start: ") == 0);
|
||||
assert(!hosted.hasApp());
|
||||
|
||||
chrome.fs.files["/.lua/main.lua"] = "return 7";
|
||||
assert(!hosted.startApp("Reader"));
|
||||
assert(chrome.log.message == "main.lua returned no table");
|
||||
}
|
||||
|
||||
// A feature callback without its provider is a wiring bug, not a silent
|
||||
// no-op.
|
||||
@@ -235,22 +251,36 @@ int main() {
|
||||
"assert(input.getTouch == nil and input.isPressed ~= nil)");
|
||||
}
|
||||
|
||||
// App loading: a fresh state per app, require reaching the app directory and
|
||||
// /.lua/lib, and navigation applied between batches rather than inside a
|
||||
// App loading: a fresh state per app, main.lua deciding where apps and
|
||||
// modules live, and navigation applied between batches rather than inside a
|
||||
// callback.
|
||||
{
|
||||
fake::Bench host;
|
||||
// The tree is main.lua's, so the test states it the way a card would.
|
||||
host.fs.files["/.lua/main.lua"] =
|
||||
"package.path = '/.lua/lib/?.lua'\n"
|
||||
"return {\n"
|
||||
" home = 'Home',\n"
|
||||
" data = '/.lua/data/?',\n"
|
||||
" start = function(route, arg)\n"
|
||||
" local dir = '/.lua/apps/' .. route\n"
|
||||
" package.path = dir .. '/?.lua;/.lua/lib/?.lua'\n"
|
||||
" app = assert(loadfile(dir .. '/main.lua'))()\n"
|
||||
" app.init(arg)\n"
|
||||
" end,\n"
|
||||
"}\n";
|
||||
host.fs.files["/.lua/lib/greet.lua"] =
|
||||
"return {hello = function() return 'hi' end}";
|
||||
host.fs.files["/.lua/apps/Home/main.lua"] =
|
||||
"local greet = require('greet')\n"
|
||||
"function init(arg) started = greet.hello() .. ':' .. tostring(arg) "
|
||||
"end";
|
||||
"return {init = function(arg) started = greet.hello() .. ':' .. "
|
||||
"tostring(arg) end}";
|
||||
host.fs.files["/.lua/apps/Reader/main.lua"] =
|
||||
"local page = require('page')\n"
|
||||
"function init(arg) started = page.name .. ':' .. arg end";
|
||||
"return {init = function(arg) started = page.name .. ':' .. arg end}";
|
||||
host.fs.files["/.lua/apps/Reader/page.lua"] = "return {name = 'page'}";
|
||||
host.fs.files["/.lua/apps/Reader/Notes/main.lua"] = "function init() end";
|
||||
host.fs.files["/.lua/apps/Reader/Notes/main.lua"] =
|
||||
"return {init = function() end}";
|
||||
|
||||
esp32lua::Runtime app(host.providers());
|
||||
assert(app.startApp("Home"));
|
||||
@@ -289,7 +319,7 @@ int main() {
|
||||
assert(!app.startApp("Absent"));
|
||||
assert(!app.hasApp() && app.state() == nullptr);
|
||||
host.fs.files["/.lua/apps/Broken/main.lua"] =
|
||||
"function init() error('nope') end";
|
||||
"return {init = function() error('nope') end}";
|
||||
assert(!app.startApp("Broken"));
|
||||
assert(!app.hasApp());
|
||||
assert(!app.startApp("../secrets"));
|
||||
|
||||
Reference in New Issue
Block a user