summaryrefslogtreecommitdiff
path: root/src/lua/property.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/lua/property.cpp')
-rw-r--r--src/lua/property.cpp460
1 files changed, 0 insertions, 460 deletions
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