diff options
| author | jacqueline <me@jacqueline.id.au> | 2023-11-14 13:20:04 +1100 |
|---|---|---|
| committer | jacqueline <me@jacqueline.id.au> | 2023-11-14 13:20:04 +1100 |
| commit | 71ed09a6f70901c9097973a44b24d6a6ced2834f (patch) | |
| tree | 3d02e4e180cd0a5caa1185eba89181607c4bccb9 /src/lua/property.cpp | |
| parent | 8a0a167adbf3d9b6f8b6f16aaf20ca39ad5549de (diff) | |
| download | tangara-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.cpp | 196 |
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 |
