summaryrefslogtreecommitdiff
path: root/src/lua
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2024-05-02 19:12:26 +1000
committerjacqueline <me@jacqueline.id.au>2024-05-02 19:12:26 +1000
commit1573a8c4cde1cd9528b422b2dcc598e37ffe94a7 (patch)
treed162822b8fd7054f81bace0c7a65ab4d5e6f93ef /src/lua
parenta231fd1c8afedbeb14b0bc77d76bad61db986059 (diff)
downloadtangara-fw-1573a8c4cde1cd9528b422b2dcc598e37ffe94a7.tar.gz
WIP merge cyclically dependent components into one big component
Diffstat (limited to 'src/lua')
-rw-r--r--src/lua/CMakeLists.txt13
-rw-r--r--src/lua/bridge.cpp143
-rw-r--r--src/lua/include/bridge.hpp54
-rw-r--r--src/lua/include/lua_controls.hpp15
-rw-r--r--src/lua/include/lua_database.hpp19
-rw-r--r--src/lua/include/lua_queue.hpp15
-rw-r--r--src/lua/include/lua_registry.hpp51
-rw-r--r--src/lua/include/lua_screen.hpp15
-rw-r--r--src/lua/include/lua_theme.hpp15
-rw-r--r--src/lua/include/lua_thread.hpp44
-rw-r--r--src/lua/include/lua_version.hpp15
-rw-r--r--src/lua/include/property.hpp107
-rw-r--r--src/lua/lua_controls.cpp58
-rw-r--r--src/lua/lua_database.cpp306
-rw-r--r--src/lua/lua_queue.cpp74
-rw-r--r--src/lua/lua_screen.cpp79
-rw-r--r--src/lua/lua_theme.cpp89
-rw-r--r--src/lua/lua_thread.cpp187
-rw-r--r--src/lua/lua_version.cpp68
-rw-r--r--src/lua/property.cpp460
-rw-r--r--src/lua/registry.cpp73
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