Make property bindings shared amongst all lua threads

custom
jacqueline 1 year ago
parent 2a25085503
commit 4b2003c78a
  1. 6
      src/app_console/app_console.cpp
  2. 74
      src/lua/bridge.cpp
  3. 27
      src/lua/include/bridge.hpp
  4. 42
      src/lua/include/lua_thread.hpp
  5. 4
      src/lua/include/property.hpp
  6. 98
      src/lua/lua_thread.cpp
  7. 78
      src/lua/property.cpp
  8. 132
      src/ui/ui_fsm.cpp

@ -628,8 +628,7 @@ static const char kReplMain[] =
"repl:run()\n";
int CmdLua(int argc, char** argv) {
std::unique_ptr<lua::LuaThread> context{
lua::LuaThread::Start(*AppConsole::sServices)};
auto context = lua::Registry::instance(*AppConsole::sServices).newThread();
if (!context) {
return 1;
}
@ -652,8 +651,7 @@ int CmdLua(int argc, char** argv) {
}
int CmdLuaRun(int argc, char** argv) {
std::unique_ptr<lua::LuaThread> context{
lua::LuaThread::Start(*AppConsole::sServices)};
auto context = lua::Registry::instance(*AppConsole::sServices).newThread();
if (!context) {
return 1;
}

@ -22,6 +22,9 @@
#include "lua_version.hpp"
#include "lvgl.h"
#include "font/lv_font_loader.h"
#include "luavgl.h"
#include "event_queue.hpp"
#include "property.hpp"
#include "service_locator.hpp"
@ -32,34 +35,62 @@ int luaopen_linenoise(lua_State* L);
int luaopen_term_core(lua_State* L);
}
LV_FONT_DECLARE(font_fusion_12);
LV_FONT_DECLARE(font_fusion_10);
namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua_bridge";
static constexpr char kBridgeKey[] = "bridge";
static auto make_font_cb(const char* name, int size, int weight)
-> const lv_font_t* {
if (std::string{"fusion"} == name) {
if (size == 12) {
return &font_fusion_12;
}
if (size == 10) {
return &font_fusion_10;
}
}
return NULL;
}
static auto delete_font_cb(lv_font_t* font) -> void {}
auto Bridge::Get(lua_State* state) -> Bridge* {
lua_pushstring(state, kBridgeKey);
lua_gettable(state, LUA_REGISTRYINDEX);
return reinterpret_cast<Bridge*>(lua_touserdata(state, -1));
}
Bridge::Bridge(system_fsm::ServiceLocator& services, lua_State& s)
: services_(services), state_(s), bindings_(s) {
lua_pushstring(&s, kBridgeKey);
lua_pushlightuserdata(&s, this);
lua_settable(&s, LUA_REGISTRYINDEX);
Bridge::Bridge(system_fsm::ServiceLocator& services) : services_(services) {}
auto Bridge::installBaseModules(lua_State* L) -> void {
lua_pushstring(L, kBridgeKey);
lua_pushlightuserdata(L, this);
lua_settable(L, LUA_REGISTRYINDEX);
bindings_.install(L);
luaL_requiref(&s, "linenoise", luaopen_linenoise, true);
lua_pop(&s, 1);
luaL_requiref(L, "linenoise", luaopen_linenoise, true);
lua_pop(L, 1);
luaL_requiref(&s, "term.core", luaopen_term_core, true);
lua_pop(&s, 1);
luaL_requiref(L, "term.core", luaopen_term_core, true);
lua_pop(L, 1);
RegisterControlsModule(L);
RegisterDatabaseModule(L);
RegisterQueueModule(L);
RegisterVersionModule(L);
}
RegisterControlsModule(&s);
RegisterDatabaseModule(&s);
RegisterQueueModule(&s);
RegisterVersionModule(&s);
auto Bridge::installLvgl(lua_State* L) -> void {
luavgl_set_pcall(L, CallProtected);
luavgl_set_font_extension(L, make_font_cb, delete_font_cb);
luaL_requiref(L, "lvgl", luaopen_lvgl, true);
lua_pop(L, 1);
}
static auto new_property_module(lua_State* state) -> int {
@ -76,32 +107,33 @@ static auto new_property_module(lua_State* state) -> int {
template <class... Ts>
inline constexpr bool always_false_v = false;
auto Bridge::AddPropertyModule(
auto Bridge::installPropertyModule(
lua_State* L,
const std::string& name,
std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>
std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>&
props) -> void {
// Create the module (or retrieve it if one with this name already exists)
luaL_requiref(&state_, name.c_str(), new_property_module, true);
luaL_requiref(L, name.c_str(), new_property_module, true);
for (const auto& prop : props) {
lua_pushstring(&state_, prop.first.c_str());
lua_pushstring(L, prop.first.c_str());
std::visit(
[&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, LuaFunction>) {
bindings_.Register(&state_, arg);
bindings_.Register(L, arg);
} else if constexpr (std::is_same_v<T, Property*>) {
bindings_.Register(&state_, arg);
bindings_.Register(L, arg);
} else {
static_assert(always_false_v<T>, "missing case");
}
},
prop.second);
lua_settable(&state_, -3); // metatable.propname = property
lua_settable(L, -3); // metatable.propname = property
}
lua_pop(&state_, 1); // pop the module off the stack
lua_pop(L, 1); // pop the module off the stack
}
} // namespace lua

@ -16,25 +16,38 @@
namespace lua {
/*
* Responsible for adding C/C++ module bindings to Lua threads. This class
* keeps no thread-specific internal state, and instead uses the LUA_REGISTRY
* table of its host threads to store data.
*/
class Bridge {
public:
/*
* Utility for retrieving the Bridge from a Lua thread in which the Bridge's
* bindings have been installed. Use by Lua's C callbacks to access the rest
* of the system.
*/
static auto Get(lua_State* state) -> Bridge*;
Bridge(system_fsm::ServiceLocator&, lua_State& s);
Bridge(system_fsm::ServiceLocator& s);
auto AddPropertyModule(
system_fsm::ServiceLocator& services() { return services_; }
auto installBaseModules(lua_State* L) -> void;
auto installLvgl(lua_State* L) -> void;
auto installPropertyModule(
lua_State* L,
const std::string&,
std::vector<
std::pair<std::string,
std::variant<LuaFunction, Property*>>>)
std::pair<std::string, std::variant<LuaFunction, Property*>>>&)
-> void;
system_fsm::ServiceLocator& services() { return services_; }
PropertyBindings& bindings() { return bindings_; }
Bridge(const Bridge&) = delete;
Bridge& operator=(const Bridge&) = delete;
private:
system_fsm::ServiceLocator& services_;
lua_State& state_;
PropertyBindings bindings_;
};

@ -23,8 +23,7 @@ auto CallProtected(lua_State*, int nargs, int nresults) -> int;
class LuaThread {
public:
static auto Start(system_fsm::ServiceLocator&, lv_obj_t* lvgl_root = nullptr)
-> LuaThread*;
static auto Start(system_fsm::ServiceLocator&) -> LuaThread*;
~LuaThread();
auto RunScript(const std::string& path) -> bool;
@ -32,15 +31,48 @@ class LuaThread {
auto DumpStack() -> void;
auto bridge() -> Bridge& { return *bridge_; }
auto state() -> lua_State* { return state_; }
LuaThread(const LuaThread&) = delete;
LuaThread& operator=(const LuaThread&) = delete;
private:
LuaThread(std::unique_ptr<Allocator>&, std::unique_ptr<Bridge>&, lua_State*);
LuaThread(std::unique_ptr<Allocator>&, lua_State*);
std::unique_ptr<Allocator> alloc_;
std::unique_ptr<Bridge> bridge_;
lua_State* state_;
};
class Registry {
public:
static auto instance(system_fsm::ServiceLocator&) -> Registry&;
auto uiThread() -> std::shared_ptr<LuaThread>;
auto newThread() -> std::shared_ptr<LuaThread>;
auto AddPropertyModule(
const std::string&,
std::vector<
std::pair<std::string, std::variant<LuaFunction, Property*>>>)
-> void;
Registry(const Registry&) = delete;
Registry& operator=(const Registry&) = delete;
private:
Registry(system_fsm::ServiceLocator&);
system_fsm::ServiceLocator& services_;
std::unique_ptr<Bridge> bridge_;
std::shared_ptr<LuaThread> ui_thread_;
std::list<std::weak_ptr<LuaThread>> threads_;
std::vector<
std::pair<std::string,
std::vector<std::pair<std::string,
std::variant<LuaFunction, Property*>>>>>
modules_;
};
} // namespace lua

@ -53,7 +53,9 @@ class Property {
class PropertyBindings {
public:
PropertyBindings(lua_State&);
PropertyBindings();
auto install(lua_State*) -> void;
auto Register(lua_State*, Property*) -> void;
auto Register(lua_State*, LuaFunction) -> void;

@ -9,22 +9,16 @@
#include <iostream>
#include <memory>
#include "lauxlib.h"
#include "lua.h"
#include "lua.hpp"
#include "font/lv_font_loader.h"
#include "luavgl.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "lua.hpp"
#include "bridge.hpp"
#include "event_queue.hpp"
#include "memory_resource.hpp"
#include "service_locator.hpp"
#include "ui_events.hpp"
LV_FONT_DECLARE(font_fusion_12);
LV_FONT_DECLARE(font_fusion_10);
namespace lua {
[[maybe_unused]] static constexpr char kTag[] = "lua";
@ -59,23 +53,7 @@ static int lua_panic(lua_State* L) {
return 0;
}
static auto make_font_cb(const char* name, int size, int weight)
-> const lv_font_t* {
if (std::string{"fusion"} == name) {
if (size == 12) {
return &font_fusion_12;
}
if (size == 10) {
return &font_fusion_10;
}
}
return NULL;
}
static auto delete_font_cb(lv_font_t* font) -> void {}
auto LuaThread::Start(system_fsm::ServiceLocator& services, lv_obj_t* lvgl_root)
-> LuaThread* {
auto LuaThread::Start(system_fsm::ServiceLocator& services) -> LuaThread* {
auto alloc = std::make_unique<Allocator>();
lua_State* state = lua_newstate(lua_alloc, alloc.get());
if (!state) {
@ -85,24 +63,11 @@ auto LuaThread::Start(system_fsm::ServiceLocator& services, lv_obj_t* lvgl_root)
luaL_openlibs(state);
lua_atpanic(state, lua_panic);
auto bridge = std::make_unique<Bridge>(services, *state);
// FIXME: luavgl init should probably be a part of the bridge.
if (lvgl_root) {
luavgl_set_pcall(state, CallProtected);
luavgl_set_font_extension(state, make_font_cb, delete_font_cb);
luavgl_set_root(state, lvgl_root);
luaL_requiref(state, "lvgl", luaopen_lvgl, true);
lua_pop(state, 1);
}
return new LuaThread(alloc, bridge, state);
return new LuaThread(alloc, state);
}
LuaThread::LuaThread(std::unique_ptr<Allocator>& alloc,
std::unique_ptr<Bridge>& bridge,
lua_State* state)
: alloc_(std::move(alloc)), bridge_(std::move(bridge)), state_(state) {}
LuaThread::LuaThread(std::unique_ptr<Allocator>& alloc, lua_State* state)
: alloc_(std::move(alloc)), state_(state) {}
LuaThread::~LuaThread() {
lua_close(state_);
@ -219,4 +184,51 @@ auto CallProtected(lua_State* s, int nargs, int nresults) -> int {
return ret;
}
auto Registry::instance(system_fsm::ServiceLocator& s) -> Registry& {
static Registry sRegistry{s};
return sRegistry;
}
Registry::Registry(system_fsm::ServiceLocator& services)
: services_(services), bridge_(new Bridge(services)) {}
auto Registry::uiThread() -> std::shared_ptr<LuaThread> {
if (!ui_thread_) {
ui_thread_ = newThread();
bridge_->installLvgl(ui_thread_->state());
}
return ui_thread_;
}
auto Registry::newThread() -> std::shared_ptr<LuaThread> {
std::shared_ptr<LuaThread> thread{LuaThread::Start(services_)};
bridge_->installBaseModules(thread->state());
for (auto& module : modules_) {
bridge_->installPropertyModule(thread->state(), module.first,
module.second);
}
threads_.push_back(thread);
return thread;
}
auto Registry::AddPropertyModule(
const std::string& name,
std::vector<std::pair<std::string, std::variant<LuaFunction, Property*>>>
properties) -> void {
modules_.push_back(std::make_pair(name, properties));
// Any live threads will need to be updated to include the new module.
auto it = threads_.begin();
while (it != threads_.end()) {
auto thread = it->lock();
if (!thread) {
// Thread has been destroyed; stop tracking it.
it = threads_.erase(it);
} else {
bridge_->installPropertyModule(thread->state(), name, properties);
it++;
}
}
}
} // namespace lua

@ -10,10 +10,12 @@
#include <cmath>
#include <memory>
#include <memory_resource>
#include <sstream>
#include <string>
#include <variant>
#include "bluetooth_types.hpp"
#include "lauxlib.h"
#include "lua.h"
#include "lua.hpp"
#include "lua_thread.hpp"
@ -76,10 +78,30 @@ static auto property_bind(lua_State* state) -> int {
return 1;
}
static const struct luaL_Reg kPropertyBindingFuncs[] = {{"get", property_get},
{"set", property_set},
{"bind", property_bind},
{NULL, NULL}};
static auto property_tostring(lua_State* state) -> int {
Property* p = check_property(state);
p->PushValue(*state);
std::stringstream str{};
str << "property { " << luaL_tolstring(state, -1, NULL);
if (!p->IsTwoWay()) {
str << ", read-only";
}
str << " }";
lua_settop(state, 0);
std::string res = str.str();
lua_pushlstring(state, res.data(), res.size());
return 1;
}
static const struct luaL_Reg kPropertyBindingFuncs[] = {
{"get", property_get},
{"set", property_set},
{"bind", property_bind},
{"__tostring", property_tostring},
{NULL, NULL}};
static auto generic_function_cb(lua_State* state) -> int {
lua_pushstring(state, kBinderKey);
@ -98,45 +120,47 @@ static auto generic_function_cb(lua_State* state) -> int {
return std::invoke(fn, state);
}
PropertyBindings::PropertyBindings(lua_State& s) {
lua_pushstring(&s, kBinderKey);
lua_pushlightuserdata(&s, this);
lua_settable(&s, LUA_REGISTRYINDEX);
PropertyBindings::PropertyBindings() : functions_(&memory::kSpiRamResource) {}
auto PropertyBindings::install(lua_State* L) -> void {
lua_pushstring(L, kBinderKey);
lua_pushlightuserdata(L, this);
lua_settable(L, LUA_REGISTRYINDEX);
// Create the metatable responsible for the Property API.
luaL_newmetatable(&s, kPropertyMetatable);
luaL_newmetatable(L, kPropertyMetatable);
lua_pushliteral(&s, "__index");
lua_pushvalue(&s, -2);
lua_settable(&s, -3); // metatable.__index = metatable
lua_pushliteral(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3); // metatable.__index = metatable
// Add our binding funcs (get, set, bind) to the metatable.
luaL_setfuncs(&s, kPropertyBindingFuncs, 0);
luaL_setfuncs(L, kPropertyBindingFuncs, 0);
// We've finished setting up the metatable, so pop it.
lua_pop(&s, 1);
lua_pop(L, 1);
// Create a weak table in the registry to hold live bindings.
lua_pushstring(&s, kBindingsTable);
lua_newtable(&s); // bindings = {}
lua_pushstring(L, kBindingsTable);
lua_newtable(L); // bindings = {}
// Metatable for the weak table. Values are weak.
lua_newtable(&s); // meta = {}
lua_pushliteral(&s, "__mode");
lua_pushliteral(&s, "v");
lua_settable(&s, -3); // meta.__mode='v'
lua_setmetatable(&s, -2); // setmetatable(bindings, meta)
lua_newtable(L); // meta = {}
lua_pushliteral(L, "__mode");
lua_pushliteral(L, "v");
lua_settable(L, -3); // meta.__mode='v'
lua_setmetatable(L, -2); // setmetatable(bindings, meta)
lua_settable(&s, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings
lua_settable(L, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings
// Create the metatable for C++ functions.
luaL_newmetatable(&s, kFunctionMetatable);
luaL_newmetatable(L, kFunctionMetatable);
lua_pushliteral(&s, "__call");
lua_pushcfunction(&s, generic_function_cb);
lua_settable(&s, -3); // metatable.__call = metatable
lua_pushliteral(L, "__call");
lua_pushcfunction(L, generic_function_cb);
lua_settable(L, -3); // metatable.__call = metatable
lua_pop(&s, 1); // Clean up the function metatable
lua_pop(L, 1); // Clean up the function metatable
}
auto PropertyBindings::Register(lua_State* s, Property* prop) -> void {

@ -128,29 +128,29 @@ lua::Property UiState::sPlaybackPosition{0};
lua::Property UiState::sQueuePosition{0};
lua::Property UiState::sQueueSize{0};
lua::Property UiState::sQueueRepeat{false, [](const lua::LuaValue& val) {
if (!std::holds_alternative<bool>(val)) {
return false;
}
bool new_val = std::get<bool>(val);
sServices->track_queue().repeat(new_val);
return true;
}};
if (!std::holds_alternative<bool>(val)) {
return false;
}
bool new_val = std::get<bool>(val);
sServices->track_queue().repeat(new_val);
return true;
}};
lua::Property UiState::sQueueReplay{false, [](const lua::LuaValue& val) {
if (!std::holds_alternative<bool>(val)) {
return false;
}
bool new_val = std::get<bool>(val);
sServices->track_queue().replay(new_val);
return true;
}};
if (!std::holds_alternative<bool>(val)) {
return false;
}
bool new_val = std::get<bool>(val);
sServices->track_queue().replay(new_val);
return true;
}};
lua::Property UiState::sQueueRandom{false, [](const lua::LuaValue& val) {
if (!std::holds_alternative<bool>(val)) {
return false;
}
bool new_val = std::get<bool>(val);
sServices->track_queue().random(new_val);
return true;
}};
if (!std::holds_alternative<bool>(val)) {
return false;
}
bool new_val = std::get<bool>(val);
sServices->track_queue().random(new_val);
return true;
}};
lua::Property UiState::sVolumeCurrentPct{
0, [](const lua::LuaValue& val) {
@ -442,27 +442,26 @@ void Lua::entry() {
alert_timer_callback);
sAlertContainer = lv_obj_create(sCurrentScreen->alert());
sLua.reset(lua::LuaThread::Start(*sServices, sCurrentScreen->content()));
sLua->bridge().AddPropertyModule("power",
{
{"battery_pct", &sBatteryPct},
{"battery_millivolts", &sBatteryMv},
{"plugged_in", &sBatteryCharging},
});
sLua->bridge().AddPropertyModule(
"bluetooth", {
{"enabled", &sBluetoothEnabled},
{"connected", &sBluetoothConnected},
{"paired_device", &sBluetoothPairedDevice},
{"devices", &sBluetoothDevices},
});
sLua->bridge().AddPropertyModule("playback",
{
{"playing", &sPlaybackPlaying},
{"track", &sPlaybackTrack},
{"position", &sPlaybackPosition},
});
sLua->bridge().AddPropertyModule(
auto& registry = lua::Registry::instance(*sServices);
sLua = registry.uiThread();
registry.AddPropertyModule("power", {
{"battery_pct", &sBatteryPct},
{"battery_millivolts", &sBatteryMv},
{"plugged_in", &sBatteryCharging},
});
registry.AddPropertyModule("bluetooth",
{
{"enabled", &sBluetoothEnabled},
{"connected", &sBluetoothConnected},
{"paired_device", &sBluetoothPairedDevice},
{"devices", &sBluetoothDevices},
});
registry.AddPropertyModule("playback", {
{"playing", &sPlaybackPlaying},
{"track", &sPlaybackTrack},
{"position", &sPlaybackPosition},
});
registry.AddPropertyModule(
"queue",
{
{"next", [&](lua_State* s) { return QueueNext(s); }},
@ -473,40 +472,39 @@ void Lua::entry() {
{"repeat_track", &sQueueRepeat},
{"random", &sQueueRandom},
});
sLua->bridge().AddPropertyModule("volume",
{
{"current_pct", &sVolumeCurrentPct},
{"current_db", &sVolumeCurrentDb},
{"left_bias", &sVolumeLeftBias},
{"limit_db", &sVolumeLimit},
});
sLua->bridge().AddPropertyModule("display",
{
{"brightness", &sDisplayBrightness},
});
sLua->bridge().AddPropertyModule("controls",
{
{"scheme", &sControlsScheme},
{"scroll_sensitivity", &sScrollSensitivity},
});
sLua->bridge().AddPropertyModule(
registry.AddPropertyModule("volume",
{
{"current_pct", &sVolumeCurrentPct},
{"current_db", &sVolumeCurrentDb},
{"left_bias", &sVolumeLeftBias},
{"limit_db", &sVolumeLimit},
});
registry.AddPropertyModule("display",
{
{"brightness", &sDisplayBrightness},
});
registry.AddPropertyModule("controls",
{
{"scheme", &sControlsScheme},
{"scroll_sensitivity", &sScrollSensitivity},
});
registry.AddPropertyModule(
"backstack",
{
{"push", [&](lua_State* s) { return PushLuaScreen(s); }},
{"pop", [&](lua_State* s) { return PopLuaScreen(s); }},
});
sLua->bridge().AddPropertyModule(
registry.AddPropertyModule(
"alerts", {
{"show", [&](lua_State* s) { return ShowAlert(s); }},
{"hide", [&](lua_State* s) { return HideAlert(s); }},
});
sLua->bridge().AddPropertyModule("database",
{
{"updating", &sDatabaseUpdating},
});
registry.AddPropertyModule("database", {
{"updating", &sDatabaseUpdating},
});
auto bt = sServices->bluetooth();
sBluetoothEnabled.Update(bt.IsEnabled());

Loading…
Cancel
Save