From fc1430af9503a2fe59b095d3621572a07ec13ce2 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 29 Jan 2023 17:06:09 +0100 Subject: [PATCH 01/11] Move implementation of UI Content to Lua --- apps/openmw/mwlua/uibindings.cpp | 42 +---- .../openmw_test_suite/lua/test_ui_content.cpp | 92 +++++++---- components/CMakeLists.txt | 1 + components/lua_ui/content.cpp | 111 +++----------- components/lua_ui/content.hpp | 124 +++++++++++---- components/lua_ui/content.lua | 144 ++++++++++++++++++ components/lua_ui/element.cpp | 4 +- 7 files changed, 326 insertions(+), 192 deletions(-) create mode 100644 components/lua_ui/content.lua diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 7095b2df63..e113178dd4 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -97,46 +97,6 @@ namespace MWLua sol::table initUserInterfacePackage(const Context& context) { - auto uiContent = context.mLua->sol().new_usertype("UiContent"); - uiContent[sol::meta_function::length] = [](const LuaUi::Content& content) { return content.size(); }; - uiContent[sol::meta_function::index] - = sol::overload([](const LuaUi::Content& content, size_t index) { return content.at(fromLuaIndex(index)); }, - [](const LuaUi::Content& content, std::string_view name) { return content.at(name); }); - uiContent[sol::meta_function::new_index] - = sol::overload([](LuaUi::Content& content, size_t index, - const sol::table& table) { content.assign(fromLuaIndex(index), table); }, - [](LuaUi::Content& content, size_t index, sol::nil_t nil) { content.remove(fromLuaIndex(index)); }, - [](LuaUi::Content& content, std::string_view name, const sol::table& table) { - content.assign(name, table); - }, - [](LuaUi::Content& content, std::string_view name, sol::nil_t nil) { content.remove(name); }); - uiContent["insert"] = [](LuaUi::Content& content, size_t index, const sol::table& table) { - content.insert(fromLuaIndex(index), table); - }; - uiContent["add"] - = [](LuaUi::Content& content, const sol::table& table) { content.insert(content.size(), table); }; - uiContent["indexOf"] = [](const LuaUi::Content& content, const sol::table& table) -> sol::optional { - size_t index = content.indexOf(table); - if (index < content.size()) - return toLuaIndex(index); - else - return sol::nullopt; - }; - { - auto pairs = [](const LuaUi::Content& content) { - auto next - = [](const LuaUi::Content& content, size_t i) -> sol::optional> { - if (i < content.size()) - return std::make_tuple(i + 1, content.at(i)); - else - return sol::nullopt; - }; - return std::make_tuple(next, content, 0); - }; - uiContent[sol::meta_function::ipairs] = pairs; - uiContent[sol::meta_function::pairs] = pairs; - } - auto element = context.mLua->sol().new_usertype("Element"); element["layout"] = sol::property([](LuaUi::Element& element) { return element.mLayout; }, [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); @@ -181,7 +141,7 @@ namespace MWLua luaManager->addAction([wm, obj = obj.as()] { wm->setConsoleSelectedObject(obj.ptr()); }); } }; - api["content"] = [](const sol::table& table) { return LuaUi::Content(table); }; + api["content"] = LuaUi::Content::makeFactory(context.mLua->sol()); api["create"] = [context](const sol::table& layout) { auto element = LuaUi::Element::make(layout); context.mLuaManager->addAction(std::make_unique(UiAction::CREATE, element, context.mLua)); diff --git a/apps/openmw_test_suite/lua/test_ui_content.cpp b/apps/openmw_test_suite/lua/test_ui_content.cpp index f478c618dc..b9b24e9684 100644 --- a/apps/openmw_test_suite/lua/test_ui_content.cpp +++ b/apps/openmw_test_suite/lua/test_ui_content.cpp @@ -1,62 +1,96 @@ #include #include +#include #include namespace { using namespace testing; - sol::state state; - - sol::table makeTable() + struct LuaUiContentTest : Test { - return sol::table(state, sol::create); - } + LuaUtil::LuaState mLuaState{ nullptr, nullptr }; + sol::state_view mSol; + sol::protected_function mNew; + LuaUiContentTest() + : mSol(mLuaState.sol()) + , mNew(LuaUi::Content::makeFactory(mSol)) + { + mSol.open_libraries(sol::lib::base, sol::lib::table); + } - sol::table makeTable(std::string name) - { - auto result = makeTable(); - result["name"] = name; - return result; - } + LuaUi::Content::View makeContent(sol::table source) + { + auto result = mNew.call(source); + if (result.get_type() != sol::type::table) + throw std::logic_error("Expected table"); + return LuaUi::Content::View(result.get()); + } - TEST(LuaUiContentTest, Create) + sol::table makeTable() { return sol::table(mSol, sol::create); } + + sol::table makeTable(std::string name) + { + auto result = makeTable(); + result["name"] = name; + return result; + } + }; + + TEST_F(LuaUiContentTest, Create) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable()); table.add(makeTable()); - LuaUi::Content content(table); + LuaUi::Content::View content = makeContent(table); EXPECT_EQ(content.size(), 3); } - TEST(LuaUiContentTest, CreateWithHole) + TEST_F(LuaUiContentTest, Insert) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable()); - table[4] = makeTable(); - EXPECT_ANY_THROW(LuaUi::Content content(table)); + table.add(makeTable()); + LuaUi::Content::View content = makeContent(table); + content.insert(2, makeTable("inserted")); + EXPECT_EQ(content.size(), 4); + auto inserted = content.at("inserted"); + auto index = content.indexOf(inserted); + EXPECT_TRUE(index.has_value()); + EXPECT_EQ(index.value(), 2); } - TEST(LuaUiContentTest, WrongType) + TEST_F(LuaUiContentTest, MakeHole) + { + auto table = makeTable(); + table.add(makeTable()); + table.add(makeTable()); + LuaUi::Content::View content = makeContent(table); + sol::table t = makeTable(); + EXPECT_ANY_THROW(content.assign(3, t)); + } + + TEST_F(LuaUiContentTest, WrongType) { auto table = makeTable(); table.add(makeTable()); table.add("a"); table.add(makeTable()); - EXPECT_ANY_THROW(LuaUi::Content content(table)); + EXPECT_ANY_THROW(makeContent(table)); } - TEST(LuaUiContentTest, NameAccess) + TEST_F(LuaUiContentTest, NameAccess) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable("a")); - LuaUi::Content content(table); + LuaUi::Content::View content = makeContent(table); EXPECT_NO_THROW(content.at("a")); content.remove("a"); + EXPECT_EQ(content.size(), 1); content.assign(content.size(), makeTable("b")); content.assign("b", makeTable()); EXPECT_ANY_THROW(content.at("b")); @@ -67,31 +101,33 @@ namespace EXPECT_ANY_THROW(content.at("c")); } - TEST(LuaUiContentTest, IndexOf) + TEST_F(LuaUiContentTest, IndexOf) { auto table = makeTable(); table.add(makeTable()); table.add(makeTable()); table.add(makeTable()); - LuaUi::Content content(table); + LuaUi::Content::View content = makeContent(table); auto child = makeTable(); content.assign(2, child); - EXPECT_EQ(content.indexOf(child), 2); - EXPECT_EQ(content.indexOf(makeTable()), content.size()); + EXPECT_EQ(content.indexOf(child).value(), 2); + EXPECT_TRUE(!content.indexOf(makeTable()).has_value()); } - TEST(LuaUiContentTest, BoundsChecks) + TEST_F(LuaUiContentTest, BoundsChecks) { auto table = makeTable(); - LuaUi::Content content(table); + LuaUi::Content::View content = makeContent(table); EXPECT_ANY_THROW(content.at(0)); content.assign(content.size(), makeTable()); content.assign(content.size(), makeTable()); content.assign(content.size(), makeTable()); + EXPECT_EQ(content.size(), 3); EXPECT_ANY_THROW(content.at(3)); EXPECT_ANY_THROW(content.remove(3)); - EXPECT_NO_THROW(content.remove(1)); - EXPECT_NO_THROW(content.at(1)); + EXPECT_NO_THROW(content.remove(1)); // TODO: something cursed happens here, even __newindex is not called! + EXPECT_EQ(content.size(), 2); + EXPECT_NO_THROW(content.at(2)); EXPECT_EQ(content.size(), 2); } } diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 405c36b18e..c738019e14 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -272,6 +272,7 @@ add_component_dir (lua_ui properties widget element util layers content alignment resources adapter text textedit window image container flex ) +list (APPEND OPENMW_FILES "lua_ui/content.lua") if(WIN32) diff --git a/components/lua_ui/content.cpp b/components/lua_ui/content.cpp index 3f66c17c4a..ba9d8b6805 100644 --- a/components/lua_ui/content.cpp +++ b/components/lua_ui/content.cpp @@ -1,108 +1,33 @@ #include "content.hpp" -namespace LuaUi +namespace LuaUi::Content { - int64_t Content::sInstanceCount = 0; - - Content::Content(const sol::table& table) + namespace { - sInstanceCount++; - size_t size = table.size(); - for (size_t index = 0; index < size; ++index) + sol::table loadMetatable(sol::state_view sol) { - sol::object value = table.get(index + 1); - if (value.is()) - assign(index, value.as()); - else - throw std::logic_error("UI Content children must all be tables."); + std::string scriptBody = +#include "content.lua" + ; + auto result = sol.safe_script(scriptBody); + if (result.get_type() != sol::type::table) + throw std::logic_error("Expected a meta table"); + return result.get(); } } - void Content::assign(size_t index, const sol::table& table) + sol::protected_function makeFactory(sol::state_view sol) { - if (mOrdered.size() < index) - throw std::logic_error("Can't have gaps in UI Content."); - if (index == mOrdered.size()) - mOrdered.push_back(table); - else - { - sol::optional oldName = mOrdered[index]["name"]; - if (oldName.has_value()) - mNamed.erase(oldName.value()); - mOrdered[index] = table; - } - sol::optional name = table["name"]; - if (name.has_value()) - mNamed[name.value()] = index; + sol::table metatable = loadMetatable(sol); + if (metatable["new"].get_type() != sol::type::function) + throw std::logic_error("Expected function"); + return metatable["new"].get(); } - void Content::assign(std::string_view name, const sol::table& table) - { - auto it = mNamed.find(name); - if (it != mNamed.end()) - assign(it->second, table); - else - throw std::logic_error(std::string("Can't find a UI Content child with name ") += name); - } + int64_t View::sInstanceCount = 0; - void Content::insert(size_t index, const sol::table& table) + int64_t getInstanceCount() { - if (mOrdered.size() < index) - throw std::logic_error("Can't have gaps in UI Content."); - mOrdered.insert(mOrdered.begin() + index, table); - for (size_t i = index; i < mOrdered.size(); ++i) - { - sol::optional name = mOrdered[i]["name"]; - if (name.has_value()) - mNamed[name.value()] = index; - } - } - - sol::table Content::at(size_t index) const - { - if (index > size()) - throw std::logic_error("Invalid UI Content index."); - return mOrdered.at(index); - } - - sol::table Content::at(std::string_view name) const - { - auto it = mNamed.find(name); - if (it == mNamed.end()) - throw std::logic_error("Invalid UI Content name."); - return mOrdered.at(it->second); - } - - size_t Content::remove(size_t index) - { - sol::table table = at(index); - sol::optional name = table["name"]; - if (name.has_value()) - { - auto it = mNamed.find(name.value()); - if (it != mNamed.end()) - mNamed.erase(it); - } - mOrdered.erase(mOrdered.begin() + index); - return index; - } - - size_t Content::remove(std::string_view name) - { - auto it = mNamed.find(name); - if (it == mNamed.end()) - throw std::logic_error("Invalid UI Content name."); - size_t index = it->second; - remove(index); - return index; - } - - size_t Content::indexOf(const sol::table& table) const - { - auto it = std::find(mOrdered.begin(), mOrdered.end(), table); - if (it == mOrdered.end()) - return size(); - else - return it - mOrdered.begin(); + return View::sInstanceCount; } } diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index df005dc1c2..8487dea27a 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -6,52 +6,120 @@ #include -namespace LuaUi +namespace LuaUi::Content { - class Content + sol::protected_function makeFactory(sol::state_view); + + class View { public: - using iterator = std::vector::iterator; + static int64_t sInstanceCount; // debug information, shown in Lua profiler - Content() { sInstanceCount++; } - ~Content() { sInstanceCount--; } - Content(const Content& c) + // accepts only Lua tables returned by ui.content + explicit View(sol::table table) + : mTable(std::move(table)) { - this->mNamed = c.mNamed; - this->mOrdered = c.mOrdered; + if (!isValid(mTable)) + throw std::domain_error("Expected a Content table"); sInstanceCount++; } - Content(Content&& c) + View(const View& c) { - this->mNamed = std::move(c.mNamed); - this->mOrdered = std::move(c.mOrdered); + this->mTable = c.mTable; sInstanceCount++; } + View(View&& c) + { + this->mTable = std::move(c.mTable); + sInstanceCount++; + } + ~View() { sInstanceCount--; } - // expects a Lua array - a table with keys from 1 to n without any nil values in between - // any other keys are ignored - explicit Content(const sol::table&); + static bool isValid(const sol::object& object) + { + if (object.get_type() != sol::type::table) + return false; + sol::table table = object; + return table.traverse_get>(sol::metatable_key, "__Content").value_or(false); + } - size_t size() const { return mOrdered.size(); } + size_t size() const { return mTable.size(); } - void assign(std::string_view name, const sol::table& table); - void assign(size_t index, const sol::table& table); - void insert(size_t index, const sol::table& table); + void assign(std::string_view name, const sol::table& table) + { + if (indexOf(name).has_value()) + mTable[name] = table; + else + throw std::domain_error("Invalid Content key"); + } + void assign(size_t index, const sol::table& table) + { + if (index <= size()) + mTable[toLua(index)] = table; + else + throw std::domain_error("Invalid Content index"); + } + void insert(size_t index, const sol::table& table) { callMethod("insert", toLua(index), table); } - sol::table at(size_t index) const; - sol::table at(std::string_view name) const; - size_t remove(size_t index); - size_t remove(std::string_view name); - size_t indexOf(const sol::table& table) const; - - static int64_t getInstanceCount() { return sInstanceCount; } + sol::table at(size_t index) const + { + if (index < size()) + return mTable.get(toLua(index)); + else + throw std::domain_error("Invalid Content index"); + } + sol::table at(std::string_view name) const + { + if (indexOf(name).has_value()) + return mTable.get(name); + else + throw std::domain_error("Invalid Content key"); + } + void remove(size_t index) + { + if (index < size()) + mTable[toLua(index)] = sol::nil; + else + throw std::domain_error("Invalid Content index"); + } + void remove(std::string_view name) + { + if (indexOf(name).has_value()) + mTable[name] = sol::nil; + else + throw std::domain_error("Invalid Content index"); + } + std::optional indexOf(std::string_view name) const + { + sol::object result = callMethod("indexOf", name); + if (result.is()) + return fromLua(result.as()); + else + return std::nullopt; + } + std::optional indexOf(const sol::table& table) const + { + sol::object result = callMethod("indexOf", table); + if (result.is()) + return fromLua(result.as()); + else + return std::nullopt; + } private: - std::map> mNamed; - std::vector mOrdered; - static int64_t sInstanceCount; // debug information, shown in Lua profiler + sol::table mTable; + + template + sol::object callMethod(std::string_view name, Arg&&... arg) const + { + return mTable.get(name)(mTable, arg...); + } + + static inline size_t toLua(size_t index) { return index + 1; } + static inline size_t fromLua(size_t index) { return index - 1; } }; + int64_t getInstanceCount(); } #endif // COMPONENTS_LUAUI_CONTENT diff --git a/components/lua_ui/content.lua b/components/lua_ui/content.lua new file mode 100644 index 0000000000..b7bec3701c --- /dev/null +++ b/components/lua_ui/content.lua @@ -0,0 +1,144 @@ +R"( +local M = {} +M.__Content = true +M.new = function(source) + local result = {} + result.__nameIndex = {} + for i, v in ipairs(source) do + if type(v) ~= 'table' then + error("Content can only contain tables") + end + result[i] = v + if type(v.name) == 'string' then + result.__nameIndex[v.name] = i + end + end + return setmetatable(result, M) +end +local function validateIndex(self, index) + if type(index) ~= 'number' then + error('Unexpected Content key: ' .. tostring(index)) + end + if index < 1 or (#self + 1) < index then + error('Invalid Content index: ' .. tostring(index)) + end +end +local function getIndexFromKey(self, key) + local index = key + if type(key) == 'string' then + index = self.__nameIndex[key] + if not index then + error("Unexpected content key:" .. key) + end + end + validateIndex(self, index) + return index +end +local function nameAt(self, index) + local v = rawget(self, index) + return v and type(v.name) == 'string' and v.name +end +local methods = { + insert = function(self, index, value) + validateIndex(self, index) + if type(value) ~= 'table' then + error('Content can only contain tables') + end + for i = #self, index, -1 do + rawset(self, i + 1, rawget(self, i)) + local name = rawget(self, i + 1) + if name then + self.__nameIndex[name] = i + 1 + end + end + rawset(self, index, value) + if value.name then + self.__nameIndex[value.name] = index + end + end, + indexOf = function(self, value) + if type(value) == 'string' then + return self.__nameIndex[value] + elseif type(value) == 'table' then + for i = 1, #self do + if rawget(self, i) == value then + return i + end + end + end + return nil + end, + add = function(self, value) + self:insert(#self + 1, value) + return #self + end, +} +M.__index = function(self, key) + if methods[key] then return methods[key] end + local index = getIndexFromKey(self, key) + return rawget(self, index) +end +local function remove(self, index) + print('remove', #self, index) + local oldName = nameAt(self, index) + if oldName then + self.__nameIndex[oldName] = nil + end + if index > #self then + error('Invalid Content index:' .. tostring(index)) + end + for i = index, #self - 1 do + local v = rawget(self, i + 1) + rawset(self, i, v) + if type(v.name) == 'string' then + self.__nameIndex[v.name] = i + end + end + rawset(self, #self, nil) + print('removed', #self) +end +local function assign(self, index, value) + local oldName = nameAt(self, index) + if oldName then + self.__nameIndex[oldName] = nil + end + rawset(self, index, value) + if value.name then + self.__nameIndex[value.name] = index + end +end +M.__newindex = function(self, key, value) + print('__newindex ', key, value) + local index = getIndexFromKey(self, key) + if value == nil then + remove(self, index) + elseif type(value) == 'table' then + assign(self, index, value) + else + error('Content can only contain tables') + end +end +local function next(self, index) + local v = rawget(self, index) + if v then + return index + 1, v + else + return nil, nil + end +end +M.__pairs = function(self) + return next, self, 1 +end +M.__ipairs = M.__pairs + +local test = M.new({}) +test:insert(1, {}) +test[2] = {} +assert(#test == 2, "Wrong size") +test:add({ name = 'a' }) +assert(getIndexFromKey(test, 'a') == 3, getIndexFromKey(test, 'a')) +assert(type(test.a) == 'table', type(test.a)) +assert(test.a.name == 'a', 'wrong table') + +return M +)" \ No newline at end of file diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index 41de2536cd..c3fb9c2450 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -63,9 +63,9 @@ namespace LuaUi destroyWidget(w); return result; } - if (!contentObj.is()) + if (!Content::View::isValid(contentObj)) throw std::logic_error("Layout content field must be a openmw.ui.content"); - const Content& content = contentObj.as(); + Content::View content(contentObj.as()); result.resize(content.size()); size_t minSize = std::min(children.size(), content.size()); for (size_t i = 0; i < minSize; i++) From 3618b3f409ecdd79f4a9fff2f4cecdf31a3a707e Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 29 Jan 2023 18:52:18 +0100 Subject: [PATCH 02/11] Fix Content::View::remove --- apps/openmw_test_suite/lua/test_ui_content.cpp | 8 +++++--- components/lua_ui/content.hpp | 10 ++++++---- components/lua_ui/content.lua | 12 ------------ 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_ui_content.cpp b/apps/openmw_test_suite/lua/test_ui_content.cpp index b9b24e9684..57b744b005 100644 --- a/apps/openmw_test_suite/lua/test_ui_content.cpp +++ b/apps/openmw_test_suite/lua/test_ui_content.cpp @@ -119,15 +119,17 @@ namespace auto table = makeTable(); LuaUi::Content::View content = makeContent(table); EXPECT_ANY_THROW(content.at(0)); + EXPECT_EQ(content.size(), 0); content.assign(content.size(), makeTable()); + EXPECT_EQ(content.size(), 1); content.assign(content.size(), makeTable()); + EXPECT_EQ(content.size(), 2); content.assign(content.size(), makeTable()); EXPECT_EQ(content.size(), 3); EXPECT_ANY_THROW(content.at(3)); EXPECT_ANY_THROW(content.remove(3)); - EXPECT_NO_THROW(content.remove(1)); // TODO: something cursed happens here, even __newindex is not called! - EXPECT_EQ(content.size(), 2); - EXPECT_NO_THROW(content.at(2)); + content.remove(2); EXPECT_EQ(content.size(), 2); + EXPECT_ANY_THROW(content.at(2)); } } diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index 8487dea27a..72db6440d3 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -78,16 +78,18 @@ namespace LuaUi::Content void remove(size_t index) { if (index < size()) - mTable[toLua(index)] = sol::nil; + // for some reason mTable[key] = value doesn't call __newindex + mTable[sol::metatable_key][sol::meta_function::new_index].get()(mTable, toLua(index), sol::nil); else throw std::domain_error("Invalid Content index"); } void remove(std::string_view name) { - if (indexOf(name).has_value()) - mTable[name] = sol::nil; + auto index = indexOf(name); + if (index.has_value()) + remove(index.value()); else - throw std::domain_error("Invalid Content index"); + throw std::domain_error("Invalid Content key"); } std::optional indexOf(std::string_view name) const { diff --git a/components/lua_ui/content.lua b/components/lua_ui/content.lua index b7bec3701c..79fd87c79e 100644 --- a/components/lua_ui/content.lua +++ b/components/lua_ui/content.lua @@ -79,7 +79,6 @@ M.__index = function(self, key) return rawget(self, index) end local function remove(self, index) - print('remove', #self, index) local oldName = nameAt(self, index) if oldName then self.__nameIndex[oldName] = nil @@ -95,7 +94,6 @@ local function remove(self, index) end end rawset(self, #self, nil) - print('removed', #self) end local function assign(self, index, value) local oldName = nameAt(self, index) @@ -108,7 +106,6 @@ local function assign(self, index, value) end end M.__newindex = function(self, key, value) - print('__newindex ', key, value) local index = getIndexFromKey(self, key) if value == nil then remove(self, index) @@ -131,14 +128,5 @@ M.__pairs = function(self) end M.__ipairs = M.__pairs -local test = M.new({}) -test:insert(1, {}) -test[2] = {} -assert(#test == 2, "Wrong size") -test:add({ name = 'a' }) -assert(getIndexFromKey(test, 'a') == 3, getIndexFromKey(test, 'a')) -assert(type(test.a) == 'table', type(test.a)) -assert(test.a.name == 'a', 'wrong table') - return M )" \ No newline at end of file From 749c89e26efb625009fa65a207c61fcdf741cc07 Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 29 Jan 2023 19:28:54 +0100 Subject: [PATCH 03/11] Add missing to_string --- components/lua_ui/content.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/lua_ui/content.lua b/components/lua_ui/content.lua index 79fd87c79e..6e536358a7 100644 --- a/components/lua_ui/content.lua +++ b/components/lua_ui/content.lua @@ -115,6 +115,9 @@ M.__newindex = function(self, key, value) error('Content can only contain tables') end end +M.__tostring = function(self) + return ('UiContent{%d layouts}'):format(#self) +end local function next(self, index) local v = rawget(self, index) if v then From 259f1043113f8f01f6468870fda691b49ec5f33c Mon Sep 17 00:00:00 2001 From: uramer Date: Sun, 29 Jan 2023 19:36:21 +0100 Subject: [PATCH 04/11] Clean up --- apps/openmw_test_suite/lua/test_ui_content.cpp | 2 +- components/lua_ui/content.hpp | 3 ++- components/lua_ui/content.lua | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_ui_content.cpp b/apps/openmw_test_suite/lua/test_ui_content.cpp index 57b744b005..cceb7b342a 100644 --- a/apps/openmw_test_suite/lua/test_ui_content.cpp +++ b/apps/openmw_test_suite/lua/test_ui_content.cpp @@ -119,7 +119,7 @@ namespace auto table = makeTable(); LuaUi::Content::View content = makeContent(table); EXPECT_ANY_THROW(content.at(0)); - EXPECT_EQ(content.size(), 0); + EXPECT_EQ(content.size(), 0); content.assign(content.size(), makeTable()); EXPECT_EQ(content.size(), 1); content.assign(content.size(), makeTable()); diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index 72db6440d3..8b641037ef 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -79,7 +79,8 @@ namespace LuaUi::Content { if (index < size()) // for some reason mTable[key] = value doesn't call __newindex - mTable[sol::metatable_key][sol::meta_function::new_index].get()(mTable, toLua(index), sol::nil); + mTable[sol::metatable_key][sol::meta_function::new_index].get()( + mTable, toLua(index), sol::nil); else throw std::domain_error("Invalid Content index"); } diff --git a/components/lua_ui/content.lua b/components/lua_ui/content.lua index 6e536358a7..8c0e935bb9 100644 --- a/components/lua_ui/content.lua +++ b/components/lua_ui/content.lua @@ -34,10 +34,6 @@ local function getIndexFromKey(self, key) validateIndex(self, index) return index end -local function nameAt(self, index) - local v = rawget(self, index) - return v and type(v.name) == 'string' and v.name -end local methods = { insert = function(self, index, value) validateIndex(self, index) @@ -78,6 +74,10 @@ M.__index = function(self, key) local index = getIndexFromKey(self, key) return rawget(self, index) end +local function nameAt(self, index) + local v = rawget(self, index) + return v and type(v.name) == 'string' and v.name +end local function remove(self, index) local oldName = nameAt(self, index) if oldName then From c7b0c0a406e698be30b13fae7a0c670d0d502619 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 30 Jan 2023 23:22:18 +0100 Subject: [PATCH 05/11] Protecd UI Content's metatable --- components/lua_ui/content.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/components/lua_ui/content.lua b/components/lua_ui/content.lua index 8c0e935bb9..650a15ecc6 100644 --- a/components/lua_ui/content.lua +++ b/components/lua_ui/content.lua @@ -6,7 +6,7 @@ M.new = function(source) result.__nameIndex = {} for i, v in ipairs(source) do if type(v) ~= 'table' then - error("Content can only contain tables") + error('Content can only contain tables') end result[i] = v if type(v.name) == 'string' then @@ -28,7 +28,7 @@ local function getIndexFromKey(self, key) if type(key) == 'string' then index = self.__nameIndex[key] if not index then - error("Unexpected content key:" .. key) + error('Unexpected content key:' .. key) end end validateIndex(self, index) @@ -130,6 +130,10 @@ M.__pairs = function(self) return next, self, 1 end M.__ipairs = M.__pairs +M.__metatable = {} + +assert(not pcall(function() setmetatable(M.new({}), {}) end), 'Metatable is not protected') +assert(getmetatable(M.new) ~= M, 'Metatable is not protected') return M )" \ No newline at end of file From 539ee7788832eff6fb09588a6815b8829ee7da5f Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 30 Jan 2023 23:29:00 +0100 Subject: [PATCH 06/11] Note Layout names colliding with Content methods --- files/lua_api/openmw/ui.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/files/lua_api/openmw/ui.lua b/files/lua_api/openmw/ui.lua index 164d8768a9..1638aa4e71 100644 --- a/files/lua_api/openmw/ui.lua +++ b/files/lua_api/openmw/ui.lua @@ -172,6 +172,12 @@ -- for i = 1, #content do -- print('widget',content[i].name,'at',i) -- end +-- @usage +-- -- Note: layout names can collide with method names. Because of that you can't use a layout name such as "insert": +-- local content = ui.content { +-- { name = 'insert '} +-- } +-- content.insert.content = ui.content {} -- fails here, content.insert is a function! --- -- Puts the layout at given index by shifting all the elements after it From bbbef96087c371c4250d2d37a8df610faf594702 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 31 Jan 2023 19:50:33 +0100 Subject: [PATCH 07/11] Switch to loadInternalLib --- apps/openmw/mwlua/uibindings.cpp | 2 +- .../openmw_test_suite/lua/test_ui_content.cpp | 8 +++----- components/CMakeLists.txt | 2 +- components/lua_ui/content.cpp | 20 ++++--------------- components/lua_ui/content.hpp | 4 +++- components/lua_ui/content.lua | 12 +++++++---- 6 files changed, 20 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index e113178dd4..04282f74ca 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -141,7 +141,7 @@ namespace MWLua luaManager->addAction([wm, obj = obj.as()] { wm->setConsoleSelectedObject(obj.ptr()); }); } }; - api["content"] = LuaUi::Content::makeFactory(context.mLua->sol()); + api["content"] = LuaUi::Content::loadConstructor(context.mLua); api["create"] = [context](const sol::table& layout) { auto element = LuaUi::Element::make(layout); context.mLuaManager->addAction(std::make_unique(UiAction::CREATE, element, context.mLua)); diff --git a/apps/openmw_test_suite/lua/test_ui_content.cpp b/apps/openmw_test_suite/lua/test_ui_content.cpp index cceb7b342a..10b67638fd 100644 --- a/apps/openmw_test_suite/lua/test_ui_content.cpp +++ b/apps/openmw_test_suite/lua/test_ui_content.cpp @@ -11,13 +11,11 @@ namespace struct LuaUiContentTest : Test { LuaUtil::LuaState mLuaState{ nullptr, nullptr }; - sol::state_view mSol; sol::protected_function mNew; LuaUiContentTest() - : mSol(mLuaState.sol()) - , mNew(LuaUi::Content::makeFactory(mSol)) { - mSol.open_libraries(sol::lib::base, sol::lib::table); + mLuaState.addInternalLibSearchPath("resources/lua_libs"); + mNew = LuaUi::Content::loadConstructor(&mLuaState); } LuaUi::Content::View makeContent(sol::table source) @@ -28,7 +26,7 @@ namespace return LuaUi::Content::View(result.get()); } - sol::table makeTable() { return sol::table(mSol, sol::create); } + sol::table makeTable() { return sol::table(mLuaState.sol(), sol::create); } sol::table makeTable(std::string name) { diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index c738019e14..0a61e9d5db 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -272,7 +272,7 @@ add_component_dir (lua_ui properties widget element util layers content alignment resources adapter text textedit window image container flex ) -list (APPEND OPENMW_FILES "lua_ui/content.lua") +copy_resource_file("lua_ui/content.lua" "${OPENMW_RESOURCES_ROOT}" "resources/lua_libs/content.lua") if(WIN32) diff --git a/components/lua_ui/content.cpp b/components/lua_ui/content.cpp index ba9d8b6805..01dfe9faf6 100644 --- a/components/lua_ui/content.cpp +++ b/components/lua_ui/content.cpp @@ -2,23 +2,11 @@ namespace LuaUi::Content { - namespace + sol::protected_function loadConstructor(LuaUtil::LuaState* state) { - sol::table loadMetatable(sol::state_view sol) - { - std::string scriptBody = -#include "content.lua" - ; - auto result = sol.safe_script(scriptBody); - if (result.get_type() != sol::type::table) - throw std::logic_error("Expected a meta table"); - return result.get(); - } - } - - sol::protected_function makeFactory(sol::state_view sol) - { - sol::table metatable = loadMetatable(sol); + sol::function loader = state->loadInternalLib("content"); + sol::set_environment(state->newInternalLibEnvironment(), loader); + sol::table metatable = loader().get(); if (metatable["new"].get_type() != sol::type::function) throw std::logic_error("Expected function"); return metatable["new"].get(); diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index 8b641037ef..85c8a3a8fc 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -6,9 +6,11 @@ #include +#include + namespace LuaUi::Content { - sol::protected_function makeFactory(sol::state_view); + sol::protected_function loadConstructor(LuaUtil::LuaState* state); class View { diff --git a/components/lua_ui/content.lua b/components/lua_ui/content.lua index 650a15ecc6..7fe1d2cff1 100644 --- a/components/lua_ui/content.lua +++ b/components/lua_ui/content.lua @@ -1,4 +1,3 @@ -R"( local M = {} M.__Content = true M.new = function(source) @@ -23,6 +22,7 @@ local function validateIndex(self, index) error('Invalid Content index: ' .. tostring(index)) end end + local function getIndexFromKey(self, key) local index = key if type(key) == 'string' then @@ -34,6 +34,7 @@ local function getIndexFromKey(self, key) validateIndex(self, index) return index end + local methods = { insert = function(self, index, value) validateIndex(self, index) @@ -78,6 +79,7 @@ local function nameAt(self, index) local v = rawget(self, index) return v and type(v.name) == 'string' and v.name end + local function remove(self, index) local oldName = nameAt(self, index) if oldName then @@ -95,6 +97,7 @@ local function remove(self, index) end rawset(self, #self, nil) end + local function assign(self, index, value) local oldName = nameAt(self, index) if oldName then @@ -105,6 +108,7 @@ local function assign(self, index, value) self.__nameIndex[value.name] = index end end + M.__newindex = function(self, key, value) local index = getIndexFromKey(self, key) if value == nil then @@ -126,14 +130,14 @@ local function next(self, index) return nil, nil end end + M.__pairs = function(self) return next, self, 1 end M.__ipairs = M.__pairs M.__metatable = {} -assert(not pcall(function() setmetatable(M.new({}), {}) end), 'Metatable is not protected') -assert(getmetatable(M.new) ~= M, 'Metatable is not protected') +assert(not pcall(function() setmetatable(M.new {}, {}) end), 'Metatable is not protected') +assert(getmetatable(M.new {}) ~= M, 'Metatable is not protected') return M -)" \ No newline at end of file From fb0646dda1c90552079ac6f4a79e394ae5a39f3b Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 31 Jan 2023 19:52:28 +0100 Subject: [PATCH 08/11] Remove Ui Content counter in Lua profiler --- apps/openmw/mwlua/luamanagerimp.cpp | 1 - components/lua_ui/content.cpp | 7 ------- components/lua_ui/content.hpp | 16 ---------------- 3 files changed, 24 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 2dfb5dbc35..e856372ee7 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -644,7 +644,6 @@ namespace MWLua outMemSize(mLua.getTotalMemoryUsage()); out << "\n"; out << "LuaUtil::ScriptsContainer count: " << LuaUtil::ScriptsContainer::getInstanceCount() << "\n"; - out << "LuaUi::Content count: " << LuaUi::Content::getInstanceCount() << "\n"; out << "\n"; out << "small alloc max size = " << smallAllocSize << " (section [Lua] in settings.cfg)\n"; out << "Smaller values give more information for the profiler, but increase performance overhead.\n"; diff --git a/components/lua_ui/content.cpp b/components/lua_ui/content.cpp index 01dfe9faf6..fd1b56d427 100644 --- a/components/lua_ui/content.cpp +++ b/components/lua_ui/content.cpp @@ -11,11 +11,4 @@ namespace LuaUi::Content throw std::logic_error("Expected function"); return metatable["new"].get(); } - - int64_t View::sInstanceCount = 0; - - int64_t getInstanceCount() - { - return View::sInstanceCount; - } } diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index 85c8a3a8fc..c3da9e7365 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -15,27 +15,13 @@ namespace LuaUi::Content class View { public: - static int64_t sInstanceCount; // debug information, shown in Lua profiler - // accepts only Lua tables returned by ui.content explicit View(sol::table table) : mTable(std::move(table)) { if (!isValid(mTable)) throw std::domain_error("Expected a Content table"); - sInstanceCount++; } - View(const View& c) - { - this->mTable = c.mTable; - sInstanceCount++; - } - View(View&& c) - { - this->mTable = std::move(c.mTable); - sInstanceCount++; - } - ~View() { sInstanceCount--; } static bool isValid(const sol::object& object) { @@ -123,8 +109,6 @@ namespace LuaUi::Content static inline size_t toLua(size_t index) { return index + 1; } static inline size_t fromLua(size_t index) { return index - 1; } }; - - int64_t getInstanceCount(); } #endif // COMPONENTS_LUAUI_CONTENT From 2a35bae655a066e20f5977dd522d7162ca50bafc Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 1 Feb 2023 16:24:45 +0100 Subject: [PATCH 09/11] Use range_error for invalid indexes --- components/lua_ui/content.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index c3da9e7365..310d0b9cbe 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -45,7 +45,7 @@ namespace LuaUi::Content if (index <= size()) mTable[toLua(index)] = table; else - throw std::domain_error("Invalid Content index"); + throw std::range_error("Invalid Content index"); } void insert(size_t index, const sol::table& table) { callMethod("insert", toLua(index), table); } @@ -54,14 +54,14 @@ namespace LuaUi::Content if (index < size()) return mTable.get(toLua(index)); else - throw std::domain_error("Invalid Content index"); + throw std::range_error("Invalid Content index"); } sol::table at(std::string_view name) const { if (indexOf(name).has_value()) return mTable.get(name); else - throw std::domain_error("Invalid Content key"); + throw std::range_error("Invalid Content key"); } void remove(size_t index) { @@ -70,7 +70,7 @@ namespace LuaUi::Content mTable[sol::metatable_key][sol::meta_function::new_index].get()( mTable, toLua(index), sol::nil); else - throw std::domain_error("Invalid Content index"); + throw std::range_error("Invalid Content index"); } void remove(std::string_view name) { From d24c506b0e946df4079118dd45ddb580c1ca7a0c Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 1 Feb 2023 16:34:32 +0100 Subject: [PATCH 10/11] Move metatable protection asserts to tests --- apps/openmw_test_suite/lua/test_ui_content.cpp | 11 +++++++++++ components/lua_ui/content.hpp | 4 +++- components/lua_ui/content.lua | 3 --- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/apps/openmw_test_suite/lua/test_ui_content.cpp b/apps/openmw_test_suite/lua/test_ui_content.cpp index 10b67638fd..13ee9b6a7d 100644 --- a/apps/openmw_test_suite/lua/test_ui_content.cpp +++ b/apps/openmw_test_suite/lua/test_ui_content.cpp @@ -36,6 +36,17 @@ namespace } }; + TEST_F(LuaUiContentTest, ProtectedMetatable) + { + mLuaState.sol()["makeContent"] = mNew; + mLuaState.sol()["M"] = makeContent(makeTable()).getMetatable(); + std::string testScript = R"( + assert(not pcall(function() setmetatable(makeContent{}, {}) end), 'Metatable is not protected') + assert(getmetatable(makeContent{}) ~= M, 'Metatable is not protected') + )"; + EXPECT_NO_THROW(mLuaState.sol().safe_script(testScript)); + } + TEST_F(LuaUiContentTest, Create) { auto table = makeTable(); diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index 310d0b9cbe..b29bf82818 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -67,7 +67,7 @@ namespace LuaUi::Content { if (index < size()) // for some reason mTable[key] = value doesn't call __newindex - mTable[sol::metatable_key][sol::meta_function::new_index].get()( + getMetatable()[sol::meta_function::new_index].get()( mTable, toLua(index), sol::nil); else throw std::range_error("Invalid Content index"); @@ -97,6 +97,8 @@ namespace LuaUi::Content return std::nullopt; } + sol::table getMetatable() const { return mTable[sol::metatable_key].get(); } + private: sol::table mTable; diff --git a/components/lua_ui/content.lua b/components/lua_ui/content.lua index 7fe1d2cff1..cd63efc7ca 100644 --- a/components/lua_ui/content.lua +++ b/components/lua_ui/content.lua @@ -137,7 +137,4 @@ end M.__ipairs = M.__pairs M.__metatable = {} -assert(not pcall(function() setmetatable(M.new {}, {}) end), 'Metatable is not protected') -assert(getmetatable(M.new {}) ~= M, 'Metatable is not protected') - return M From e96681151ce6265745be032a273a09e93018262f Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 1 Feb 2023 17:18:50 +0100 Subject: [PATCH 11/11] Get rid of the LuaUI::Content namespace --- apps/openmw/mwlua/uibindings.cpp | 2 +- .../openmw_test_suite/lua/test_ui_content.cpp | 18 ++++++++--------- components/lua_ui/content.cpp | 12 +++++++++-- components/lua_ui/content.hpp | 20 +++++++------------ components/lua_ui/element.cpp | 4 +--- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 04282f74ca..282892213a 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -141,7 +141,7 @@ namespace MWLua luaManager->addAction([wm, obj = obj.as()] { wm->setConsoleSelectedObject(obj.ptr()); }); } }; - api["content"] = LuaUi::Content::loadConstructor(context.mLua); + api["content"] = LuaUi::loadContentConstructor(context.mLua); api["create"] = [context](const sol::table& layout) { auto element = LuaUi::Element::make(layout); context.mLuaManager->addAction(std::make_unique(UiAction::CREATE, element, context.mLua)); diff --git a/apps/openmw_test_suite/lua/test_ui_content.cpp b/apps/openmw_test_suite/lua/test_ui_content.cpp index 13ee9b6a7d..bf33bbd697 100644 --- a/apps/openmw_test_suite/lua/test_ui_content.cpp +++ b/apps/openmw_test_suite/lua/test_ui_content.cpp @@ -15,15 +15,15 @@ namespace LuaUiContentTest() { mLuaState.addInternalLibSearchPath("resources/lua_libs"); - mNew = LuaUi::Content::loadConstructor(&mLuaState); + mNew = LuaUi::loadContentConstructor(&mLuaState); } - LuaUi::Content::View makeContent(sol::table source) + LuaUi::ContentView makeContent(sol::table source) { auto result = mNew.call(source); if (result.get_type() != sol::type::table) throw std::logic_error("Expected table"); - return LuaUi::Content::View(result.get()); + return LuaUi::ContentView(result.get()); } sol::table makeTable() { return sol::table(mLuaState.sol(), sol::create); } @@ -53,7 +53,7 @@ namespace table.add(makeTable()); table.add(makeTable()); table.add(makeTable()); - LuaUi::Content::View content = makeContent(table); + LuaUi::ContentView content = makeContent(table); EXPECT_EQ(content.size(), 3); } @@ -63,7 +63,7 @@ namespace table.add(makeTable()); table.add(makeTable()); table.add(makeTable()); - LuaUi::Content::View content = makeContent(table); + LuaUi::ContentView content = makeContent(table); content.insert(2, makeTable("inserted")); EXPECT_EQ(content.size(), 4); auto inserted = content.at("inserted"); @@ -77,7 +77,7 @@ namespace auto table = makeTable(); table.add(makeTable()); table.add(makeTable()); - LuaUi::Content::View content = makeContent(table); + LuaUi::ContentView content = makeContent(table); sol::table t = makeTable(); EXPECT_ANY_THROW(content.assign(3, t)); } @@ -96,7 +96,7 @@ namespace auto table = makeTable(); table.add(makeTable()); table.add(makeTable("a")); - LuaUi::Content::View content = makeContent(table); + LuaUi::ContentView content = makeContent(table); EXPECT_NO_THROW(content.at("a")); content.remove("a"); EXPECT_EQ(content.size(), 1); @@ -116,7 +116,7 @@ namespace table.add(makeTable()); table.add(makeTable()); table.add(makeTable()); - LuaUi::Content::View content = makeContent(table); + LuaUi::ContentView content = makeContent(table); auto child = makeTable(); content.assign(2, child); EXPECT_EQ(content.indexOf(child).value(), 2); @@ -126,7 +126,7 @@ namespace TEST_F(LuaUiContentTest, BoundsChecks) { auto table = makeTable(); - LuaUi::Content::View content = makeContent(table); + LuaUi::ContentView content = makeContent(table); EXPECT_ANY_THROW(content.at(0)); EXPECT_EQ(content.size(), 0); content.assign(content.size(), makeTable()); diff --git a/components/lua_ui/content.cpp b/components/lua_ui/content.cpp index fd1b56d427..2e1d4ca0c4 100644 --- a/components/lua_ui/content.cpp +++ b/components/lua_ui/content.cpp @@ -1,8 +1,8 @@ #include "content.hpp" -namespace LuaUi::Content +namespace LuaUi { - sol::protected_function loadConstructor(LuaUtil::LuaState* state) + sol::protected_function loadContentConstructor(LuaUtil::LuaState* state) { sol::function loader = state->loadInternalLib("content"); sol::set_environment(state->newInternalLibEnvironment(), loader); @@ -11,4 +11,12 @@ namespace LuaUi::Content throw std::logic_error("Expected function"); return metatable["new"].get(); } + + bool isValidContent(const sol::object& object) + { + if (object.get_type() != sol::type::table) + return false; + sol::table table = object; + return table.traverse_get>(sol::metatable_key, "__Content").value_or(false); + } } diff --git a/components/lua_ui/content.hpp b/components/lua_ui/content.hpp index b29bf82818..2caa1ff8dc 100644 --- a/components/lua_ui/content.hpp +++ b/components/lua_ui/content.hpp @@ -8,29 +8,23 @@ #include -namespace LuaUi::Content +namespace LuaUi { - sol::protected_function loadConstructor(LuaUtil::LuaState* state); + sol::protected_function loadContentConstructor(LuaUtil::LuaState* state); - class View + bool isValidContent(const sol::object& object); + + class ContentView { public: // accepts only Lua tables returned by ui.content - explicit View(sol::table table) + explicit ContentView(sol::table table) : mTable(std::move(table)) { - if (!isValid(mTable)) + if (!isValidContent(mTable)) throw std::domain_error("Expected a Content table"); } - static bool isValid(const sol::object& object) - { - if (object.get_type() != sol::type::table) - return false; - sol::table table = object; - return table.traverse_get>(sol::metatable_key, "__Content").value_or(false); - } - size_t size() const { return mTable.size(); } void assign(std::string_view name, const sol::table& table) diff --git a/components/lua_ui/element.cpp b/components/lua_ui/element.cpp index c3fb9c2450..91d1acc433 100644 --- a/components/lua_ui/element.cpp +++ b/components/lua_ui/element.cpp @@ -63,9 +63,7 @@ namespace LuaUi destroyWidget(w); return result; } - if (!Content::View::isValid(contentObj)) - throw std::logic_error("Layout content field must be a openmw.ui.content"); - Content::View content(contentObj.as()); + ContentView content(contentObj.as()); result.resize(content.size()); size_t minSize = std::min(children.size(), content.size()); for (size_t i = 0; i < minSize; i++)