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:
2026-08-04 20:51:25 -04:00
parent e85adfa757
commit e3153f57c5
5 changed files with 192 additions and 108 deletions
+27 -20
View File
@@ -15,14 +15,10 @@ namespace esp32lua {
// build.
constexpr int32_t API_VERSION = 1;
// Where the runtime looks for apps, their data, and shared modules.
struct Paths {
std::string apps = "/.lua/apps";
std::string data = "/.lua/data";
std::string lib = "/.lua/lib";
// Where sys.back() lands once history is empty. It is an app like any other.
std::string home = "Home";
};
// The one path the firmware knows. Everything below it -- where apps live,
// where their data goes, what chrome surrounds them -- is decided by the table
// this file returns.
constexpr const char* MAIN_PATH = "/.lua/main.lua";
// Firmware supplies every core provider; a null feature provider is how
// sys.hasFeature() answers false, and its namespace additions are simply never
@@ -44,7 +40,7 @@ struct Providers {
class Runtime {
public:
explicit Runtime(const Providers& providers, const Paths& paths = Paths());
explicit Runtime(const Providers& providers);
~Runtime();
Runtime(const Runtime&) = delete;
@@ -55,9 +51,9 @@ public:
void close();
lua_State* state() const { return state_; }
// Replaces the running app with a fresh lua_State, loads
// <apps>/<path>/main.lua, and calls init(arg). A failure leaves no app
// running rather than a half-built one.
// Replaces the running app with a fresh lua_State, loads main.lua, and hands
// it the route through start(route, arg). A failure leaves no app running
// rather than a half-built one.
bool startApp(const std::string& path,
const std::string& arg = std::string());
bool hasApp() const { return !appPath_.empty(); }
@@ -99,11 +95,11 @@ public:
ui::Tree& tree() { return tree_; }
// Entry points into the app. The firmware decides whether an event reaches
// the app at all -- jitter, chrome and debouncing are its business -- and the
// runtime decides what the app sees. Only a failed init() stops an app; every
// other callback logs and carries on.
bool callInit(const std::string& arg);
// Entry points into main.lua, which forwards whatever the app it mounted
// defines. The firmware decides whether an event happens at all -- jitter and
// debouncing are its business -- and main.lua decides who sees it. Only a
// failed start() stops an app; every other callback logs and carries on.
bool callStart(const std::string& route, const std::string& arg);
void callDraw(int32_t deltaMs);
// An Up phase also fires the on_touch tap alias, in that order.
void callTouch(TouchPhase phase, int32_t x, int32_t y);
@@ -136,7 +132,12 @@ private:
};
bool loadScript(const std::string& path);
void installLoader(const std::string& appDir);
// Runs main.lua and keeps the table it returns; the app is mounted by it, not
// by the runtime.
bool loadMain();
void installLoader();
// A field of the main table, or the fallback when main.lua names none.
std::string mainField(const char* key, const char* fallback);
static int searchModule(lua_State* state);
static int searchEmbedded(lua_State* state);
static int loadFile(lua_State* state);
@@ -155,9 +156,10 @@ private:
Runtime& runtime_;
};
// Pushes the named global, or returns false when the app does not define it.
// Pushes main.<name>, or returns false when main.lua defines no such handler.
bool beginCall(const char* name);
bool finishCall(const char* name, int argc);
bool finishCallValue(const char* name);
void cancelAllTimers();
Providers providers_;
@@ -167,7 +169,12 @@ private:
TimerId nextTimerId_ = 1;
int batchDepth_ = 0;
Paths paths_;
// Registry reference to the table main.lua returned, or 0 before one loads.
int mainRef_ = 0;
// Read from main.lua once per load, because sys.back() out of the last app
// needs the route after that app's state is gone.
std::string home_;
std::string dataTemplate_;
std::string appPath_;
std::string appTitle_;
std::vector<Route> history_;