Merge branch 'lua_profiler' into 'master'

Lua profiler

See merge request OpenMW/openmw!2523
7098-improve-post-process-behavior-with-transparent-objects
psi29a 2 years ago
commit 1d55be8214

@ -455,7 +455,7 @@ macOS12_Xcode13:
after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: ninja-v4
key: ninja-v5
paths:
- ccache
- deps
@ -556,7 +556,7 @@ macOS12_Xcode13:
after_script:
- Copy-Item C:\ProgramData\chocolatey\logs\chocolatey.log
cache:
key: msbuild-v4
key: msbuild-v5
paths:
- ccache
- deps

@ -100,6 +100,8 @@ namespace MWBase
virtual void handleConsoleCommand(
const std::string& consoleMode, const std::string& command, const MWWorld::Ptr& selectedPtr)
= 0;
virtual std::string formatResourceUsageStats() const = 0;
};
}

@ -159,6 +159,7 @@ namespace MWBase
virtual void updateSpellWindow() = 0;
virtual void setConsoleSelectedObject(const MWWorld::Ptr& object) = 0;
virtual MWWorld::Ptr getConsoleSelectedObject() const = 0;
virtual void setConsoleMode(const std::string& mode) = 0;
static constexpr std::string_view sConsoleColor_Default = "#FFFFFF";

@ -23,6 +23,7 @@ namespace MWGui
public:
/// Set the implicit object for script execution
void setSelectedObject(const MWWorld::Ptr& object);
MWWorld::Ptr getSelectedObject() const { return mPtr; }
MyGUI::EditBox* mCommandLine;
MyGUI::EditBox* mHistory;

@ -8,6 +8,9 @@
#include <components/debug/debugging.hpp>
#include <components/settings/settings.hpp>
#include "../mwbase/environment.hpp"
#include "../mwbase/luamanager.hpp"
#include <mutex>
#ifndef BT_NO_PROFILE
@ -106,6 +109,12 @@ namespace MWGui
= itemLV->createWidgetReal<MyGUI::EditBox>("LogEdit", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Stretch);
mLogView->setEditReadOnly(true);
MyGUI::TabItem* itemLuaProfiler = mTabControl->addItem("Lua Profiler");
itemLuaProfiler->setCaptionWithReplacing(" #{DebugMenu:LuaProfiler} ");
mLuaProfiler = itemLuaProfiler->createWidgetReal<MyGUI::EditBox>(
"LogEdit", MyGUI::FloatCoord(0, 0, 1, 1), MyGUI::Align::Stretch);
mLuaProfiler->setEditReadOnly(true);
#ifndef BT_NO_PROFILE
MyGUI::TabItem* item = mTabControl->addItem("Physics Profiler");
item->setCaptionWithReplacing(" #{DebugMenu:PhysicsProfiler} ");
@ -206,6 +215,16 @@ namespace MWGui
mLogView->setVScrollPosition(scrollPos);
}
void DebugWindow::updateLuaProfile()
{
if (mLuaProfiler->isTextSelection())
return;
size_t previousPos = mLuaProfiler->getVScrollPosition();
mLuaProfiler->setCaption(MWBase::Environment::get().getLuaManager()->formatResourceUsageStats());
mLuaProfiler->setVScrollPosition(std::min(previousPos, mLuaProfiler->getVScrollRange() - 1));
}
void DebugWindow::updateBulletProfile()
{
#ifndef BT_NO_PROFILE
@ -229,9 +248,18 @@ namespace MWGui
return;
timer = 0.25;
if (mTabControl->getIndexSelected() == 0)
updateLogView();
else
updateBulletProfile();
switch (mTabControl->getIndexSelected())
{
case 0:
updateLogView();
break;
case 1:
updateLuaProfile();
break;
case 2:
updateBulletProfile();
break;
default:;
}
}
}

@ -17,10 +17,12 @@ namespace MWGui
private:
void updateLogView();
void updateLuaProfile();
void updateBulletProfile();
MyGUI::TabControl* mTabControl;
MyGUI::EditBox* mLogView;
MyGUI::EditBox* mLuaProfiler;
MyGUI::EditBox* mBulletProfilerEdit;
};

