Fork of Tangara with customizations
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tangara-fw/src/lua/lua_database.cpp

260 lines
7.8 KiB

/*
* 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 "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<database::IndexInfo**>(
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<LuaRecord> == 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<LuaRecord*>(
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<database::Iterator**>(
luaL_checkudata(L, stack_pos, kDbIteratorMetatable));
return it;
}
static auto push_iterator(lua_State* state,
std::variant<database::Iterator*,
database::Continuation,
database::IndexInfo> val) -> void {
Bridge* instance = Bridge::Get(state);
database::Iterator** data = reinterpret_cast<database::Iterator**>(
lua_newuserdata(state, sizeof(uintptr_t)));
std::visit(
[&](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, database::Iterator*>) {
*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<database::IndexRecord> 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<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));
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<database::IndexInfo**>(
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<database::IndexInfo**>(
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<database::IndexInfo**>(
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