summaryrefslogtreecommitdiff
path: root/src/lua/property.cpp
diff options
context:
space:
mode:
authorjacqueline <me@jacqueline.id.au>2023-11-14 13:20:04 +1100
committerjacqueline <me@jacqueline.id.au>2023-11-14 13:20:04 +1100
commit71ed09a6f70901c9097973a44b24d6a6ced2834f (patch)
tree3d02e4e180cd0a5caa1185eba89181607c4bccb9 /src/lua/property.cpp
parent8a0a167adbf3d9b6f8b6f16aaf20ca39ad5549de (diff)
downloadtangara-fw-71ed09a6f70901c9097973a44b24d6a6ced2834f.tar.gz
Add two-way databinding for lua, and flesh out the lua statusbar
Diffstat (limited to 'src/lua/property.cpp')
-rw-r--r--src/lua/property.cpp196
1 files changed, 196 insertions, 0 deletions
diff --git a/src/lua/property.cpp b/src/lua/property.cpp
new file mode 100644
index 00000000..3130077b
--- /dev/null
+++ b/src/lua/property.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2023 jacqueline <me@jacqueline.id.au>
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include "property.hpp"
+
+#include <memory>
+#include <string>
+
+#include "lua.h"
+#include "lua.hpp"
+#include "lvgl.h"
+#include "service_locator.hpp"
+
+namespace lua {
+
+static const char kMetatableName[] = "property";
+static const char kBindingsTable[] = "bindings";
+
+static auto check_property(lua_State* state) -> Property* {
+ void* data = luaL_checkudata(state, 1, kMetatableName);
+ 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);
+
+ // Copy the function, as we need to invoke it then store our reference.
+ lua_pushvalue(state, 2);
+ // ...and another copy, since we return the original closure.
+ lua_pushvalue(state, 2);
+
+ // FIXME: This should ideally be lua_pcall, for safety.
+ p->PushValue(*state);
+ lua_call(state, 1, 0); // Invoke the initial binding.
+
+ lua_pushstring(state, kBindingsTable);
+ lua_gettable(state, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable]
+ lua_insert(state, -2); // Move bindings to the bottom, with fn above.
+ int ref = luaL_ref(state, -2); // bindings[ref] = fn
+
+ p->AddLuaBinding(state, ref);
+
+ // Pop the bindings table, leaving one of the copiesw of the callback fn at
+ // the top of the stack.
+ lua_pop(state, 1);
+
+ return 1;
+}
+
+static const struct luaL_Reg kPropertyBindingFuncs[] = {{"get", property_get},
+ {"set", property_set},
+ {"bind", property_bind},
+ {NULL, NULL}};
+
+PropertyBindings::PropertyBindings(lua_State& s) {
+ // Create the metatable responsible for the Property API.
+ luaL_newmetatable(&s, kMetatableName);
+
+ lua_pushliteral(&s, "__index");
+ lua_pushvalue(&s, -2);
+ lua_settable(&s, -3); // metatable.__index = metatable
+
+ // Add our binding funcs (get, set, bind) to the metatable.
+ luaL_setfuncs(&s, kPropertyBindingFuncs, 0);
+
+ // Create a weak table in the registry to hold live bindings.
+ lua_pushstring(&s, kBindingsTable);
+ lua_newtable(&s); // bindings = {}
+
+ // Metatable for the weak table. Values are weak.
+ lua_newtable(&s); // meta = {}
+ lua_pushliteral(&s, "__mode");
+ lua_pushliteral(&s, "v");
+ lua_settable(&s, -3); // meta.__mode='v'
+ lua_setmetatable(&s, -2); // setmetatable(bindings, meta)
+
+ lua_settable(&s, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable] = bindings
+}
+
+auto PropertyBindings::Register(lua_State* s, Property* prop) -> void {
+ Property** data =
+ reinterpret_cast<Property**>(lua_newuserdata(s, sizeof(Property*)));
+ *data = prop;
+
+ luaL_setmetatable(s, kMetatableName);
+}
+
+template <class... Ts>
+inline constexpr bool always_false_v = false;
+
+Property::Property(const LuaValue& val) : value_(val), cb_() {}
+
+Property::Property(const LuaValue& val,
+ std::function<bool(const LuaValue& val)> cb)
+ : value_(val), cb_(cb) {}
+
+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, float>) {
+ lua_pushnumber(&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 {
+ static_assert(always_false_v<T>, "PushValue missing type");
+ }
+ },
+ value_);
+ return 1;
+}
+
+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 = lua_tonumber(&s, 2);
+ }
+ break;
+ case LUA_TBOOLEAN:
+ new_val = lua_toboolean(&s, 2);
+ break;
+ case LUA_TSTRING:
+ new_val = lua_tostring(&s, 2);
+ break;
+ default:
+ return false;
+ }
+
+ if (cb_ && std::invoke(*cb_, new_val)) {
+ Update(new_val);
+ return true;
+ }
+ return false;
+}
+
+auto Property::Update(const LuaValue& v) -> void {
+ value_ = v;
+
+ for (int i = bindings_.size() - 1; i >= 0; i--) {
+ auto& b = bindings_[i];
+
+ lua_pushstring(b.first, kBindingsTable);
+ lua_gettable(b.first, LUA_REGISTRYINDEX); // REGISTRY[kBindingsTable]
+ int type = lua_rawgeti(b.first, -1, b.second); // push bindings[i]
+
+ // Has closure has been GCed?
+ if (type == LUA_TNIL) {
+ // Clean up after ourselves.
+ lua_pop(b.first, 1);
+ // Remove the binding.
+ bindings_.erase(bindings_.begin() + i);
+ continue;
+ }
+
+ PushValue(*b.first); // push the argument
+ lua_call(b.first, 1, 0); // invoke the closure
+ }
+}
+
+auto Property::AddLuaBinding(lua_State* state, int ref) -> void {
+ bindings_.push_back({state, ref});
+}
+
+} // namespace lua