@ -2159,6 +2159,11 @@ namespace MWGui
mConsole->setSelectedObject(object);
}
MWWorld::Ptr WindowManager::getConsoleSelectedObject() const
{
return mConsole->getSelectedObject();
}
void WindowManager::printToConsole(const std::string& msg, std::string_view color)
{
mConsole->print(msg, color);

@ -188,6 +188,7 @@ namespace MWGui
void updateSpellWindow() override;
void setConsoleSelectedObject(const MWWorld::Ptr& object) override;
MWWorld::Ptr getConsoleSelectedObject() const override;
void printToConsole(const std::string& msg, std::string_view color) override;
void setConsoleMode(const std::string& mode) override;

@ -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);

@ -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&);
}

@ -38,8 +38,18 @@
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"),
.mLogMemoryUsage = Settings::Manager::getBool("log memory usage", "Lua") };
}
LuaManager::LuaManager(const VFS::Manager* vfs, const std::filesystem::path& libsDir)
: mLua(vfs, &mConfiguration)
: mLua(vfs, &mConfiguration, createLuaStateSettings())
, mUiResourceManager(vfs)
{
Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion();
@ -123,6 +133,10 @@ namespace MWLua
void LuaManager::update()
{
static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua");
static const int gcStepCount = Settings::Manager::getInt("gc steps per frame", "Lua");
if (gcStepCount > 0)
lua_gc(mLua.sol(), LUA_GCSTEP, gcStepCount);
if (mPlayer.isEmpty())
return; // The game is not started yet.
@ -140,6 +154,10 @@ namespace MWLua
mWorldView.update();
mGlobalScripts.statsNextFrame();
for (LocalScripts* scripts : mActiveLocalScripts)
scripts->statsNextFrame();
std::vector<GlobalEvent> globalEvents = std::move(mGlobalEvents);
std::vector<LocalEvent> localEvents = std::move(mLocalEvents);
mGlobalEvents = std::vector<GlobalEvent>();
@ -602,9 +620,123 @@ namespace MWLua
mActionQueue.push_back(std::make_unique<FunctionAction>(&mLua, std::move(action), name));
}
void LuaManager::reportStats(unsigned int frameNumber, osg::Stats& stats)
void LuaManager::reportStats(unsigned int frameNumber, osg::Stats& stats) const
{
stats.setAttribute(frameNumber, "Lua UsedMemory", mLua.getTotalMemoryUsage());
}
std::string LuaManager::formatResourceUsageStats() const
{
const sol::state_view state(mLua.sol());
stats.setAttribute(frameNumber, "Lua UsedMemory", state.memory_used());
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:";
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:";
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;
std::vector<Stats> activeStats;
mGlobalScripts.collectStats(activeStats);
for (LocalScripts* scripts : mActiveLocalScripts)
scripts->collectStats(activeStats);
std::vector<Stats> selectedStats;
MWWorld::Ptr selectedPtr = MWBase::Environment::get().getWindowManager()->getConsoleSelectedObject();
LocalScripts* selectedScripts = nullptr;
if (!selectedPtr.isEmpty())
{
selectedScripts = selectedPtr.getRefData().getLuaScripts();
if (selectedScripts)
selectedScripts->collectStats(selectedStats);
out << "Profiled object (selected in the in-game console): " << ptrToString(selectedPtr) << "\n";
}
else
out << "No selected object. Use the in-game console to select an object for detailed profile.\n";
out << "\n";
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) << "ops";
out << std::setw(valueW) << "memory";
out << std::setw(valueW) << "memory";
out << std::setw(valueW) << "ops";
out << std::setw(valueW) << "memory";
out << "\n";
out << std::left << " " << std::setw(nameW + 2) << "[name]" << std::right;
out << std::setw(valueW) << "[all]";
out << std::setw(valueW) << "[active]";
out << std::setw(valueW) << "[inactive]";
out << std::setw(valueW * 2) << "[for selected object]";
out << "\n";
for (size_t i = 0; i < mConfiguration.size(); ++i)
{
bool isGlobal = mConfiguration[i].mFlags & ESM::LuaScriptCfg::sGlobal;
out << std::left;
out << " " << std::setw(nameW) << mConfiguration[i].mScriptPath;
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].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) << "-";
outMemSize(selectedStats[i].mMemoryUsage);
}
else
{
out << std::setw(valueW) << static_cast<int64_t>(selectedStats[i].mAvgInstructionCount);
outMemSize(selectedStats[i].mMemoryUsage);
}
out << "\n";
}
return out.str();
}
}

