mirror of
https://gitlab.com/OpenMW/openmw.git
synced 2025-04-28 12:58:00 +03:00
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)
This commit is contained in:
parent
02a9069a0e
commit
55db95d4cf
15 changed files with 221 additions and 130 deletions
|
@ -20,8 +20,9 @@ namespace MWLua
|
|||
saveLuaBinaryData(esm, event.mEventData);
|
||||
}
|
||||
|
||||
void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents, LocalEventQueue& localEvents,
|
||||
const std::map<int, int>& contentFileMapping, const LuaUtil::UserdataSerializer* serializer)
|
||||
void loadEvents(sol::state_view& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents,
|
||||
LocalEventQueue& localEvents, const std::map<int, int>& contentFileMapping,
|
||||
const LuaUtil::UserdataSerializer* serializer)
|
||||
{
|
||||
while (esm.isNextSub("LUAE"))
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ namespace MWLua
|
|||
using GlobalEventQueue = std::vector<GlobalEvent>;
|
||||
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);
|
||||
void saveEvents(ESM::ESMWriter& esm, const GlobalEventQueue&, const LocalEventQueue&);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ namespace MWLua
|
|||
|
||||
static LuaUtil::LuaStateSettings createLuaStateSettings()
|
||||
{
|
||||
if (!Settings::Manager::getBool("lua profiler", "Lua"))
|
||||
LuaUtil::LuaState::disableProfiler();
|
||||
return { .mInstructionLimit = Settings::Manager::getUInt64("instruction limit per call", "Lua"),
|
||||
.mMemoryLimit = Settings::Manager::getUInt64("memory limit", "Lua"),
|
||||
.mSmallAllocMaxSize = Settings::Manager::getUInt64("small alloc max size", "Lua"),
|
||||
|
@ -147,9 +149,9 @@ namespace MWLua
|
|||
|
||||
mWorldView.update();
|
||||
|
||||
mGlobalScripts.CPUusageNextFrame();
|
||||
mGlobalScripts.statsNextFrame();
|
||||
for (LocalScripts* scripts : mActiveLocalScripts)
|
||||
scripts->CPUusageNextFrame();
|
||||
scripts->statsNextFrame();
|
||||
|
||||
std::vector<GlobalEvent> globalEvents = std::move(mGlobalEvents);
|
||||
std::vector<LocalEvent> localEvents = std::move(mLocalEvents);
|
||||
|
@ -620,17 +622,39 @@ namespace MWLua
|
|||
|
||||
std::string LuaManager::formatResourceUsageStats() const
|
||||
{
|
||||
if (!LuaUtil::LuaState::isProfilerEnabled())
|
||||
return "Lua profiler is disabled";
|
||||
|
||||
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");
|
||||
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 << "Smaller values give more information for the profiler, but increase performance overhead.\n";
|
||||
out << " Memory allocations <= " << smallAllocSize << " bytes: " << mLua.getSmallAllocMemoryUsage()
|
||||
<< " (not tracked)\n";
|
||||
out << " Memory allocations > " << smallAllocSize
|
||||
<< " bytes: " << mLua.getTotalMemoryUsage() - mLua.getSmallAllocMemoryUsage() << " (see the table below)\n";
|
||||
out << "\n";
|
||||
out << " Memory allocations <= " << smallAllocSize << " bytes:";
|
||||
outMemSize(mLua.getSmallAllocMemoryUsage());
|
||||
out << " (not tracked)\n";
|
||||
out << " Memory allocations > " << smallAllocSize << " bytes:";
|
||||
outMemSize(mLua.getTotalMemoryUsage() - mLua.getSmallAllocMemoryUsage());
|
||||
out << " (see the table below)\n\n";
|
||||
|
||||
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 << "\n";
|
||||
|
||||
constexpr int nameW = 50;
|
||||
constexpr int valueW = 12;
|
||||
out << "Legend\n";
|
||||
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::setw(nameW + 2) << "*** Resource usage per script";
|
||||
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) << "CPU";
|
||||
out << std::setw(valueW) << "ops";
|
||||
out << std::setw(valueW) << "memory";
|
||||
out << "\n";
|
||||
out << std::left << " " << std::setw(nameW + 2) << "[name]" << std::right;
|
||||
|
@ -681,19 +711,24 @@ namespace MWLua
|
|||
if (mConfiguration[i].mScriptPath.size() > nameW)
|
||||
out << "\n " << std::setw(nameW) << ""; // if path is too long, break line
|
||||
out << std::right;
|
||||
out << std::setw(valueW) << static_cast<int64_t>(activeStats[i].mCPUusage);
|
||||
out << std::setw(valueW) << activeStats[i].mMemoryUsage;
|
||||
out << std::setw(valueW) << mLua.getMemoryUsageByScriptIndex(i) - activeStats[i].mMemoryUsage;
|
||||
out << std::setw(valueW) << static_cast<int64_t>(activeStats[i].mAvgInstructionCount);
|
||||
outMemSize(activeStats[i].mMemoryUsage);
|
||||
outMemSize(mLua.getMemoryUsageByScriptIndex(i) - activeStats[i].mMemoryUsage);
|
||||
|
||||
if (isGlobal)
|
||||
out << std::setw(valueW * 2) << "NA (global script)";
|
||||
else if (selectedPtr.isEmpty())
|
||||
out << std::setw(valueW * 2) << "NA (not selected) ";
|
||||
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
|
||||
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";
|
||||
}
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ namespace MWLua
|
|||
void registerObjectList(const std::string& prefix, const Context& context)
|
||||
{
|
||||
using ListT = ObjectList<ObjectT>;
|
||||
sol::state& lua = context.mLua->sol();
|
||||
sol::state_view& lua = context.mLua->sol();
|
||||
ObjectRegistry* registry = context.mWorldView->getObjectRegistry();
|
||||
sol::usertype<ListT> listT = lua.new_usertype<ListT>(prefix + "ObjectList");
|
||||
listT[sol::meta_function::to_string]
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace
|
|||
using namespace TestingOpenMW;
|
||||
|
||||
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>();
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ you_have_arrows: "Arrows count: {count}"
|
|||
TEST_F(LuaL10nTest, L10n)
|
||||
{
|
||||
LuaUtil::LuaState lua{ mVFS.get(), &mCfg };
|
||||
sol::state& l = lua.sol();
|
||||
sol::state_view& l = lua.sol();
|
||||
internal::CaptureStdout();
|
||||
l10n::Manager l10nManager(mVFS.get());
|
||||
l10nManager.setPreferredLocales({ "de", "en" });
|
||||
|
@ -164,5 +164,4 @@ you_have_arrows: "Arrows count: {count}"
|
|||
l10nManager.setPreferredLocales({ "en" });
|
||||
EXPECT_EQ(get<std::string>(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace
|
|||
using namespace testing;
|
||||
|
||||
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>();
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ namespace
|
|||
{
|
||||
// Note: LuaUtil::Callback can be used only if Lua is initialized via LuaUtil::LuaState
|
||||
LuaUtil::LuaState luaState{ nullptr, nullptr };
|
||||
sol::state& mLua = luaState.sol();
|
||||
sol::state_view& mLua = luaState.sol();
|
||||
LuaUtil::LuaStorage::initLuaBindings(mLua);
|
||||
LuaUtil::LuaStorage storage(mLua);
|
||||
|
||||
|
|
|
@ -46,8 +46,9 @@ namespace sol
|
|||
|
||||
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");
|
||||
ctxDef[sol::meta_function::call]
|
||||
= [](const L10nContext& ctx, std::string_view key, sol::optional<sol::table> args) {
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace l10n
|
|||
|
||||
namespace LuaUtil
|
||||
{
|
||||
sol::function initL10nLoader(sol::state& lua, l10n::Manager* manager);
|
||||
sol::function initL10nLoader(lua_State*, l10n::Manager* manager);
|
||||
}
|
||||
|
||||
#endif // COMPONENTS_LUA_L10N_H
|
||||
|
|
|
@ -52,21 +52,24 @@ namespace LuaUtil
|
|||
"tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" };
|
||||
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)
|
||||
{
|
||||
LuaState* THIS;
|
||||
(void)lua_getallocf(L, reinterpret_cast<void**>(&THIS));
|
||||
if (!THIS->mActiveScriptId.mContainer)
|
||||
LuaState* self;
|
||||
(void)lua_getallocf(L, reinterpret_cast<void**>(&self));
|
||||
if (self->mActiveScriptIdStack.empty())
|
||||
return;
|
||||
THIS->mActiveScriptId.mContainer->addCPUusage(THIS->mActiveScriptId.mIndex, countHookStep);
|
||||
THIS->mCurrentCallInstructionCounter += countHookStep;
|
||||
if (THIS->mSettings.mInstructionLimit > 0
|
||||
&& THIS->mCurrentCallInstructionCounter > THIS->mSettings.mInstructionLimit)
|
||||
const ScriptId& activeScript = self->mActiveScriptIdStack.back();
|
||||
activeScript.mContainer->addInstructionCount(activeScript.mIndex, countHookStep);
|
||||
self->mWatchdogInstructionCounter += countHookStep;
|
||||
if (self->mSettings.mInstructionLimit > 0
|
||||
&& self->mWatchdogInstructionCounter > self->mSettings.mInstructionLimit)
|
||||
{
|
||||
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");
|
||||
lua_error(L);
|
||||
}
|
||||
|
@ -74,9 +77,9 @@ namespace LuaUtil
|
|||
|
||||
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;
|
||||
LuaState* self = static_cast<LuaState*>(ud);
|
||||
const uint64_t smallAllocSize = self->mSettings.mSmallAllocMaxSize;
|
||||
const uint64_t memoryLimit = self->mSettings.mMemoryLimit;
|
||||
|
||||
if (!ptr)
|
||||
osize = 0;
|
||||
|
@ -90,14 +93,14 @@ namespace LuaUtil
|
|||
else
|
||||
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
|
||||
<< " is blocked because Lua memory limit (configurable in settings.cfg) is exceeded";
|
||||
return nullptr;
|
||||
}
|
||||
THIS->mTotalMemoryUsage += smallAllocDelta + bigAllocDelta;
|
||||
THIS->mSmallAllocMemoryUsage += smallAllocDelta;
|
||||
self->mTotalMemoryUsage += smallAllocDelta + bigAllocDelta;
|
||||
self->mSmallAllocMemoryUsage += smallAllocDelta;
|
||||
|
||||
void* newPtr = nullptr;
|
||||
if (nsize == 0)
|
||||
|
@ -107,59 +110,83 @@ namespace LuaUtil
|
|||
|
||||
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;
|
||||
if (it != THIS->mBigAllocOwners.end())
|
||||
if (it != self->mBigAllocOwners.end())
|
||||
{
|
||||
if (it->second.mContainer)
|
||||
id = ScriptId{ *it->second.mContainer, it->second.mScriptIndex };
|
||||
if (ptr != newPtr || nsize <= smallAllocSize)
|
||||
THIS->mBigAllocOwners.erase(it);
|
||||
self->mBigAllocOwners.erase(it);
|
||||
}
|
||||
else if (bigAllocDelta > 0)
|
||||
{
|
||||
id = THIS->mActiveScriptId;
|
||||
if (!self->mActiveScriptIdStack.empty())
|
||||
id = self->mActiveScriptIdStack.back();
|
||||
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;
|
||||
if (static_cast<size_t>(id.mIndex) >= self->mMemoryUsage.size())
|
||||
self->mMemoryUsage.resize(id.mIndex + 1);
|
||||
self->mMemoryUsage[id.mIndex] += bigAllocDelta;
|
||||
id.mContainer->addMemoryUsage(id.mIndex, bigAllocDelta);
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
: mSettings(settings)
|
||||
, mLua(sol::default_at_panic, &trackingAllocator, this)
|
||||
, mLuaHolder(createLuaRuntime(this))
|
||||
, mSol(mLuaHolder.get())
|
||||
, mConf(conf)
|
||||
, mVFS(vfs)
|
||||
{
|
||||
lua_sethook(mLua.lua_state(), &countHook, LUA_MASKCOUNT, countHookStep);
|
||||
Log(Debug::Verbose) << "Initializing LuaUtil::LuaState";
|
||||
if (sProfilerEnabled)
|
||||
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);
|
||||
|
||||
mLua["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr)));
|
||||
mLua["math"]["randomseed"] = [] {};
|
||||
mSol["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr)));
|
||||
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
|
||||
if (mLua["unpack"] == sol::nil)
|
||||
mLua["unpack"] = mLua["table"]["unpack"];
|
||||
else if (mLua["table"]["unpack"] == sol::nil)
|
||||
mLua["table"]["unpack"] = mLua["unpack"];
|
||||
if (mSol["unpack"] == sol::nil)
|
||||
mSol["unpack"] = mSol["table"]["unpack"];
|
||||
else if (mSol["table"]["unpack"] == sol::nil)
|
||||
mSol["table"]["unpack"] = mSol["unpack"];
|
||||
if (LUA_VERSION_NUM <= 501)
|
||||
{
|
||||
mLua.script(R"(
|
||||
mSol.script(R"(
|
||||
local _pairs = pairs
|
||||
local _ipairs = ipairs
|
||||
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 strs = {}
|
||||
for i = 1, select('#', ...) do
|
||||
|
@ -212,31 +239,24 @@ namespace LuaUtil
|
|||
end
|
||||
)");
|
||||
|
||||
mSandboxEnv = sol::table(mLua, sol::create);
|
||||
mSandboxEnv["_VERSION"] = mLua["_VERSION"];
|
||||
mSandboxEnv = sol::table(mSol, sol::create);
|
||||
mSandboxEnv["_VERSION"] = mSol["_VERSION"];
|
||||
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);
|
||||
mSandboxEnv[s] = mLua[s];
|
||||
mSandboxEnv[s] = mSol[s];
|
||||
}
|
||||
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);
|
||||
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"]
|
||||
= makeReadOnly(tableFromPairs<std::string_view, sol::function>({ { "date", mLua["os"]["date"] },
|
||||
{ "difftime", mLua["os"]["difftime"] }, { "time", mLua["os"]["time"] } }));
|
||||
}
|
||||
|
||||
LuaState::~LuaState()
|
||||
{
|
||||
// Should be cleaned before destructing mLua.
|
||||
mCommonPackages.clear();
|
||||
mSandboxEnv = sol::nil;
|
||||
= makeReadOnly(tableFromPairs<std::string_view, sol::function>({ { "date", mSol["os"]["date"] },
|
||||
{ "difftime", mSol["os"]["difftime"] }, { "time", mSol["os"]["time"] } }));
|
||||
}
|
||||
|
||||
sol::table makeReadOnly(const sol::table& table, bool strictIndex)
|
||||
|
@ -280,9 +300,9 @@ namespace LuaUtil
|
|||
{
|
||||
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 + "]:";
|
||||
env["print"] = mLua["printGen"](envName);
|
||||
env["print"] = mSol["printGen"](envName);
|
||||
env["_G"] = env;
|
||||
env[sol::metatable_key]["__metatable"] = false;
|
||||
|
||||
|
@ -298,18 +318,18 @@ namespace LuaUtil
|
|||
else
|
||||
return package;
|
||||
};
|
||||
sol::table loaded(mLua, sol::create);
|
||||
sol::table loaded(mSol, sol::create);
|
||||
for (const auto& [key, value] : mCommonPackages)
|
||||
loaded[key] = maybeRunLoader(value);
|
||||
for (const auto& [key, value] : packages)
|
||||
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];
|
||||
if (package != sol::nil)
|
||||
return package;
|
||||
sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS));
|
||||
sol::set_environment(env, packageLoader);
|
||||
package = call(scriptId, packageLoader, packageName);
|
||||
package = call(packageLoader, packageName);
|
||||
loaded[packageName] = package;
|
||||
return package;
|
||||
};
|
||||
|
@ -320,8 +340,8 @@ namespace LuaUtil
|
|||
|
||||
sol::environment LuaState::newInternalLibEnvironment()
|
||||
{
|
||||
sol::environment env(mLua, sol::create, mSandboxEnv);
|
||||
sol::table loaded(mLua, sol::create);
|
||||
sol::environment env(mSol, sol::create, mSandboxEnv);
|
||||
sol::table loaded(mSol, sol::create);
|
||||
for (const std::string& s : safePackages)
|
||||
loaded[s] = static_cast<sol::object>(mSandboxEnv[s]);
|
||||
env["require"] = [this, loaded, env](const std::string& module) mutable {
|
||||
|
@ -347,7 +367,7 @@ namespace LuaUtil
|
|||
{
|
||||
auto iter = mCompiledScripts.find(path);
|
||||
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);
|
||||
mCompiledScripts[path] = res.dump();
|
||||
return res;
|
||||
|
@ -356,7 +376,7 @@ namespace LuaUtil
|
|||
sol::function LuaState::loadFromVFS(const std::string& 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())
|
||||
throw std::runtime_error("Lua error: " + res.get<std::string>());
|
||||
return res;
|
||||
|
@ -365,7 +385,7 @@ namespace LuaUtil
|
|||
sol::function LuaState::loadInternalLib(std::string_view libName)
|
||||
{
|
||||
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())
|
||||
throw std::runtime_error("Lua error: " + res.get<std::string>());
|
||||
return res;
|
||||
|
|
|
@ -54,23 +54,21 @@ namespace LuaUtil
|
|||
LuaState(const LuaState&) = delete;
|
||||
LuaState(LuaState&&) = delete;
|
||||
|
||||
~LuaState();
|
||||
|
||||
// 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.
|
||||
// Makes no sense if called not from Lua code.
|
||||
// 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.
|
||||
sol::table newTable() { return sol::table(mLua, sol::create); }
|
||||
sol::table newTable() { return sol::table(mSol, sol::create); }
|
||||
|
||||
template <typename Key, typename Value>
|
||||
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)
|
||||
res[k] = v;
|
||||
return res;
|
||||
|
@ -105,7 +103,7 @@ namespace LuaUtil
|
|||
sol::function loadFromVFS(const std::string& path);
|
||||
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 getMemoryUsageByScriptIndex(unsigned id) const
|
||||
{
|
||||
|
@ -114,6 +112,10 @@ namespace LuaUtil
|
|||
|
||||
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:
|
||||
static sol::protected_function_result throwIfError(sol::protected_function_result&&);
|
||||
template <typename... Args>
|
||||
|
@ -126,6 +128,8 @@ namespace LuaUtil
|
|||
static void countHook(lua_State* L, lua_Debug* ar);
|
||||
static void* trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize);
|
||||
|
||||
lua_State* createLuaRuntime(LuaState* luaState);
|
||||
|
||||
struct AllocOwner
|
||||
{
|
||||
std::shared_ptr<ScriptsContainer*> mContainer;
|
||||
|
@ -134,21 +138,43 @@ namespace LuaUtil
|
|||
|
||||
const LuaStateSettings mSettings;
|
||||
|
||||
// Needed to track resource usage per script, must be initialized before mLua.
|
||||
ScriptId mActiveScriptId;
|
||||
uint64_t mCurrentCallInstructionCounter = 0;
|
||||
// Needed to track resource usage per script, must be initialized before mLuaHolder.
|
||||
std::vector<ScriptId> mActiveScriptIdStack;
|
||||
uint64_t mWatchdogInstructionCounter = 0;
|
||||
std::map<void*, AllocOwner> mBigAllocOwners;
|
||||
uint64_t mTotalMemoryUsage = 0;
|
||||
uint64_t mSmallAllocMemoryUsage = 0;
|
||||
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;
|
||||
sol::table mSandboxEnv;
|
||||
std::map<std::string, sol::bytecode> mCompiledScripts;
|
||||
std::map<std::string, sol::object> mCommonPackages;
|
||||
const VFS::Manager* mVFS;
|
||||
std::vector<std::filesystem::path> mLibSearchPaths;
|
||||
|
||||
static bool sProfilerEnabled;
|
||||
};
|
||||
|
||||
// LuaUtil::call should be used for every call of every Lua function.
|
||||
|
@ -178,25 +204,30 @@ namespace LuaUtil
|
|||
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;
|
||||
LuaState* luaState = nullptr;
|
||||
if (LuaState::sProfilerEnabled && scriptId.mContainer)
|
||||
{
|
||||
(void)lua_getallocf(fn.lua_state(), reinterpret_cast<void**>(&luaState));
|
||||
luaState->mActiveScriptIdStack.push_back(scriptId);
|
||||
luaState->mWatchdogInstructionCounter = 0;
|
||||
}
|
||||
try
|
||||
{
|
||||
auto res = LuaState::throwIfError(fn(std::forward<Args>(args)...));
|
||||
luaState->mActiveScriptId = {};
|
||||
if (luaState)
|
||||
luaState->mActiveScriptIdStack.pop_back();
|
||||
return res;
|
||||
}
|
||||
catch (std::exception&)
|
||||
{
|
||||
luaState->mActiveScriptId = {};
|
||||
if (luaState)
|
||||
luaState->mActiveScriptIdStack.pop_back();
|
||||
throw;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
luaState->mActiveScriptId = {};
|
||||
if (luaState)
|
||||
luaState->mActiveScriptIdStack.pop_back();
|
||||
throw std::runtime_error("Unknown error");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,7 +75,7 @@ namespace LuaUtil
|
|||
script.mHiddenData[sScriptIdKey] = ScriptId{ this, scriptId };
|
||||
script.mHiddenData[sScriptDebugNameKey] = debugName;
|
||||
script.mPath = path;
|
||||
script.mStats.mCPUusage = 0;
|
||||
script.mStats.mAvgInstructionCount = 0;
|
||||
|
||||
const auto oldMemoryUsageIt = mRemovedScriptsMemoryUsage.find(scriptId);
|
||||
if (oldMemoryUsageIt != mRemovedScriptsMemoryUsage.end())
|
||||
|
@ -590,24 +590,24 @@ namespace LuaUtil
|
|||
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)
|
||||
{
|
||||
// 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
|
||||
script.mStats.mAvgInstructionCount *= 1 - instructionCountAvgCoef;
|
||||
if (script.mStats.mAvgInstructionCount < 5)
|
||||
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);
|
||||
if (it != mScripts.end())
|
||||
it->second.mStats.mCPUusage += CPUusage * CPUusageAvgCoef;
|
||||
it->second.mStats.mAvgInstructionCount += instructionCount * instructionCountAvgCoef;
|
||||
}
|
||||
|
||||
void ScriptsContainer::addMemoryUsage(int scriptId, int64_t memoryDelta)
|
||||
|
@ -640,7 +640,7 @@ namespace LuaUtil
|
|||
stats.resize(mLua.getConfiguration().size());
|
||||
for (auto& [id, script] : mScripts)
|
||||
{
|
||||
stats[id].mCPUusage += script.mStats.mCPUusage;
|
||||
stats[id].mAvgInstructionCount += script.mStats.mAvgInstructionCount;
|
||||
stats[id].mMemoryUsage += script.mStats.mMemoryUsage;
|
||||
}
|
||||
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.
|
||||
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();
|
||||
// Informs that new frame is started. Needed to track Lua instruction count per frame.
|
||||
void statsNextFrame();
|
||||
|
||||
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
|
||||
};
|
||||
void collectStats(std::vector<ScriptStats>& stats) const;
|
||||
|
@ -227,7 +227,7 @@ namespace LuaUtil
|
|||
using EventHandlerList = std::vector<Handler>;
|
||||
|
||||
friend class LuaState;
|
||||
void addCPUusage(int scriptId, int64_t CPUusage);
|
||||
void addInstructionCount(int scriptId, int64_t instructionCount);
|
||||
void addMemoryUsage(int scriptId, int64_t memoryDelta);
|
||||
|
||||
// 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);
|
||||
|
||||
// Lua bindings for Vec2
|
||||
|
|
|
@ -34,8 +34,7 @@ namespace LuaUtil
|
|||
return { q };
|
||||
}
|
||||
|
||||
sol::table initUtilPackage(sol::state&);
|
||||
|
||||
sol::table initUtilPackage(lua_State*);
|
||||
}
|
||||
|
||||
#endif // COMPONENTS_LUA_UTILPACKAGE_H
|
||||
|
|
|
@ -1141,17 +1141,21 @@ lua debug = false
|
|||
# If zero, Lua scripts are processed in the main thread.
|
||||
lua num threads = 1
|
||||
|
||||
# Enable Lua profiler
|
||||
lua profiler = true
|
||||
|
||||
# No ownership tracking for allocations below or equal this size.
|
||||
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.
|
||||
# Default value is 2GB.
|
||||
# Memory limit for Lua runtime (only if lua profiler = true). If exceeded then only small allocations are allowed.
|
||||
# Small allocations are always allowed, so e.g. Lua console can function. Default value is 2GB.
|
||||
memory limit = 2147483648
|
||||
|
||||
# Print debug info about memory usage.
|
||||
# Print debug info about memory usage (only if lua profiler = true).
|
||||
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
|
||||
|
||||
[Stereo]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue