From 8a0a167adbf3d9b6f8b6f16aaf20ca39ad5549de Mon Sep 17 00:00:00 2001 From: jacqueline Date: Sun, 12 Nov 2023 19:14:09 +1100 Subject: Convert the main menu screen to lua lol --- src/lua/CMakeLists.txt | 9 ++++ src/lua/bridge.cpp | 96 +++++++++++++++++++++++++++++++++++++++++ src/lua/include/bridge.hpp | 29 +++++++++++++ src/lua/include/lua_thread.hpp | 38 ++++++++++++++++ src/lua/lua_thread.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 270 insertions(+) create mode 100644 src/lua/CMakeLists.txt create mode 100644 src/lua/bridge.cpp create mode 100644 src/lua/include/bridge.hpp create mode 100644 src/lua/include/lua_thread.hpp create mode 100644 src/lua/lua_thread.cpp (limited to 'src/lua') diff --git a/src/lua/CMakeLists.txt b/src/lua/CMakeLists.txt new file mode 100644 index 00000000..a2dd8739 --- /dev/null +++ b/src/lua/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2023 jacqueline +# +# SPDX-License-Identifier: GPL-3.0-only + +idf_component_register( + SRCS "lua_thread.cpp" "bridge.cpp" + INCLUDE_DIRS "include" + REQUIRES "drivers" "lvgl" "tinyfsm" "events" "system_fsm" "database" "esp_timer" "battery" "esp-idf-lua" "luavgl") +target_compile_options(${COMPONENT_LIB} PRIVATE ${EXTRA_WARNINGS}) diff --git a/src/lua/bridge.cpp b/src/lua/bridge.cpp new file mode 100644 index 00000000..acc64c31 --- /dev/null +++ b/src/lua/bridge.cpp @@ -0,0 +1,96 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "bridge.hpp" + +#include +#include + +#include "esp_log.h" +#include "event_queue.hpp" +#include "lua.h" +#include "lua.hpp" +#include "lvgl.h" +#include "service_locator.hpp" +#include "ui_events.hpp" + +namespace lua { + +[[maybe_unused]] static constexpr char kTag[] = "lua_bridge"; +static constexpr char kBridgeKey[] = "bridge"; + +static auto open_settings_fn(lua_State* state) -> int { + events::Ui().Dispatch(ui::internal::ShowSettingsPage{ + .page = ui::internal::ShowSettingsPage::Page::kRoot}); + return 0; +} + +static auto open_now_playing_fn(lua_State* state) -> int { + events::Ui().Dispatch(ui::internal::ShowNowPlaying{}); + return 0; +} + +static auto open_browse_fn(lua_State* state) -> int { + int index = luaL_checkinteger(state, 1); + events::Ui().Dispatch(ui::internal::IndexSelected{ + .id = static_cast(index), + }); + return 0; +} + +static const struct luaL_Reg kLegacyUiFuncs[] = { + {"open_settings", open_settings_fn}, + {"open_now_playing", open_now_playing_fn}, + {"open_browse", open_browse_fn}, + {NULL, NULL}}; + +static auto lua_legacy_ui(lua_State* state) -> int { + luaL_newlib(state, kLegacyUiFuncs); + return 1; +} + +static auto get_indexes(lua_State* state) -> int { + lua_pushstring(state, kBridgeKey); + lua_gettable(state, LUA_REGISTRYINDEX); + Bridge* instance = reinterpret_cast(lua_touserdata(state, -1)); + + lua_newtable(state); + + auto db = instance->services().database().lock(); + if (!db) { + return 1; + } + + for (const auto& i : db->GetIndexes()) { + lua_pushstring(state, i.name.c_str()); + lua_rawseti(state, -2, i.id); + } + + return 1; +} + +static const struct luaL_Reg kDatabaseFuncs[] = {{"get_indexes", get_indexes}, + {NULL, NULL}}; + +static auto lua_database(lua_State* state) -> int { + luaL_newlib(state, kDatabaseFuncs); + return 1; +} + +Bridge::Bridge(system_fsm::ServiceLocator& services, lua_State& s) + : services_(services), state_(s) { + lua_pushstring(&s, kBridgeKey); + lua_pushlightuserdata(&s, this); + lua_settable(&s, LUA_REGISTRYINDEX); + + luaL_requiref(&s, "legacy_ui", lua_legacy_ui, true); + lua_pop(&s, 1); + + luaL_requiref(&s, "database", lua_database, true); + lua_pop(&s, 1); +} + +} // namespace lua diff --git a/src/lua/include/bridge.hpp b/src/lua/include/bridge.hpp new file mode 100644 index 00000000..059d0604 --- /dev/null +++ b/src/lua/include/bridge.hpp @@ -0,0 +1,29 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +#include "lua.hpp" +#include "lvgl.h" +#include "service_locator.hpp" + +namespace lua { + +class Bridge { + public: + Bridge(system_fsm::ServiceLocator&, lua_State& s); + + system_fsm::ServiceLocator& services() { return services_; } + + private: + system_fsm::ServiceLocator& services_; + lua_State& state_; +}; + +} // namespace lua diff --git a/src/lua/include/lua_thread.hpp b/src/lua/include/lua_thread.hpp new file mode 100644 index 00000000..381b1bdb --- /dev/null +++ b/src/lua/include/lua_thread.hpp @@ -0,0 +1,38 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#pragma once + +#include +#include + +#include "lua.hpp" +#include "lvgl.h" + +#include "bridge.hpp" +#include "service_locator.hpp" + +namespace lua { + +class Allocator; + +class LuaThread { + public: + static auto Start(system_fsm::ServiceLocator&, lv_obj_t* lvgl_root = nullptr) + -> LuaThread*; + ~LuaThread(); + + auto RunScript(const std::string& path) -> bool; + + private: + LuaThread(std::unique_ptr&, std::unique_ptr&, lua_State*); + + std::unique_ptr alloc_; + std::unique_ptr bridge_; + lua_State* state_; +}; + +} // namespace lua diff --git a/src/lua/lua_thread.cpp b/src/lua/lua_thread.cpp new file mode 100644 index 00000000..cb7066a5 --- /dev/null +++ b/src/lua/lua_thread.cpp @@ -0,0 +1,98 @@ +/* + * Copyright 2023 jacqueline + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "lua_thread.hpp" +#include + +#include "esp_heap_caps.h" +#include "esp_log.h" +#include "lua.h" +#include "lua.hpp" +#include "luavgl.h" +#include "service_locator.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(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, lv_obj_t* lvgl_root) + -> LuaThread* { + auto alloc = std::make_unique(); + lua_State* state = lua_newstate(lua_alloc, alloc.get()); + if (!state) { + return nullptr; + } + + luaL_openlibs(state); + lua_atpanic(state, lua_panic); + + auto bridge = std::make_unique(services, *state); + + // FIXME: luavgl init should probably be a part of the bridge. + if (lvgl_root) { + luavgl_set_root(state, lvgl_root); + luaL_requiref(state, "lvgl", luaopen_lvgl, true); + lua_pop(state, 1); + } + + return new LuaThread(alloc, bridge, state); +} + +LuaThread::LuaThread(std::unique_ptr& alloc, + std::unique_ptr& bridge, + lua_State* state) + : alloc_(std::move(alloc)), bridge_(std::move(bridge)), 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; + } + res = lua_pcall(state_, 0, 0, 0); + if (res) { + const char* msg = lua_tostring(state_, -1); + lua_writestring(msg, strlen(msg)); + lua_writeline(); + lua_pop(state_, 1); + } + return true; +} + +} // namespace lua -- cgit v1.2.3