163 lines
5.6 KiB
C++
163 lines
5.6 KiB
C++
#pragma once
|
|
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <lua/layout.h>
|
|
#include <lua/providers.h>
|
|
|
|
struct lua_State;
|
|
|
|
namespace esp32lua {
|
|
|
|
// The version sys.getAPIVersion() reports: this contract, not the firmware's 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";
|
|
};
|
|
|
|
// Firmware supplies every core provider; a null feature provider is how sys.hasFeature()
|
|
// answers false, and its namespace additions are simply never registered.
|
|
struct Providers {
|
|
LogProvider* log = nullptr;
|
|
SettingsProvider* settings = nullptr;
|
|
SysProvider* sys = nullptr;
|
|
FsProvider* fs = nullptr;
|
|
GuiProvider* gui = nullptr;
|
|
HttpProvider* http = nullptr;
|
|
TimerProvider* timer = nullptr;
|
|
WifiProvider* wifi = nullptr;
|
|
BleProvider* ble = nullptr;
|
|
|
|
TouchProvider* touch = nullptr;
|
|
ButtonsProvider* buttons = nullptr;
|
|
};
|
|
|
|
class Runtime {
|
|
public:
|
|
explicit Runtime(const Providers& providers, const Paths& paths = Paths());
|
|
~Runtime();
|
|
|
|
Runtime(const Runtime&) = delete;
|
|
Runtime& operator=(const Runtime&) = delete;
|
|
|
|
// Fails when a core provider is missing, leaving no Lua state behind.
|
|
bool open();
|
|
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.
|
|
bool startApp(const std::string& path, const std::string& arg = std::string());
|
|
bool hasApp() const { return !appPath_.empty(); }
|
|
// The app-relative route, its immutable first component, and the title the app chose.
|
|
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; }
|
|
// 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.
|
|
bool applyPendingNavigation();
|
|
|
|
LogProvider& log() const { return *providers_.log; }
|
|
SettingsProvider& settings() const { return *providers_.settings; }
|
|
SysProvider& sys() const { return *providers_.sys; }
|
|
FsProvider& fs() const { return *providers_.fs; }
|
|
GuiProvider& gui() const { return *providers_.gui; }
|
|
HttpProvider& http() const { return *providers_.http; }
|
|
TimerProvider& timer() const { return *providers_.timer; }
|
|
WifiProvider& wifi() const { return *providers_.wifi; }
|
|
BleProvider& ble() const { return *providers_.ble; }
|
|
TouchProvider& touch() const { return *providers_.touch; }
|
|
ButtonsProvider& buttons() const { return *providers_.buttons; }
|
|
|
|
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);
|
|
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);
|
|
// A release also fires the on_button tap alias, in that order.
|
|
void callButton(const std::string& button, bool pressed);
|
|
|
|
// Timer identity and callback retention are the runtime's; deadlines are the firmware's.
|
|
TimerId addTimer(int callbackRef, int32_t intervalMs, bool repeating);
|
|
bool cancelTimer(TimerId id);
|
|
// Called on the Lua thread when a scheduled deadline elapses.
|
|
void callTimer(TimerId id);
|
|
|
|
static Runtime* from(lua_State* state);
|
|
|
|
private:
|
|
struct Timer {
|
|
int callbackRef;
|
|
bool repeating;
|
|
};
|
|
|
|
struct Route {
|
|
std::string path;
|
|
std::string arg;
|
|
};
|
|
|
|
struct Pending {
|
|
enum Kind { None, Launch, Replace, Back } kind = None;
|
|
Route route;
|
|
};
|
|
|
|
bool loadScript(const std::string& path);
|
|
void installLoader(const std::string& appDir);
|
|
static int searchModule(lua_State* state);
|
|
static int loadFile(lua_State* state);
|
|
|
|
// A batch is one visit to the app, however many callbacks it fans out into: a tap fires
|
|
// on_touch_up and then the on_touch alias, and a timer can fire inside draw. Committing per
|
|
// callback would refresh an e-ink panel twice for one visible change, so the display is
|
|
// committed when the outermost call returns.
|
|
class Batch {
|
|
public:
|
|
explicit Batch(Runtime& runtime);
|
|
~Batch();
|
|
|
|
private:
|
|
Runtime& runtime_;
|
|
};
|
|
|
|
// Pushes the named global, or returns false when the app does not define it.
|
|
bool beginCall(const char* name);
|
|
bool finishCall(const char* name, int argc);
|
|
void cancelAllTimers();
|
|
|
|
Providers providers_;
|
|
lua_State* state_ = nullptr;
|
|
ui::Tree tree_;
|
|
std::map<TimerId, Timer> timers_;
|
|
TimerId nextTimerId_ = 1;
|
|
int batchDepth_ = 0;
|
|
|
|
Paths paths_;
|
|
std::string appPath_;
|
|
std::string appTitle_;
|
|
std::vector<Route> history_;
|
|
Pending pending_;
|
|
};
|
|
|
|
} // namespace esp32lua
|