/* * Copyright 2023 jacqueline * * SPDX-License-Identifier: GPL-3.0-only */ #include "lua_database.hpp" #include #include #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"; 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()) { database::IndexInfo** data = reinterpret_cast( lua_newuserdata(state, sizeof(uintptr_t))); luaL_setmetatable(state, kDbIndexMetatable); *data = new database::IndexInfo{i}; lua_rawseti(state, -2, i.id); } return 1; } static const struct luaL_Reg kDatabaseFuncs[] = {{"indexes", indexes}, {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 { database::TrackId id_or_zero; database::IndexKey::Header header_at_next_depth; size_t text_size; char text[]; }; static_assert(std::is_trivially_copyable_v == true); static auto push_lua_record(lua_State* L, const database::IndexRecord& r) -> void { // Bake out the text into something concrete. auto text = r.text().value_or(""); // Create and init the userdata. LuaRecord* record = reinterpret_cast( lua_newuserdata(L, sizeof(LuaRecord) + text.size())); luaL_setmetatable(L, kDbRecordMetatable); // Init all the fields *record = { .id_or_zero = r.track().value_or(0), .header_at_next_depth = r.ExpandHeader(), .text_size = text.size(), }; // Copy the string data across. std::memcpy(record->text, text.data(), text.size()); } auto db_check_iterator(lua_State* L, int stack_pos) -> database::Iterator* { database::Iterator* it = *reinterpret_cast( luaL_checkudata(L, stack_pos, kDbIteratorMetatable)); return it; } static auto push_iterator(lua_State* state, std::variant val) -> void { Bridge* instance = Bridge::Get(state); database::Iterator** data = reinterpret_cast( lua_newuserdata(state, sizeof(uintptr_t))); std::visit( [&](auto&& arg) { using T = std::decay_t; if constexpr (std::is_same_v) { *data = new database::Iterator(*arg); } else { *data = new database::Iterator(instance->services().database(), arg); } }, val); luaL_setmetatable(state, kDbIteratorMetatable); } static auto db_iterate(lua_State* state) -> int { database::Iterator* it = db_check_iterator(state, 1); luaL_checktype(state, 2, LUA_TFUNCTION); int callback_ref = luaL_ref(state, LUA_REGISTRYINDEX); it->Next([=](std::optional res) { events::Ui().RunOnTask([=]() { lua_rawgeti(state, LUA_REGISTRYINDEX, callback_ref); if (res) { push_lua_record(state, *res); } else { lua_pushnil(state); } CallProtected(state, 1, 0); luaL_unref(state, LUA_REGISTRYINDEX, callback_ref); }); }); return 0; } 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}, {"clone", db_iterator_clone}, {"__gc", db_iterator_gc}, {NULL, NULL}}; static auto record_text(lua_State* state) -> int { LuaRecord* data = reinterpret_cast( 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( luaL_checkudata(state, 1, kDbRecordMetatable)); if (data->id_or_zero) { lua_pushinteger(state, data->id_or_zero); } else { std::string p = database::EncodeIndexPrefix(data->header_at_next_depth); push_iterator(state, database::Continuation{ .prefix = {p.data(), p.size()}, .start_key = {p.data(), p.size()}, .forward = true, .was_prev_forward = true, .page_size = 1, }); } 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 { database::IndexInfo** data = reinterpret_cast( luaL_checkudata(state, 1, kDbIndexMetatable)); if (data == NULL) { return 0; } lua_pushstring(state, (*data)->name.c_str()); return 1; } static auto index_iter(lua_State* state) -> int { database::IndexInfo** data = reinterpret_cast( luaL_checkudata(state, 1, kDbIndexMetatable)); if (data == NULL) { return 0; } push_iterator(state, **data); return 1; } static auto index_gc(lua_State* state) -> int { database::IndexInfo** data = reinterpret_cast( luaL_checkudata(state, 1, kDbIndexMetatable)); if (data != NULL) { delete *data; } return 0; } static const struct luaL_Reg kDbIndexFuncs[] = {{"name", index_name}, {"iter", index_iter}, {"__tostring", index_name}, {"__gc", index_gc}, {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