mirror of
https://github.com/OpenMW/openmw.git
synced 2025-02-06 07:45:36 +00:00
Track memory and CPU usage per script in LuaUtil::LuaState
This commit is contained in:
parent
f4ac32efc9
commit
6fa65e4729
10 changed files with 319 additions and 50 deletions
|
@ -63,7 +63,7 @@ namespace MWLua
|
|||
= [](const LuaUtil::Callback& callback, sol::variadic_args va) { return callback.call(sol::as_args(va)); };
|
||||
|
||||
auto initializer = [](sol::table hiddenData) {
|
||||
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey];
|
||||
LuaUtil::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey];
|
||||
return AsyncPackageId{ id.mContainer, id.mIndex, hiddenData };
|
||||
};
|
||||
return sol::make_object(context.mLua->sol(), initializer);
|
||||
|
|
|
@ -139,6 +139,10 @@ namespace MWLua
|
|||
|
||||
mWorldView.update();
|
||||
|
||||
mGlobalScripts.CPUusageNextFrame();
|
||||
for (LocalScripts* scripts : mActiveLocalScripts)
|
||||
scripts->CPUusageNextFrame();
|
||||
|
||||
std::vector<GlobalEvent> globalEvents = std::move(mGlobalEvents);
|
||||
std::vector<LocalEvent> localEvents = std::move(mLocalEvents);
|
||||
mGlobalEvents = std::vector<GlobalEvent>();
|
||||
|
|
|
@ -15,41 +15,40 @@ namespace
|
|||
{
|
||||
void SetUp() override
|
||||
{
|
||||
mLua.open_libraries(sol::lib::coroutine);
|
||||
mLua["callback"] = [&](sol::protected_function fn) -> LuaUtil::Callback {
|
||||
sol::table hiddenData(mLua, sol::create);
|
||||
hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = sol::table(mLua, sol::create);
|
||||
mLua.sol()["callback"] = [&](sol::protected_function fn) -> LuaUtil::Callback {
|
||||
sol::table hiddenData(mLua.sol(), sol::create);
|
||||
hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptId{};
|
||||
return LuaUtil::Callback{ std::move(fn), hiddenData };
|
||||
};
|
||||
mLua["pass"] = [this](LuaUtil::Callback callback) { mCb = callback; };
|
||||
mLua.sol()["pass"] = [this](LuaUtil::Callback callback) { mCb = callback; };
|
||||
}
|
||||
|
||||
sol::state mLua;
|
||||
LuaUtil::LuaState mLua{ nullptr, nullptr };
|
||||
LuaUtil::Callback mCb;
|
||||
};
|
||||
|
||||
TEST_F(LuaCoroutineCallbackTest, CoroutineCallbacks)
|
||||
{
|
||||
internal::CaptureStdout();
|
||||
mLua.safe_script(R"X(
|
||||
mLua.sol().safe_script(R"X(
|
||||
local s = 'test'
|
||||
coroutine.wrap(function()
|
||||
pass(callback(function(v) print(s) end))
|
||||
end)()
|
||||
)X");
|
||||
mLua.collect_garbage();
|
||||
mLua.sol().collect_garbage();
|
||||
mCb.call();
|
||||
EXPECT_THAT(internal::GetCapturedStdout(), "test\n");
|
||||
}
|
||||
|
||||
TEST_F(LuaCoroutineCallbackTest, ErrorInCoroutineCallbacks)
|
||||
{
|
||||
mLua.safe_script(R"X(
|
||||
mLua.sol().safe_script(R"X(
|
||||
coroutine.wrap(function()
|
||||
pass(callback(function() error('COROUTINE CALLBACK') end))
|
||||
end)()
|
||||
)X");
|
||||
mLua.collect_garbage();
|
||||
mLua.sol().collect_garbage();
|
||||
EXPECT_ERROR(mCb.call(), "COROUTINE CALLBACK");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,9 +82,9 @@ you_have_arrows: "Arrows count: {count}"
|
|||
|
||||
TEST_F(LuaL10nTest, L10n)
|
||||
{
|
||||
internal::CaptureStdout();
|
||||
LuaUtil::LuaState lua{ mVFS.get(), &mCfg };
|
||||
sol::state& l = lua.sol();
|
||||
internal::CaptureStdout();
|
||||
l10n::Manager l10nManager(mVFS.get());
|
||||
l10nManager.setPreferredLocales({ "de", "en" });
|
||||
EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: de en\n");
|
||||
|
|
|
@ -448,8 +448,7 @@ CUSTOM, PLAYER: useInterface.lua
|
|||
{
|
||||
LuaUtil::Callback callback{ mLua.sol()["print"], mLua.newTable() };
|
||||
callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptDebugNameKey] = "some_script.lua";
|
||||
callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey]
|
||||
= LuaUtil::ScriptsContainer::ScriptId{ nullptr, 0 };
|
||||
callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptId{ nullptr, 0 };
|
||||
|
||||
testing::internal::CaptureStdout();
|
||||
callback.call(1.5);
|
||||
|
|
|
@ -17,7 +17,9 @@ namespace
|
|||
|
||||
TEST(LuaUtilStorageTest, Basic)
|
||||
{
|
||||
sol::state mLua;
|
||||
// Note: LuaUtil::Callback can be used only if Lua is initialized via LuaUtil::LuaState
|
||||
LuaUtil::LuaState luaState{ nullptr, nullptr };
|
||||
sol::state& mLua = luaState.sol();
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua);
|
||||
LuaUtil::LuaStorage storage(mLua);
|
||||
|
||||
|
@ -30,7 +32,7 @@ namespace
|
|||
callbackCalls.push_back(section + "_*");
|
||||
}),
|
||||
sol::table(mLua, sol::create) };
|
||||
callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = "fakeId";
|
||||
callback.mHiddenData[LuaUtil::ScriptsContainer::sScriptIdKey] = LuaUtil::ScriptId{};
|
||||
|
||||
mLua["mutable"] = storage.getMutableSection("test");
|
||||
mLua["ro"] = storage.getReadOnlySection("test");
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include <components/files/conversion.hpp>
|
||||
#include <components/vfs/manager.hpp>
|
||||
|
||||
#include "scriptscontainer.hpp"
|
||||
|
||||
namespace LuaUtil
|
||||
{
|
||||
|
||||
|
@ -50,10 +52,98 @@ namespace LuaUtil
|
|||
"tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" };
|
||||
static const std::string safePackages[] = { "coroutine", "math", "string", "table" };
|
||||
|
||||
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf)
|
||||
: mConf(conf)
|
||||
static constexpr int64_t countHookStep = 2000;
|
||||
|
||||
void LuaState::countHook(lua_State* L, lua_Debug* ar)
|
||||
{
|
||||
LuaState* THIS;
|
||||
(void)lua_getallocf(L, reinterpret_cast<void**>(&THIS));
|
||||
if (!THIS->mActiveScriptId.mContainer)
|
||||
return;
|
||||
THIS->mActiveScriptId.mContainer->addCPUusage(THIS->mActiveScriptId.mIndex, countHookStep);
|
||||
THIS->mCurrentCallInstructionCounter += countHookStep;
|
||||
if (THIS->mSettings.mInstructionLimit > 0
|
||||
&& THIS->mCurrentCallInstructionCounter > THIS->mSettings.mInstructionLimit)
|
||||
{
|
||||
lua_pushstring(L,
|
||||
"Lua CPU usage exceeded, probably an infinite loop in a script. "
|
||||
"To change the limit set \"[Lua] instruction limit per call\" in settings.cfg");
|
||||
lua_error(L);
|
||||
}
|
||||
}
|
||||
|
||||
void* LuaState::trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize)
|
||||
{
|
||||
LuaState* THIS = static_cast<LuaState*>(ud);
|
||||
const uint64_t smallAllocSize = THIS->mSettings.mSmallAllocMaxSize;
|
||||
const uint64_t memoryLimit = THIS->mSettings.mMemoryLimit;
|
||||
|
||||
if (!ptr)
|
||||
osize = 0;
|
||||
int64_t smallAllocDelta = 0, bigAllocDelta = 0;
|
||||
if (osize <= smallAllocSize)
|
||||
smallAllocDelta -= osize;
|
||||
else
|
||||
bigAllocDelta -= osize;
|
||||
if (nsize <= smallAllocSize)
|
||||
smallAllocDelta += nsize;
|
||||
else
|
||||
bigAllocDelta += nsize;
|
||||
|
||||
if (bigAllocDelta > 0 && memoryLimit > 0 && THIS->mTotalMemoryUsage + nsize - osize > memoryLimit)
|
||||
{
|
||||
Log(Debug::Error) << "Lua realloc " << osize << "->" << nsize
|
||||
<< " is blocked because Lua memory limit (configurable in settings.cfg) is exceeded";
|
||||
return nullptr;
|
||||
}
|
||||
THIS->mTotalMemoryUsage += smallAllocDelta + bigAllocDelta;
|
||||
THIS->mSmallAllocMemoryUsage += smallAllocDelta;
|
||||
|
||||
void* newPtr = nullptr;
|
||||
if (nsize == 0)
|
||||
free(ptr);
|
||||
else
|
||||
newPtr = realloc(ptr, nsize);
|
||||
|
||||
if (bigAllocDelta != 0)
|
||||
{
|
||||
auto it = osize > smallAllocSize ? THIS->mBigAllocOwners.find(ptr) : THIS->mBigAllocOwners.end();
|
||||
ScriptId id;
|
||||
if (it != THIS->mBigAllocOwners.end())
|
||||
{
|
||||
if (it->second.mContainer)
|
||||
id = ScriptId{ *it->second.mContainer, it->second.mScriptIndex };
|
||||
if (ptr != newPtr || nsize <= smallAllocSize)
|
||||
THIS->mBigAllocOwners.erase(it);
|
||||
}
|
||||
else if (bigAllocDelta > 0)
|
||||
{
|
||||
id = THIS->mActiveScriptId;
|
||||
bigAllocDelta = nsize;
|
||||
}
|
||||
if (id.mContainer)
|
||||
{
|
||||
if (static_cast<size_t>(id.mIndex) >= THIS->mMemoryUsage.size())
|
||||
THIS->mMemoryUsage.resize(id.mIndex + 1);
|
||||
THIS->mMemoryUsage[id.mIndex] += bigAllocDelta;
|
||||
id.mContainer->addMemoryUsage(id.mIndex, bigAllocDelta);
|
||||
if (newPtr && nsize > smallAllocSize)
|
||||
THIS->mBigAllocOwners.emplace(newPtr, AllocOwner{ id.mContainer->mThis, id.mIndex });
|
||||
}
|
||||
}
|
||||
|
||||
return newPtr;
|
||||
}
|
||||
|
||||
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf, const LuaStateSettings& settings)
|
||||
: mSettings(settings)
|
||||
, mLua(sol::default_at_panic, &trackingAllocator, this)
|
||||
, mConf(conf)
|
||||
, mVFS(vfs)
|
||||
{
|
||||
lua_sethook(mLua.lua_state(), &countHook, LUA_MASKCOUNT, countHookStep);
|
||||
Log(Debug::Verbose) << "Initializing LuaUtil::LuaState";
|
||||
|
||||
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string,
|
||||
sol::lib::table, sol::lib::os, sol::lib::debug);
|
||||
|
||||
|
@ -196,9 +286,15 @@ namespace LuaUtil
|
|||
env["_G"] = env;
|
||||
env[sol::metatable_key]["__metatable"] = false;
|
||||
|
||||
auto maybeRunLoader = [&hiddenData](const sol::object& package) -> sol::object {
|
||||
ScriptId scriptId;
|
||||
if (hiddenData.is<sol::table>())
|
||||
scriptId = hiddenData.as<sol::table>()
|
||||
.get<sol::optional<ScriptId>>(ScriptsContainer::sScriptIdKey)
|
||||
.value_or(ScriptId{});
|
||||
|
||||
auto maybeRunLoader = [&hiddenData, scriptId](const sol::object& package) -> sol::object {
|
||||
if (package.is<sol::function>())
|
||||
return call(package.as<sol::function>(), hiddenData);
|
||||
return call(scriptId, package.as<sol::function>(), hiddenData);
|
||||
else
|
||||
return package;
|
||||
};
|
||||
|
@ -207,19 +303,19 @@ namespace LuaUtil
|
|||
loaded[key] = maybeRunLoader(value);
|
||||
for (const auto& [key, value] : packages)
|
||||
loaded[key] = maybeRunLoader(value);
|
||||
env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) mutable {
|
||||
env["require"] = [this, env, loaded, hiddenData, scriptId](std::string_view packageName) mutable {
|
||||
sol::object package = loaded[packageName];
|
||||
if (package != sol::nil)
|
||||
return package;
|
||||
sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS));
|
||||
sol::set_environment(env, packageLoader);
|
||||
package = call(packageLoader, packageName);
|
||||
package = call(scriptId, packageLoader, packageName);
|
||||
loaded[packageName] = package;
|
||||
return package;
|
||||
};
|
||||
|
||||
sol::set_environment(env, script);
|
||||
return call(script);
|
||||
return call(scriptId, script);
|
||||
}
|
||||
|
||||
sol::environment LuaState::newInternalLibEnvironment()
|
||||
|
@ -233,7 +329,7 @@ namespace LuaUtil
|
|||
return loaded[module];
|
||||
sol::protected_function initializer = loadInternalLib(module);
|
||||
sol::set_environment(env, initializer);
|
||||
loaded[module] = call(initializer, module);
|
||||
loaded[module] = call({}, initializer, module);
|
||||
return loaded[module];
|
||||
};
|
||||
return env;
|
||||
|
|
|
@ -19,6 +19,21 @@ namespace LuaUtil
|
|||
|
||||
std::string getLuaVersion();
|
||||
|
||||
class ScriptsContainer;
|
||||
struct ScriptId
|
||||
{
|
||||
ScriptsContainer* mContainer = nullptr;
|
||||
int mIndex; // index in LuaUtil::ScriptsConfiguration
|
||||
};
|
||||
|
||||
struct LuaStateSettings
|
||||
{
|
||||
uint64_t mInstructionLimit = 0; // 0 is unlimited
|
||||
uint64_t mMemoryLimit = 0; // 0 is unlimited
|
||||
uint64_t mSmallAllocMaxSize = 1024 * 1024; // big default value efficiently disables memory tracking
|
||||
bool mLogMemoryUsage = false;
|
||||
};
|
||||
|
||||
// Holds Lua state.
|
||||
// Provides additional features:
|
||||
// - Load scripts from the virtual filesystem;
|
||||
|
@ -34,7 +49,11 @@ namespace LuaUtil
|
|||
class LuaState
|
||||
{
|
||||
public:
|
||||
explicit LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf);
|
||||
explicit LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf,
|
||||
const LuaStateSettings& settings = LuaStateSettings{});
|
||||
LuaState(const LuaState&) = delete;
|
||||
LuaState(LuaState&&) = delete;
|
||||
|
||||
~LuaState();
|
||||
|
||||
// Returns underlying sol::state.
|
||||
|
@ -86,12 +105,42 @@ namespace LuaUtil
|
|||
sol::function loadFromVFS(const std::string& path);
|
||||
sol::environment newInternalLibEnvironment();
|
||||
|
||||
uint64_t getTotalMemoryUsage() const { return mTotalMemoryUsage; }
|
||||
uint64_t getSmallAllocMemoryUsage() const { return mSmallAllocMemoryUsage; }
|
||||
uint64_t getMemoryUsageByScriptIndex(unsigned id) const
|
||||
{
|
||||
return id < mMemoryUsage.size() ? mMemoryUsage[id] : 0;
|
||||
}
|
||||
|
||||
const LuaStateSettings& getSettings() const { return mSettings; }
|
||||
|
||||
private:
|
||||
static sol::protected_function_result throwIfError(sol::protected_function_result&&);
|
||||
template <typename... Args>
|
||||
friend sol::protected_function_result call(const sol::protected_function& fn, Args&&... args);
|
||||
template <typename... Args>
|
||||
friend sol::protected_function_result call(
|
||||
ScriptId scriptId, const sol::protected_function& fn, Args&&... args);
|
||||
|
||||
sol::function loadScriptAndCache(const std::string& path);
|
||||
static void countHook(lua_State* L, lua_Debug* ar);
|
||||
static void* trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize);
|
||||
|
||||
struct AllocOwner
|
||||
{
|
||||
std::shared_ptr<ScriptsContainer*> mContainer;
|
||||
int mScriptIndex;
|
||||
};
|
||||
|
||||
const LuaStateSettings mSettings;
|
||||
|
||||
// Needed to track resource usage per script, must be initialized before mLua.
|
||||
ScriptId mActiveScriptId;
|
||||
uint64_t mCurrentCallInstructionCounter = 0;
|
||||
std::map<void*, AllocOwner> mBigAllocOwners;
|
||||
uint64_t mTotalMemoryUsage = 0;
|
||||
uint64_t mSmallAllocMemoryUsage = 0;
|
||||
std::vector<int64_t> mMemoryUsage;
|
||||
|
||||
sol::state mLua;
|
||||
const ScriptsConfiguration* mConf;
|
||||
|
@ -102,14 +151,18 @@ namespace LuaUtil
|
|||
std::vector<std::filesystem::path> mLibSearchPaths;
|
||||
};
|
||||
|
||||
// Should be used for every call of every Lua function.
|
||||
// It is a workaround for a bug in `sol`. See https://github.com/ThePhD/sol2/issues/1078
|
||||
// LuaUtil::call should be used for every call of every Lua function.
|
||||
// 1) It is a workaround for a bug in `sol`. See https://github.com/ThePhD/sol2/issues/1078
|
||||
// 2) When called with ScriptId it tracks resource usage (scriptId refers to the script that is responsible for this
|
||||
// call).
|
||||
|
||||
template <typename... Args>
|
||||
sol::protected_function_result call(const sol::protected_function& fn, Args&&... args)
|
||||
{
|
||||
try
|
||||
{
|
||||
return LuaState::throwIfError(fn(std::forward<Args>(args)...));
|
||||
auto res = LuaState::throwIfError(fn(std::forward<Args>(args)...));
|
||||
return res;
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
|
@ -121,6 +174,33 @@ namespace LuaUtil
|
|||
}
|
||||
}
|
||||
|
||||
// Lua must be initialized through LuaUtil::LuaState, otherwise this function will segfault.
|
||||
template <typename... Args>
|
||||
sol::protected_function_result call(ScriptId scriptId, const sol::protected_function& fn, Args&&... args)
|
||||
{
|
||||
LuaState* luaState;
|
||||
(void)lua_getallocf(fn.lua_state(), reinterpret_cast<void**>(&luaState));
|
||||
assert(luaState->mActiveScriptId.mContainer == nullptr && "recursive call of LuaUtil::call(scriptId, ...) ?");
|
||||
luaState->mActiveScriptId = scriptId;
|
||||
luaState->mCurrentCallInstructionCounter = 0;
|
||||
try
|
||||
{
|
||||
auto res = LuaState::throwIfError(fn(std::forward<Args>(args)...));
|
||||
luaState->mActiveScriptId = {};
|
||||
return res;
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
luaState->mActiveScriptId = {};
|
||||
throw;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
luaState->mActiveScriptId = {};
|
||||
throw std::runtime_error("Unknown error");
|
||||
}
|
||||
}
|
||||
|
||||
// getFieldOrNil(table, "a", "b", "c") returns table["a"]["b"]["c"] or nil if some of the fields doesn't exist.
|
||||
template <class... Str>
|
||||
sol::object getFieldOrNil(const sol::object& table, std::string_view first, const Str&... str)
|
||||
|
|
|
@ -18,6 +18,7 @@ namespace LuaUtil
|
|||
ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix)
|
||||
: mNamePrefix(namePrefix)
|
||||
, mLua(*lua)
|
||||
, mThis(std::make_shared<ScriptsContainer*>(this))
|
||||
{
|
||||
registerEngineHandlers({ &mUpdateHandlers });
|
||||
mPublicInterfaces = sol::table(lua->sol(), sol::create);
|
||||
|
@ -74,6 +75,16 @@ namespace LuaUtil
|
|||
script.mHiddenData[sScriptIdKey] = ScriptId{ this, scriptId };
|
||||
script.mHiddenData[sScriptDebugNameKey] = debugName;
|
||||
script.mPath = path;
|
||||
script.mStats.mCPUusage = 0;
|
||||
|
||||
const auto oldMemoryUsageIt = mRemovedScriptsMemoryUsage.find(scriptId);
|
||||
if (oldMemoryUsageIt != mRemovedScriptsMemoryUsage.end())
|
||||
{
|
||||
script.mStats.mMemoryUsage = oldMemoryUsageIt->second;
|
||||
mRemovedScriptsMemoryUsage.erase(oldMemoryUsageIt);
|
||||
}
|
||||
else
|
||||
script.mStats.mMemoryUsage = 0;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -146,8 +157,10 @@ namespace LuaUtil
|
|||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
mScripts[scriptId].mHiddenData[sScriptIdKey] = sol::nil;
|
||||
mScripts.erase(scriptId);
|
||||
auto iter = mScripts.find(scriptId);
|
||||
iter->second.mHiddenData[sScriptIdKey] = sol::nil;
|
||||
mRemovedScriptsMemoryUsage[scriptId] = iter->second.mStats.mMemoryUsage;
|
||||
mScripts.erase(iter);
|
||||
Log(Debug::Error) << "Can't start " << debugName << "; " << e.what();
|
||||
return false;
|
||||
}
|
||||
|
@ -162,6 +175,7 @@ namespace LuaUtil
|
|||
if (script.mInterface)
|
||||
removeInterface(scriptId, script);
|
||||
script.mHiddenData[sScriptIdKey] = sol::nil;
|
||||
mRemovedScriptsMemoryUsage[scriptId] = script.mStats.mMemoryUsage;
|
||||
mScripts.erase(scriptIter);
|
||||
for (auto& [_, handlers] : mEngineHandlers)
|
||||
removeHandler(handlers->mList, scriptId);
|
||||
|
@ -192,7 +206,7 @@ namespace LuaUtil
|
|||
{
|
||||
try
|
||||
{
|
||||
LuaUtil::call(*script.mOnOverride, *prev->mInterface);
|
||||
LuaUtil::call({ this, scriptId }, *script.mOnOverride, *prev->mInterface);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -203,7 +217,7 @@ namespace LuaUtil
|
|||
{
|
||||
try
|
||||
{
|
||||
LuaUtil::call(*next->mOnOverride, *script.mInterface);
|
||||
LuaUtil::call({ this, nextId }, *next->mOnOverride, *script.mInterface);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -242,7 +256,7 @@ namespace LuaUtil
|
|||
prevInterface = *prev->mInterface;
|
||||
try
|
||||
{
|
||||
LuaUtil::call(*next->mOnOverride, prevInterface);
|
||||
LuaUtil::call({ this, nextId }, *next->mOnOverride, prevInterface);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -298,16 +312,17 @@ namespace LuaUtil
|
|||
EventHandlerList& list = it->second;
|
||||
for (int i = list.size() - 1; i >= 0; --i)
|
||||
{
|
||||
const Handler& h = list[i];
|
||||
try
|
||||
{
|
||||
sol::object res = LuaUtil::call(list[i].mFn, data);
|
||||
sol::object res = LuaUtil::call({ this, h.mScriptId }, h.mFn, data);
|
||||
if (res != sol::nil && !res.as<bool>())
|
||||
break; // Skip other handlers if 'false' was returned.
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
Log(Debug::Error) << mNamePrefix << "[" << scriptPath(list[i].mScriptId) << "] eventHandler["
|
||||
<< eventName << "] failed. " << e.what();
|
||||
Log(Debug::Error) << mNamePrefix << "[" << scriptPath(h.mScriptId) << "] eventHandler[" << eventName
|
||||
<< "] failed. " << e.what();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -322,7 +337,7 @@ namespace LuaUtil
|
|||
{
|
||||
try
|
||||
{
|
||||
LuaUtil::call(onInit, deserialize(mLua.sol(), data, mSerializer));
|
||||
LuaUtil::call({ this, scriptId }, onInit, deserialize(mLua.sol(), data, mSerializer));
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -358,7 +373,7 @@ namespace LuaUtil
|
|||
{
|
||||
try
|
||||
{
|
||||
sol::object state = LuaUtil::call(*script.mOnSave);
|
||||
sol::object state = LuaUtil::call({ this, scriptId }, *script.mOnSave);
|
||||
savedScript.mData = serialize(state, mSerializer);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
|
@ -421,7 +436,7 @@ namespace LuaUtil
|
|||
{
|
||||
sol::object state = deserialize(mLua.sol(), scriptInfo.mSavedData->mData, mSavedDataDeserializer);
|
||||
sol::object initializationData = deserialize(mLua.sol(), scriptInfo.mInitData, mSerializer);
|
||||
LuaUtil::call(*onLoad, state, initializationData);
|
||||
LuaUtil::call({ this, scriptId }, *onLoad, state, initializationData);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -464,14 +479,18 @@ namespace LuaUtil
|
|||
{
|
||||
for (auto& [_, script] : mScripts)
|
||||
script.mHiddenData[sScriptIdKey] = sol::nil;
|
||||
*mThis = nullptr;
|
||||
}
|
||||
|
||||
// Note: shouldn't be called from destructor because mEngineHandlers has pointers on
|
||||
// external objects that are already removed during child class destruction.
|
||||
void ScriptsContainer::removeAllScripts()
|
||||
{
|
||||
for (auto& [_, script] : mScripts)
|
||||
for (auto& [id, script] : mScripts)
|
||||
{
|
||||
script.mHiddenData[sScriptIdKey] = sol::nil;
|
||||
mRemovedScriptsMemoryUsage[id] = script.mStats.mMemoryUsage;
|
||||
}
|
||||
mScripts.clear();
|
||||
for (auto& [_, handlers] : mEngineHandlers)
|
||||
handlers->mList.clear();
|
||||
|
@ -540,12 +559,12 @@ namespace LuaUtil
|
|||
auto it = script.mRegisteredCallbacks.find(callbackName);
|
||||
if (it == script.mRegisteredCallbacks.end())
|
||||
throw std::logic_error("Callback '" + callbackName + "' doesn't exist");
|
||||
LuaUtil::call(it->second, t.mArg);
|
||||
LuaUtil::call({ this, t.mScriptId }, it->second, t.mArg);
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t id = std::get<int64_t>(t.mCallback);
|
||||
LuaUtil::call(script.mTemporaryCallbacks.at(id));
|
||||
LuaUtil::call({ this, t.mScriptId }, script.mTemporaryCallbacks.at(id));
|
||||
script.mTemporaryCallbacks.erase(id);
|
||||
}
|
||||
}
|
||||
|
@ -571,4 +590,60 @@ namespace LuaUtil
|
|||
updateTimerQueue(mGameTimersQueue, gameTime);
|
||||
}
|
||||
|
||||
static constexpr float CPUusageAvgCoef = 1.0 / 30; // averaging over approximately 30 frames
|
||||
|
||||
void ScriptsContainer::CPUusageNextFrame()
|
||||
{
|
||||
for (auto& [scriptId, script] : mScripts)
|
||||
{
|
||||
// The averaging formula is: averageValue = averageValue * (1-c) + newValue * c
|
||||
script.mStats.mCPUusage *= 1 - CPUusageAvgCoef;
|
||||
if (script.mStats.mCPUusage < 5)
|
||||
script.mStats.mCPUusage = 0; // speeding up converge to zero if newValue is zero
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptsContainer::addCPUusage(int scriptId, int64_t CPUusage)
|
||||
{
|
||||
auto it = mScripts.find(scriptId);
|
||||
if (it != mScripts.end())
|
||||
it->second.mStats.mCPUusage += CPUusage * CPUusageAvgCoef;
|
||||
}
|
||||
|
||||
void ScriptsContainer::addMemoryUsage(int scriptId, int64_t memoryDelta)
|
||||
{
|
||||
int64_t* usage;
|
||||
auto it = mScripts.find(scriptId);
|
||||
if (it != mScripts.end())
|
||||
usage = &it->second.mStats.mMemoryUsage;
|
||||
else
|
||||
{
|
||||
auto [rIt, _] = mRemovedScriptsMemoryUsage.emplace(scriptId, 0);
|
||||
usage = &rIt->second;
|
||||
}
|
||||
*usage += memoryDelta;
|
||||
|
||||
if (mLua.getSettings().mLogMemoryUsage)
|
||||
{
|
||||
int64_t after = *usage;
|
||||
int64_t before = after - memoryDelta;
|
||||
// Logging only if one of the most significant bits of used memory size was changed.
|
||||
// Otherwise it is too verbose.
|
||||
if ((before ^ after) * 8 > after)
|
||||
Log(Debug::Verbose) << mNamePrefix << "[" << scriptPath(scriptId) << "] memory usage " << before
|
||||
<< " -> " << after;
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptsContainer::collectStats(std::vector<ScriptStats>& stats) const
|
||||
{
|
||||
stats.resize(mLua.getConfiguration().size());
|
||||
for (auto& [id, script] : mScripts)
|
||||
{
|
||||
stats[id].mCPUusage += script.mStats.mCPUusage;
|
||||
stats[id].mMemoryUsage += script.mStats.mMemoryUsage;
|
||||
}
|
||||
for (auto& [id, mem] : mRemovedScriptsMemoryUsage)
|
||||
stats[id].mMemoryUsage += mem;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,11 +69,6 @@ namespace LuaUtil
|
|||
// Present in mHiddenData even after removal of the script from ScriptsContainer.
|
||||
constexpr static std::string_view sScriptDebugNameKey = "_name";
|
||||
|
||||
struct ScriptId
|
||||
{
|
||||
ScriptsContainer* mContainer;
|
||||
int mIndex; // index in LuaUtil::ScriptsConfiguration
|
||||
};
|
||||
using TimerType = ESM::LuaTimer::Type;
|
||||
|
||||
// `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print`
|
||||
|
@ -151,6 +146,16 @@ namespace LuaUtil
|
|||
// because they can not be stored in saves. I.e. loading a saved game will not fully restore the state.
|
||||
void setupUnsavableTimer(TimerType type, double time, int scriptId, sol::main_protected_function callback);
|
||||
|
||||
// Informs that new frame is started. Needed to track CPU usage per frame.
|
||||
void CPUusageNextFrame();
|
||||
|
||||
struct ScriptStats
|
||||
{
|
||||
float mCPUusage = 0; // averaged number of Lua instructions per frame
|
||||
int64_t mMemoryUsage = 0; // bytes
|
||||
};
|
||||
void collectStats(std::vector<ScriptStats>& stats) const;
|
||||
|
||||
protected:
|
||||
struct Handler
|
||||
{
|
||||
|
@ -178,7 +183,7 @@ namespace LuaUtil
|
|||
{
|
||||
try
|
||||
{
|
||||
LuaUtil::call(handler.mFn, args...);
|
||||
LuaUtil::call({ this, handler.mScriptId }, handler.mFn, args...);
|
||||
}
|
||||
catch (std::exception& e)
|
||||
{
|
||||
|
@ -206,6 +211,7 @@ namespace LuaUtil
|
|||
std::map<std::string, sol::main_protected_function> mRegisteredCallbacks;
|
||||
std::map<int64_t, sol::main_protected_function> mTemporaryCallbacks;
|
||||
std::string mPath;
|
||||
ScriptStats mStats;
|
||||
};
|
||||
struct Timer
|
||||
{
|
||||
|
@ -220,6 +226,10 @@ namespace LuaUtil
|
|||
};
|
||||
using EventHandlerList = std::vector<Handler>;
|
||||
|
||||
friend class LuaState;
|
||||
void addCPUusage(int scriptId, int64_t CPUusage);
|
||||
void addMemoryUsage(int scriptId, int64_t memoryDelta);
|
||||
|
||||
// Add to container without calling onInit/onLoad.
|
||||
bool addScript(int scriptId, std::optional<sol::function>& onInit, std::optional<sol::function>& onLoad);
|
||||
|
||||
|
@ -252,6 +262,9 @@ namespace LuaUtil
|
|||
std::vector<Timer> mSimulationTimersQueue;
|
||||
std::vector<Timer> mGameTimersQueue;
|
||||
int64_t mTemporaryCallbackCounter = 0;
|
||||
|
||||
std::map<int, int64_t> mRemovedScriptsMemoryUsage;
|
||||
std::shared_ptr<ScriptsContainer*> mThis; // used by LuaState to track ownership of memory allocations
|
||||
};
|
||||
|
||||
// Wrapper for a Lua function.
|
||||
|
@ -267,8 +280,9 @@ namespace LuaUtil
|
|||
template <typename... Args>
|
||||
sol::object call(Args&&... args) const
|
||||
{
|
||||
if (isValid())
|
||||
return LuaUtil::call(mFunc, std::forward<Args>(args)...);
|
||||
sol::optional<ScriptId> scriptId = mHiddenData[ScriptsContainer::sScriptIdKey];
|
||||
if (scriptId.has_value())
|
||||
return LuaUtil::call(scriptId.value(), mFunc, std::forward<Args>(args)...);
|
||||
else
|
||||
Log(Debug::Debug) << "Ignored callback to the removed script "
|
||||
<< mHiddenData.get<std::string>(ScriptsContainer::sScriptDebugNameKey);
|
||||
|
|
Loading…
Reference in a new issue