feat(runtime)!: sys.startApp replaces routing, history and app identity
The runtime kept a back stack, a launcher fallback, an app id and a title because a teardown destroys the Lua that would otherwise hold them. Only the first of those is true: everything about where an app came from can ride in the arguments, and the arguments are the one value that has to outlive the VM. So the runtime now does four things -- close the state, load a path, hand the next state its arguments, defer the swap to a batch boundary -- and sys.startApp(path, args) is the whole of navigation. Routing, history, titles and data directories move to the Lua file a firmware boots, where they can differ per product without a flag on Runtime. Arguments cross as JSON, encoded while the sending state still holds the table, so a function or a cycle raises at the call rather than stranding a launch. start(args) receives the decoded table, or nil at boot, which is how the entry file knows to open its own launcher. Removes launch, replace, back, canGoBack, getAppID, getAppTitle, setAppTitle and getAppDataPath, along with the home and data fields. LANDSCAPE.md goes with them: it recorded a divergence from firmwares that have since migrated.
This commit is contained in:
@@ -15,9 +15,9 @@ namespace esp32lua {
|
||||
// build.
|
||||
constexpr int32_t API_VERSION = 1;
|
||||
|
||||
// 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.
|
||||
// The path a firmware boots. Nothing else here knows it: startApp() takes
|
||||
// whatever path it is given, and where apps live, where their data goes and
|
||||
// what chrome surrounds them are decided by the Lua it loads.
|
||||
constexpr const char* MAIN_PATH = "/.lua/main.lua";
|
||||
|
||||
// Firmware supplies every core provider; a null feature provider is how
|
||||
@@ -37,6 +37,13 @@ struct Providers {
|
||||
ButtonsProvider* buttons = nullptr;
|
||||
};
|
||||
|
||||
// The arguments a launch carries cross the teardown as JSON, because the table
|
||||
// they came from dies with the state that built it. Encoding raises, so an app
|
||||
// that passes a function sees the error at its own sys.startApp() call;
|
||||
// decoding cannot, because by then there is no app to report it to.
|
||||
std::string encodeJson(lua_State* state, int index);
|
||||
bool decodeJson(lua_State* state, const std::string& json);
|
||||
|
||||
class Runtime {
|
||||
public:
|
||||
explicit Runtime(const Providers& providers);
|
||||
@@ -50,34 +57,24 @@ public:
|
||||
void close();
|
||||
lua_State* state() const { return state_; }
|
||||
|
||||
// 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.
|
||||
// Replaces the running app with a fresh lua_State, loads the path, and hands
|
||||
// the table it returns its arguments through start(args). A failure leaves no
|
||||
// app running rather than a half-built one. `argsJson` is the JSON a previous
|
||||
// state encoded, and is the only thing that crosses the teardown.
|
||||
bool startApp(const std::string& path,
|
||||
const std::string& arg = std::string());
|
||||
const std::string& argsJson = std::string());
|
||||
bool hasApp() const { return !appPath_.empty(); }
|
||||
// The app-relative route, its immutable first component, and the title the
|
||||
// app chose.
|
||||
// The path that was loaded, which is all the runtime knows about an app.
|
||||
const std::string& appPath() const { return appPath_; }
|
||||
std::string appId() const;
|
||||
std::string appDataPath() const;
|
||||
const std::string& appTitle() const { return appTitle_; }
|
||||
void setAppTitle(const std::string& title) { appTitle_ = title; }
|
||||
bool hasFeature(const std::string& feature) const;
|
||||
|
||||
// sys.launch/replace/back record intent and return; swapping the lua_State
|
||||
// inside a callback would free the VM that is still executing. The firmware
|
||||
// applies it between batches.
|
||||
void requestLaunch(const std::string& path, const std::string& arg,
|
||||
bool replace);
|
||||
void requestBack();
|
||||
bool hasPendingNavigation() const { return pending_.kind != Pending::None; }
|
||||
// Whether sys.back() would return somewhere rather than land on the launcher,
|
||||
// which is what firmware chrome needs to decide whether to offer a back
|
||||
// control.
|
||||
bool canGoBack() const { return !history_.empty(); }
|
||||
// Loads whatever was requested. False means the app failed to start or
|
||||
// history ran out at the launcher, in which case no app is running.
|
||||
// sys.startApp records intent and returns; swapping the lua_State inside a
|
||||
// callback would free the VM that is still executing. The firmware applies it
|
||||
// between batches.
|
||||
void requestStart(const std::string& path, const std::string& argsJson);
|
||||
bool hasPendingNavigation() const { return pending_.pending; }
|
||||
// Loads whatever was requested. False means the app failed to start, in which
|
||||
// case no app is running.
|
||||
bool applyPendingNavigation();
|
||||
|
||||
LogProvider& log() const { return *providers_.log; }
|
||||
@@ -97,7 +94,7 @@ public:
|
||||
// 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);
|
||||
bool callStart(const std::string& argsJson);
|
||||
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);
|
||||
@@ -119,23 +116,17 @@ private:
|
||||
bool repeating;
|
||||
};
|
||||
|
||||
struct Route {
|
||||
std::string path;
|
||||
std::string arg;
|
||||
};
|
||||
|
||||
struct Pending {
|
||||
enum Kind { None, Launch, Replace, Back } kind = None;
|
||||
Route route;
|
||||
bool pending = false;
|
||||
std::string path;
|
||||
std::string argsJson;
|
||||
};
|
||||
|
||||
bool loadScript(const std::string& path);
|
||||
// Runs main.lua and keeps the table it returns; the app is mounted by it, not
|
||||
// by the runtime.
|
||||
bool loadMain();
|
||||
// Runs the app's entry file and keeps the table it returns; the app is
|
||||
// mounted by that table, not by the runtime.
|
||||
bool loadMain(const std::string& path);
|
||||
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);
|
||||
@@ -167,15 +158,10 @@ private:
|
||||
TimerId nextTimerId_ = 1;
|
||||
int batchDepth_ = 0;
|
||||
|
||||
// Registry reference to the table main.lua returned, or 0 before one loads.
|
||||
// Registry reference to the table the entry file 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_;
|
||||
Pending pending_;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user