Something went wrong. Try again.
OpenMW is an open implementation of the Morrowind engine
Something went wrong. Try again.
14 kB · 335 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336#include "storage.hpp"
#include <filesystem>#include <fstream>
#include <components/debug/debuglog.hpp>
#include "luastate.hpp"
namespace sol{ template <> struct is_automagical<LuaUtil::LuaStorage::SectionView> : std::false_type { };}
namespace LuaUtil{ LuaStorage::Value LuaStorage::Section::sEmpty;
void LuaStorage::registerLifeTime(LuaUtil::LuaView& view, sol::table& res) { res["LIFE_TIME"] = LuaUtil::makeStrictReadOnly(tableFromPairs<std::string_view, Section::LifeTime>(view.sol(), { { "Persistent", Section::LifeTime::Persistent }, { "GameSession", Section::LifeTime::GameSession }, { "Temporary", Section::LifeTime::Temporary }, })); }
sol::object LuaStorage::Value::getCopy(lua_State* state) const { return deserialize(state, mSerializedValue); }
sol::object LuaStorage::Value::getReadOnly(lua_State* state) const { if (mReadOnlyValue == sol::nil && !mSerializedValue.empty()) mReadOnlyValue = sol::main_object(deserialize(state, mSerializedValue, nullptr, true)); return mReadOnlyValue; }
const LuaStorage::Value& LuaStorage::Section::get(std::string_view key) const { checkIfActive(); auto it = mValues.find(key); if (it != mValues.end()) return it->second; else return sEmpty; }
void LuaStorage::Section::runCallbacks(sol::optional<std::string_view> changedKey) { mStorage->mRunningCallbacks.insert(this); mCallbacks.erase(std::remove_if(mCallbacks.begin(), mCallbacks.end(), [&](const Callback& callback) { bool valid = callback.isValid(); if (valid) callback.tryCall(mSectionName, changedKey); return !valid; }), mCallbacks.end()); mMenuScriptsCallbacks.erase(std::remove_if(mMenuScriptsCallbacks.begin(), mMenuScriptsCallbacks.end(), [&](const Callback& callback) { bool valid = callback.isValid(); if (valid) callback.tryCall(mSectionName, changedKey); return !valid; }), mMenuScriptsCallbacks.end()); mStorage->mRunningCallbacks.erase(this); }
void LuaStorage::Section::throwIfCallbackRecursionIsTooDeep() { if (mStorage->mRunningCallbacks.count(this) > 0) throw std::runtime_error( "Storage handler shouldn't change the storage section it handles (leads to an infinite recursion)"); if (mStorage->mRunningCallbacks.size() > 10) throw std::runtime_error( "Too many subscribe callbacks triggering in a chain, likely an infinite recursion"); }
void LuaStorage::Section::set(std::string_view key, const sol::object& value) { checkIfActive(); throwIfCallbackRecursionIsTooDeep(); if (value != sol::nil) mValues[std::string(key)] = Value(value); else { auto it = mValues.find(key); if (it != mValues.end()) mValues.erase(it); } if (mStorage->mListener) mStorage->mListener->valueChanged(mSectionName, key, value); runCallbacks(key); }
void LuaStorage::Section::setAll(const sol::optional<sol::table>& values) { checkIfActive(); throwIfCallbackRecursionIsTooDeep(); mValues.clear(); if (values) { for (const auto& [k, v] : *values) mValues[cast<std::string>(k)] = Value(v); } if (mStorage->mListener) mStorage->mListener->sectionReplaced(mSectionName, values); runCallbacks(sol::nullopt); }
sol::table LuaStorage::Section::asTable(lua_State* state) { checkIfActive(); sol::table res(state, sol::create); for (const auto& [k, v] : mValues) res[k] = v.getCopy(state); return res; }
void LuaStorage::initLuaBindings(LuaUtil::LuaView& view) { sol::usertype<SectionView> sview = view.sol().new_usertype<SectionView>("Section"); sview["get"] = [](sol::this_state s, const SectionView& section, std::string_view key) { return section.mSection->get(key).getReadOnly(s); }; sview["getCopy"] = [](sol::this_state s, const SectionView& section, std::string_view key) { return section.mSection->get(key).getCopy(s); }; sview["asTable"] = [](sol::this_state lua, const SectionView& section) { return section.mSection->asTable(lua); }; sview["subscribe"] = [](const SectionView& section, const sol::table& callback) { std::vector<Callback>& callbacks = section.mForMenuScripts ? section.mSection->mMenuScriptsCallbacks : section.mSection->mCallbacks; if (!callbacks.empty() && callbacks.size() == callbacks.capacity()) { callbacks.erase( std::remove_if(callbacks.begin(), callbacks.end(), [&](const Callback& c) { return !c.isValid(); }), callbacks.end()); } callbacks.push_back(Callback::fromLua(callback)); }; sview["reset"] = [](const SectionView& section, const sol::optional<sol::table>& newValues) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); section.mSection->setAll(newValues); }; sview["removeOnExit"] = [](const SectionView& section) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); section.mSection->mLifeTime = Section::Temporary; }; sview["setLifeTime"] = [](const SectionView& section, Section::LifeTime lifeTime) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); section.mSection->mLifeTime = lifeTime; }; sview["set"] = [](const SectionView& section, std::string_view key, const sol::object& value) { if (section.mReadOnly) throw std::runtime_error("Access to storage is read only"); section.mSection->set(key, value); }; }
sol::table LuaStorage::initGlobalPackage(LuaUtil::LuaView& view, LuaStorage* globalStorage) { sol::table res(view.sol(), sol::create); registerLifeTime(view, res);
res["globalSection"] = [globalStorage](sol::this_state lua, std::string_view section) { return globalStorage->getMutableSection(lua, section); }; res["allGlobalSections"] = [globalStorage](sol::this_state lua) { return globalStorage->getAllSections(lua); }; return LuaUtil::makeReadOnly(res); }
sol::table LuaStorage::initLocalPackage(LuaUtil::LuaView& view, LuaStorage* globalStorage) { sol::table res(view.sol(), sol::create); registerLifeTime(view, res);
res["globalSection"] = [globalStorage](sol::this_state lua, std::string_view section) { return globalStorage->getReadOnlySection(lua, section); }; return LuaUtil::makeReadOnly(res); }
sol::table LuaStorage::initPlayerPackage( LuaUtil::LuaView& view, LuaStorage* globalStorage, LuaStorage* playerStorage) { sol::table res(view.sol(), sol::create); registerLifeTime(view, res);
res["globalSection"] = [globalStorage](sol::this_state lua, std::string_view section) { return globalStorage->getReadOnlySection(lua, section); }; res["playerSection"] = [playerStorage](sol::this_state lua, std::string_view section) { return playerStorage->getMutableSection(lua, section); }; res["allPlayerSections"] = [playerStorage](sol::this_state lua) { return playerStorage->getAllSections(lua); }; return LuaUtil::makeReadOnly(res); }
sol::table LuaStorage::initMenuPackage(LuaUtil::LuaView& view, LuaStorage* globalStorage, LuaStorage* playerStorage) { sol::table res(view.sol(), sol::create); registerLifeTime(view, res);
res["playerSection"] = [playerStorage](sol::this_state lua, std::string_view section) { return playerStorage->getMutableSection(lua, section, /*forMenuScripts=*/true); }; res["globalSection"] = [globalStorage](sol::this_state lua, std::string_view section) { return globalStorage->getReadOnlySection(lua, section); }; res["allPlayerSections"] = [playerStorage](sol::this_state lua) { return playerStorage->getAllSections(lua); }; return LuaUtil::makeReadOnly(res); }
sol::table LuaStorage::initLoadPackage(LuaUtil::LuaView& view, LuaStorage* playerStorage) { sol::table res(view.sol(), sol::create); registerLifeTime(view, res);
res["playerSection"] = [playerStorage](sol::this_state lua, std::string_view section) { return playerStorage->getReadOnlySection(lua, section); }; return LuaUtil::makeReadOnly(res); }
void LuaStorage::clearTemporaryAndRemoveCallbacks() { auto it = mData.begin(); while (it != mData.end()) { it->second->mCallbacks.clear(); // Note that we don't clear menu callbacks for permanent sections // because starting/loading a game doesn't reset menu scripts. if (it->second->mLifeTime == Section::Temporary) { it->second->mMenuScriptsCallbacks.clear(); it->second->mValues.clear(); it = mData.erase(it); } else ++it; } }
void LuaStorage::load(lua_State* state, const std::filesystem::path& path) { assert(mData.empty()); // Shouldn't be used before loading try { std::uintmax_t fileSize = std::filesystem::file_size(path); Log(Debug::Info) << "Loading Lua storage \"" << path << "\" (" << fileSize << " bytes)"; if (fileSize == 0) throw std::runtime_error("Storage file has zero length");
std::ifstream fin(path, std::ios::binary); if (!fin) throw std::runtime_error("Failed to open storage file");
std::string serializedData((std::istreambuf_iterator<char>(fin)), std::istreambuf_iterator<char>()); if (serializedData.size() != fileSize) throw std::runtime_error("Failed to read storage file");
sol::object serializedObject = deserialize(state, serializedData); if (!serializedObject.is<sol::table>()) throw std::runtime_error("Storage data is not a table");
sol::table data = serializedObject.as<sol::table>(); for (const auto& [sectionName, sectionTable] : data) { const std::shared_ptr<Section>& section = getSection(cast<std::string_view>(sectionName)); for (const auto& [key, value] : cast<sol::table>(sectionTable)) section->set(cast<std::string_view>(key), value); } } catch (std::exception& e) { Log(Debug::Error) << "Cannot read \"" << path << "\": " << e.what(); } }
void LuaStorage::save(lua_State* state, const std::filesystem::path& path) const { sol::table data(state, sol::create); for (const auto& [sectionName, section] : mData) { if (section->mLifeTime == Section::Persistent && !section->mValues.empty()) data[sectionName] = section->asTable(state); } 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(); }
const std::shared_ptr<LuaStorage::Section>& LuaStorage::getSection(std::string_view sectionName) { checkIfActive(); auto it = mData.find(sectionName); if (it != mData.end()) return it->second; auto section = std::make_shared<Section>(this, std::string(sectionName)); sectionName = section->mSectionName; auto [newIt, _] = mData.emplace(sectionName, std::move(section)); return newIt->second; }
sol::object LuaStorage::getSection( lua_State* state, std::string_view sectionName, bool readOnly, bool forMenuScripts) { checkIfActive(); const std::shared_ptr<Section>& section = getSection(sectionName); return sol::make_object<SectionView>(state, SectionView{ section, readOnly, forMenuScripts }); }
sol::table LuaStorage::getAllSections(lua_State* state, bool readOnly) { checkIfActive(); sol::table res(state, sol::create); for (const auto& [sectionName, _] : mData) res[sectionName] = getSection(state, sectionName, readOnly); return res; }
}