diff options
| author | jacqueline <me@jacqueline.id.au> | 2024-05-02 19:12:26 +1000 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2024-05-02 19:12:26 +1000 |
| commit | 1573a8c4cde1cd9528b422b2dcc598e37ffe94a7 (patch) | |
| tree | d162822b8fd7054f81bace0c7a65ab4d5e6f93ef /src/lua | |
| parent | a231fd1c8afedbeb14b0bc77d76bad61db986059 (diff) | |
| download | tangara-fw-1573a8c4cde1cd9528b422b2dcc598e37ffe94a7.tar.gz | |
WIP merge cyclically dependent components into one big component
Diffstat (limited to 'src/lua')
| -rw-r--r-- | src/lua/CMakeLists.txt | 13 | ||||
| -rw-r--r-- | src/lua/bridge.cpp | 143 | ||||
| -rw-r--r-- | src/lua/include/bridge.hpp | 54 | ||||
| -rw-r--r-- | src/lua/include/lua_controls.hpp | 15 | ||||
| -rw-r--r-- | src/lua/include/lua_database.hpp | 19 | ||||
| -rw-r--r-- | src/lua/include/lua_queue.hpp | 15 | ||||
| -rw-r--r-- | src/lua/include/lua_registry.hpp | 51 | ||||
| -rw-r--r-- | src/lua/include/lua_screen.hpp | 15 | ||||
| -rw-r--r-- | src/lua/include/lua_theme.hpp | 15 | ||||
| -rw-r--r-- | src/lua/include/lua_thread.hpp | 44 | ||||
| -rw-r--r-- | src/lua/include/lua_version.hpp | 15 | ||||
| -rw-r--r-- | src/lua/include/property.hpp | 107 | ||||
| -rw-r--r-- | src/lua/lua_controls.cpp | 58 | ||||
| -rw-r--r-- | src/lua/lua_database.cpp | 306 | ||||
| -rw-r--r-- | src/lua/lua_queue.cpp | 74 | ||||
| -rw-r--r-- | src/lua/lua_screen.cpp | 79 | ||||
| -rw-r--r-- | src/lua/lua_theme.cpp | 89 | ||||
| -rw-r--r-- | src/lua/lua_thread.cpp | 187 | ||||
| -rw-r--r-- | src/lua/lua_version.cpp | 68 | ||||
| -rw-r--r-- | src/lua/property.cpp | 460 | ||||
| -rw-r--r-- | src/lua/registry.cpp | 73 |
21 files changed, 0 insertions, 1900 deletions
diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt deleted file mode 100644 index 0240a50c..00000000 --- a/src/lua/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2023 jacqueline <me@jacqueline.id.au> -# -# SPDX-License-Identifier: GPL-3.0-only - -idf_component_register( - SRCS "lua_theme.cpp" "lua_thread.cpp" "bridge.cpp" "property.cpp" "lua_database.cpp" - "lua_queue.cpp" "lua_version.cpp" "lua_theme.cpp" "lua_controls.cpp" "registry.cpp" - "lua_screen.cpp" - INCLUDE_DIRS "include" - REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" - "esp_timer" "battery" "esp-idf-lua" "luavgl" "lua-linenoise" "lua-term" - "esp_app_format") -target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/lua/bridge.cpp b/src/lua/bridge.cpp deleted file mode 100644 index cfa9d5f7..00000000 --- a/src/lua/bridge.cpp +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "bridge.hpp" -#include <sys/_stdint.h> - -#include <memory> -#include <string> - -#include "database.hpp" -#include "esp_log.h" -#include "index.hpp" -#include "lauxlib.h" -#include "lua.h" -#include "lua.hpp" -#include "lua_controls.hpp" -#include "lua_database.hpp" -#include "lua_queue.hpp" -#include "lua_screen.hpp" -#include "lua_version.hpp" -#include "lua_theme.hpp" -#include "lvgl.h" - -#include "font/lv_font_loader.h" -#include "luavgl.h" - -#include "event_queue.hpp" -#include "property.hpp" -#include "service_locator.hpp" -#include "ui_events.hpp" - -extern "C" { -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) : 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(L, "linenoise", luaopen_linenoise, true); - lua_pop(L, 1); - - luaL_requiref(L, "term.core", luaopen_term_core, true); - lua_pop(L, 1); - - RegisterControlsModule(L); - RegisterDatabaseModule(L); - RegisterQueueModule(L); - RegisterVersionModule(L); - RegisterThemeModule(L); - RegisterScreenModule(L); -} - -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 { - const char* name = luaL_checkstring(state, 1); - luaL_newmetatable(state, name); - - lua_pushstring(state, "__index"); - lua_pushvalue(state, -2); - lua_settable(state, -3); // metatable.__index = metatable - - return 1; -} - -template <class... Ts> -inline constexpr bool always_false_v = false; - -auto Bridge::installPropertyModule( - lua_State* L, - const std::string& name, - 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(L, name.c_str(), new_property_module, true); - - for (const auto& prop : props) { - 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(L, arg); - } else if constexpr (std::is_same_v<T, Property*>) { - bindings_.Register(L, arg); - } else { - static_assert(always_false_v<T>, "missing case"); - } - }, - prop.second); - - lua_settable(L, -3); // metatable.propname = property - } - - lua_pop(L, 1); // pop the module off the stack -} - -} // namespace lua diff --git a/src/lua/include/bridge.hpp b/src/lua/include/bridge.hpp deleted file mode 100644 index 64f14e0e..00000000 --- a/src/lua/include/bridge.hpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <memory> -#include <string> - -#include "lua.hpp" -#include "lvgl.h" -#include "property.hpp" -#include "service_locator.hpp" - -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& s); - - 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*>>>&) - -> void; - - Bridge(const Bridge&) = delete; - Bridge& operator=(const Bridge&) = delete; - - private: - system_fsm::ServiceLocator& services_; - PropertyBindings bindings_; -}; - -} // namespace lua diff --git a/src/lua/include/lua_controls.hpp b/src/lua/include/lua_controls.hpp deleted file mode 100644 index 18ad261d..00000000 --- a/src/lua/include/lua_controls.hpp +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "lua.hpp" - -namespace lua { - -auto RegisterControlsModule(lua_State*) -> void; - -} // namespace lua diff --git a/src/lua/include/lua_database.hpp b/src/lua/include/lua_database.hpp deleted file mode 100644 index b0d2acbd..00000000 --- a/src/lua/include/lua_database.hpp +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "lua.hpp" - -#include "database.hpp" - -namespace lua { - -auto db_check_iterator(lua_State*, int stack_pos) -> database::Iterator*; - -auto RegisterDatabaseModule(lua_State*) -> void; - -} // namespace lua diff --git a/src/lua/include/lua_queue.hpp b/src/lua/include/lua_queue.hpp deleted file mode 100644 index c5cfe04d..00000000 --- a/src/lua/include/lua_queue.hpp +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "lua.hpp" - -namespace lua { - -auto RegisterQueueModule(lua_State*) -> void; - -} // namespace lua diff --git a/src/lua/include/lua_registry.hpp b/src/lua/include/lua_registry.hpp deleted file mode 100644 index abc5063e..00000000 --- a/src/lua/include/lua_registry.hpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <memory> -#include <string> - -#include "lua.hpp" - -#include "bridge.hpp" -#include "lua_thread.hpp" -#include "service_locator.hpp" - -namespace lua { - -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 diff --git a/src/lua/include/lua_screen.hpp b/src/lua/include/lua_screen.hpp deleted file mode 100644 index 1c3bed1a..00000000 --- a/src/lua/include/lua_screen.hpp +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "lua.hpp" - -namespace lua { - -auto RegisterScreenModule(lua_State*) -> void; - -} // namespace lua diff --git a/src/lua/include/lua_theme.hpp b/src/lua/include/lua_theme.hpp deleted file mode 100644 index fed710e0..00000000 --- a/src/lua/include/lua_theme.hpp +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2024 ailurux <ailuruxx@gmail.com> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "lua.hpp" - -namespace lua { - -auto RegisterThemeModule(lua_State*) -> void; - -} // namespace lua diff --git a/src/lua/include/lua_thread.hpp b/src/lua/include/lua_thread.hpp deleted file mode 100644 index 384de61d..00000000 --- a/src/lua/include/lua_thread.hpp +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <memory> -#include <string> - -#include "lua.hpp" - -#include "service_locator.hpp" - -namespace lua { - -class Allocator; - -auto CallProtected(lua_State*, int nargs, int nresults) -> int; - -class LuaThread { - public: - static auto Start(system_fsm::ServiceLocator&) -> LuaThread*; - ~LuaThread(); - - auto RunScript(const std::string& path) -> bool; - auto RunString(const std::string& path) -> bool; - - auto DumpStack() -> void; - - auto state() -> lua_State* { return state_; } - - LuaThread(const LuaThread&) = delete; - LuaThread& operator=(const LuaThread&) = delete; - - private: - LuaThread(std::unique_ptr<Allocator>&, lua_State*); - - std::unique_ptr<Allocator> alloc_; - lua_State* state_; -}; - -} // namespace lua diff --git a/src/lua/include/lua_version.hpp b/src/lua/include/lua_version.hpp deleted file mode 100644 index 4ba4be94..00000000 --- a/src/lua/include/lua_version.hpp +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include "lua.hpp" - -namespace lua { - -auto RegisterVersionModule(lua_State*) -> void; - -} // namespace lua diff --git a/src/lua/include/property.hpp b/src/lua/include/property.hpp deleted file mode 100644 index 724261be..00000000 --- a/src/lua/include/property.hpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#pragma once - -#include <stdint.h> -#include <memory> -#include <string> - -#include "audio_events.hpp" -#include "bluetooth_types.hpp" -#include "lua.hpp" -#include "lvgl.h" -#include "service_locator.hpp" - -namespace lua { - -// FIXME: We should use some kind of interface for this instead. -using LuaValue = std::variant<std::monostate, - int, - bool, - std::string, - audio::TrackInfo, - drivers::bluetooth::Device, - std::vector<drivers::bluetooth::Device>>; - -using LuaFunction = std::function<int(lua_State*)>; - -class Property { - public: - Property() : Property(std::monostate{}) {} - Property(const LuaValue&); - Property(const LuaValue&, std::function<bool(const LuaValue&)> filter); - - auto get() -> const LuaValue& { return *value_; } - - /* - * Assigns a new value to this property, bypassing the filter fn. All - * bindings will be marked as dirty, and if active, will be reapplied. - */ - auto setDirect(const LuaValue&) -> void; - /* - * Invokes the filter fn, and if successful, assigns the new value to this - * property. All bindings will be marked as dirty, and if active, will be - * reapplied. - */ - auto set(const LuaValue&) -> bool; - - /* Returns whether or not this Property can be written from Lua. */ - auto isTwoWay() -> bool { return cb_.has_value(); } - - auto pushValue(lua_State& s) -> int; - auto popValue(lua_State& s) -> bool; - - /* Reapplies all active, dirty bindings associated with this Property. */ - auto reapplyAll() -> void; - - auto addLuaBinding(lua_State*, int ref) -> void; - auto applySingle(lua_State*, int ref, bool mark_dirty) -> bool; - - private: - std::unique_ptr<LuaValue> value_; - std::optional<std::function<bool(const LuaValue&)>> cb_; - std::pmr::vector<std::pair<lua_State*, int>> bindings_; -}; - -/* - * Container for a Lua function that should be invoked whenever a Property's - * value changes, as well as some extra accounting metadata. - */ -struct Binding { - /* Checks the value at idx is a Binding, returning a pointer to it if so. */ - static auto get(lua_State*, int idx) -> Binding*; - /* - * If the value at idx is a dirty, active Binding, applies the current value - * from its Property. Returns false if the binding was active and dirty, but - * invoking the Lua callback failed. - */ - static auto apply(lua_State*, int idx) -> bool; - - Property* property; - bool active; - bool dirty; -}; - -static_assert(std::is_trivially_destructible<Binding>()); -static_assert(std::is_trivially_copy_assignable<Binding>()); - -class PropertyBindings { - public: - PropertyBindings(); - - auto install(lua_State*) -> void; - - auto Register(lua_State*, Property*) -> void; - auto Register(lua_State*, LuaFunction) -> void; - - auto GetFunction(size_t i) -> const LuaFunction&; - - private: - std::pmr::vector<LuaFunction> functions_; -}; - -} // namespace lua diff --git a/src/lua/lua_controls.cpp b/src/lua/lua_controls.cpp deleted file mode 100644 index 2da0ed11..00000000 --- a/src/lua/lua_controls.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "lua_controls.hpp" - -#include <memory> -#include <string> - -#include "lua.hpp" - -#include "esp_log.h" -#include "lauxlib.h" -#include "lua.h" -#include "lvgl.h" - -#include "nvs.hpp" -#include "ui_events.hpp" - -namespace lua { - -[[maybe_unused]] static constexpr char kTag[] = "lua_controls"; - -static auto controls_schemes(lua_State* L) -> int { - lua_newtable(L); - - lua_pushliteral(L, "Buttons Only"); - lua_rawseti(L, -2, - static_cast<int>(drivers::NvsStorage::InputModes::kButtonsOnly)); - - lua_pushliteral(L, "D-Pad"); - lua_rawseti( - L, -2, - static_cast<int>(drivers::NvsStorage::InputModes::kDirectionalWheel)); - - lua_pushliteral(L, "Touchwheel"); - lua_rawseti( - L, -2, static_cast<int>(drivers::NvsStorage::InputModes::kRotatingWheel)); - - return 1; -} - -static const struct luaL_Reg kControlsFuncs[] = {{"schemes", controls_schemes}, - {NULL, NULL}}; - -static auto lua_controls(lua_State* state) -> int { - luaL_newlib(state, kControlsFuncs); - return 1; -} - -auto RegisterControlsModule(lua_State* s) -> void { - luaL_requiref(s, "controls", lua_controls, true); - lua_pop(s, 1); -} - -} // namespace lua diff --git a/src/lua/lua_database.cpp b/src/lua/lua_database.cpp deleted file mode 100644 index d0612fdd..00000000 --- a/src/lua/lua_database.cpp +++ /dev/null @@ -1,306 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "lua_database.hpp" - -#include <memory> -#include <string> -#include <type_traits> -#include <variant> - -#include "bridge.hpp" -#include "lua.hpp" - -#include "esp_log.h" -#include "lauxlib.h" -#include "lua.h" -#include "lua_thread.hpp" -#include "lvgl.h" - -#include "database.hpp" -#include "event_queue.hpp" -#include "index.hpp" -#include "property.hpp" -#include "records.hpp" -#include "service_locator.hpp" -#include "track.hpp" -#include "ui_events.hpp" - -namespace lua { - -[[maybe_unused]] static constexpr char kTag[] = "lua_db"; - -static constexpr char kDbIndexMetatable[] = "db_index"; -static constexpr char kDbRecordMetatable[] = "db_record"; -static constexpr char kDbIteratorMetatable[] = "db_iterator"; - -struct LuaIndexInfo { - database::IndexId id; - size_t name_size; - char name_data[]; - - auto name() -> std::string_view { return {name_data, name_size}; } -}; - -static_assert(std::is_trivially_destructible<LuaIndexInfo>()); -static_assert(std::is_trivially_copy_assignable<LuaIndexInfo>()); - -static auto indexes(lua_State* state) -> int { - Bridge* instance = Bridge::Get(state); - - lua_newtable(state); - - auto db = instance->services().database().lock(); - if (!db) { - return 1; - } - - for (const auto& i : db->getIndexes()) { - LuaIndexInfo* data = reinterpret_cast<LuaIndexInfo*>( - lua_newuserdata(state, sizeof(LuaIndexInfo) + i.name.size())); - luaL_setmetatable(state, kDbIndexMetatable); - *data = LuaIndexInfo{ - .id = i.id, - .name_size = i.name.size(), - }; - std::memcpy(data->name_data, i.name.data(), i.name.size()); - lua_rawseti(state, -2, i.id); - } - - return 1; -} - -static auto version(lua_State* L) -> int { - Bridge* instance = Bridge::Get(L); - auto db = instance->services().database().lock(); - if (!db) { - return 0; - } - auto res = db->schemaVersion(); - lua_pushlstring(L, res.data(), res.size()); - return 1; -} - -static auto size(lua_State* L) -> int { - Bridge* instance = Bridge::Get(L); - auto db = instance->services().database().lock(); - if (!db) { - return 0; - } - lua_pushinteger(L, db->sizeOnDiskBytes()); - return 1; -} - -static auto recreate(lua_State* L) -> int { - ESP_LOGI(kTag, "recreate"); - return 0; -} - -static auto update(lua_State* L) -> int { - Bridge* instance = Bridge::Get(L); - auto db = instance->services().database().lock(); - if (!db) { - return 0; - } - - instance->services().bg_worker().Dispatch<void>( - [=]() { db->updateIndexes(); }); - return 0; -} - -static const struct luaL_Reg kDatabaseFuncs[] = { - {"indexes", indexes}, {"version", version}, {"size", size}, - {"recreate", recreate}, {"update", update}, {NULL, NULL}}; - -/* - * Struct to be used as userdata for the Lua representation of database records. - * In order to push these large values into PSRAM as much as possible, memory - * for these is allocated and managed by Lua. This struct must therefore be - * trivially copyable. - */ -struct LuaRecord { - std::variant<database::TrackId, database::IndexKey::Header> contents; - size_t text_size; - char text[]; -}; - -static_assert(std::is_trivially_destructible<LuaRecord>()); -static_assert(std::is_trivially_copy_assignable<LuaRecord>()); - -static auto push_lua_record(lua_State* L, const database::Record& r) -> void { - // Create and init the userdata. - LuaRecord* record = reinterpret_cast<LuaRecord*>( - lua_newuserdata(L, sizeof(LuaRecord) + r.text().size())); - luaL_setmetatable(L, kDbRecordMetatable); - - // Init all the fields - *record = { - .contents = r.contents(), - .text_size = r.text().size(), - }; - - // Copy the string data across. - std::memcpy(record->text, r.text().data(), r.text().size()); -} - -auto db_check_iterator(lua_State* L, int stack_pos) -> database::Iterator* { - database::Iterator* it = *reinterpret_cast<database::Iterator**>( - luaL_checkudata(L, stack_pos, kDbIteratorMetatable)); - return it; -} - -static auto push_iterator(lua_State* state, const database::Iterator& it) - -> void { - database::Iterator** data = reinterpret_cast<database::Iterator**>( - lua_newuserdata(state, sizeof(uintptr_t))); - *data = new database::Iterator(it); - luaL_setmetatable(state, kDbIteratorMetatable); -} - -static auto db_iterate_prev(lua_State* state) -> int { - database::Iterator* it = db_check_iterator(state, 1); - std::optional<database::Record> res = (*it)--; - - if (res) { - push_lua_record(state, *res); - } else { - lua_pushnil(state); - } - - return 1; -} - -static auto db_iterate(lua_State* state) -> int { - database::Iterator* it = db_check_iterator(state, 1); - std::optional<database::Record> res = (*it)++; - - if (res) { - push_lua_record(state, *res); - } else { - lua_pushnil(state); - } - - return 1; -} - -static auto db_iterator_clone(lua_State* state) -> int { - database::Iterator* it = db_check_iterator(state, 1); - push_iterator(state, *it); - return 1; -} - -static auto db_iterator_gc(lua_State* state) -> int { - database::Iterator* it = db_check_iterator(state, 1); - delete it; - return 0; -} - -static const struct luaL_Reg kDbIteratorFuncs[] = {{"next", db_iterate}, - {"prev", db_iterate_prev}, - {"clone", db_iterator_clone}, - {"__call", db_iterate}, - {"__gc", db_iterator_gc}, - {NULL, NULL}}; - -static auto record_text(lua_State* state) -> int { - LuaRecord* data = reinterpret_cast<LuaRecord*>( - luaL_checkudata(state, 1, kDbRecordMetatable)); - lua_pushlstring(state, data->text, data->text_size); - return 1; -} - -static auto record_contents(lua_State* state) -> int { - LuaRecord* data = reinterpret_cast<LuaRecord*>( - luaL_checkudata(state, 1, kDbRecordMetatable)); - - std::visit( - [&](auto&& arg) { - using T = std::decay_t<decltype(arg)>; - if constexpr (std::is_same_v<T, database::TrackId>) { - lua_pushinteger(state, arg); - } else if constexpr (std::is_same_v<T, database::IndexKey::Header>) { - Bridge* bridge = Bridge::Get(state); - auto db = bridge->services().database().lock(); - if (!db) { - lua_pushnil(state); - } else { - push_iterator(state, database::Iterator{db, arg}); - } - } - }, - data->contents); - - return 1; -} - -static const struct luaL_Reg kDbRecordFuncs[] = {{"title", record_text}, - {"contents", record_contents}, - {"__tostring", record_text}, - {NULL, NULL}}; - -static auto index_name(lua_State* state) -> int { - LuaIndexInfo* data = reinterpret_cast<LuaIndexInfo*>( - luaL_checkudata(state, 1, kDbIndexMetatable)); - if (data == NULL) { - return 0; - } - lua_pushlstring(state, data->name_data, data->name_size); - return 1; -} - -static auto index_iter(lua_State* state) -> int { - LuaIndexInfo* data = reinterpret_cast<LuaIndexInfo*>( - luaL_checkudata(state, 1, kDbIndexMetatable)); - if (data == NULL) { - return 0; - } - Bridge* bridge = Bridge::Get(state); - auto db = bridge->services().database().lock(); - if (!db) { - lua_pushnil(state); - } - push_iterator(state, database::Iterator{db, data->id}); - return 1; -} - -static const struct luaL_Reg kDbIndexFuncs[] = {{"name", index_name}, - {"iter", index_iter}, - {"__tostring", index_name}, - {NULL, NULL}}; - -static auto lua_database(lua_State* state) -> int { - // Metatable for indexes - luaL_newmetatable(state, kDbIndexMetatable); - - lua_pushliteral(state, "__index"); - lua_pushvalue(state, -2); - lua_settable(state, -3); // metatable.__index = metatable - - // Add member funcs to the metatable. - luaL_setfuncs(state, kDbIndexFuncs, 0); - - luaL_newmetatable(state, kDbIteratorMetatable); - lua_pushliteral(state, "__index"); - lua_pushvalue(state, -2); - lua_settable(state, -3); // metatable.__index = metatable - luaL_setfuncs(state, kDbIteratorFuncs, 0); - - luaL_newmetatable(state, kDbRecordMetatable); - lua_pushliteral(state, "__index"); - lua_pushvalue(state, -2); - lua_settable(state, -3); // metatable.__index = metatable - luaL_setfuncs(state, kDbRecordFuncs, 0); - - luaL_newlib(state, kDatabaseFuncs); - return 1; -} - -auto RegisterDatabaseModule(lua_State* s) -> void { - luaL_requiref(s, "database", lua_database, true); - lua_pop(s, 1); -} - -} // namespace lua diff --git a/src/lua/lua_queue.cpp b/src/lua/lua_queue.cpp deleted file mode 100644 index dfb820c2..00000000 --- a/src/lua/lua_queue.cpp +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "lua_database.hpp" - -#include <memory> -#include <string> - -#include "lua.hpp" - -#include "esp_log.h" -#include "lauxlib.h" -#include "lua.h" -#include "lvgl.h" - -#include "bridge.hpp" -#include "database.hpp" -#include "event_queue.hpp" -#include "index.hpp" -#include "property.hpp" -#include "service_locator.hpp" -#include "track.hpp" -#include "track_queue.hpp" -#include "ui_events.hpp" - -namespace lua { - -[[maybe_unused]] static constexpr char kTag[] = "lua_queue"; - -static auto queue_add(lua_State* state) -> int { - Bridge* instance = Bridge::Get(state); - - if (lua_isinteger(state, 1)) { - database::TrackId id = luaL_checkinteger(state, 1); - instance->services().bg_worker().Dispatch<void>([=]() { - audio::TrackQueue& queue = instance->services().track_queue(); - queue.append(id); - }); - } else { - database::Iterator* it = db_check_iterator(state, 1); - instance->services().bg_worker().Dispatch<void>([=]() { - audio::TrackQueue& queue = instance->services().track_queue(); - queue.append(database::TrackIterator{*it}); - }); - } - - return 0; -} - -static auto queue_clear(lua_State* state) -> int { - Bridge* instance = Bridge::Get(state); - audio::TrackQueue& queue = instance->services().track_queue(); - queue.clear(); - return 0; -} - -static const struct luaL_Reg kQueueFuncs[] = {{"add", queue_add}, - {"clear", queue_clear}, - {NULL, NULL}}; - -static auto lua_queue(lua_State* state) -> int { - luaL_newlib(state, kQueueFuncs); - return 1; -} - -auto RegisterQueueModule(lua_State* s) -> void { - luaL_requiref(s, "queue", lua_queue, true); - lua_pop(s, 1); -} - -} // namespace lua diff --git a/src/lua/lua_screen.cpp b/src/lua/lua_screen.cpp deleted file mode 100644 index f17f6b1a..00000000 --- a/src/lua/lua_screen.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "lua_screen.hpp" - -#include <memory> -#include <string> - -#include "lua.hpp" - -#include "esp_log.h" -#include "lauxlib.h" -#include "lua.h" -#include "lvgl.h" - -#include "bridge.hpp" -#include "database.hpp" -#include "event_queue.hpp" -#include "index.hpp" -#include "property.hpp" -#include "service_locator.hpp" -#include "track.hpp" -#include "track_queue.hpp" -#include "ui_events.hpp" - -namespace lua { - -static auto screen_new(lua_State* L) -> int { - // o = o or {} - if (lua_gettop(L) != 2) { - lua_settop(L, 1); - lua_newtable(L); - } - // Swap o and self on the stack. - lua_insert(L, 1); - - lua_pushliteral(L, "__index"); - lua_pushvalue(L, 1); - lua_settable(L, 1); // self.__index = self - - lua_setmetatable(L, 1); // setmetatable(o, self) - - return 1; // return o -} - -static auto screen_noop(lua_State* state) -> int { - return 0; -} - -static auto screen_true(lua_State* state) -> int { - lua_pushboolean(state, true); - return 1; -} - -static const struct luaL_Reg kScreenFuncs[] = { - {"new", screen_new}, {"createUi", screen_noop}, - {"onShown", screen_noop}, {"onHidden", screen_noop}, - {"canPop", screen_true}, {NULL, NULL}}; - -static auto lua_screen(lua_State* state) -> int { - luaL_newlib(state, kScreenFuncs); - - lua_pushliteral(state, "__index"); - lua_pushvalue(state, -2); - lua_rawset(state, -3); - - return 1; -} - -auto RegisterScreenModule(lua_State* s) -> void { - luaL_requiref(s, "screen", lua_screen, true); - - lua_pop(s, 1); -} - -} // namespace lua diff --git a/src/lua/lua_theme.cpp b/src/lua/lua_theme.cpp deleted file mode 100644 index 72434d97..00000000 --- a/src/lua/lua_theme.cpp +++ /dev/null @@ -1,89 +0,0 @@ - -/* - * Copyright 2023 ailurux <ailuruxx@gmail.com> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "lua_version.hpp" - -#include <string> - -#include "bridge.hpp" -#include "lua.hpp" - -#include "esp_app_desc.h" -#include "esp_log.h" -#include "lauxlib.h" -#include "lua.h" -#include "lua_thread.hpp" -#include "luavgl.h" -#include "themes.hpp" - -namespace lua { - -static auto set_style(lua_State* L) -> int { - // Get the object and class name from the stack - std::string class_name = luaL_checkstring(L, -1); - lv_obj_t* obj = luavgl_to_obj(L, -2); - if (obj != NULL) { - ui::themes::Theme::instance()->ApplyStyle(obj, class_name); - } - return 0; -} - -static auto set_theme(lua_State* L) -> int { - std::string class_name; - luaL_checktype(L, -1, LUA_TTABLE); - lua_pushnil(L); /* first key */ - while (lua_next(L, -2) != 0) { - /* uses 'key' (at index -2) and 'value' (at index -1) */ - if (lua_type(L, -2) == LUA_TSTRING) { - class_name = lua_tostring(L, -2); - } - if (lua_type(L, -1) == LUA_TTABLE) { - // Nesting - lua_pushnil(L); // First key - while (lua_next(L, -2) != 0) { - // Nesting the second - int selector = -1; - lua_pushnil(L); // First key - while (lua_next(L, -2) != 0) { - int idx = lua_tointeger(L, -2); - if (idx == 1) { - // Selector - selector = lua_tointeger(L, -1); - } else if (idx == 2) { - // Style - lv_style_t* style = luavgl_to_style(L, -1); - if (style == NULL) { - ESP_LOGI("lua_theme", "Style was null or malformed"); - return 0; - } else { - ui::themes::Theme::instance()->AddStyle(class_name, selector, style); - } - } - lua_pop(L, 1); - } - lua_pop(L, 1); - } - } - /* removes 'value'; keeps 'key' for next iteration */ - lua_pop(L, 1); - } - return 0; -} - -static const struct luaL_Reg kThemeFuncs[] = {{"set", set_theme}, {"set_style", set_style}, {NULL, NULL}}; - -static auto lua_theme(lua_State* L) -> int { - luaL_newlib(L, kThemeFuncs); - return 1; -} - -auto RegisterThemeModule(lua_State* L) -> void { - luaL_requiref(L, "theme", lua_theme, true); - lua_pop(L, 1); -} - -} // namespace lua diff --git a/src/lua/lua_thread.cpp b/src/lua/lua_thread.cpp deleted file mode 100644 index dd72e41d..00000000 --- a/src/lua/lua_thread.cpp +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "lua_thread.hpp" - -#include <iostream> -#include <memory> - -#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" - -namespace lua { - -[[maybe_unused]] static constexpr char kTag[] = "lua"; - -class Allocator { - public: - Allocator() : total_allocated_(0) {} - - auto alloc(void* ptr, size_t osize, size_t nsize) -> void* { - total_allocated_ = total_allocated_ - osize + nsize; - // ESP_LOGI(kTag, "lua realloc -> %u KiB", total_allocated_ / 1024); - if (nsize == 0) { - heap_caps_free(ptr); - return NULL; - } else { - return heap_caps_realloc(ptr, nsize, MALLOC_CAP_SPIRAM); - } - } - - private: - size_t total_allocated_; -}; - -static auto lua_alloc(void* ud, void* ptr, size_t osize, size_t nsize) - -> void* { - Allocator* instance = reinterpret_cast<Allocator*>(ud); - return instance->alloc(ptr, osize, nsize); -} - -static int lua_panic(lua_State* L) { - ESP_LOGE(kTag, "!! PANIC !! %s", lua_tostring(L, -1)); - return 0; -} - -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) { - return nullptr; - } - - luaL_openlibs(state); - lua_atpanic(state, lua_panic); - - return new LuaThread(alloc, state); -} - -LuaThread::LuaThread(std::unique_ptr<Allocator>& alloc, lua_State* state) - : alloc_(std::move(alloc)), state_(state) {} - -LuaThread::~LuaThread() { - lua_close(state_); -} - -auto LuaThread::RunScript(const std::string& path) -> bool { - int res = luaL_loadfilex(state_, path.c_str(), NULL); - if (res != LUA_OK) { - return false; - } - CallProtected(state_, 0, 0); - return true; -} - -auto LuaThread::RunString(const std::string& script) -> bool { - int res = luaL_loadstring(state_, script.c_str()); - if (res != LUA_OK) { - return false; - } - CallProtected(state_, 0, 0); - return true; -} - -auto LuaThread::DumpStack() -> void { - int top = lua_gettop(state_); - std::cout << "stack size: " << top << std::endl; - for (size_t i = 1; i <= top; i++) { - std::cout << "[" << i << "]\t" << luaL_typename(state_, i); - switch (lua_type(state_, i)) { - case LUA_TNUMBER: - std::cout << "\t("; - if (lua_isinteger(state_, i)) { - std::cout << lua_tointeger(state_, i); - } else { - std::cout << lua_tonumber(state_, i); - } - std::cout << ")"; - break; - case LUA_TSTRING: - std::cout << "\t('" << lua_tostring(state_, i) << "')"; - break; - case LUA_TBOOLEAN: - std::cout << "\t(" << lua_toboolean(state_, i) << ")"; - break; - case LUA_TNIL: - // Value is implied. - break; - case LUA_TTABLE: - lua_pushnil(state_); - while (lua_next(state_, i) != 0) { - // Keys - std::cout << std::endl << "\t\t" << luaL_typename(state_, -2); - if (lua_type(state_, -2) == LUA_TSTRING) { - std::cout << "\t(" << lua_tostring(state_, -2) << ")"; - } else if (lua_type(state_, -2) == LUA_TNUMBER) { - std::cout << "\t(" << lua_tonumber(state_, -2) << ")"; - } - - // Values - std::cout << "\t\t" << luaL_typename(state_, -1); - if (lua_type(state_, -1) == LUA_TSTRING) { - std::cout << "\t(" << lua_tostring(state_, -1) << ")"; - } else if (lua_type(state_, -1) == LUA_TNUMBER) { - std::cout << "\t(" << lua_tonumber(state_, -1) << ")"; - } - // Pop the value; we don't care about it. Leave the key on the stack - // for the next call to lua_next. - lua_pop(state_, 1); - } - break; - default: - std::cout << "\t(" << lua_topointer(state_, i) << ")"; - break; - } - std::cout << std::endl; - } -} - -static int msg_handler(lua_State* L) { - if (!lua_isstring(L, 1)) { - return 1; - } - - const char* msg = lua_tostring(L, 1); - if (msg == NULL) { /* is error object not a string? */ - if (luaL_callmeta(L, 1, "__tostring") && /* does it have a metamethod */ - lua_type(L, -1) == LUA_TSTRING) /* that produces a string? */ - return 1; /* that is the message */ - else - msg = lua_pushfstring(L, "(error object is a %s value)", - luaL_typename(L, 1)); - } - - /* append a standard traceback */ - luaL_traceback(L, L, msg, 1); - return 1; -} - -auto CallProtected(lua_State* s, int nargs, int nresults) -> int { - int base = lua_gettop(s) - nargs; - // Place our message handler under the function to be called. - lua_pushcfunction(s, msg_handler); - lua_insert(s, base); - - // Invoke the function. - int ret = lua_pcall(s, nargs, nresults, base); - if (ret != LUA_OK) { - events::Ui().Dispatch(ui::OnLuaError{.message = lua_tostring(s, -1)}); - } - - // Clean up our message handler - lua_remove(s, base); - - return ret; -} - -} // namespace lua diff --git a/src/lua/lua_version.cpp b/src/lua/lua_version.cpp deleted file mode 100644 index e5f06bb5..00000000 --- a/src/lua/lua_version.cpp +++ /dev/null @@ -1,68 +0,0 @@ - -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "lua_version.hpp" - -#include <string> - -#include "bridge.hpp" -#include "lua.hpp" - -#include "esp_app_desc.h" -#include "esp_log.h" -#include "lauxlib.h" -#include "lua.h" -#include "lua_thread.hpp" - -namespace lua { - -static auto esp(lua_State* L) -> int { - auto desc = esp_app_get_description(); - lua_pushstring(L, desc->version); - return 1; -} - -static auto samd(lua_State* L) -> int { - Bridge* instance = Bridge::Get(L); - auto& samd = instance->services().samd(); - auto version = samd.Version(); - lua_pushlstring(L, version.data(), version.size()); - return 1; -} - -static auto update_samd(lua_State* L) -> int { - Bridge* instance = Bridge::Get(L); - auto& samd = instance->services().samd(); - samd.ResetToFlashSamd(); - return 0; -} - -static auto collator(lua_State* L) -> int { - Bridge* instance = Bridge::Get(L); - auto& collator = instance->services().collator(); - auto version = collator.Describe().value_or("None"); - lua_pushlstring(L, version.data(), version.size()); - return 1; -} - -static const struct luaL_Reg kVersionFuncs[] = {{"esp", esp}, - {"samd", samd}, - {"collator", collator}, - {"update_samd", update_samd}, - {NULL, NULL}}; - -static auto lua_version(lua_State* L) -> int { - luaL_newlib(L, kVersionFuncs); - return 1; -} - -auto RegisterVersionModule(lua_State* L) -> void { - luaL_requiref(L, "version", lua_version, true); - lua_pop(L, 1); -} - -} // namespace lua diff --git a/src/lua/property.cpp b/src/lua/property.cpp deleted file mode 100644 index 9f4a1908..00000000 --- a/src/lua/property.cpp +++ /dev/null @@ -1,460 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "property.hpp" -#include <sys/_stdint.h> - -#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" -#include "lvgl.h" -#include "memory_resource.hpp" -#include "service_locator.hpp" -#include "track.hpp" -#include "types.hpp" - -namespace lua { - -static const char kPropertyMetatable[] = "property"; -static const char kFunctionMetatable[] = "c_func"; -static const char kBindingMetatable[] = "binding"; -static const char kBindingsTable[] = "bindings"; -static const char kBinderKey[] = "binder"; - -auto Binding::get(lua_State* L, int idx) -> Binding* { - return reinterpret_cast<Binding*>(luaL_testudata(L, idx, kBindingMetatable)); -} - -auto Binding::apply(lua_State* L, int idx) -> bool { - Binding* b = get(L, idx); - if (b->dirty && b->active) { - b->dirty = false; - // The binding needs to be reapplied. Push the Lua callback, then its arg. - lua_getiuservalue(L, idx, 1); - b->property->pushValue(*L); - - // Invoke the callback. - return CallProtected(L, 1, 0) == LUA_OK; - } - return true; -} - -static auto check_property(lua_State* state) -> Property* { - void* data = luaL_checkudata(state, 1, kPropertyMetatable); - luaL_argcheck(state, data != NULL, 1, "`property` expected"); - return *reinterpret_cast<Property**>(data); -} - -static auto property_get(lua_State* state) -> int { - Property* p = check_property(state); - p->pushValue(*state); - return 1; -} - -static auto property_set(lua_State* state) -> int { - Property* p = check_property(state); - luaL_argcheck(state, p->isTwoWay(), 1, "property is read-only"); - bool valid = p->popValue(*state); - lua_pushboolean(state, valid); - return 1; -} - -static auto property_bind(lua_State* state) -> int { - Property* p = check_property(state); - luaL_checktype(state, 2, LUA_TFUNCTION); - - // Fetch the table of live bindings. - lua_pushstring(state, kBindingsTable); - lua_gettable(state, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] - - // Create the userdata holding the new binding's metadata. - Binding* binding = - reinterpret_cast<Binding*>(lua_newuserdatauv(state, sizeof(Binding), 1)); - *binding = Binding{.property = p, .active = true, .dirty = true}; - luaL_setmetatable(state, kBindingMetatable); - - // Associate the callback function with the new binding. - lua_pushvalue(state, 2); - lua_setiuservalue(state, -2, 1); - - // Put a reference to the binding into the bindings table, so that we can - // look it up later. - lua_pushvalue(state, -1); - int binding_ref = luaL_ref(state, 3); - - // Tell the property about the new binding. This was also perform the initial - // bind. - p->addLuaBinding(state, binding_ref); - - // Return the only remaining strong reference to the new Binding. - return 1; -} - -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); - lua_gettable(state, LUA_REGISTRYINDEX); - PropertyBindings* binder = - reinterpret_cast<PropertyBindings*>(lua_touserdata(state, -1)); - - size_t* index = - reinterpret_cast<size_t*>(luaL_checkudata(state, 1, kFunctionMetatable)); - const LuaFunction& fn = binder->GetFunction(*index); - - // Ensure the C++ function is called with a clean stack; we don't want it to - // see the index we just used. - lua_remove(state, 1); - - return std::invoke(fn, state); -} - -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(L, kPropertyMetatable); - - 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(L, kPropertyBindingFuncs, 0); - - // We've finished setting up the metatable, so pop it. - lua_pop(L, 1); - - // Create the metatable responsible for each Binding. This metatable is empty - // as it's only used for identification. - luaL_newmetatable(L, kBindingMetatable); - lua_pop(L, 1); - - // Create a weak table in the registry to hold live bindings. - lua_pushstring(L, kBindingsTable); - lua_newtable(L); // bindings = {} - - // Metatable for the weak table. Values are weak. - 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(L, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings - - // Create the metatable for C++ functions. - luaL_newmetatable(L, kFunctionMetatable); - - lua_pushliteral(L, "__call"); - lua_pushcfunction(L, generic_function_cb); - lua_settable(L, -3); // metatable.__call = metatable - - lua_pop(L, 1); // Clean up the function metatable -} - -auto PropertyBindings::Register(lua_State* s, Property* prop) -> void { - Property** data = - reinterpret_cast<Property**>(lua_newuserdata(s, sizeof(uintptr_t))); - *data = prop; - - luaL_setmetatable(s, kPropertyMetatable); -} - -auto PropertyBindings::Register(lua_State* s, LuaFunction fn) -> void { - size_t* index = reinterpret_cast<size_t*>(lua_newuserdata(s, sizeof(size_t))); - *index = functions_.size(); - functions_.push_back(fn); - - luaL_setmetatable(s, kFunctionMetatable); -} - -auto PropertyBindings::GetFunction(size_t i) -> const LuaFunction& { - assert(i < functions_.size()); - return functions_[i]; -}; - -template <class... Ts> -inline constexpr bool always_false_v = false; - -Property::Property(const LuaValue& val) - : value_(memory::SpiRamAllocator<LuaValue>().new_object<LuaValue>(val)), - cb_(), - bindings_(&memory::kSpiRamResource) {} - -Property::Property(const LuaValue& val, - std::function<bool(const LuaValue& val)> cb) - : value_(memory::SpiRamAllocator<LuaValue>().new_object<LuaValue>(val)), - cb_(cb), - bindings_(&memory::kSpiRamResource) {} - -auto Property::setDirect(const LuaValue& val) -> void { - *value_ = val; - reapplyAll(); -} - -auto Property::set(const LuaValue& val) -> bool { - if (cb_ && !std::invoke(*cb_, val)) { - return false; - } - setDirect(val); - return true; -} - -static auto pushTagValue(lua_State* L, const database::TagValue& val) -> void { - std::visit( - [&](auto&& arg) { - using T = std::decay_t<decltype(arg)>; - if constexpr (std::is_same_v<T, std::pmr::string>) { - lua_pushlstring(L, arg.data(), arg.size()); - } else if constexpr (std::is_same_v< - T, std::span<const std::pmr::string>>) { - lua_createtable(L, 0, arg.size()); - for (const auto& i : arg) { - lua_pushlstring(L, i.data(), i.size()); - lua_pushboolean(L, true); - lua_rawset(L, -3); - } - } else if constexpr (std::is_same_v<T, uint32_t>) { - lua_pushinteger(L, arg); - } else { - lua_pushnil(L); - } - }, - val); -} - -static void pushTrack(lua_State* L, const audio::TrackInfo& track) { - lua_newtable(L); - - for (const auto& tag : track.tags->allPresent()) { - lua_pushstring(L, database::tagName(tag).c_str()); - pushTagValue(L, track.tags->get(tag)); - lua_settable(L, -3); - } - - if (track.duration) { - lua_pushliteral(L, "duration"); - lua_pushinteger(L, track.duration.value()); - lua_settable(L, -3); - } - - if (track.bitrate_kbps) { - lua_pushliteral(L, "bitrate_kbps"); - lua_pushinteger(L, track.bitrate_kbps.value()); - lua_settable(L, -3); - } - - lua_pushliteral(L, "encoding"); - lua_pushstring(L, codecs::StreamTypeToString(track.encoding).c_str()); - lua_settable(L, -3); -} - -static void pushDevice(lua_State* L, const drivers::bluetooth::Device& dev) { - lua_createtable(L, 0, 4); - - lua_pushliteral(L, "address"); - auto* mac = reinterpret_cast<drivers::bluetooth::mac_addr_t*>( - lua_newuserdata(L, sizeof(drivers::bluetooth::mac_addr_t))); - *mac = dev.address; - lua_rawset(L, -3); - - // What I just did there was perfectly safe. Look, I can prove it: - static_assert( - std::is_trivially_copy_assignable<drivers::bluetooth::mac_addr_t>()); - static_assert( - std::is_trivially_destructible<drivers::bluetooth::mac_addr_t>()); - - lua_pushliteral(L, "name"); - lua_pushlstring(L, dev.name.data(), dev.name.size()); - lua_rawset(L, -3); - - // FIXME: This field deserves a little more structure. - lua_pushliteral(L, "class"); - lua_pushinteger(L, dev.class_of_device); - lua_rawset(L, -3); - - lua_pushliteral(L, "signal_strength"); - lua_pushinteger(L, dev.signal_strength); - lua_rawset(L, -3); -} - -auto Property::pushValue(lua_State& s) -> int { - std::visit( - [&](auto&& arg) { - using T = std::decay_t<decltype(arg)>; - if constexpr (std::is_same_v<T, std::monostate>) { - lua_pushnil(&s); - } else if constexpr (std::is_same_v<T, int>) { - lua_pushinteger(&s, arg); - } else if constexpr (std::is_same_v<T, bool>) { - lua_pushboolean(&s, arg); - } else if constexpr (std::is_same_v<T, std::string>) { - lua_pushstring(&s, arg.c_str()); - } else if constexpr (std::is_same_v<T, audio::TrackInfo>) { - pushTrack(&s, arg); - } else if constexpr (std::is_same_v<T, drivers::bluetooth::Device>) { - pushDevice(&s, arg); - } else if constexpr (std::is_same_v< - T, std::vector<drivers::bluetooth::Device>>) { - lua_createtable(&s, arg.size(), 0); - size_t i = 1; - for (const auto& dev : arg) { - pushDevice(&s, dev); - lua_rawseti(&s, -2, i++); - } - } else { - static_assert(always_false_v<T>, "PushValue missing type"); - } - }, - *value_); - return 1; -} - -auto popRichType(lua_State* L) -> LuaValue { - lua_pushliteral(L, "address"); - lua_gettable(L, -2); - - if (lua_isuserdata(L, -1)) { - // This must be a bt device! - drivers::bluetooth::mac_addr_t mac = - *reinterpret_cast<drivers::bluetooth::mac_addr_t*>( - lua_touserdata(L, -1)); - lua_pop(L, 1); - - lua_pushliteral(L, "name"); - lua_gettable(L, -2); - - std::pmr::string name = lua_tostring(L, -1); - lua_pop(L, 1); - - return drivers::bluetooth::Device{ - .address = mac, - .name = name, - .class_of_device = 0, - .signal_strength = 0, - }; - } - - return std::monostate{}; -} - -auto Property::popValue(lua_State& s) -> bool { - LuaValue new_val; - switch (lua_type(&s, 2)) { - case LUA_TNIL: - new_val = std::monostate{}; - break; - case LUA_TNUMBER: - if (lua_isinteger(&s, 2)) { - new_val = lua_tointeger(&s, 2); - } else { - new_val = static_cast<lua_Integer>(std::round(lua_tonumber(&s, 2))); - } - break; - case LUA_TBOOLEAN: - new_val = static_cast<bool>(lua_toboolean(&s, 2)); - break; - case LUA_TSTRING: - new_val = lua_tostring(&s, 2); - break; - default: - if (lua_istable(&s, 2)) { - new_val = popRichType(&s); - if (std::holds_alternative<std::monostate>(new_val)) { - return false; - } - } else { - return false; - } - } - - return set(new_val); -} - -auto Property::reapplyAll() -> void { - for (int i = bindings_.size() - 1; i >= 0; i--) { - auto& b = bindings_[i]; - if (!applySingle(b.first, b.second, true)) { - // Remove the binding if we weren't able to apply it. This is usually due - // to the binding getting GC'd. - bindings_.erase(bindings_.begin() + i); - } - } -} - -auto Property::applySingle(lua_State* L, int ref, bool mark_dirty) -> bool { - int top = lua_gettop(L); - - // Push the table of bindings. - lua_pushstring(L, kBindingsTable); - lua_gettable(L, LUA_REGISTRYINDEX); - - // Resolve the reference. - int type = lua_rawgeti(L, -1, ref); - if (type == LUA_TNIL) { - lua_settop(L, top); - return false; - } - - // Defensively check that the ref was actually for a Binding. - Binding* b = Binding::get(L, -1); - if (!b) { - lua_settop(L, top); - return false; - } - - if (mark_dirty) { - b->dirty = true; - } - - bool ret = Binding::apply(L, -1); - lua_settop(L, top); - return ret; -} - -auto Property::addLuaBinding(lua_State* state, int ref) -> void { - bindings_.push_back({state, ref}); - applySingle(state, ref, true); -} - -} // namespace lua diff --git a/src/lua/registry.cpp b/src/lua/registry.cpp deleted file mode 100644 index a6487858..00000000 --- a/src/lua/registry.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2023 jacqueline <me@jacqueline.id.au> - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include "lua_registry.hpp" - -#include <iostream> -#include <memory> - -#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" - -namespace lua { - -[[maybe_unused]] static constexpr char kTag[] = "lua"; - -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 |
