From 80ced23a69d5a94358c124f1ca020f55fa294c20 Mon Sep 17 00:00:00 2001 From: uramer Date: Wed, 14 Feb 2024 17:30:50 +0100 Subject: [PATCH 1/4] Lua Context: Replace exclusive bools with type enum --- apps/openmw/mwlua/context.hpp | 9 +++++++-- apps/openmw/mwlua/luamanagerimp.cpp | 27 +++++++++++++-------------- apps/openmw/mwlua/uibindings.cpp | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwlua/context.hpp b/apps/openmw/mwlua/context.hpp index def38a5309..dfab241b4d 100644 --- a/apps/openmw/mwlua/context.hpp +++ b/apps/openmw/mwlua/context.hpp @@ -15,8 +15,13 @@ namespace MWLua struct Context { - bool mIsMenu; - bool mIsGlobal; + enum Type + { + Menu, + Global, + Local, + }; + Type mType; LuaManager* mLuaManager; LuaUtil::LuaState* mLua; LuaUtil::UserdataSerializer* mSerializer; diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 5c8a3ff909..4d726994fa 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -83,25 +83,24 @@ namespace MWLua void LuaManager::init() { - Context context; - context.mIsMenu = false; - context.mIsGlobal = true; - context.mLuaManager = this; - context.mLua = &mLua; - context.mObjectLists = &mObjectLists; - context.mLuaEvents = &mLuaEvents; - context.mSerializer = mGlobalSerializer.get(); + Context globalContext; + globalContext.mType = Context::Global; + globalContext.mLuaManager = this; + globalContext.mLua = &mLua; + globalContext.mObjectLists = &mObjectLists; + globalContext.mLuaEvents = &mLuaEvents; + globalContext.mSerializer = mGlobalSerializer.get(); - Context localContext = context; - localContext.mIsGlobal = false; + Context localContext = globalContext; + localContext.mType = Context::Local; localContext.mSerializer = mLocalSerializer.get(); - Context menuContext = context; - menuContext.mIsMenu = true; + Context menuContext = globalContext; + menuContext.mType = Context::Menu; - for (const auto& [name, package] : initCommonPackages(context)) + for (const auto& [name, package] : initCommonPackages(globalContext)) mLua.addCommonPackage(name, package); - for (const auto& [name, package] : initGlobalPackages(context)) + for (const auto& [name, package] : initGlobalPackages(globalContext)) mGlobalScripts.addPackage(name, package); for (const auto& [name, package] : initMenuPackages(menuContext)) mMenuScripts.addPackage(name, package); diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 4ac9940b92..3502149116 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -295,7 +295,7 @@ namespace MWLua { std::string_view menuCache = "openmw_ui_menu"; std::string_view gameCache = "openmw_ui_game"; - std::string_view cacheKey = context.mIsMenu ? menuCache : gameCache; + std::string_view cacheKey = context.mType == Context::Menu ? menuCache : gameCache; { sol::state_view& lua = context.mLua->sol(); if (lua[cacheKey] != sol::nil) From 6851e6e56aff9a1dcfb59ef675e14c6fece2b74e Mon Sep 17 00:00:00 2001 From: Anton Uramer Date: Tue, 23 Jul 2024 12:19:01 +0200 Subject: [PATCH 2/4] Context helpers for caching packages, split up core bindings into multiple caches, remove getRealFrameDuration in global context --- apps/openmw/mwlua/context.hpp | 67 ++++++++++++++++++++++++++ apps/openmw/mwlua/corebindings.cpp | 75 ++++++++++++++++-------------- apps/openmw/mwlua/corebindings.hpp | 4 -- apps/openmw/mwlua/luabindings.cpp | 2 +- components/lua/luastate.hpp | 14 ++++++ 5 files changed, 123 insertions(+), 39 deletions(-) diff --git a/apps/openmw/mwlua/context.hpp b/apps/openmw/mwlua/context.hpp index dfab241b4d..0bda4ca4e3 100644 --- a/apps/openmw/mwlua/context.hpp +++ b/apps/openmw/mwlua/context.hpp @@ -1,6 +1,8 @@ #ifndef MWLUA_CONTEXT_H #define MWLUA_CONTEXT_H +#include + namespace LuaUtil { class LuaState; @@ -27,6 +29,71 @@ namespace MWLua LuaUtil::UserdataSerializer* mSerializer; ObjectLists* mObjectLists; LuaEvents* mLuaEvents; + + std::string_view typeName() const + { + switch (mType) + { + case Menu: + return "menu"; + case Global: + return "global"; + case Local: + return "local"; + default: + throw std::domain_error("Unhandled context type"); + } + } + + template + sol::object getCachedPackage(std::string_view first, const Str&... str) const + { + sol::object package = mLua->sol()[first]; + if constexpr (sizeof...(str) == 0) + return package; + else + return LuaUtil::getFieldOrNil(package, str...); + } + + template + const sol::object& setCachedPackage(const sol::object& value, std::string_view first, const Str&... str) const + { + sol::state_view& lua = mLua->sol(); + if constexpr (sizeof...(str) == 0) + lua[first] = value; + else + { + if (lua[first] == sol::nil) + lua[first] = sol::table(lua, sol::create); + sol::table table = lua[first]; + LuaUtil::setDeepField(table, value, str...); + } + return value; + } + + sol::object getTypePackage(std::string_view key) const { return getCachedPackage(key, typeName()); } + + const sol::object& setTypePackage(const sol::object& value, std::string_view key) const + { + return setCachedPackage(value, key, typeName()); + } + + template + sol::object cachePackage(std::string_view key, Factory factory) const + { + sol::object cached = getCachedPackage(key); + if (cached != sol::nil) + return cached; + else + return setCachedPackage(factory(), key); + } + + bool initializeOnce(std::string_view key) const + { + sol::object flag = mLua->sol()[key]; + mLua->sol()[key] = sol::make_object(mLua->sol(), true); + return flag == sol::nil; + } }; } diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 22c317f742..f57948a211 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -64,16 +64,18 @@ namespace MWLua api["getRealTime"] = []() { return std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); }; - // TODO: remove in global context? - api["getRealFrameDuration"] = []() { return MWBase::Environment::get().getFrameDuration(); }; + + if (context.mType != Context::Global) + api["getRealFrameDuration"] = []() { return MWBase::Environment::get().getFrameDuration(); }; } sol::table initCorePackage(const Context& context) { auto* lua = context.mLua; - if (lua->sol()["openmw_core"] != sol::nil) - return lua->sol()["openmw_core"]; + sol::object cached = context.getTypePackage("openmw_core"); + if (cached != sol::nil) + return cached; sol::table api(lua->sol(), sol::create); api["API_REVISION"] = Version::getLuaApiRevision(); // specified in CMakeLists.txt @@ -81,13 +83,7 @@ namespace MWLua Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback(); MWBase::Environment::get().getStateManager()->requestQuit(); }; - api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { - context.mLuaEvents->addGlobalEvent( - { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); - }; api["contentFiles"] = initContentFilesBindings(lua->sol()); - api["sound"] = initCoreSoundBindings(context); - api["vfx"] = initCoreVfxBindings(context); api["getFormId"] = [](std::string_view contentFile, unsigned int index) -> std::string { const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); for (size_t i = 0; i < contentList.size(); ++i) @@ -96,12 +92,19 @@ namespace MWLua throw std::runtime_error("Content file not found: " + std::string(contentFile)); }; addCoreTimeBindings(api, context); - api["magic"] = initCoreMagicBindings(context); - api["stats"] = initCoreStatsBindings(context); - api["factions"] = initCoreFactionBindings(context); - api["dialogue"] = initCoreDialogueBindings(context); - api["l10n"] = LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); + api["magic"] + = context.cachePackage("openmw_core_magic", [context]() { return initCoreMagicBindings(context); }); + + api["stats"] + = context.cachePackage("openmw_core_stats", [context]() { return initCoreStatsBindings(context); }); + + api["factions"] + = context.cachePackage("openmw_core_factions", [context]() { return initCoreFactionBindings(context); }); + api["dialogue"] + = context.cachePackage("openmw_core_dialogue", [context]() { return initCoreDialogueBindings(context); }); + api["l10n"] = context.cachePackage("openmw_core_l10n", + [lua]() { return LuaUtil::initL10nLoader(lua->sol(), MWBase::Environment::get().getL10nManager()); }); const MWWorld::Store* gmstStore = &MWBase::Environment::get().getESMStore()->get(); api["getGMST"] = [lua = context.mLua, gmstStore](const std::string& setting) -> sol::object { @@ -126,25 +129,29 @@ namespace MWLua return sol::nil; }; - lua->sol()["openmw_core"] = LuaUtil::makeReadOnly(api); - return lua->sol()["openmw_core"]; - } + if (context.mType != Context::Menu) + { + api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { + context.mLuaEvents->addGlobalEvent( + { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); + }; + api["sound"] + = context.cachePackage("openmw_core_sound", [context]() { return initCoreSoundBindings(context); }); + api["vfx"] = initCoreVfxBindings(context); + } + else + { + api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { + if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) + { + throw std::logic_error("Can't send global events when no game is loaded"); + } + context.mLuaEvents->addGlobalEvent( + { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); + }; + } - sol::table initCorePackageForMenuScripts(const Context& context) - { - sol::table api(context.mLua->sol(), sol::create); - for (auto& [k, v] : LuaUtil::getMutableFromReadOnly(initCorePackage(context))) - api[k] = v; - api["sendGlobalEvent"] = [context](std::string eventName, const sol::object& eventData) { - if (MWBase::Environment::get().getStateManager()->getState() == MWBase::StateManager::State_NoGame) - { - throw std::logic_error("Can't send global events when no game is loaded"); - } - context.mLuaEvents->addGlobalEvent( - { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); - }; - api["sound"] = sol::nil; - api["vfx"] = sol::nil; - return LuaUtil::makeReadOnly(api); + sol::table readOnly = LuaUtil::makeReadOnly(api); + return context.setTypePackage(readOnly, "openmw_core"); } } diff --git a/apps/openmw/mwlua/corebindings.hpp b/apps/openmw/mwlua/corebindings.hpp index d086d3884c..ef385ca993 100644 --- a/apps/openmw/mwlua/corebindings.hpp +++ b/apps/openmw/mwlua/corebindings.hpp @@ -10,10 +10,6 @@ namespace MWLua void addCoreTimeBindings(sol::table& api, const Context& context); sol::table initCorePackage(const Context&); - - // Returns `openmw.core`, but disables the functionality that shouldn't - // be availabe in menu scripts (to prevent cheating in mutiplayer via menu console). - sol::table initCorePackageForMenuScripts(const Context&); } #endif // MWLUA_COREBINDINGS_H diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index 553b8af8f6..5b4357ae8e 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -80,7 +80,7 @@ namespace MWLua std::map initMenuPackages(const Context& context) { return { - { "openmw.core", initCorePackageForMenuScripts(context) }, + { "openmw.core", initCorePackage(context) }, { "openmw.ambient", initAmbientPackage(context) }, { "openmw.ui", initUserInterfacePackage(context) }, { "openmw.menu", initMenuPackage(context) }, diff --git a/components/lua/luastate.hpp b/components/lua/luastate.hpp index 8430586d89..5dfad7af32 100644 --- a/components/lua/luastate.hpp +++ b/components/lua/luastate.hpp @@ -265,6 +265,20 @@ namespace LuaUtil return getFieldOrNil(value, str...); } + template + void setDeepField(sol::table& table, const sol::object& value, std::string_view first, const Str&... str) + { + if constexpr (sizeof...(str) == 0) + table[first] = value; + else + { + if (table[first] == sol::nil) + table[first] = sol::table(table.lua_state(), sol::create); + sol::table nextTable = table[first]; + setDeepField(nextTable, value, str...); + } + } + // String representation of a Lua object. Should be used for debugging/logging purposes only. std::string toString(const sol::object&); From a5d1db2afd53a9d9cbe3ff7ae65aaa52a9244c5b Mon Sep 17 00:00:00 2001 From: Anton Uramer Date: Tue, 23 Jul 2024 12:39:56 +0200 Subject: [PATCH 3/4] Use new Context helpers for UI bindings --- apps/openmw/mwlua/uibindings.cpp | 86 ++++++++++++++++---------------- 1 file changed, 42 insertions(+), 44 deletions(-) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index 3502149116..d08ef0ca66 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -80,8 +80,10 @@ namespace MWLua }(); } - sol::table registerUiApi(const Context& context, bool menu) + sol::table registerUiApi(const Context& context) { + bool menu = context.mType == Context::Menu; + MWBase::WindowManager* windowManager = MWBase::Environment::get().getWindowManager(); sol::table api = context.mLua->newTable(); @@ -293,51 +295,47 @@ namespace MWLua sol::table initUserInterfacePackage(const Context& context) { - std::string_view menuCache = "openmw_ui_menu"; - std::string_view gameCache = "openmw_ui_game"; - std::string_view cacheKey = context.mType == Context::Menu ? menuCache : gameCache; + if (context.initializeOnce("openmw_ui_usertypes")) { - sol::state_view& lua = context.mLua->sol(); - if (lua[cacheKey] != sol::nil) - return lua[cacheKey]; + auto element = context.mLua->sol().new_usertype("UiElement"); + element[sol::meta_function::to_string] = [](const LuaUi::Element& element) { + std::stringstream res; + res << "UiElement"; + if (element.mLayer != "") + res << "[" << element.mLayer << "]"; + return res.str(); + }; + element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, + [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); + element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { + if (element->mState != LuaUi::Element::Created) + return; + element->mState = LuaUi::Element::Update; + luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); + }; + element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { + if (element->mState == LuaUi::Element::Destroyed) + return; + element->mState = LuaUi::Element::Destroy; + luaManager->addAction( + [element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI"); + }; + + auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); + uiLayer["name"] + = sol::readonly_property([](LuaUi::Layer& self) -> std::string_view { return self.name(); }); + uiLayer["size"] = sol::readonly_property([](LuaUi::Layer& self) { return self.size(); }); + uiLayer[sol::meta_function::to_string] + = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; } - auto element = context.mLua->sol().new_usertype("UiElement"); - element[sol::meta_function::to_string] = [](const LuaUi::Element& element) { - std::stringstream res; - res << "UiElement"; - if (element.mLayer != "") - res << "[" << element.mLayer << "]"; - return res.str(); - }; - element["layout"] = sol::property([](const LuaUi::Element& element) { return element.mLayout; }, - [](LuaUi::Element& element, const sol::table& layout) { element.mLayout = layout; }); - element["update"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mState != LuaUi::Element::Created) - return; - element->mState = LuaUi::Element::Update; - luaManager->addAction([element] { wrapAction(element, [&] { element->update(); }); }, "Update UI"); - }; - element["destroy"] = [luaManager = context.mLuaManager](const std::shared_ptr& element) { - if (element->mState == LuaUi::Element::Destroyed) - return; - element->mState = LuaUi::Element::Destroy; - luaManager->addAction( - [element] { wrapAction(element, [&] { LuaUi::Element::erase(element.get()); }); }, "Destroy UI"); - }; - - auto uiLayer = context.mLua->sol().new_usertype("UiLayer"); - uiLayer["name"] = sol::readonly_property([](LuaUi::Layer& self) -> std::string_view { return self.name(); }); - uiLayer["size"] = sol::readonly_property([](LuaUi::Layer& self) { return self.size(); }); - uiLayer[sol::meta_function::to_string] - = [](LuaUi::Layer& self) { return Misc::StringUtils::format("UiLayer(%s)", self.name()); }; - - sol::table menuApi = registerUiApi(context, true); - sol::table gameApi = registerUiApi(context, false); - - sol::state_view& lua = context.mLua->sol(); - lua[menuCache] = LuaUtil::makeReadOnly(menuApi); - lua[gameCache] = LuaUtil::makeReadOnly(gameApi); - return lua[cacheKey]; + sol::object cached = context.getTypePackage("openmw_ui"); + if (cached != sol::nil) + return cached; + else + { + sol::table api = LuaUtil::makeReadOnly(registerUiApi(context)); + return context.setTypePackage(api, "openmw_ui"); + } } } From f15888f474362f381e2a3b5d03a1fa194ca6b115 Mon Sep 17 00:00:00 2001 From: uramer Date: Tue, 23 Jul 2024 20:52:37 +0200 Subject: [PATCH 4/4] Lua docs: getRealFrameDuration not available in global --- files/lua_api/openmw/core.lua | 2 +- files/lua_api/openmw/world.lua | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 9fce8e70a8..88f2a63371 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -52,7 +52,7 @@ -- @return #number --- --- Frame duration in seconds +-- Frame duration in seconds. Not available in global scripts. -- @function [parent=#core] getRealFrameDuration -- @return #number diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index f6269375ec..b497dfeacd 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -118,11 +118,6 @@ -- @function [parent=#world] setGameTimeScale -- @param #number ratio ---- --- Frame duration in seconds --- @function [parent=#world] getRealFrameDuration --- @return #number - --- -- Whether the world is paused (onUpdate doesn't work when the world is paused). -- @function [parent=#world] isWorldPaused