diff --git a/apps/components_tests/lua/test_scriptscontainer.cpp b/apps/components_tests/lua/test_scriptscontainer.cpp index 04397fe767..4f3cca1b87 100644 --- a/apps/components_tests/lua/test_scriptscontainer.cpp +++ b/apps/components_tests/lua/test_scriptscontainer.cpp @@ -161,6 +161,29 @@ return { end } } +)X"); + + constexpr VFS::Path::NormalizedView customDataPath("customdata.lua"); + + VFSTestFile customDataScript(R"X( +data = nil +return { + engineHandlers = { + onSave = function() + return data + end, + onLoad = function(state) + data = state + end, + onInit = function(state) + data = state + end + }, + eventHandlers = { + WakeUp = function() + end + } +} )X"); struct LuaScriptsContainerTest : Test @@ -178,6 +201,7 @@ return { { overrideInterfacePath, &overrideInterfaceScript }, { useInterfacePath, &useInterfaceScript }, { unloadPath, &unloadScript }, + { customDataPath, &customDataScript }, }); LuaUtil::ScriptsConfiguration mCfg; @@ -199,6 +223,7 @@ CUSTOM, PLAYER: testInterface.lua CUSTOM, PLAYER: overrideInterface.lua CUSTOM, PLAYER: useInterface.lua CUSTOM: unload.lua +CUSTOM: customdata.lua )X"); mCfg.init(std::move(cfg)); } @@ -570,4 +595,58 @@ CUSTOM: unload.lua scripts1.load(data); EXPECT_EQ(tracker.size(), 0); } + + TEST_F(LuaScriptsContainerTest, LoadOrderChange) + { + LuaUtil::ScriptTracker tracker; + LuaUtil::ScriptsContainer scripts1(&mLua, "Test", &tracker, false); + LuaUtil::BasicSerializer serializer1; + LuaUtil::BasicSerializer serializer2([](int contentFileIndex) -> int { + if (contentFileIndex == 12) + return 34; + else if (contentFileIndex == 37) + return 12; + return contentFileIndex; + }); + scripts1.setSerializer(&serializer1); + scripts1.setSavedDataDeserializer(&serializer2); + + mLua.protectedCall([&](LuaUtil::LuaView& lua) { + sol::object id1 = sol::make_object_userdata(lua.sol(), ESM::RefNum{ 42, 12 }); + sol::object id2 = sol::make_object_userdata(lua.sol(), ESM::RefNum{ 13, 37 }); + sol::table table = lua.newTable(); + table[id1] = id2; + LuaUtil::BinaryData serialized = LuaUtil::serialize(table, &serializer1); + + EXPECT_TRUE(scripts1.addCustomScript(*mCfg.findId(customDataPath), serialized)); + EXPECT_EQ(tracker.size(), 1); + for (int i = 0; i < 600; ++i) + tracker.unloadInactiveScripts(lua); + EXPECT_EQ(tracker.size(), 0); + scripts1.receiveEvent("WakeUp", {}); + EXPECT_EQ(tracker.size(), 1); + }); + + ESM::LuaScripts data1; + ESM::LuaScripts data2; + scripts1.save(data1); + scripts1.load(data1); + scripts1.save(data2); + EXPECT_NE(data1.mScripts[0].mData, data2.mScripts[0].mData); + + mLua.protectedCall([&](LuaUtil::LuaView& lua) { + sol::object deserialized = LuaUtil::deserialize(lua.sol(), data2.mScripts[0].mData, &serializer1); + EXPECT_TRUE(deserialized.is()); + sol::table table = deserialized; + for (const auto& [key, value] : table) + { + EXPECT_TRUE(key.is()); + EXPECT_TRUE(value.is()); + EXPECT_EQ(key.as(), (ESM::RefNum{ 42, 34 })); + EXPECT_EQ(value.as(), (ESM::RefNum{ 13, 12 })); + return; + } + EXPECT_FALSE(true); + }); + } } diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index b2d1527d9c..780ddaf9a9 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -569,7 +569,6 @@ namespace MWLua scripts->addPackage(name, package); } scripts->setSerializer(mLocalSerializer.get()); - scripts->setSavedDataDeserializer(mLocalLoader.get()); MWWorld::RefData& refData = ptr.getRefData(); refData.setLuaScripts(std::move(scripts)); diff --git a/components/lua/scriptscontainer.cpp b/components/lua/scriptscontainer.cpp index e07f3138d0..11d347dade 100644 --- a/components/lua/scriptscontainer.cpp +++ b/components/lua/scriptscontainer.cpp @@ -485,19 +485,34 @@ namespace LuaUtil if (scriptInfo.mSavedData == nullptr) continue; ESM::LuaScript& script = container.mScripts.emplace_back(*scriptInfo.mSavedData); - for (ESM::LuaTimer& savedTimer : script.mTimers) + if (!script.mData.empty()) { try { - sol::object arg = deserialize(view.sol(), savedTimer.mCallbackArgument, mSavedDataDeserializer); + sol::object state = deserialize(view.sol(), script.mData, mSavedDataDeserializer); + script.mData = serialize(state, mSerializer); + } + catch (std::exception& e) + { + printError(scriptId, "onLoad failed", e); + script.mData.clear(); + } + } + for (auto it = script.mTimers.begin(); it != script.mTimers.end();) + { + try + { + sol::object arg = deserialize(view.sol(), it->mCallbackArgument, mSavedDataDeserializer); // It is important if the order of content files was changed. The deserialize-serialize // procedure updates refnums, so timer.mSerializedArg may be not equal to // savedTimer.mCallbackArgument. - savedTimer.mCallbackArgument = serialize(arg, mSerializer); + it->mCallbackArgument = serialize(arg, mSerializer); + ++it; } catch (std::exception& e) { printError(scriptId, "can not load timer", e); + it = script.mTimers.erase(it); } } } @@ -547,8 +562,7 @@ namespace LuaUtil { try { - sol::object state - = deserialize(view.sol(), scriptInfo.mSavedData->mData, mSavedDataDeserializer); + sol::object state = deserialize(view.sol(), scriptInfo.mSavedData->mData, mSerializer); sol::object initializationData = deserialize(view.sol(), scriptInfo.mInitData, mSerializer); LuaUtil::call({ this, scriptId }, *onLoad, state, initializationData); } @@ -567,8 +581,8 @@ namespace LuaUtil try { - timer.mArg = sol::main_object( - deserialize(view.sol(), savedTimer.mCallbackArgument, mSavedDataDeserializer)); + timer.mArg + = sol::main_object(deserialize(view.sol(), savedTimer.mCallbackArgument, mSerializer)); // It is important if the order of content files was changed. The deserialize-serialize // procedure updates refnums, so timer.mSerializedArg may be not equal to // savedTimer.mCallbackArgument.