mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-02-22 21:40:42 +00:00
Merge branch 'lua_settings' into 'master'
Permanent storage for Lua See merge request OpenMW/openmw!1489
This commit is contained in:
commit
c07fb75bf7
@ -881,6 +881,7 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings)
|
||||
}
|
||||
|
||||
mLuaManager->init();
|
||||
mLuaManager->loadPermanentStorage(mCfgMgr.getUserConfigPath().string());
|
||||
}
|
||||
|
||||
class OMW::Engine::LuaWorker
|
||||
@ -1103,6 +1104,7 @@ void OMW::Engine::go()
|
||||
|
||||
// Save user settings
|
||||
settings.saveUser(settingspath);
|
||||
mLuaManager->savePermanentStorage(mCfgMgr.getUserConfigPath().string());
|
||||
|
||||
Log(Debug::Info) << "Quitting peacefully.";
|
||||
}
|
||||
|
@ -6,7 +6,9 @@
|
||||
|
||||
#include "../mwbase/environment.hpp"
|
||||
#include "../mwbase/statemanager.hpp"
|
||||
#include "../mwworld/esmstore.hpp"
|
||||
#include "../mwworld/inventorystore.hpp"
|
||||
#include "../mwworld/store.hpp"
|
||||
|
||||
#include "eventqueue.hpp"
|
||||
#include "worldview.hpp"
|
||||
@ -47,7 +49,7 @@ namespace MWLua
|
||||
{
|
||||
auto* lua = context.mLua;
|
||||
sol::table api(lua->sol(), sol::create);
|
||||
api["API_REVISION"] = 13;
|
||||
api["API_REVISION"] = 14;
|
||||
api["quit"] = [lua]()
|
||||
{
|
||||
Log(Debug::Warning) << "Quit requested by a Lua script.\n" << lua->debugTraceback();
|
||||
@ -85,6 +87,17 @@ namespace MWLua
|
||||
{"Ammunition", MWWorld::InventoryStore::Slot_Ammunition}
|
||||
}));
|
||||
api["i18n"] = [i18n=context.mI18n](const std::string& context) { return i18n->getContext(context); };
|
||||
const MWWorld::Store<ESM::GameSetting>* gmst = &MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>();
|
||||
api["getGMST"] = [lua=context.mLua, gmst](const std::string& setting) -> sol::object
|
||||
{
|
||||
const ESM::Variant& value = gmst->find(setting)->mValue;
|
||||
if (value.getType() == ESM::VT_String)
|
||||
return sol::make_object<std::string>(lua->sol(), value.getString());
|
||||
else if (value.getType() == ESM::VT_Int)
|
||||
return sol::make_object<int>(lua->sol(), value.getInteger());
|
||||
else
|
||||
return sol::make_object<float>(lua->sol(), value.getFloat());
|
||||
};
|
||||
return LuaUtil::makeReadOnly(api);
|
||||
}
|
||||
|
||||
@ -163,5 +176,29 @@ namespace MWLua
|
||||
return LuaUtil::makeReadOnly(res);
|
||||
}
|
||||
|
||||
sol::table initGlobalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage)
|
||||
{
|
||||
sol::table res(context.mLua->sol(), sol::create);
|
||||
res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getMutableSection(section); };
|
||||
res["allGlobalSections"] = [globalStorage]() { return globalStorage->getAllSections(); };
|
||||
return LuaUtil::makeReadOnly(res);
|
||||
}
|
||||
|
||||
sol::table initLocalStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage)
|
||||
{
|
||||
sol::table res(context.mLua->sol(), sol::create);
|
||||
res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); };
|
||||
return LuaUtil::makeReadOnly(res);
|
||||
}
|
||||
|
||||
sol::table initPlayerStoragePackage(const Context& context, LuaUtil::LuaStorage* globalStorage, LuaUtil::LuaStorage* playerStorage)
|
||||
{
|
||||
sol::table res(context.mLua->sol(), sol::create);
|
||||
res["globalSection"] = [globalStorage](std::string_view section) { return globalStorage->getReadOnlySection(section); };
|
||||
res["playerSection"] = [playerStorage](std::string_view section) { return playerStorage->getMutableSection(section); };
|
||||
res["allPlayerSections"] = [playerStorage]() { return playerStorage->getAllSections(); };
|
||||
return LuaUtil::makeReadOnly(res);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/lua/serialization.hpp>
|
||||
#include <components/lua/scriptscontainer.hpp>
|
||||
#include <components/lua/storage.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
#include "eventqueue.hpp"
|
||||
@ -25,6 +26,10 @@ namespace MWLua
|
||||
|
||||
sol::table initFieldGroup(const Context&, const QueryFieldGroup&);
|
||||
|
||||
sol::table initGlobalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage);
|
||||
sol::table initLocalStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage);
|
||||
sol::table initPlayerStoragePackage(const Context&, LuaUtil::LuaStorage* globalStorage, LuaUtil::LuaStorage* playerStorage);
|
||||
|
||||
// Implemented in nearbybindings.cpp
|
||||
sol::table initNearbyPackage(const Context&);
|
||||
|
||||
@ -65,7 +70,6 @@ namespace MWLua
|
||||
|
||||
// Implemented in settingsbindings.cpp
|
||||
sol::table initGlobalSettingsPackage(const Context&);
|
||||
sol::table initLocalSettingsPackage(const Context&);
|
||||
sol::table initPlayerSettingsPackage(const Context&);
|
||||
|
||||
// openmw.self package is implemented in localscripts.cpp
|
||||
|
@ -1,5 +1,7 @@
|
||||
#include "luamanagerimp.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
#include <components/esm/esmreader.hpp>
|
||||
@ -69,6 +71,7 @@ namespace MWLua
|
||||
initObjectBindingsForLocalScripts(localContext);
|
||||
initCellBindingsForLocalScripts(localContext);
|
||||
LocalScripts::initializeSelfPackage(localContext);
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua.sol());
|
||||
|
||||
mLua.addCommonPackage("openmw.async", getAsyncPackageInitializer(context));
|
||||
mLua.addCommonPackage("openmw.util", LuaUtil::initUtilPackage(mLua.sol()));
|
||||
@ -76,17 +79,37 @@ namespace MWLua
|
||||
mLua.addCommonPackage("openmw.query", initQueryPackage(context));
|
||||
mGlobalScripts.addPackage("openmw.world", initWorldPackage(context));
|
||||
mGlobalScripts.addPackage("openmw.settings", initGlobalSettingsPackage(context));
|
||||
mGlobalScripts.addPackage("openmw.storage", initGlobalStoragePackage(context, &mGlobalStorage));
|
||||
mCameraPackage = initCameraPackage(localContext);
|
||||
mUserInterfacePackage = initUserInterfacePackage(localContext);
|
||||
mInputPackage = initInputPackage(localContext);
|
||||
mNearbyPackage = initNearbyPackage(localContext);
|
||||
mLocalSettingsPackage = initLocalSettingsPackage(localContext);
|
||||
mLocalSettingsPackage = initGlobalSettingsPackage(localContext);
|
||||
mPlayerSettingsPackage = initPlayerSettingsPackage(localContext);
|
||||
mLocalStoragePackage = initLocalStoragePackage(localContext, &mGlobalStorage);
|
||||
mPlayerStoragePackage = initPlayerStoragePackage(localContext, &mGlobalStorage, &mPlayerStorage);
|
||||
|
||||
initConfiguration();
|
||||
mInitialized = true;
|
||||
}
|
||||
|
||||
void LuaManager::loadPermanentStorage(const std::string& userConfigPath)
|
||||
{
|
||||
auto globalPath = std::filesystem::path(userConfigPath) / "global_storage.bin";
|
||||
auto playerPath = std::filesystem::path(userConfigPath) / "player_storage.bin";
|
||||
if (std::filesystem::exists(globalPath))
|
||||
mGlobalStorage.load(globalPath.string());
|
||||
if (std::filesystem::exists(playerPath))
|
||||
mPlayerStorage.load(playerPath.string());
|
||||
}
|
||||
|
||||
void LuaManager::savePermanentStorage(const std::string& userConfigPath)
|
||||
{
|
||||
std::filesystem::path confDir(userConfigPath);
|
||||
mGlobalStorage.save((confDir / "global_storage.bin").string());
|
||||
mPlayerStorage.save((confDir / "player_storage.bin").string());
|
||||
}
|
||||
|
||||
void LuaManager::update()
|
||||
{
|
||||
if (mPlayer.isEmpty())
|
||||
@ -232,6 +255,8 @@ namespace MWLua
|
||||
mPlayer = MWWorld::Ptr();
|
||||
}
|
||||
clearUserInterface();
|
||||
mGlobalStorage.clearTemporary();
|
||||
mPlayerStorage.clearTemporary();
|
||||
}
|
||||
|
||||
void LuaManager::setupPlayer(const MWWorld::Ptr& ptr)
|
||||
@ -353,11 +378,13 @@ namespace MWLua
|
||||
scripts->addPackage("openmw.camera", mCameraPackage);
|
||||
scripts->addPackage("openmw.input", mInputPackage);
|
||||
scripts->addPackage("openmw.settings", mPlayerSettingsPackage);
|
||||
scripts->addPackage("openmw.storage", mPlayerStoragePackage);
|
||||
}
|
||||
else
|
||||
{
|
||||
scripts = std::make_shared<LocalScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()), flag);
|
||||
scripts->addPackage("openmw.settings", mLocalSettingsPackage);
|
||||
scripts->addPackage("openmw.storage", mLocalStoragePackage);
|
||||
}
|
||||
scripts->addPackage("openmw.nearby", mNearbyPackage);
|
||||
scripts->setSerializer(mLocalSerializer.get());
|
||||
|
@ -4,8 +4,9 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/lua/i18n.hpp>
|
||||
#include <components/lua/luastate.hpp>
|
||||
#include <components/lua/storage.hpp>
|
||||
|
||||
#include "../mwbase/luamanager.hpp"
|
||||
|
||||
@ -28,6 +29,9 @@ namespace MWLua
|
||||
// Called by engine.cpp when the environment is fully initialized.
|
||||
void init();
|
||||
|
||||
void loadPermanentStorage(const std::string& userConfigPath);
|
||||
void savePermanentStorage(const std::string& userConfigPath);
|
||||
|
||||
// Called by engine.cpp every frame. For performance reasons it works in a separate
|
||||
// thread (in parallel with osg Cull). Can not use scene graph.
|
||||
void update();
|
||||
@ -99,6 +103,8 @@ namespace MWLua
|
||||
sol::table mInputPackage;
|
||||
sol::table mLocalSettingsPackage;
|
||||
sol::table mPlayerSettingsPackage;
|
||||
sol::table mLocalStoragePackage;
|
||||
sol::table mPlayerStoragePackage;
|
||||
|
||||
GlobalScripts mGlobalScripts{&mLua};
|
||||
std::set<LocalScripts*> mActiveLocalScripts;
|
||||
@ -139,6 +145,9 @@ namespace MWLua
|
||||
std::vector<std::unique_ptr<Action>> mActionQueue;
|
||||
std::unique_ptr<TeleportAction> mTeleportPlayerAction;
|
||||
std::vector<std::string> mUIMessages;
|
||||
|
||||
LuaUtil::LuaStorage mGlobalStorage{mLua.sol()};
|
||||
LuaUtil::LuaStorage mPlayerStorage{mLua.sol()};
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
namespace MWLua
|
||||
{
|
||||
|
||||
static sol::table initSettingsPackage(const Context& context, bool /*global*/, bool player)
|
||||
static sol::table initSettingsPackage(const Context& context, bool player)
|
||||
{
|
||||
LuaUtil::LuaState* lua = context.mLua;
|
||||
sol::table config(lua->sol(), sol::create);
|
||||
@ -65,8 +65,7 @@ namespace MWLua
|
||||
return LuaUtil::makeReadOnly(config);
|
||||
}
|
||||
|
||||
sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, true, false); }
|
||||
sol::table initLocalSettingsPackage(const Context& context) { return initSettingsPackage(context, false, false); }
|
||||
sol::table initPlayerSettingsPackage(const Context& context) { return initSettingsPackage(context, false, true); }
|
||||
sol::table initGlobalSettingsPackage(const Context& context) { return initSettingsPackage(context, false); }
|
||||
sol::table initPlayerSettingsPackage(const Context& context) { return initSettingsPackage(context, true); }
|
||||
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ if (GTEST_FOUND AND GMOCK_FOUND)
|
||||
lua/test_querypackage.cpp
|
||||
lua/test_configuration.cpp
|
||||
lua/test_i18n.cpp
|
||||
lua/test_storage.cpp
|
||||
|
||||
lua/test_ui_content.cpp
|
||||
|
||||
|
103
apps/openmw_test_suite/lua/test_storage.cpp
Normal file
103
apps/openmw_test_suite/lua/test_storage.cpp
Normal file
@ -0,0 +1,103 @@
|
||||
#include <filesystem>
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <components/lua/storage.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
using namespace testing;
|
||||
|
||||
template <typename T>
|
||||
T get(sol::state& lua, std::string luaCode)
|
||||
{
|
||||
return lua.safe_script("return " + luaCode).get<T>();
|
||||
}
|
||||
|
||||
TEST(LuaUtilStorageTest, Basic)
|
||||
{
|
||||
sol::state mLua;
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua);
|
||||
LuaUtil::LuaStorage storage(mLua);
|
||||
mLua["mutable"] = storage.getMutableSection("test");
|
||||
mLua["ro"] = storage.getReadOnlySection("test");
|
||||
|
||||
mLua.safe_script("mutable:set('x', 5)");
|
||||
EXPECT_EQ(get<int>(mLua, "mutable:get('x')"), 5);
|
||||
EXPECT_EQ(get<int>(mLua, "ro:get('x')"), 5);
|
||||
EXPECT_FALSE(get<bool>(mLua, "mutable:wasChanged()"));
|
||||
EXPECT_TRUE(get<bool>(mLua, "ro:wasChanged()"));
|
||||
EXPECT_FALSE(get<bool>(mLua, "ro:wasChanged()"));
|
||||
|
||||
EXPECT_THROW(mLua.safe_script("ro:set('y', 3)"), std::exception);
|
||||
|
||||
mLua.safe_script("t1 = mutable:asTable()");
|
||||
mLua.safe_script("t2 = ro:asTable()");
|
||||
EXPECT_EQ(get<int>(mLua, "t1.x"), 5);
|
||||
EXPECT_EQ(get<int>(mLua, "t2.x"), 5);
|
||||
|
||||
mLua.safe_script("mutable:reset()");
|
||||
EXPECT_TRUE(get<bool>(mLua, "ro:get('x') == nil"));
|
||||
|
||||
mLua.safe_script("mutable:reset({x=4, y=7})");
|
||||
EXPECT_EQ(get<int>(mLua, "ro:get('x')"), 4);
|
||||
EXPECT_EQ(get<int>(mLua, "ro:get('y')"), 7);
|
||||
EXPECT_FALSE(get<bool>(mLua, "mutable:wasChanged()"));
|
||||
EXPECT_TRUE(get<bool>(mLua, "ro:wasChanged()"));
|
||||
EXPECT_FALSE(get<bool>(mLua, "ro:wasChanged()"));
|
||||
}
|
||||
|
||||
TEST(LuaUtilStorageTest, Table)
|
||||
{
|
||||
sol::state mLua;
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua);
|
||||
LuaUtil::LuaStorage storage(mLua);
|
||||
mLua["mutable"] = storage.getMutableSection("test");
|
||||
mLua["ro"] = storage.getReadOnlySection("test");
|
||||
|
||||
mLua.safe_script("mutable:set('x', { y = 'abc', z = 7 })");
|
||||
EXPECT_EQ(get<int>(mLua, "mutable:get('x').z"), 7);
|
||||
EXPECT_THROW(mLua.safe_script("mutable:get('x').z = 3"), std::exception);
|
||||
EXPECT_NO_THROW(mLua.safe_script("mutable:getCopy('x').z = 3"));
|
||||
EXPECT_EQ(get<int>(mLua, "mutable:get('x').z"), 7);
|
||||
EXPECT_EQ(get<int>(mLua, "ro:get('x').z"), 7);
|
||||
EXPECT_EQ(get<std::string>(mLua, "ro:get('x').y"), "abc");
|
||||
}
|
||||
|
||||
TEST(LuaUtilStorageTest, Saving)
|
||||
{
|
||||
sol::state mLua;
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua);
|
||||
LuaUtil::LuaStorage storage(mLua);
|
||||
|
||||
mLua["permanent"] = storage.getMutableSection("permanent");
|
||||
mLua["temporary"] = storage.getMutableSection("temporary");
|
||||
mLua.safe_script("temporary:removeOnExit()");
|
||||
mLua.safe_script("permanent:set('x', 1)");
|
||||
mLua.safe_script("temporary:set('y', 2)");
|
||||
|
||||
std::string tmpFile = (std::filesystem::temp_directory_path() / "test_storage.bin").string();
|
||||
storage.save(tmpFile);
|
||||
EXPECT_EQ(get<int>(mLua, "permanent:get('x')"), 1);
|
||||
EXPECT_EQ(get<int>(mLua, "temporary:get('y')"), 2);
|
||||
|
||||
storage.clearTemporary();
|
||||
mLua["permanent"] = storage.getMutableSection("permanent");
|
||||
mLua["temporary"] = storage.getMutableSection("temporary");
|
||||
EXPECT_EQ(get<int>(mLua, "permanent:get('x')"), 1);
|
||||
EXPECT_TRUE(get<bool>(mLua, "temporary:get('y') == nil"));
|
||||
|
||||
mLua.safe_script("permanent:set('x', 3)");
|
||||
mLua.safe_script("permanent:set('z', 4)");
|
||||
|
||||
LuaUtil::LuaStorage storage2(mLua);
|
||||
mLua["permanent"] = storage2.getMutableSection("permanent");
|
||||
mLua["temporary"] = storage2.getMutableSection("temporary");
|
||||
|
||||
storage2.load(tmpFile);
|
||||
EXPECT_EQ(get<int>(mLua, "permanent:get('x')"), 1);
|
||||
EXPECT_TRUE(get<bool>(mLua, "permanent:get('z') == nil"));
|
||||
EXPECT_TRUE(get<bool>(mLua, "temporary:get('y') == nil"));
|
||||
}
|
||||
|
||||
}
|
@ -29,7 +29,7 @@ endif (GIT_CHECKOUT)
|
||||
# source files
|
||||
|
||||
add_component_dir (lua
|
||||
luastate scriptscontainer utilpackage serialization configuration i18n
|
||||
luastate scriptscontainer utilpackage serialization configuration i18n storage
|
||||
)
|
||||
|
||||
add_component_dir (settings
|
||||
@ -160,7 +160,7 @@ add_component_dir (fallback
|
||||
add_component_dir (queries
|
||||
query luabindings
|
||||
)
|
||||
|
||||
|
||||
add_component_dir (lua_ui
|
||||
widget widgetlist element layers content
|
||||
text textedit window
|
||||
|
198
components/lua/storage.cpp
Normal file
198
components/lua/storage.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
#include "storage.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
||||
namespace sol
|
||||
{
|
||||
template <>
|
||||
struct is_automagical<LuaUtil::LuaStorage::SectionMutableView> : std::false_type {};
|
||||
template <>
|
||||
struct is_automagical<LuaUtil::LuaStorage::SectionReadOnlyView> : std::false_type {};
|
||||
}
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
LuaStorage::Value LuaStorage::Section::sEmpty;
|
||||
|
||||
sol::object LuaStorage::Value::getCopy(lua_State* L) const
|
||||
{
|
||||
return deserialize(L, mSerializedValue);
|
||||
}
|
||||
|
||||
sol::object LuaStorage::Value::getReadOnly(lua_State* L) const
|
||||
{
|
||||
if (mReadOnlyValue == sol::nil && !mSerializedValue.empty())
|
||||
mReadOnlyValue = deserialize(L, mSerializedValue, nullptr, true);
|
||||
return mReadOnlyValue;
|
||||
}
|
||||
|
||||
const LuaStorage::Value& LuaStorage::Section::get(std::string_view key) const
|
||||
{
|
||||
auto it = mValues.find(key);
|
||||
if (it != mValues.end())
|
||||
return it->second;
|
||||
else
|
||||
return sEmpty;
|
||||
}
|
||||
|
||||
void LuaStorage::Section::set(std::string_view key, const sol::object& value)
|
||||
{
|
||||
mValues[std::string(key)] = Value(value);
|
||||
mChangeCounter++;
|
||||
if (mStorage->mListener)
|
||||
(*mStorage->mListener)(mSectionName, key, value);
|
||||
}
|
||||
|
||||
bool LuaStorage::Section::wasChanged(int64_t& lastCheck)
|
||||
{
|
||||
bool res = lastCheck < mChangeCounter;
|
||||
lastCheck = mChangeCounter;
|
||||
return res;
|
||||
}
|
||||
|
||||
sol::table LuaStorage::Section::asTable()
|
||||
{
|
||||
sol::table res(mStorage->mLua, sol::create);
|
||||
for (const auto& [k, v] : mValues)
|
||||
res[k] = v.getCopy(mStorage->mLua);
|
||||
return res;
|
||||
}
|
||||
|
||||
void LuaStorage::initLuaBindings(lua_State* L)
|
||||
{
|
||||
sol::state_view lua(L);
|
||||
sol::usertype<SectionReadOnlyView> roView = lua.new_usertype<SectionReadOnlyView>("ReadOnlySection");
|
||||
sol::usertype<SectionMutableView> mutableView = lua.new_usertype<SectionMutableView>("MutableSection");
|
||||
roView["get"] = [](sol::this_state s, SectionReadOnlyView& section, std::string_view key)
|
||||
{
|
||||
return section.mSection->get(key).getReadOnly(s);
|
||||
};
|
||||
roView["getCopy"] = [](sol::this_state s, SectionReadOnlyView& section, std::string_view key)
|
||||
{
|
||||
return section.mSection->get(key).getCopy(s);
|
||||
};
|
||||
roView["wasChanged"] = [](SectionReadOnlyView& section) { return section.mSection->wasChanged(section.mLastCheck); };
|
||||
roView["asTable"] = [](SectionReadOnlyView& section) { return section.mSection->asTable(); };
|
||||
mutableView["get"] = [](sol::this_state s, SectionMutableView& section, std::string_view key)
|
||||
{
|
||||
return section.mSection->get(key).getReadOnly(s);
|
||||
};
|
||||
mutableView["getCopy"] = [](sol::this_state s, SectionMutableView& section, std::string_view key)
|
||||
{
|
||||
return section.mSection->get(key).getCopy(s);
|
||||
};
|
||||
mutableView["wasChanged"] = [](SectionMutableView& section) { return section.mSection->wasChanged(section.mLastCheck); };
|
||||
mutableView["asTable"] = [](SectionMutableView& section) { return section.mSection->asTable(); };
|
||||
mutableView["reset"] = [](SectionMutableView& section, sol::optional<sol::table> newValues)
|
||||
{
|
||||
section.mSection->mValues.clear();
|
||||
if (newValues)
|
||||
{
|
||||
for (const auto& [k, v] : *newValues)
|
||||
{
|
||||
try
|
||||
{
|
||||
section.mSection->set(k.as<std::string_view>(), v);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "LuaUtil::LuaStorage::Section::reset(table): " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
section.mSection->mChangeCounter++;
|
||||
section.mLastCheck = section.mSection->mChangeCounter;
|
||||
};
|
||||
mutableView["removeOnExit"] = [](SectionMutableView& section) { section.mSection->mPermanent = false; };
|
||||
mutableView["set"] = [](SectionMutableView& section, std::string_view key, const sol::object& value)
|
||||
{
|
||||
if (section.mLastCheck == section.mSection->mChangeCounter)
|
||||
section.mLastCheck++;
|
||||
section.mSection->set(key, value);
|
||||
};
|
||||
}
|
||||
|
||||
void LuaStorage::clearTemporary()
|
||||
{
|
||||
auto it = mData.begin();
|
||||
while (it != mData.end())
|
||||
{
|
||||
if (!it->second->mPermanent)
|
||||
it = mData.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
void LuaStorage::load(const std::string& path)
|
||||
{
|
||||
mData.clear();
|
||||
try
|
||||
{
|
||||
Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << std::filesystem::file_size(path) << " bytes)";
|
||||
std::ifstream fin(path, std::fstream::binary);
|
||||
std::string serializedData((std::istreambuf_iterator<char>(fin)), std::istreambuf_iterator<char>());
|
||||
sol::table data = deserialize(mLua, serializedData);
|
||||
for (const auto& [sectionName, sectionTable] : data)
|
||||
{
|
||||
Section* section = getSection(sectionName.as<std::string_view>());
|
||||
for (const auto& [key, value] : sol::table(sectionTable))
|
||||
section->set(key.as<std::string_view>(), value);
|
||||
}
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << "Can not read \"" << path << "\": " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void LuaStorage::save(const std::string& path) const
|
||||
{
|
||||
sol::table data(mLua, sol::create);
|
||||
for (const auto& [sectionName, section] : mData)
|
||||
{
|
||||
if (section->mPermanent)
|
||||
data[sectionName] = section->asTable();
|
||||
}
|
||||
std::string serializedData = serialize(data);
|
||||
Log(Debug::Info) << "Saving Lua storage \"" << path << "\" (" << serializedData.size() << " bytes)";
|
||||
std::ofstream fout(path, std::fstream::binary);
|
||||
fout.write(serializedData.data(), serializedData.size());
|
||||
fout.close();
|
||||
}
|
||||
|
||||
LuaStorage::Section* LuaStorage::getSection(std::string_view sectionName)
|
||||
{
|
||||
auto it = mData.find(sectionName);
|
||||
if (it != mData.end())
|
||||
return it->second.get();
|
||||
auto section = std::make_unique<Section>(this, std::string(sectionName));
|
||||
sectionName = section->mSectionName;
|
||||
auto [newIt, _] = mData.emplace(sectionName, std::move(section));
|
||||
return newIt->second.get();
|
||||
}
|
||||
|
||||
sol::object LuaStorage::getReadOnlySection(std::string_view sectionName)
|
||||
{
|
||||
Section* section = getSection(sectionName);
|
||||
return sol::make_object<SectionReadOnlyView>(mLua, SectionReadOnlyView{section, section->mChangeCounter});
|
||||
}
|
||||
|
||||
sol::object LuaStorage::getMutableSection(std::string_view sectionName)
|
||||
{
|
||||
Section* section = getSection(sectionName);
|
||||
return sol::make_object<SectionMutableView>(mLua, SectionMutableView{section, section->mChangeCounter});
|
||||
}
|
||||
|
||||
sol::table LuaStorage::getAllSections()
|
||||
{
|
||||
sol::table res(mLua, sol::create);
|
||||
for (const auto& [sectionName, _] : mData)
|
||||
res[sectionName] = getMutableSection(sectionName);
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
81
components/lua/storage.hpp
Normal file
81
components/lua/storage.hpp
Normal file
@ -0,0 +1,81 @@
|
||||
#ifndef COMPONENTS_LUA_STORAGE_H
|
||||
#define COMPONENTS_LUA_STORAGE_H
|
||||
|
||||
#include <map>
|
||||
#include <sol/sol.hpp>
|
||||
|
||||
#include "serialization.hpp"
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
|
||||
class LuaStorage
|
||||
{
|
||||
public:
|
||||
static void initLuaBindings(lua_State*);
|
||||
|
||||
explicit LuaStorage(lua_State* lua) : mLua(lua) {}
|
||||
|
||||
void clearTemporary();
|
||||
void load(const std::string& path);
|
||||
void save(const std::string& path) const;
|
||||
|
||||
sol::object getReadOnlySection(std::string_view sectionName);
|
||||
sol::object getMutableSection(std::string_view sectionName);
|
||||
sol::table getAllSections();
|
||||
|
||||
void set(std::string_view section, std::string_view key, const sol::object& value) { getSection(section)->set(key, value); }
|
||||
|
||||
using ListenerFn = std::function<void(std::string_view, std::string_view, const sol::object&)>;
|
||||
void setListener(ListenerFn fn) { mListener = std::move(fn); }
|
||||
|
||||
private:
|
||||
class Value
|
||||
{
|
||||
public:
|
||||
Value() {}
|
||||
Value(const sol::object& value) : mSerializedValue(serialize(value)) {}
|
||||
sol::object getCopy(lua_State* L) const;
|
||||
sol::object getReadOnly(lua_State* L) const;
|
||||
|
||||
private:
|
||||
std::string mSerializedValue;
|
||||
mutable sol::object mReadOnlyValue = sol::nil;
|
||||
};
|
||||
|
||||
struct Section
|
||||
{
|
||||
explicit Section(LuaStorage* storage, std::string name) : mStorage(storage), mSectionName(std::move(name)) {}
|
||||
const Value& get(std::string_view key) const;
|
||||
void set(std::string_view key, const sol::object& value);
|
||||
bool wasChanged(int64_t& lastCheck);
|
||||
sol::table asTable();
|
||||
|
||||
LuaStorage* mStorage;
|
||||
std::string mSectionName;
|
||||
std::map<std::string, Value, std::less<>> mValues;
|
||||
bool mPermanent = true;
|
||||
int64_t mChangeCounter = 0;
|
||||
static Value sEmpty;
|
||||
};
|
||||
struct SectionMutableView
|
||||
{
|
||||
Section* mSection = nullptr;
|
||||
int64_t mLastCheck = 0;
|
||||
};
|
||||
struct SectionReadOnlyView
|
||||
{
|
||||
Section* mSection = nullptr;
|
||||
int64_t mLastCheck = 0;
|
||||
};
|
||||
|
||||
Section* getSection(std::string_view sectionName);
|
||||
|
||||
lua_State* mLua;
|
||||
std::map<std::string_view, std::unique_ptr<Section>> mData;
|
||||
std::optional<ListenerFn> mListener;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // COMPONENTS_LUA_STORAGE_H
|
@ -8,7 +8,7 @@ Lua API reference
|
||||
engine_handlers
|
||||
user_interface
|
||||
openmw_util
|
||||
openmw_settings
|
||||
openmw_storage
|
||||
openmw_core
|
||||
openmw_async
|
||||
openmw_query
|
||||
@ -45,8 +45,8 @@ Player scripts are local scripts that are attached to a player.
|
||||
|:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, |
|
||||
| | | | that don't depend on the game world. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.settings <Package openmw.settings>` | everywhere | | Access to GMST records in content files (implemented) and |
|
||||
| | | | to mod settings (not implemented). |
|
||||
|:ref:`openmw.storage <Package openmw.storage>` | everywhere | | Storage API. In particular can be used to store data |
|
||||
| | | | between game sessions. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|
@ -1,5 +0,0 @@
|
||||
Package openmw.settings
|
||||
=======================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/openmw_settings.html
|
5
docs/source/reference/lua-scripting/openmw_storage.rst
Normal file
5
docs/source/reference/lua-scripting/openmw_storage.rst
Normal file
@ -0,0 +1,5 @@
|
||||
Package openmw.storage
|
||||
======================
|
||||
|
||||
.. raw:: html
|
||||
:file: generated_html/openmw_storage.html
|
@ -334,8 +334,8 @@ Player scripts are local scripts that are attached to a player.
|
||||
|:ref:`openmw.util <Package openmw.util>` | everywhere | | Defines utility functions and classes like 3D vectors, |
|
||||
| | | | that don't depend on the game world. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.settings <Package openmw.settings>` | everywhere | | Access to GMST records in content files (implemented) and |
|
||||
| | | | to mod settings (not implemented). |
|
||||
|:ref:`openmw.storage <Package openmw.storage>` | everywhere | | Storage API. In particular can be used to store data |
|
||||
| | | | between game sessions. |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|:ref:`openmw.core <Package openmw.core>` | everywhere | | Functions that are common for both global and local scripts |
|
||||
+---------------------------------------------------------+--------------------+---------------------------------------------------------------+
|
||||
|
@ -46,6 +46,12 @@
|
||||
-- @function [parent=#core] isWorldPaused
|
||||
-- @return #boolean
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Get a GMST setting from content files.
|
||||
-- @function [parent=#core] getGMST
|
||||
-- @param #string setting Setting name
|
||||
-- @return #any
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Return i18n formatting function for the given context.
|
||||
-- It is based on `i18n.lua` library.
|
||||
|
@ -1,14 +0,0 @@
|
||||
-------------------------------------------------------------------------------
|
||||
-- `openmw.settings` provides read-only access to GMST records in content files.
|
||||
-- @module settings
|
||||
-- @usage
|
||||
-- local settings = require('openmw.settings')
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Get a GMST setting from content files.
|
||||
-- @function [parent=#settings] getGMST
|
||||
-- @param #string setting
|
||||
|
||||
|
||||
return nil
|
||||
|
96
files/lua_api/openmw/storage.lua
Normal file
96
files/lua_api/openmw/storage.lua
Normal file
@ -0,0 +1,96 @@
|
||||
---
|
||||
-- `openmw.storage` contains functions to work with permanent Lua storage.
|
||||
-- @module storage
|
||||
-- @usage
|
||||
-- local storage = require('openmw.storage')
|
||||
-- local myModData = storage.globalSection('MyModExample')
|
||||
-- myModData:set("someVariable", 1.0)
|
||||
-- myModData:set("anotherVariable", { exampleStr='abc', exampleBool=true })
|
||||
-- local function update()
|
||||
-- if myModCfg:checkChanged() then
|
||||
-- print('Data was changes by another script:')
|
||||
-- print('MyModExample.someVariable =', myModData:get('someVariable'))
|
||||
-- print('MyModExample.anotherVariable.exampleStr =',
|
||||
-- myModData:get('anotherVariable').exampleStr)
|
||||
-- end
|
||||
-- end
|
||||
|
||||
---
|
||||
-- Get a section of the global storage; can be used by any script, but only global scripts can change values.
|
||||
-- Creates the section if it doesn't exist.
|
||||
-- @function [parent=#storage] globalSection
|
||||
-- @param #string sectionName
|
||||
-- @return #StorageSection
|
||||
|
||||
---
|
||||
-- Get a section of the player storage; can be used by player scripts only.
|
||||
-- Creates the section if it doesn't exist.
|
||||
-- @function [parent=#storage] playerSection
|
||||
-- @param #string sectionName
|
||||
-- @return #StorageSection
|
||||
|
||||
---
|
||||
-- Get all global sections as a table; can be used by global scripts only.
|
||||
-- Note that adding/removing items to the returned table doesn't create or remove sections.
|
||||
-- @function [parent=#storage] allGlobalSections
|
||||
-- @return #table
|
||||
|
||||
---
|
||||
-- Get all global sections as a table; can be used by player scripts only.
|
||||
-- Note that adding/removing items to the returned table doesn't create or remove sections.
|
||||
-- @function [parent=#storage] allPlayerSections
|
||||
-- @return #table
|
||||
|
||||
---
|
||||
-- A map `key -> value` that represents a storage section.
|
||||
-- @type StorageSection
|
||||
|
||||
---
|
||||
-- Get value by a string key; if value is a table makes it readonly.
|
||||
-- @function [parent=#StorageSection] get
|
||||
-- @param self
|
||||
-- @param #string key
|
||||
|
||||
---
|
||||
-- Get value by a string key; if value is a table returns a copy.
|
||||
-- @function [parent=#StorageSection] getCopy
|
||||
-- @param self
|
||||
-- @param #string key
|
||||
|
||||
---
|
||||
-- Return `True` if any value in this section was changed by another script since the last `wasChanged`.
|
||||
-- @function [parent=#StorageSection] wasChanged
|
||||
-- @param self
|
||||
-- @return #boolean
|
||||
|
||||
---
|
||||
-- Copy all values and return them as a table.
|
||||
-- @function [parent=#StorageSection] asTable
|
||||
-- @param self
|
||||
-- @return #table
|
||||
|
||||
---
|
||||
-- Remove all existing values and assign values from given (the arg is optional) table.
|
||||
-- Note: `section:reset()` removes all values, but not the section itself. Use `section:removeOnExit()` to remove the section completely.
|
||||
-- @function [parent=#StorageSection] reset
|
||||
-- @param self
|
||||
-- @param #table values (optional) New values
|
||||
|
||||
---
|
||||
-- Make the whole section temporary: will be removed on exit or when load a save.
|
||||
-- No section can be removed immediately because other scripts may use it at the moment.
|
||||
-- Temporary sections have the same interface to get/set values, the only difference is they will not
|
||||
-- be saved to the permanent storage on exit.
|
||||
-- This function can not be used for a global storage section from a local script.
|
||||
-- @function [parent=#StorageSection] removeOnExit
|
||||
-- @param self
|
||||
|
||||
---
|
||||
-- Set value by a string key; can not be used for global storage from a local script.
|
||||
-- @function [parent=#StorageSection] set
|
||||
-- @param self
|
||||
-- @param #string key
|
||||
-- @param #any value
|
||||
|
||||
return nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user