Track memory and CPU usage per script in LuaUtil::LuaState

7098-improve-post-process-behavior-with-transparent-objects
Petr Mikheev 2 years ago
parent f4ac32efc9
commit 6fa65e4729

@ -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,21 +151,52 @@ 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&)
{
throw;
}
catch (...)
{
throw std::runtime_error("Unknown error");
}
}
// 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");
}
}

@ -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…
Cancel
Save