Update Lua profiler; add ability to run OpenMW with old LuaJit that doesn't allow custom allocator (Lua profiler will be disabled in this case)

7098-improve-post-process-behavior-with-transparent-objects
Petr Mikheev 2 years ago
parent 02a9069a0e
commit 55db95d4cf

@ -20,8 +20,9 @@ namespace MWLua
saveLuaBinaryData(esm, event.mEventData); saveLuaBinaryData(esm, event.mEventData);
} }
void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents, LocalEventQueue& localEvents, void loadEvents(sol::state_view& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents,
const std::map<int, int>& contentFileMapping, const LuaUtil::UserdataSerializer* serializer) LocalEventQueue& localEvents, const std::map<int, int>& contentFileMapping,
const LuaUtil::UserdataSerializer* serializer)
{ {
while (esm.isNextSub("LUAE")) while (esm.isNextSub("LUAE"))
{ {

@ -35,7 +35,7 @@ namespace MWLua
using GlobalEventQueue = std::vector<GlobalEvent>; using GlobalEventQueue = std::vector<GlobalEvent>;
using LocalEventQueue = std::vector<LocalEvent>; using LocalEventQueue = std::vector<LocalEvent>;
void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue&, LocalEventQueue&, void loadEvents(sol::state_view& lua, ESM::ESMReader& esm, GlobalEventQueue&, LocalEventQueue&,
const std::map<int, int>& contentFileMapping, const LuaUtil::UserdataSerializer* serializer); const std::map<int, int>& contentFileMapping, const LuaUtil::UserdataSerializer* serializer);
void saveEvents(ESM::ESMWriter& esm, const GlobalEventQueue&, const LocalEventQueue&); void saveEvents(ESM::ESMWriter& esm, const GlobalEventQueue&, const LocalEventQueue&);
} }

@ -39,6 +39,8 @@ namespace MWLua
static LuaUtil::LuaStateSettings createLuaStateSettings() static LuaUtil::LuaStateSettings createLuaStateSettings()
{ {
if (!Settings::Manager::getBool("lua profiler", "Lua"))
LuaUtil::LuaState::disableProfiler();
return { .mInstructionLimit = Settings::Manager::getUInt64("instruction limit per call", "Lua"), return { .mInstructionLimit = Settings::Manager::getUInt64("instruction limit per call", "Lua"),
.mMemoryLimit = Settings::Manager::getUInt64("memory limit", "Lua"), .mMemoryLimit = Settings::Manager::getUInt64("memory limit", "Lua"),
.mSmallAllocMaxSize = Settings::Manager::getUInt64("small alloc max size", "Lua"), .mSmallAllocMaxSize = Settings::Manager::getUInt64("small alloc max size", "Lua"),
@ -147,9 +149,9 @@ namespace MWLua
mWorldView.update(); mWorldView.update();
mGlobalScripts.CPUusageNextFrame(); mGlobalScripts.statsNextFrame();
for (LocalScripts* scripts : mActiveLocalScripts) for (LocalScripts* scripts : mActiveLocalScripts)
scripts->CPUusageNextFrame(); scripts->statsNextFrame();
std::vector<GlobalEvent> globalEvents = std::move(mGlobalEvents); std::vector<GlobalEvent> globalEvents = std::move(mGlobalEvents);
std::vector<LocalEvent> localEvents = std::move(mLocalEvents); std::vector<LocalEvent> localEvents = std::move(mLocalEvents);
@ -620,17 +622,39 @@ namespace MWLua
std::string LuaManager::formatResourceUsageStats() const std::string LuaManager::formatResourceUsageStats() const
{ {
if (!LuaUtil::LuaState::isProfilerEnabled())
return "Lua profiler is disabled";
std::stringstream out; std::stringstream out;
constexpr int nameW = 50;
constexpr int valueW = 12;
auto outMemSize = [&](int64_t bytes) {
constexpr int64_t limit = 10000;
out << std::right << std::setw(valueW - 3);
if (bytes < limit)
out << bytes << " B ";
else if (bytes < limit * 1024)
out << (bytes / 1024) << " KB";
else if (bytes < limit * 1024 * 1024)
out << (bytes / (1024 * 1024)) << " MB";
else
out << (bytes / (1024 * 1024 * 1024)) << " GB";
};
static const uint64_t smallAllocSize = Settings::Manager::getUInt64("small alloc max size", "Lua"); static const uint64_t smallAllocSize = Settings::Manager::getUInt64("small alloc max size", "Lua");
out << "Total memory usage: " << mLua.getTotalMemoryUsage() << "\n"; out << "Total memory usage:";
outMemSize(mLua.getTotalMemoryUsage());
out << "\n";
out << "small alloc max size = " << smallAllocSize << " (section [Lua] in settings.cfg)\n"; out << "small alloc max size = " << smallAllocSize << " (section [Lua] in settings.cfg)\n";
out << "Smaller values give more information for the profiler, but increase performance overhead.\n"; out << "Smaller values give more information for the profiler, but increase performance overhead.\n";
out << " Memory allocations <= " << smallAllocSize << " bytes: " << mLua.getSmallAllocMemoryUsage() out << " Memory allocations <= " << smallAllocSize << " bytes:";
<< " (not tracked)\n"; outMemSize(mLua.getSmallAllocMemoryUsage());
out << " Memory allocations > " << smallAllocSize out << " (not tracked)\n";
<< " bytes: " << mLua.getTotalMemoryUsage() - mLua.getSmallAllocMemoryUsage() << " (see the table below)\n"; out << " Memory allocations > " << smallAllocSize << " bytes:";
out << "\n"; outMemSize(mLua.getTotalMemoryUsage() - mLua.getSmallAllocMemoryUsage());
out << " (see the table below)\n\n";
using Stats = LuaUtil::ScriptsContainer::ScriptStats; using Stats = LuaUtil::ScriptsContainer::ScriptStats;
@ -653,16 +677,22 @@ namespace MWLua
out << "No selected object. Use the in-game console to select an object for detailed profile.\n"; out << "No selected object. Use the in-game console to select an object for detailed profile.\n";
out << "\n"; out << "\n";
constexpr int nameW = 50; out << "Legend\n";
constexpr int valueW = 12; out << " ops: Averaged number of Lua instruction per frame;\n";
out << " memory: Aggregated size of Lua allocations > " << smallAllocSize << " bytes;\n";
out << " [all]: Sum over all instances of each script;\n";
out << " [active]: Sum over all active (i.e. currently in scene) instances of each script;\n";
out << " [inactive]: Sum over all inactive instances of each script;\n";
out << " [for selected object]: Only for the object that is selected in the console;\n";
out << "\n";
out << std::left; out << std::left;
out << " " << std::setw(nameW + 2) << "*** Resource usage per script"; out << " " << std::setw(nameW + 2) << "*** Resource usage per script";
out << std::right; out << std::right;
out << std::setw(valueW) << "CPU"; out << std::setw(valueW) << "ops";
out << std::setw(valueW) << "memory"; out << std::setw(valueW) << "memory";
out << std::setw(valueW) << "memory"; out << std::setw(valueW) << "memory";
out << std::setw(valueW) << "CPU"; out << std::setw(valueW) << "ops";
out << std::setw(valueW) << "memory"; out << std::setw(valueW) << "memory";
out << "\n"; out << "\n";
out << std::left << " " << std::setw(nameW + 2) << "[name]" << std::right; out << std::left << " " << std::setw(nameW + 2) << "[name]" << std::right;
@ -681,19 +711,24 @@ namespace MWLua
if (mConfiguration[i].mScriptPath.size() > nameW) if (mConfiguration[i].mScriptPath.size() > nameW)
out << "\n " << std::setw(nameW) << ""; // if path is too long, break line out << "\n " << std::setw(nameW) << ""; // if path is too long, break line
out << std::right; out << std::right;
out << std::setw(valueW) << static_cast<int64_t>(activeStats[i].mCPUusage); out << std::setw(valueW) << static_cast<int64_t>(activeStats[i].mAvgInstructionCount);
out << std::setw(valueW) << activeStats[i].mMemoryUsage; outMemSize(activeStats[i].mMemoryUsage);
out << std::setw(valueW) << mLua.getMemoryUsageByScriptIndex(i) - activeStats[i].mMemoryUsage; outMemSize(mLua.getMemoryUsageByScriptIndex(i) - activeStats[i].mMemoryUsage);
if (isGlobal) if (isGlobal)
out << std::setw(valueW * 2) << "NA (global script)"; out << std::setw(valueW * 2) << "NA (global script)";
else if (selectedPtr.isEmpty()) else if (selectedPtr.isEmpty())
out << std::setw(valueW * 2) << "NA (not selected) "; out << std::setw(valueW * 2) << "NA (not selected) ";
else if (!selectedScripts || !selectedScripts->hasScript(i)) else if (!selectedScripts || !selectedScripts->hasScript(i))
out << std::setw(valueW) << "-" << std::setw(valueW) << selectedStats[i].mMemoryUsage; {
out << std::setw(valueW) << "-";
outMemSize(selectedStats[i].mMemoryUsage);
}
else else
out << std::setw(valueW) << static_cast<int64_t>(selectedStats[i].mCPUusage) << std::setw(valueW) {
<< selectedStats[i].mMemoryUsage; out << std::setw(valueW) << static_cast<int64_t>(selectedStats[i].mAvgInstructionCount);
outMemSize(selectedStats[i].mMemoryUsage);
}
out << "\n"; out << "\n";
} }

@ -146,7 +146,7 @@ namespace MWLua
void registerObjectList(const std::string& prefix, const Context& context) void registerObjectList(const std::string& prefix, const Context& context)
{ {
using ListT = ObjectList<ObjectT>; using ListT = ObjectList<ObjectT>;
sol::state& lua = context.mLua->sol(); sol::state_view& lua = context.mLua->sol();
ObjectRegistry* registry = context.mWorldView->getObjectRegistry(); ObjectRegistry* registry = context.mWorldView->getObjectRegistry();
sol::usertype<ListT> listT = lua.new_usertype<ListT>(prefix + "ObjectList"); sol::usertype<ListT> listT = lua.new_usertype<ListT>(prefix + "ObjectList");
listT[sol::meta_function::to_string] listT[sol::meta_function::to_string]

@ -15,7 +15,7 @@ namespace
using namespace TestingOpenMW; using namespace TestingOpenMW;
template <typename T> template <typename T>
T get(sol::state& lua, const std::string& luaCode) T get(sol::state_view& lua, const std::string& luaCode)
{ {
return lua.safe_script("return " + luaCode).get<T>(); return lua.safe_script("return " + luaCode).get<T>();
} }
@ -83,7 +83,7 @@ you_have_arrows: "Arrows count: {count}"
TEST_F(LuaL10nTest, L10n) TEST_F(LuaL10nTest, L10n)
{ {
LuaUtil::LuaState lua{ mVFS.get(), &mCfg }; LuaUtil::LuaState lua{ mVFS.get(), &mCfg };
sol::state& l = lua.sol(); sol::state_view& l = lua.sol();
internal::CaptureStdout(); internal::CaptureStdout();
l10n::Manager l10nManager(mVFS.get()); l10n::Manager l10nManager(mVFS.get());
l10nManager.setPreferredLocales({ "de", "en" }); l10nManager.setPreferredLocales({ "de", "en" });
@ -164,5 +164,4 @@ you_have_arrows: "Arrows count: {count}"
l10nManager.setPreferredLocales({ "en" }); l10nManager.setPreferredLocales({ "en" });
EXPECT_EQ(get<std::string>(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!"); EXPECT_EQ(get<std::string>(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!");
} }
} }

@ -10,7 +10,7 @@ namespace
using namespace testing; using namespace testing;
template <typename T> template <typename T>
T get(sol::state& lua, std::string luaCode) T get(sol::state_view& lua, std::string luaCode)
{ {
return lua.safe_script("return " + luaCode).get<T>(); return lua.safe_script("return " + luaCode).get<T>();
} }
@ -19,7 +19,7 @@ namespace
{ {
// Note: LuaUtil::Callback can be used only if Lua is initialized via LuaUtil::LuaState // Note: LuaUtil::Callback can be used only if Lua is initialized via LuaUtil::LuaState
LuaUtil::LuaState luaState{ nullptr, nullptr }; LuaUtil::LuaState luaState{ nullptr, nullptr };
sol::state& mLua = luaState.sol(); sol::state_view& mLua = luaState.sol();
LuaUtil::LuaStorage::initLuaBindings(mLua); LuaUtil::LuaStorage::initLuaBindings(mLua);
LuaUtil::LuaStorage storage(mLua); LuaUtil::LuaStorage storage(mLua);

@ -46,8 +46,9 @@ namespace sol
namespace LuaUtil namespace LuaUtil
{ {
sol::function initL10nLoader(sol::state& lua, l10n::Manager* manager) sol::function initL10nLoader(lua_State* L, l10n::Manager* manager)
{ {
sol::state_view lua(L);
sol::usertype<L10nContext> ctxDef = lua.new_usertype<L10nContext>("L10nContext"); sol::usertype<L10nContext> ctxDef = lua.new_usertype<L10nContext>("L10nContext");
ctxDef[sol::meta_function::call] ctxDef[sol::meta_function::call]
= [](const L10nContext& ctx, std::string_view key, sol::optional<sol::table> args) { = [](const L10nContext& ctx, std::string_view key, sol::optional<sol::table> args) {

@ -10,7 +10,7 @@ namespace l10n
namespace LuaUtil namespace LuaUtil
{ {
sol::function initL10nLoader(sol::state& lua, l10n::Manager* manager); sol::function initL10nLoader(lua_State*, l10n::Manager* manager);
} }
#endif // COMPONENTS_LUA_L10N_H #endif // COMPONENTS_LUA_L10N_H

@ -52,21 +52,24 @@ namespace LuaUtil
"tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" }; "tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" };
static const std::string safePackages[] = { "coroutine", "math", "string", "table" }; static const std::string safePackages[] = { "coroutine", "math", "string", "table" };
static constexpr int64_t countHookStep = 2000; static constexpr int64_t countHookStep = 1000;
bool LuaState::sProfilerEnabled = true;
void LuaState::countHook(lua_State* L, lua_Debug* ar) void LuaState::countHook(lua_State* L, lua_Debug* ar)
{ {
LuaState* THIS; LuaState* self;
(void)lua_getallocf(L, reinterpret_cast<void**>(&THIS)); (void)lua_getallocf(L, reinterpret_cast<void**>(&self));
if (!THIS->mActiveScriptId.mContainer) if (self->mActiveScriptIdStack.empty())
return; return;
THIS->mActiveScriptId.mContainer->addCPUusage(THIS->mActiveScriptId.mIndex, countHookStep); const ScriptId& activeScript = self->mActiveScriptIdStack.back();
THIS->mCurrentCallInstructionCounter += countHookStep; activeScript.mContainer->addInstructionCount(activeScript.mIndex, countHookStep);
if (THIS->mSettings.mInstructionLimit > 0 self->mWatchdogInstructionCounter += countHookStep;
&& THIS->mCurrentCallInstructionCounter > THIS->mSettings.mInstructionLimit) if (self->mSettings.mInstructionLimit > 0
&& self->mWatchdogInstructionCounter > self->mSettings.mInstructionLimit)
{ {
lua_pushstring(L, lua_pushstring(L,
"Lua CPU usage exceeded, probably an infinite loop in a script. " "Lua instruction count exceeded, probably an infinite loop in a script. "
"To change the limit set \"[Lua] instruction limit per call\" in settings.cfg"); "To change the limit set \"[Lua] instruction limit per call\" in settings.cfg");
lua_error(L); lua_error(L);
} }
@ -74,9 +77,9 @@ namespace LuaUtil
void* LuaState::trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize) void* LuaState::trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize)
{ {
LuaState* THIS = static_cast<LuaState*>(ud); LuaState* self = static_cast<LuaState*>(ud);
const uint64_t smallAllocSize = THIS->mSettings.mSmallAllocMaxSize; const uint64_t smallAllocSize = self->mSettings.mSmallAllocMaxSize;
const uint64_t memoryLimit = THIS->mSettings.mMemoryLimit; const uint64_t memoryLimit = self->mSettings.mMemoryLimit;
if (!ptr) if (!ptr)
osize = 0; osize = 0;
@ -90,14 +93,14 @@ namespace LuaUtil
else else
bigAllocDelta += nsize; bigAllocDelta += nsize;
if (bigAllocDelta > 0 && memoryLimit > 0 && THIS->mTotalMemoryUsage + nsize - osize > memoryLimit) if (bigAllocDelta > 0 && memoryLimit > 0 && self->mTotalMemoryUsage + nsize - osize > memoryLimit)
{ {
Log(Debug::Error) << "Lua realloc " << osize << "->" << nsize Log(Debug::Error) << "Lua realloc " << osize << "->" << nsize
<< " is blocked because Lua memory limit (configurable in settings.cfg) is exceeded"; << " is blocked because Lua memory limit (configurable in settings.cfg) is exceeded";
return nullptr; return nullptr;
} }
THIS->mTotalMemoryUsage += smallAllocDelta + bigAllocDelta; self->mTotalMemoryUsage += smallAllocDelta + bigAllocDelta;
THIS->mSmallAllocMemoryUsage += smallAllocDelta; self->mSmallAllocMemoryUsage += smallAllocDelta;
void* newPtr = nullptr; void* newPtr = nullptr;
if (nsize == 0) if (nsize == 0)
@ -107,59 +110,83 @@ namespace LuaUtil
if (bigAllocDelta != 0) if (bigAllocDelta != 0)
{ {
auto it = osize > smallAllocSize ? THIS->mBigAllocOwners.find(ptr) : THIS->mBigAllocOwners.end(); auto it = osize > smallAllocSize ? self->mBigAllocOwners.find(ptr) : self->mBigAllocOwners.end();
ScriptId id; ScriptId id;
if (it != THIS->mBigAllocOwners.end()) if (it != self->mBigAllocOwners.end())
{ {
if (it->second.mContainer) if (it->second.mContainer)
id = ScriptId{ *it->second.mContainer, it->second.mScriptIndex }; id = ScriptId{ *it->second.mContainer, it->second.mScriptIndex };
if (ptr != newPtr || nsize <= smallAllocSize) if (ptr != newPtr || nsize <= smallAllocSize)
THIS->mBigAllocOwners.erase(it); self->mBigAllocOwners.erase(it);
} }
else if (bigAllocDelta > 0) else if (bigAllocDelta > 0)
{ {
id = THIS->mActiveScriptId; if (!self->mActiveScriptIdStack.empty())
id = self->mActiveScriptIdStack.back();
bigAllocDelta = nsize; bigAllocDelta = nsize;
} }
if (id.mContainer) if (id.mContainer)
{ {
if (static_cast<size_t>(id.mIndex) >= THIS->mMemoryUsage.size()) if (static_cast<size_t>(id.mIndex) >= self->mMemoryUsage.size())
THIS->mMemoryUsage.resize(id.mIndex + 1); self->mMemoryUsage.resize(id.mIndex + 1);
THIS->mMemoryUsage[id.mIndex] += bigAllocDelta; self->mMemoryUsage[id.mIndex] += bigAllocDelta;
id.mContainer->addMemoryUsage(id.mIndex, bigAllocDelta); id.mContainer->addMemoryUsage(id.mIndex, bigAllocDelta);
if (newPtr && nsize > smallAllocSize) if (newPtr && nsize > smallAllocSize)
THIS->mBigAllocOwners.emplace(newPtr, AllocOwner{ id.mContainer->mThis, id.mIndex }); self->mBigAllocOwners.emplace(newPtr, AllocOwner{ id.mContainer->mThis, id.mIndex });
} }
} }
return newPtr; return newPtr;
} }
lua_State* LuaState::createLuaRuntime(LuaState* luaState)
{
if (sProfilerEnabled)
{
Log(Debug::Info) << "Initializing LuaUtil::LuaState with profiler";
lua_State* L = lua_newstate(&trackingAllocator, luaState);
if (L)
return L;
else
{
sProfilerEnabled = false;
Log(Debug::Error)
<< "Failed to initialize LuaUtil::LuaState with custom allocator; disabling Lua profiler";
}
}
Log(Debug::Info) << "Initializing LuaUtil::LuaState without profiler";
lua_State* L = luaL_newstate();
if (!L)
throw std::runtime_error("Can't create Lua runtime");
return L;
}
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf, const LuaStateSettings& settings) LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf, const LuaStateSettings& settings)
: mSettings(settings) : mSettings(settings)
, mLua(sol::default_at_panic, &trackingAllocator, this) , mLuaHolder(createLuaRuntime(this))
, mSol(mLuaHolder.get())
, mConf(conf) , mConf(conf)
, mVFS(vfs) , mVFS(vfs)
{ {
lua_sethook(mLua.lua_state(), &countHook, LUA_MASKCOUNT, countHookStep); if (sProfilerEnabled)
Log(Debug::Verbose) << "Initializing LuaUtil::LuaState"; lua_sethook(mLuaHolder.get(), &countHook, LUA_MASKCOUNT, countHookStep);
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string, mSol.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); sol::lib::table, sol::lib::os, sol::lib::debug);
mLua["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr))); mSol["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr)));
mLua["math"]["randomseed"] = [] {}; mSol["math"]["randomseed"] = [] {};
mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; mSol["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
// Some fixes for compatibility between different Lua versions // Some fixes for compatibility between different Lua versions
if (mLua["unpack"] == sol::nil) if (mSol["unpack"] == sol::nil)
mLua["unpack"] = mLua["table"]["unpack"]; mSol["unpack"] = mSol["table"]["unpack"];
else if (mLua["table"]["unpack"] == sol::nil) else if (mSol["table"]["unpack"] == sol::nil)
mLua["table"]["unpack"] = mLua["unpack"]; mSol["table"]["unpack"] = mSol["unpack"];
if (LUA_VERSION_NUM <= 501) if (LUA_VERSION_NUM <= 501)
{ {
mLua.script(R"( mSol.script(R"(
local _pairs = pairs local _pairs = pairs
local _ipairs = ipairs local _ipairs = ipairs
pairs = function(v) return (rawget(getmetatable(v) or {}, '__pairs') or _pairs)(v) end pairs = function(v) return (rawget(getmetatable(v) or {}, '__pairs') or _pairs)(v) end
@ -167,7 +194,7 @@ namespace LuaUtil
)"); )");
} }
mLua.script(R"( mSol.script(R"(
local printToLog = function(...) local printToLog = function(...)
local strs = {} local strs = {}
for i = 1, select('#', ...) do for i = 1, select('#', ...) do
@ -212,31 +239,24 @@ namespace LuaUtil
end end
)"); )");
mSandboxEnv = sol::table(mLua, sol::create); mSandboxEnv = sol::table(mSol, sol::create);
mSandboxEnv["_VERSION"] = mLua["_VERSION"]; mSandboxEnv["_VERSION"] = mSol["_VERSION"];
for (const std::string& s : safeFunctions) for (const std::string& s : safeFunctions)
{ {
if (mLua[s] == sol::nil) if (mSol[s] == sol::nil)
throw std::logic_error("Lua function not found: " + s); throw std::logic_error("Lua function not found: " + s);
mSandboxEnv[s] = mLua[s]; mSandboxEnv[s] = mSol[s];
} }
for (const std::string& s : safePackages) for (const std::string& s : safePackages)
{ {
if (mLua[s] == sol::nil) if (mSol[s] == sol::nil)
throw std::logic_error("Lua package not found: " + s); throw std::logic_error("Lua package not found: " + s);
mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]); mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mSol[s]);
} }
mSandboxEnv["getmetatable"] = mLua["getSafeMetatable"]; mSandboxEnv["getmetatable"] = mSol["getSafeMetatable"];
mCommonPackages["os"] = mSandboxEnv["os"] mCommonPackages["os"] = mSandboxEnv["os"]
= makeReadOnly(tableFromPairs<std::string_view, sol::function>({ { "date", mLua["os"]["date"] }, = makeReadOnly(tableFromPairs<std::string_view, sol::function>({ { "date", mSol["os"]["date"] },
{ "difftime", mLua["os"]["difftime"] }, { "time", mLua["os"]["time"] } })); { "difftime", mSol["os"]["difftime"] }, { "time", mSol["os"]["time"] } }));
}
LuaState::~LuaState()
{
// Should be cleaned before destructing mLua.
mCommonPackages.clear();
mSandboxEnv = sol::nil;
} }
sol::table makeReadOnly(const sol::table& table, bool strictIndex) sol::table makeReadOnly(const sol::table& table, bool strictIndex)
@ -280,9 +300,9 @@ namespace LuaUtil
{ {
sol::protected_function script = loadScriptAndCache(path); sol::protected_function script = loadScriptAndCache(path);
sol::environment env(mLua, sol::create, mSandboxEnv); sol::environment env(mSol, sol::create, mSandboxEnv);
std::string envName = namePrefix + "[" + path + "]:"; std::string envName = namePrefix + "[" + path + "]:";
env["print"] = mLua["printGen"](envName); env["print"] = mSol["printGen"](envName);
env["_G"] = env; env["_G"] = env;
env[sol::metatable_key]["__metatable"] = false; env[sol::metatable_key]["__metatable"] = false;
@ -298,18 +318,18 @@ namespace LuaUtil
else else
return package; return package;
}; };
sol::table loaded(mLua, sol::create); sol::table loaded(mSol, sol::create);
for (const auto& [key, value] : mCommonPackages) for (const auto& [key, value] : mCommonPackages)
loaded[key] = maybeRunLoader(value); loaded[key] = maybeRunLoader(value);
for (const auto& [key, value] : packages) for (const auto& [key, value] : packages)
loaded[key] = maybeRunLoader(value); loaded[key] = maybeRunLoader(value);
env["require"] = [this, env, loaded, hiddenData, scriptId](std::string_view packageName) mutable { env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) mutable {
sol::object package = loaded[packageName]; sol::object package = loaded[packageName];
if (package != sol::nil) if (package != sol::nil)
return package; return package;
sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS)); sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS));
sol::set_environment(env, packageLoader); sol::set_environment(env, packageLoader);
package = call(scriptId, packageLoader, packageName); package = call(packageLoader, packageName);
loaded[packageName] = package; loaded[packageName] = package;
return package; return package;
}; };
@ -320,8 +340,8 @@ namespace LuaUtil
sol::environment LuaState::newInternalLibEnvironment() sol::environment LuaState::newInternalLibEnvironment()
{ {
sol::environment env(mLua, sol::create, mSandboxEnv); sol::environment env(mSol, sol::create, mSandboxEnv);
sol::table loaded(mLua, sol::create); sol::table loaded(mSol, sol::create);
for (const std::string& s : safePackages) for (const std::string& s : safePackages)
loaded[s] = static_cast<sol::object>(mSandboxEnv[s]); loaded[s] = static_cast<sol::object>(mSandboxEnv[s]);
env["require"] = [this, loaded, env](const std::string& module) mutable { env["require"] = [this, loaded, env](const std::string& module) mutable {
@ -347,7 +367,7 @@ namespace LuaUtil
{ {
auto iter = mCompiledScripts.find(path); auto iter = mCompiledScripts.find(path);
if (iter != mCompiledScripts.end()) if (iter != mCompiledScripts.end())
return mLua.load(iter->second.as_string_view(), path, sol::load_mode::binary); return mSol.load(iter->second.as_string_view(), path, sol::load_mode::binary);
sol::function res = loadFromVFS(path); sol::function res = loadFromVFS(path);
mCompiledScripts[path] = res.dump(); mCompiledScripts[path] = res.dump();
return res; return res;
@ -356,7 +376,7 @@ namespace LuaUtil
sol::function LuaState::loadFromVFS(const std::string& path) sol::function LuaState::loadFromVFS(const std::string& path)
{ {
std::string fileContent(std::istreambuf_iterator<char>(*mVFS->get(path)), {}); std::string fileContent(std::istreambuf_iterator<char>(*mVFS->get(path)), {});
sol::load_result res = mLua.load(fileContent, path, sol::load_mode::text); sol::load_result res = mSol.load(fileContent, path, sol::load_mode::text);
if (!res.valid()) if (!res.valid())
throw std::runtime_error("Lua error: " + res.get<std::string>()); throw std::runtime_error("Lua error: " + res.get<std::string>());
return res; return res;
@ -365,7 +385,7 @@ namespace LuaUtil
sol::function LuaState::loadInternalLib(std::string_view libName) sol::function LuaState::loadInternalLib(std::string_view libName)
{ {
const auto path = packageNameToPath(libName, mLibSearchPaths); const auto path = packageNameToPath(libName, mLibSearchPaths);
sol::load_result res = mLua.load_file(Files::pathToUnicodeString(path), sol::load_mode::text); sol::load_result res = mSol.load_file(Files::pathToUnicodeString(path), sol::load_mode::text);
if (!res.valid()) if (!res.valid())
throw std::runtime_error("Lua error: " + res.get<std::string>()); throw std::runtime_error("Lua error: " + res.get<std::string>());
return res; return res;

@ -54,23 +54,21 @@ namespace LuaUtil
LuaState(const LuaState&) = delete; LuaState(const LuaState&) = delete;
LuaState(LuaState&&) = delete; LuaState(LuaState&&) = delete;
~LuaState();
// Returns underlying sol::state. // Returns underlying sol::state.
sol::state& sol() { return mLua; } sol::state_view& sol() { return mSol; }
// Can be used by a C++ function that is called from Lua to get the Lua traceback. // Can be used by a C++ function that is called from Lua to get the Lua traceback.
// Makes no sense if called not from Lua code. // Makes no sense if called not from Lua code.
// Note: It is a slow function, should be used for debug purposes only. // Note: It is a slow function, should be used for debug purposes only.
std::string debugTraceback() { return mLua["debug"]["traceback"]().get<std::string>(); } std::string debugTraceback() { return mSol["debug"]["traceback"]().get<std::string>(); }
// A shortcut to create a new Lua table. // A shortcut to create a new Lua table.
sol::table newTable() { return sol::table(mLua, sol::create); } sol::table newTable() { return sol::table(mSol, sol::create); }
template <typename Key, typename Value> template <typename Key, typename Value>
sol::table tableFromPairs(std::initializer_list<std::pair<Key, Value>> list) sol::table tableFromPairs(std::initializer_list<std::pair<Key, Value>> list)
{ {
sol::table res(mLua, sol::create); sol::table res(mSol, sol::create);
for (const auto& [k, v] : list) for (const auto& [k, v] : list)
res[k] = v; res[k] = v;
return res; return res;
@ -105,7 +103,7 @@ namespace LuaUtil
sol::function loadFromVFS(const std::string& path); sol::function loadFromVFS(const std::string& path);
sol::environment newInternalLibEnvironment(); sol::environment newInternalLibEnvironment();
uint64_t getTotalMemoryUsage() const { return mTotalMemoryUsage; } uint64_t getTotalMemoryUsage() const { return mSol.memory_used(); }
uint64_t getSmallAllocMemoryUsage() const { return mSmallAllocMemoryUsage; } uint64_t getSmallAllocMemoryUsage() const { return mSmallAllocMemoryUsage; }
uint64_t getMemoryUsageByScriptIndex(unsigned id) const uint64_t getMemoryUsageByScriptIndex(unsigned id) const
{ {
@ -114,6 +112,10 @@ namespace LuaUtil
const LuaStateSettings& getSettings() const { return mSettings; } const LuaStateSettings& getSettings() const { return mSettings; }
// Note: Lua profiler can not be re-enabled after disabling.
static void disableProfiler() { sProfilerEnabled = false; }
static bool isProfilerEnabled() { return sProfilerEnabled; }
private: private:
static sol::protected_function_result throwIfError(sol::protected_function_result&&); static sol::protected_function_result throwIfError(sol::protected_function_result&&);
template <typename... Args> template <typename... Args>
@ -126,6 +128,8 @@ namespace LuaUtil
static void countHook(lua_State* L, lua_Debug* ar); static void countHook(lua_State* L, lua_Debug* ar);
static void* trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize); static void* trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize);
lua_State* createLuaRuntime(LuaState* luaState);
struct AllocOwner struct AllocOwner
{ {
std::shared_ptr<ScriptsContainer*> mContainer; std::shared_ptr<ScriptsContainer*> mContainer;
@ -134,21 +138,43 @@ namespace LuaUtil
const LuaStateSettings mSettings; const LuaStateSettings mSettings;
// Needed to track resource usage per script, must be initialized before mLua. // Needed to track resource usage per script, must be initialized before mLuaHolder.
ScriptId mActiveScriptId; std::vector<ScriptId> mActiveScriptIdStack;
uint64_t mCurrentCallInstructionCounter = 0; uint64_t mWatchdogInstructionCounter = 0;
std::map<void*, AllocOwner> mBigAllocOwners; std::map<void*, AllocOwner> mBigAllocOwners;
uint64_t mTotalMemoryUsage = 0; uint64_t mTotalMemoryUsage = 0;
uint64_t mSmallAllocMemoryUsage = 0; uint64_t mSmallAllocMemoryUsage = 0;
std::vector<int64_t> mMemoryUsage; std::vector<int64_t> mMemoryUsage;
sol::state mLua; class LuaStateHolder
{
public:
LuaStateHolder(lua_State* L)
: L(L)
{
sol::set_default_state(L);
}
~LuaStateHolder() { lua_close(L); }
LuaStateHolder(const LuaStateHolder&) = delete;
LuaStateHolder(LuaStateHolder&&) = delete;
lua_State* get() { return L; }
private:
lua_State* L;
};
// Must be declared before mSol and all sol-related objects. Then on exit it will be destructed the last.
LuaStateHolder mLuaHolder;
sol::state_view mSol;
const ScriptsConfiguration* mConf; const ScriptsConfiguration* mConf;
sol::table mSandboxEnv; sol::table mSandboxEnv;
std::map<std::string, sol::bytecode> mCompiledScripts; std::map<std::string, sol::bytecode> mCompiledScripts;
std::map<std::string, sol::object> mCommonPackages; std::map<std::string, sol::object> mCommonPackages;
const VFS::Manager* mVFS; const VFS::Manager* mVFS;
std::vector<std::filesystem::path> mLibSearchPaths; std::vector<std::filesystem::path> mLibSearchPaths;
static bool sProfilerEnabled;
}; };
// LuaUtil::call should be used for every call of every Lua function. // LuaUtil::call should be used for every call of every Lua function.
@ -178,25 +204,30 @@ namespace LuaUtil
template <typename... Args> template <typename... Args>
sol::protected_function_result call(ScriptId scriptId, const sol::protected_function& fn, Args&&... args) sol::protected_function_result call(ScriptId scriptId, const sol::protected_function& fn, Args&&... args)
{ {
LuaState* luaState; LuaState* luaState = nullptr;
if (LuaState::sProfilerEnabled && scriptId.mContainer)
{
(void)lua_getallocf(fn.lua_state(), reinterpret_cast<void**>(&luaState)); (void)lua_getallocf(fn.lua_state(), reinterpret_cast<void**>(&luaState));
assert(luaState->mActiveScriptId.mContainer == nullptr && "recursive call of LuaUtil::call(scriptId, ...) ?"); luaState->mActiveScriptIdStack.push_back(scriptId);
luaState->mActiveScriptId = scriptId; luaState->mWatchdogInstructionCounter = 0;
luaState->mCurrentCallInstructionCounter = 0; }
try try
{ {
auto res = LuaState::throwIfError(fn(std::forward<Args>(args)...)); auto res = LuaState::throwIfError(fn(std::forward<Args>(args)...));
luaState->mActiveScriptId = {}; if (luaState)
luaState->mActiveScriptIdStack.pop_back();
return res; return res;
} }
catch (std::exception&) catch (std::exception&)
{ {
luaState->mActiveScriptId = {}; if (luaState)
luaState->mActiveScriptIdStack.pop_back();
throw; throw;
} }
catch (...) catch (...)
{ {
luaState->mActiveScriptId = {}; if (luaState)
luaState->mActiveScriptIdStack.pop_back();
throw std::runtime_error("Unknown error"); throw std::runtime_error("Unknown error");
} }
} }

@ -75,7 +75,7 @@ namespace LuaUtil
script.mHiddenData[sScriptIdKey] = ScriptId{ this, scriptId }; script.mHiddenData[sScriptIdKey] = ScriptId{ this, scriptId };
script.mHiddenData[sScriptDebugNameKey] = debugName; script.mHiddenData[sScriptDebugNameKey] = debugName;
script.mPath = path; script.mPath = path;
script.mStats.mCPUusage = 0; script.mStats.mAvgInstructionCount = 0;
const auto oldMemoryUsageIt = mRemovedScriptsMemoryUsage.find(scriptId); const auto oldMemoryUsageIt = mRemovedScriptsMemoryUsage.find(scriptId);
if (oldMemoryUsageIt != mRemovedScriptsMemoryUsage.end()) if (oldMemoryUsageIt != mRemovedScriptsMemoryUsage.end())
@ -590,24 +590,24 @@ namespace LuaUtil
updateTimerQueue(mGameTimersQueue, gameTime); updateTimerQueue(mGameTimersQueue, gameTime);
} }
static constexpr float CPUusageAvgCoef = 1.0 / 30; // averaging over approximately 30 frames static constexpr float instructionCountAvgCoef = 1.0 / 30; // averaging over approximately 30 frames
void ScriptsContainer::CPUusageNextFrame() void ScriptsContainer::statsNextFrame()
{ {
for (auto& [scriptId, script] : mScripts) for (auto& [scriptId, script] : mScripts)
{ {
// The averaging formula is: averageValue = averageValue * (1-c) + newValue * c // The averaging formula is: averageValue = averageValue * (1-c) + newValue * c
script.mStats.mCPUusage *= 1 - CPUusageAvgCoef; script.mStats.mAvgInstructionCount *= 1 - instructionCountAvgCoef;
if (script.mStats.mCPUusage < 5) if (script.mStats.mAvgInstructionCount < 5)
script.mStats.mCPUusage = 0; // speeding up converge to zero if newValue is zero script.mStats.mAvgInstructionCount = 0; // speeding up converge to zero if newValue is zero
} }
} }
void ScriptsContainer::addCPUusage(int scriptId, int64_t CPUusage) void ScriptsContainer::addInstructionCount(int scriptId, int64_t instructionCount)
{ {
auto it = mScripts.find(scriptId); auto it = mScripts.find(scriptId);
if (it != mScripts.end()) if (it != mScripts.end())
it->second.mStats.mCPUusage += CPUusage * CPUusageAvgCoef; it->second.mStats.mAvgInstructionCount += instructionCount * instructionCountAvgCoef;
} }
void ScriptsContainer::addMemoryUsage(int scriptId, int64_t memoryDelta) void ScriptsContainer::addMemoryUsage(int scriptId, int64_t memoryDelta)
@ -640,7 +640,7 @@ namespace LuaUtil
stats.resize(mLua.getConfiguration().size()); stats.resize(mLua.getConfiguration().size());
for (auto& [id, script] : mScripts) for (auto& [id, script] : mScripts)
{ {
stats[id].mCPUusage += script.mStats.mCPUusage; stats[id].mAvgInstructionCount += script.mStats.mAvgInstructionCount;
stats[id].mMemoryUsage += script.mStats.mMemoryUsage; stats[id].mMemoryUsage += script.mStats.mMemoryUsage;
} }
for (auto& [id, mem] : mRemovedScriptsMemoryUsage) for (auto& [id, mem] : mRemovedScriptsMemoryUsage)

@ -146,12 +146,12 @@ namespace LuaUtil
// because they can not be stored in saves. I.e. loading a saved game will not fully restore the state. // 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); 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. // Informs that new frame is started. Needed to track Lua instruction count per frame.
void CPUusageNextFrame(); void statsNextFrame();
struct ScriptStats struct ScriptStats
{ {
float mCPUusage = 0; // averaged number of Lua instructions per frame float mAvgInstructionCount = 0; // averaged number of Lua instructions per frame
int64_t mMemoryUsage = 0; // bytes int64_t mMemoryUsage = 0; // bytes
}; };
void collectStats(std::vector<ScriptStats>& stats) const; void collectStats(std::vector<ScriptStats>& stats) const;
@ -227,7 +227,7 @@ namespace LuaUtil
using EventHandlerList = std::vector<Handler>; using EventHandlerList = std::vector<Handler>;
friend class LuaState; friend class LuaState;
void addCPUusage(int scriptId, int64_t CPUusage); void addInstructionCount(int scriptId, int64_t instructionCount);
void addMemoryUsage(int scriptId, int64_t memoryDelta); void addMemoryUsage(int scriptId, int64_t memoryDelta);
// Add to container without calling onInit/onLoad. // Add to container without calling onInit/onLoad.

@ -89,8 +89,9 @@ namespace LuaUtil
} }
} }
sol::table initUtilPackage(sol::state& lua) sol::table initUtilPackage(lua_State* L)
{ {
sol::state_view lua(L);
sol::table util(lua, sol::create); sol::table util(lua, sol::create);
// Lua bindings for Vec2 // Lua bindings for Vec2

@ -34,8 +34,7 @@ namespace LuaUtil
return { q }; return { q };
} }
sol::table initUtilPackage(sol::state&); sol::table initUtilPackage(lua_State*);
} }
#endif // COMPONENTS_LUA_UTILPACKAGE_H #endif // COMPONENTS_LUA_UTILPACKAGE_H

@ -1141,17 +1141,21 @@ lua debug = false
# If zero, Lua scripts are processed in the main thread. # If zero, Lua scripts are processed in the main thread.
lua num threads = 1 lua num threads = 1
# Enable Lua profiler
lua profiler = true
# No ownership tracking for allocations below or equal this size. # No ownership tracking for allocations below or equal this size.
small alloc max size = 1024 small alloc max size = 1024
# Memory limit for Lua runtime. If exceeded then only small allocations are allowed. Small allocations are always allowed, so e.g. Lua console can function. # Memory limit for Lua runtime (only if lua profiler = true). If exceeded then only small allocations are allowed.
# Default value is 2GB. # Small allocations are always allowed, so e.g. Lua console can function. Default value is 2GB.
memory limit = 2147483648 memory limit = 2147483648
# Print debug info about memory usage. # Print debug info about memory usage (only if lua profiler = true).
log memory usage = false log memory usage = false
# The maximal number of Lua instructions per function call. If exceeded (e.g. because of an infinite loop) the function will be terminated. # The maximal number of Lua instructions per function call (only if lua profiler = true).
# If exceeded (e.g. because of an infinite loop) the function will be terminated.
instruction limit per call = 100000000 instruction limit per call = 100000000
[Stereo] [Stereo]

Loading…
Cancel
Save