1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-04-10 15:45:37 +00:00

Merge branch 'lua_callback' into 'master'

Refactoring. Lua `Callback` is moved from apps/openmw/mwlua to components/lua.

See merge request OpenMW/openmw!1334
This commit is contained in:
psi29a 2021-11-02 20:28:34 +00:00
commit 4ff4afd50b
5 changed files with 59 additions and 56 deletions

View File

@ -52,13 +52,12 @@ namespace MWLua
}; };
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn) api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn)
{ {
return Callback{std::move(fn), asyncId.mHiddenData}; return LuaUtil::Callback{std::move(fn), asyncId.mHiddenData};
}; };
auto initializer = [](sol::table hiddenData) auto initializer = [](sol::table hiddenData)
{ {
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY]; LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::sScriptIdKey];
hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString();
return AsyncPackageId{id.mContainer, id.mIndex, hiddenData}; return AsyncPackageId{id.mContainer, id.mIndex, hiddenData};
}; };
return sol::make_object(context.mLua->sol(), initializer); return sol::make_object(context.mLua->sol(), initializer);

View File

@ -78,16 +78,6 @@ namespace MWLua
mInitialized = true; mInitialized = true;
} }
void Callback::operator()(sol::object arg) const
{
if (mHiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY] != sol::nil)
LuaUtil::call(mFunc, std::move(arg));
else
{
Log(Debug::Debug) << "Ignored callback to removed script " << mHiddenData.get<std::string>(SCRIPT_NAME_KEY);
}
}
void LuaManager::update(bool paused, float dt) void LuaManager::update(bool paused, float dt)
{ {
ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry(); ObjectRegistry* objectRegistry = mWorldView.getObjectRegistry();

View File

@ -19,19 +19,6 @@
namespace MWLua namespace MWLua
{ {
// Wrapper for a single-argument Lua function.
// Holds information about the script the function belongs to.
// Needed to prevent callback calls if the script was removed.
struct Callback
{
static constexpr std::string_view SCRIPT_NAME_KEY = "name";
sol::function mFunc;
sol::table mHiddenData;
void operator()(sol::object arg) const;
};
class LuaManager : public MWBase::LuaManager class LuaManager : public MWBase::LuaManager
{ {
public: public:
@ -82,13 +69,16 @@ namespace MWLua
void reloadAllScripts() override; void reloadAllScripts() override;
// Used to call Lua callbacks from C++ // Used to call Lua callbacks from C++
void queueCallback(Callback callback, sol::object arg) { mQueuedCallbacks.push_back({std::move(callback), std::move(arg)}); } void queueCallback(LuaUtil::Callback callback, sol::object arg)
{
mQueuedCallbacks.push_back({std::move(callback), std::move(arg)});
}
// Wraps Lua callback into an std::function. // Wraps Lua callback into an std::function.
// NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or // NOTE: Resulted function is not thread safe. Can not be used while LuaManager::update() or
// any other Lua-related function is running. // any other Lua-related function is running.
template <class Arg> template <class Arg>
std::function<void(Arg)> wrapLuaCallback(const Callback& c) std::function<void(Arg)> wrapLuaCallback(const LuaUtil::Callback& c)
{ {
return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); }; return [this, c](Arg arg) { this->queueCallback(c, sol::make_object(c.mFunc.lua_state(), arg)); };
} }
@ -131,7 +121,7 @@ namespace MWLua
struct CallbackWithData struct CallbackWithData
{ {
Callback mCallback; LuaUtil::Callback mCallback;
sol::object mArg; sol::object mArg;
}; };
std::vector<CallbackWithData> mQueuedCallbacks; std::vector<CallbackWithData> mQueuedCallbacks;

View File

@ -15,15 +15,6 @@ namespace LuaUtil
static constexpr std::string_view HANDLER_LOAD = "onLoad"; static constexpr std::string_view HANDLER_LOAD = "onLoad";
static constexpr std::string_view HANDLER_INTERFACE_OVERRIDE = "onInterfaceOverride"; static constexpr std::string_view HANDLER_INTERFACE_OVERRIDE = "onInterfaceOverride";
std::string ScriptsContainer::ScriptId::toString() const
{
std::string res = mContainer->mNamePrefix;
res.push_back('[');
res.append(mPath);
res.push_back(']');
return res;
}
ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode) ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode)
: mNamePrefix(namePrefix), mLua(*lua), mAutoStartMode(autoStartMode) : mNamePrefix(namePrefix), mLua(*lua), mAutoStartMode(autoStartMode)
{ {
@ -70,11 +61,19 @@ namespace LuaUtil
return false; // already present return false; // already present
const std::string& path = scriptPath(scriptId); const std::string& path = scriptPath(scriptId);
std::string debugName = mNamePrefix;
debugName.push_back('[');
debugName.append(path);
debugName.push_back(']');
Script& script = mScripts[scriptId];
script.mHiddenData = mLua.newTable();
script.mHiddenData[sScriptIdKey] = ScriptId{this, scriptId};
script.mHiddenData[sScriptDebugNameKey] = debugName;
script.mPath = path;
try try
{ {
Script& script = mScripts[scriptId];
script.mHiddenData = mLua.newTable();
script.mHiddenData[ScriptId::KEY] = ScriptId{this, scriptId, path};
sol::object scriptOutput = mLua.runInNewSandbox(path, mNamePrefix, mAPI, script.mHiddenData); sol::object scriptOutput = mLua.runInNewSandbox(path, mNamePrefix, mAPI, script.mHiddenData);
if (scriptOutput == sol::nil) if (scriptOutput == sol::nil)
return true; return true;
@ -91,7 +90,7 @@ namespace LuaUtil
else if (sectionName == INTERFACE) else if (sectionName == INTERFACE)
script.mInterface = value.as<sol::table>(); script.mInterface = value.as<sol::table>();
else else
Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << mNamePrefix << "[" << path << "]"; Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << debugName;
} }
if (engineHandlers != sol::nil) if (engineHandlers != sol::nil)
{ {
@ -110,8 +109,7 @@ namespace LuaUtil
{ {
auto it = mEngineHandlers.find(handlerName); auto it = mEngineHandlers.find(handlerName);
if (it == mEngineHandlers.end()) if (it == mEngineHandlers.end())
Log(Debug::Error) << "Not supported handler '" << handlerName Log(Debug::Error) << "Not supported handler '" << handlerName << "' in " << debugName;
<< "' in " << mNamePrefix << "[" << path << "]";
else else
insertHandler(it->second->mList, scriptId, fn); insertHandler(it->second->mList, scriptId, fn);
} }
@ -131,7 +129,7 @@ namespace LuaUtil
if (script.mInterfaceName.empty() == script.mInterface.has_value()) if (script.mInterfaceName.empty() == script.mInterface.has_value())
{ {
Log(Debug::Error) << mNamePrefix << "[" << path << "]: 'interfaceName' should always be used together with 'interface'"; Log(Debug::Error) << debugName << ": 'interfaceName' should always be used together with 'interface'";
script.mInterfaceName.clear(); script.mInterfaceName.clear();
script.mInterface = sol::nil; script.mInterface = sol::nil;
} }
@ -145,8 +143,9 @@ namespace LuaUtil
} }
catch (std::exception& e) catch (std::exception& e)
{ {
mScripts[scriptId].mHiddenData[sScriptIdKey] = sol::nil;
mScripts.erase(scriptId); mScripts.erase(scriptId);
Log(Debug::Error) << "Can't start " << mNamePrefix << "[" << path << "]; " << e.what(); Log(Debug::Error) << "Can't start " << debugName << "; " << e.what();
return false; return false;
} }
} }
@ -159,7 +158,7 @@ namespace LuaUtil
Script& script = scriptIter->second; Script& script = scriptIter->second;
if (script.mInterface) if (script.mInterface)
removeInterface(scriptId, script); removeInterface(scriptId, script);
script.mHiddenData[ScriptId::KEY] = sol::nil; script.mHiddenData[sScriptIdKey] = sol::nil;
mScripts.erase(scriptIter); mScripts.erase(scriptIter);
for (auto& [_, handlers] : mEngineHandlers) for (auto& [_, handlers] : mEngineHandlers)
removeHandler(handlers->mList, scriptId); removeHandler(handlers->mList, scriptId);
@ -329,7 +328,9 @@ namespace LuaUtil
for (auto& [scriptId, script] : mScripts) for (auto& [scriptId, script] : mScripts)
{ {
ESM::LuaScript savedScript; ESM::LuaScript savedScript;
savedScript.mScriptPath = script.mHiddenData.get<ScriptId>(ScriptId::KEY).mPath; // Note: We can not use `scriptPath(scriptId)` here because `save` can be called during
// evaluating "reloadlua" command when ScriptsConfiguration is already changed.
savedScript.mScriptPath = script.mPath;
if (script.mOnSave) if (script.mOnSave)
{ {
try try
@ -423,7 +424,7 @@ namespace LuaUtil
ScriptsContainer::~ScriptsContainer() ScriptsContainer::~ScriptsContainer()
{ {
for (auto& [_, script] : mScripts) for (auto& [_, script] : mScripts)
script.mHiddenData[ScriptId::KEY] = sol::nil; script.mHiddenData[sScriptIdKey] = sol::nil;
} }
// Note: shouldn't be called from destructor because mEngineHandlers has pointers on // Note: shouldn't be called from destructor because mEngineHandlers has pointers on
@ -431,7 +432,7 @@ namespace LuaUtil
void ScriptsContainer::removeAllScripts() void ScriptsContainer::removeAllScripts()
{ {
for (auto& [_, script] : mScripts) for (auto& [_, script] : mScripts)
script.mHiddenData[ScriptId::KEY] = sol::nil; script.mHiddenData[sScriptIdKey] = sol::nil;
mScripts.clear(); mScripts.clear();
for (auto& [_, handlers] : mEngineHandlers) for (auto& [_, handlers] : mEngineHandlers)
handlers->mList.clear(); handlers->mList.clear();
@ -529,4 +530,13 @@ namespace LuaUtil
updateTimerQueue(mHoursTimersQueue, gameHours); updateTimerQueue(mHoursTimersQueue, gameHours);
} }
void Callback::operator()(sol::object arg) const
{
if (mHiddenData[ScriptsContainer::sScriptIdKey] != sol::nil)
LuaUtil::call(mFunc, std::move(arg));
else
Log(Debug::Debug) << "Ignored callback to the removed script "
<< mHiddenData.get<std::string>(ScriptsContainer::sScriptDebugNameKey);
}
} }

View File

@ -60,16 +60,18 @@ namespace LuaUtil
class ScriptsContainer class ScriptsContainer
{ {
public: public:
// ScriptId of each script is stored with this key in Script::mHiddenData.
// Removed from mHiddenData when the script if removed.
constexpr static std::string_view sScriptIdKey = "_id";
// Debug identifier of each script is stored with this key in Script::mHiddenData.
// Present in mHiddenData even after removal of the script from ScriptsContainer.
constexpr static std::string_view sScriptDebugNameKey = "_name";
struct ScriptId struct ScriptId
{ {
// ScriptId is stored in hidden data (see getHiddenData) with this key.
constexpr static std::string_view KEY = "_id";
ScriptsContainer* mContainer; ScriptsContainer* mContainer;
int mIndex; // index in LuaUtil::ScriptsConfiguration int mIndex; // index in LuaUtil::ScriptsConfiguration
std::string mPath; // path to the script source in VFS
std::string toString() const;
}; };
using TimeUnit = ESM::LuaTimer::TimeUnit; using TimeUnit = ESM::LuaTimer::TimeUnit;
@ -192,6 +194,7 @@ namespace LuaUtil
sol::table mHiddenData; sol::table mHiddenData;
std::map<std::string, sol::function> mRegisteredCallbacks; std::map<std::string, sol::function> mRegisteredCallbacks;
std::map<int64_t, sol::function> mTemporaryCallbacks; std::map<int64_t, sol::function> mTemporaryCallbacks;
std::string mPath;
}; };
struct Timer struct Timer
{ {
@ -239,6 +242,17 @@ namespace LuaUtil
int64_t mTemporaryCallbackCounter = 0; int64_t mTemporaryCallbackCounter = 0;
}; };
// Wrapper for a single-argument Lua function.
// Holds information about the script the function belongs to.
// Needed to prevent callback calls if the script was removed.
struct Callback
{
sol::function mFunc;
sol::table mHiddenData; // same object as Script::mHiddenData in ScriptsContainer
void operator()(sol::object arg) const;
};
} }
#endif // COMPONENTS_LUA_SCRIPTSCONTAINER_H #endif // COMPONENTS_LUA_SCRIPTSCONTAINER_H