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&);