@ -122,7 +122,8 @@ namespace MWLua
bool isProcessingInputEvents() const { return mProcessingInputEvents; }
void reportStats(unsigned int frameNumber, osg::Stats& stats);
void reportStats(unsigned int frameNumber, osg::Stats& stats) const;
std::string formatResourceUsageStats() const override;
private:
void initConfiguration();

@ -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,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");
}
}

@ -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>();
}
@ -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();
sol::state_view& l = lua.sol();
internal::CaptureStdout();
l10n::Manager l10nManager(mVFS.get());
l10nManager.setPreferredLocales({ "de", "en" });
EXPECT_THAT(internal::GetCapturedStdout(), "Preferred locales: de en\n");
@ -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!");
}
}

@ -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);

@ -10,14 +10,16 @@ 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>();
}
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_view& 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");

@ -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

@ -10,6 +10,8 @@
#include <components/files/conversion.hpp>
#include <components/vfs/manager.hpp>
#include "scriptscontainer.hpp"
namespace LuaUtil
{
@ -50,26 +52,141 @@ 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 = 1000;
bool LuaState::sProfilerEnabled = true;
void LuaState::countHook(lua_State* L, lua_Debug* ar)
{
LuaState* self;
(void)lua_getallocf(L, reinterpret_cast<void**>(&self));
if (self->mActiveScriptIdStack.empty())
return;
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 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);
}
}
void* LuaState::trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize)
{
LuaState* self = static_cast<LuaState*>(ud);
const uint64_t smallAllocSize = self->mSettings.mSmallAllocMaxSize;
const uint64_t memoryLimit = self->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 && 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;
}
self->mTotalMemoryUsage += smallAllocDelta + bigAllocDelta;
self->mSmallAllocMemoryUsage += smallAllocDelta;
void* newPtr = nullptr;
if (nsize == 0)
free(ptr);
else
newPtr = realloc(ptr, nsize);
if (bigAllocDelta != 0)
{
auto it = osize > smallAllocSize ? self->mBigAllocOwners.find(ptr) : self->mBigAllocOwners.end();
ScriptId id;
if (it != self->mBigAllocOwners.end())
{
if (it->second.mContainer)
id = ScriptId{ *it->second.mContainer, it->second.mScriptIndex };
if (ptr != newPtr || nsize <= smallAllocSize)
self->mBigAllocOwners.erase(it);
}
else if (bigAllocDelta > 0)
{
if (!self->mActiveScriptIdStack.empty())
id = self->mActiveScriptIdStack.back();
bigAllocDelta = nsize;
}
if (id.mContainer)
{
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)
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)
, mLuaHolder(createLuaRuntime(this))
, mSol(mLuaHolder.get())
, mConf(conf)
, mVFS(vfs)
{
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string,
if (sProfilerEnabled)
lua_sethook(mLuaHolder.get(), &countHook, LUA_MASKCOUNT, countHookStep);
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
@ -77,7 +194,7 @@ namespace LuaUtil
)");
}
mLua.script(R"(
mSol.script(R"(
local printToLog = function(...)
local strs = {}
for i = 1, select('#', ...) do
@ -122,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)
@ -190,19 +300,25 @@ 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;
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;
};
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)
@ -219,13 +335,13 @@ namespace LuaUtil
};
sol::set_environment(env, script);
return call(script);
return call(scriptId, script);
}
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 {
@ -233,7 +349,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;
@ -251,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;
@ -260,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;
@ -269,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;

@ -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,24 +49,26 @@ namespace LuaUtil
class LuaState
{
public:
explicit LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf);
~LuaState();
explicit LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf,
const LuaStateSettings& settings = LuaStateSettings{});
LuaState(const LuaState&) = delete;
LuaState(LuaState&&) = delete;
// 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;
@ -86,37 +103,131 @@ namespace LuaUtil
sol::function loadFromVFS(const std::string& path);
sol::environment newInternalLibEnvironment();
uint64_t getTotalMemoryUsage() const { return mSol.memory_used(); }
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; }
// 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>
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);
lua_State* createLuaRuntime(LuaState* luaState);
struct AllocOwner
{
std::shared_ptr<ScriptsContainer*> mContainer;
int mScriptIndex;
};
const LuaStateSettings mSettings;
// 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;
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 mLua;
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;
};
// 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 = 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)...));
if (luaState)
luaState->mActiveScriptIdStack.pop_back();
return res;
}
catch (std::exception&)
{
if (luaState)
luaState->mActiveScriptIdStack.pop_back();
throw;
}
catch (...)
{
if (luaState)
luaState->mActiveScriptIdStack.pop_back();
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.mAvgInstructionCount = 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 instructionCountAvgCoef = 1.0 / 30; // averaging over approximately 30 frames
void ScriptsContainer::statsNextFrame()
{
for (auto& [scriptId, script] : mScripts)
{
// The averaging formula is: averageValue = averageValue * (1-c) + newValue * c
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::addInstructionCount(int scriptId, int64_t instructionCount)
{
auto it = mScripts.find(scriptId);
if (it != mScripts.end())
it->second.mStats.mAvgInstructionCount += instructionCount * instructionCountAvgCoef;
}
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].mAvgInstructionCount += script.mStats.mAvgInstructionCount;
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 Lua instruction count per frame.
void statsNextFrame();
struct ScriptStats
{
float mAvgInstructionCount = 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 addInstructionCount(int scriptId, int64_t instructionCount);
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);

@ -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

@ -26,3 +26,75 @@ If one, a separate thread is used.
Values >1 are not yet supported.
This setting can only be configured by editing the settings configuration file.
lua profiler
------------
:Type: boolean
:Range: True/False
:Default: True
Enables Lua profiler.
This setting can only be configured by editing the settings configuration file.
small alloc max size
--------------------
:Type: integer
:Range: >= 0
:Default: 1024
No ownership tracking for memory allocations below or equal this size (in bytes).
This setting is used only if ``lua profiler = true``.
With the default value (1024) the lua profiler will show almost no memory usage because allocation more than 1KB are rare.
Decrease the value of this setting (e.g. set it to 64) to have better memory tracking by the cost of higher overhead.
This setting can only be configured by editing the settings configuration file.
memory limit
------------
:Type: integer
:Range: > 0
:Default: 2147483648 (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.
This setting can only be configured by editing the settings configuration file.
log memory usage
----------------
:Type: boolean
:Range: True/False
:Default: False
Print debug info about memory usage (only if ``lua profiler = true``).
This setting can only be configured by editing the settings configuration file.
instruction limit per call
--------------------------
:Type: integer
:Range: > 1000
:Default: 100000000
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.
This setting can only be configured by editing the settings configuration file.
gc steps per frame
------------------
:Type: integer
:Range: >= 0
:Default: 100
Lua garbage collector steps per frame. The higher the value the more time Lua runtime can spend on freeing unused memory.
This setting can only be configured by editing the settings configuration file.

@ -1,3 +1,4 @@
DebugWindow: "Debug"
LogViewer: "Protokollansicht"
LuaProfiler: "Lua-Profiler"
PhysicsProfiler: "Physik-Profiler"

@ -1,3 +1,4 @@
DebugWindow: "Debug"
LogViewer: "Log Viewer"
LuaProfiler: "Lua Profiler"
PhysicsProfiler: "Physics Profiler"

@ -1,3 +1,4 @@
DebugWindow: "Меню отладки"
LogViewer: "Журнал логов"
LuaProfiler: "Профилировщик Луа"
PhysicsProfiler: "Профилировщик физики"

@ -1144,6 +1144,26 @@ 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 (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 (only if lua profiler = true).
log memory usage = false
# 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
# Lua garbage collector steps per frame.
gc steps per frame = 100
[Stereo]
# Enable/disable stereo view. This setting is ignored in VR.
stereo enabled = false

Loading…
Cancel
Save