|
|
|
@ -10,8 +10,10 @@ namespace LuaUtil
|
|
|
|
|
static constexpr std::string_view INTERFACE_NAME = "interfaceName";
|
|
|
|
|
static constexpr std::string_view INTERFACE = "interface";
|
|
|
|
|
|
|
|
|
|
static constexpr std::string_view HANDLER_INIT = "onInit";
|
|
|
|
|
static constexpr std::string_view HANDLER_SAVE = "onSave";
|
|
|
|
|
static constexpr std::string_view HANDLER_LOAD = "onLoad";
|
|
|
|
|
static constexpr std::string_view HANDLER_INTERFACE_OVERRIDE = "onInterfaceOverride";
|
|
|
|
|
|
|
|
|
|
static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers";
|
|
|
|
|
static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers";
|
|
|
|
@ -25,147 +27,238 @@ namespace LuaUtil
|
|
|
|
|
return res;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix) : mNamePrefix(namePrefix), mLua(*lua)
|
|
|
|
|
ScriptsContainer::ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode)
|
|
|
|
|
: mNamePrefix(namePrefix), mLua(*lua), mAutoStartMode(autoStartMode)
|
|
|
|
|
{
|
|
|
|
|
registerEngineHandlers({&mUpdateHandlers});
|
|
|
|
|
mPublicInterfaces = sol::table(lua->sol(), sol::create);
|
|
|
|
|
addPackage("openmw.interfaces", mPublicInterfaces);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::addPackage(const std::string& packageName, sol::object package)
|
|
|
|
|
void ScriptsContainer::printError(int scriptId, std::string_view msg, const std::exception& e)
|
|
|
|
|
{
|
|
|
|
|
API[packageName] = makeReadOnly(std::move(package));
|
|
|
|
|
Log(Debug::Error) << mNamePrefix << "[" << scriptPath(scriptId) << "] " << msg << ": " << e.what();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ScriptsContainer::addNewScript(const std::string& path)
|
|
|
|
|
void ScriptsContainer::addPackage(std::string packageName, sol::object package)
|
|
|
|
|
{
|
|
|
|
|
if (mScripts.count(path) != 0)
|
|
|
|
|
mAPI.emplace(std::move(packageName), makeReadOnly(std::move(package)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ScriptsContainer::addCustomScript(int scriptId)
|
|
|
|
|
{
|
|
|
|
|
assert(mLua.getConfiguration()[scriptId].mFlags & ESM::LuaScriptCfg::sCustom);
|
|
|
|
|
std::optional<sol::function> onInit, onLoad;
|
|
|
|
|
bool ok = addScript(scriptId, onInit, onLoad);
|
|
|
|
|
if (ok && onInit)
|
|
|
|
|
callOnInit(scriptId, *onInit);
|
|
|
|
|
return ok;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::addAutoStartedScripts()
|
|
|
|
|
{
|
|
|
|
|
for (int scriptId : mLua.getConfiguration().getListByFlag(mAutoStartMode))
|
|
|
|
|
{
|
|
|
|
|
std::optional<sol::function> onInit, onLoad;
|
|
|
|
|
bool ok = addScript(scriptId, onInit, onLoad);
|
|
|
|
|
if (ok && onInit)
|
|
|
|
|
callOnInit(scriptId, *onInit);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ScriptsContainer::addScript(int scriptId, std::optional<sol::function>& onInit, std::optional<sol::function>& onLoad)
|
|
|
|
|
{
|
|
|
|
|
assert(scriptId >= 0 && scriptId < static_cast<int>(mLua.getConfiguration().size()));
|
|
|
|
|
if (mScripts.count(scriptId) != 0)
|
|
|
|
|
return false; // already present
|
|
|
|
|
|
|
|
|
|
const std::string& path = scriptPath(scriptId);
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sol::table hiddenData(mLua.sol(), sol::create);
|
|
|
|
|
hiddenData[ScriptId::KEY] = ScriptId{this, path};
|
|
|
|
|
hiddenData[REGISTERED_TIMER_CALLBACKS] = mLua.newTable();
|
|
|
|
|
hiddenData[TEMPORARY_TIMER_CALLBACKS] = mLua.newTable();
|
|
|
|
|
mScripts[path].mHiddenData = hiddenData;
|
|
|
|
|
sol::object script = mLua.runInNewSandbox(path, mNamePrefix, API, hiddenData);
|
|
|
|
|
std::string interfaceName = "";
|
|
|
|
|
sol::object publicInterface = sol::nil;
|
|
|
|
|
if (script != sol::nil)
|
|
|
|
|
Script& script = mScripts[scriptId];
|
|
|
|
|
script.mHiddenData = mLua.newTable();
|
|
|
|
|
script.mHiddenData[ScriptId::KEY] = ScriptId{this, scriptId, path};
|
|
|
|
|
script.mHiddenData[REGISTERED_TIMER_CALLBACKS] = mLua.newTable();
|
|
|
|
|
script.mHiddenData[TEMPORARY_TIMER_CALLBACKS] = mLua.newTable();
|
|
|
|
|
sol::object scriptOutput = mLua.runInNewSandbox(path, mNamePrefix, mAPI, script.mHiddenData);
|
|
|
|
|
if (scriptOutput == sol::nil)
|
|
|
|
|
return true;
|
|
|
|
|
sol::object engineHandlers = sol::nil, eventHandlers = sol::nil;
|
|
|
|
|
for (const auto& [key, value] : sol::table(scriptOutput))
|
|
|
|
|
{
|
|
|
|
|
for (auto& [key, value] : sol::table(script))
|
|
|
|
|
std::string_view sectionName = key.as<std::string_view>();
|
|
|
|
|
if (sectionName == ENGINE_HANDLERS)
|
|
|
|
|
engineHandlers = value;
|
|
|
|
|
else if (sectionName == EVENT_HANDLERS)
|
|
|
|
|
eventHandlers = value;
|
|
|
|
|
else if (sectionName == INTERFACE_NAME)
|
|
|
|
|
script.mInterfaceName = value.as<std::string>();
|
|
|
|
|
else if (sectionName == INTERFACE)
|
|
|
|
|
script.mInterface = value.as<sol::table>();
|
|
|
|
|
else
|
|
|
|
|
Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << mNamePrefix << "[" << path << "]";
|
|
|
|
|
}
|
|
|
|
|
if (engineHandlers != sol::nil)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& [key, fn] : sol::table(engineHandlers))
|
|
|
|
|
{
|
|
|
|
|
std::string_view sectionName = key.as<std::string_view>();
|
|
|
|
|
if (sectionName == ENGINE_HANDLERS)
|
|
|
|
|
parseEngineHandlers(value, path);
|
|
|
|
|
else if (sectionName == EVENT_HANDLERS)
|
|
|
|
|
parseEventHandlers(value, path);
|
|
|
|
|
else if (sectionName == INTERFACE_NAME)
|
|
|
|
|
interfaceName = value.as<std::string>();
|
|
|
|
|
else if (sectionName == INTERFACE)
|
|
|
|
|
publicInterface = value.as<sol::table>();
|
|
|
|
|
std::string_view handlerName = key.as<std::string_view>();
|
|
|
|
|
if (handlerName == HANDLER_INIT)
|
|
|
|
|
onInit = sol::function(fn);
|
|
|
|
|
else if (handlerName == HANDLER_LOAD)
|
|
|
|
|
onLoad = sol::function(fn);
|
|
|
|
|
else if (handlerName == HANDLER_SAVE)
|
|
|
|
|
script.mOnSave = sol::function(fn);
|
|
|
|
|
else if (handlerName == HANDLER_INTERFACE_OVERRIDE)
|
|
|
|
|
script.mOnOverride = sol::function(fn);
|
|
|
|
|
else
|
|
|
|
|
Log(Debug::Error) << "Not supported section '" << sectionName << "' in " << mNamePrefix << "[" << path << "]";
|
|
|
|
|
{
|
|
|
|
|
auto it = mEngineHandlers.find(handlerName);
|
|
|
|
|
if (it == mEngineHandlers.end())
|
|
|
|
|
Log(Debug::Error) << "Not supported handler '" << handlerName
|
|
|
|
|
<< "' in " << mNamePrefix << "[" << path << "]";
|
|
|
|
|
else
|
|
|
|
|
insertHandler(it->second->mList, scriptId, fn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (interfaceName.empty() != (publicInterface == sol::nil))
|
|
|
|
|
if (eventHandlers != sol::nil)
|
|
|
|
|
{
|
|
|
|
|
for (const auto& [key, fn] : sol::table(eventHandlers))
|
|
|
|
|
{
|
|
|
|
|
std::string_view eventName = key.as<std::string_view>();
|
|
|
|
|
auto it = mEventHandlers.find(eventName);
|
|
|
|
|
if (it == mEventHandlers.end())
|
|
|
|
|
it = mEventHandlers.emplace(std::string(eventName), EventHandlerList()).first;
|
|
|
|
|
insertHandler(it->second, scriptId, fn);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (script.mInterfaceName.empty() == script.mInterface.has_value())
|
|
|
|
|
{
|
|
|
|
|
Log(Debug::Error) << mNamePrefix << "[" << path << "]: 'interfaceName' should always be used together with 'interface'";
|
|
|
|
|
else if (!interfaceName.empty())
|
|
|
|
|
script.as<sol::table>()[INTERFACE] = mPublicInterfaces[interfaceName] = makeReadOnly(publicInterface);
|
|
|
|
|
mScriptOrder.push_back(path);
|
|
|
|
|
mScripts[path].mInterface = std::move(script);
|
|
|
|
|
script.mInterfaceName.clear();
|
|
|
|
|
script.mInterface = sol::nil;
|
|
|
|
|
}
|
|
|
|
|
else if (script.mInterface)
|
|
|
|
|
{
|
|
|
|
|
script.mInterface = makeReadOnly(*script.mInterface);
|
|
|
|
|
insertInterface(scriptId, script);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e)
|
|
|
|
|
{
|
|
|
|
|
mScripts.erase(path);
|
|
|
|
|
mScripts.erase(scriptId);
|
|
|
|
|
Log(Debug::Error) << "Can't start " << mNamePrefix << "[" << path << "]; " << e.what();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool ScriptsContainer::removeScript(const std::string& path)
|
|
|
|
|
void ScriptsContainer::removeScript(int scriptId)
|
|
|
|
|
{
|
|
|
|
|
auto scriptIter = mScripts.find(path);
|
|
|
|
|
auto scriptIter = mScripts.find(scriptId);
|
|
|
|
|
if (scriptIter == mScripts.end())
|
|
|
|
|
return false; // no such script
|
|
|
|
|
scriptIter->second.mHiddenData[ScriptId::KEY] = sol::nil;
|
|
|
|
|
sol::object& script = scriptIter->second.mInterface;
|
|
|
|
|
if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil)
|
|
|
|
|
return; // no such script
|
|
|
|
|
Script& script = scriptIter->second;
|
|
|
|
|
if (script.mInterface)
|
|
|
|
|
removeInterface(scriptId, script);
|
|
|
|
|
script.mHiddenData[ScriptId::KEY] = sol::nil;
|
|
|
|
|
mScripts.erase(scriptIter);
|
|
|
|
|
for (auto& [_, handlers] : mEngineHandlers)
|
|
|
|
|
removeHandler(handlers->mList, scriptId);
|
|
|
|
|
for (auto& [_, handlers] : mEventHandlers)
|
|
|
|
|
removeHandler(handlers, scriptId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::insertInterface(int scriptId, const Script& script)
|
|
|
|
|
{
|
|
|
|
|
assert(script.mInterface);
|
|
|
|
|
const Script* prev = nullptr;
|
|
|
|
|
const Script* next = nullptr;
|
|
|
|
|
int nextId = 0;
|
|
|
|
|
for (const auto& [otherId, otherScript] : mScripts)
|
|
|
|
|
{
|
|
|
|
|
std::string_view interfaceName = getFieldOrNil(script, INTERFACE_NAME).as<std::string_view>();
|
|
|
|
|
if (mPublicInterfaces[interfaceName] == getFieldOrNil(script, INTERFACE))
|
|
|
|
|
if (scriptId == otherId || script.mInterfaceName != otherScript.mInterfaceName)
|
|
|
|
|
continue;
|
|
|
|
|
if (otherId < scriptId)
|
|
|
|
|
prev = &otherScript;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
mPublicInterfaces[interfaceName] = sol::nil;
|
|
|
|
|
auto prevIt = mScriptOrder.rbegin();
|
|
|
|
|
while (*prevIt != path)
|
|
|
|
|
prevIt++;
|
|
|
|
|
prevIt++;
|
|
|
|
|
while (prevIt != mScriptOrder.rend())
|
|
|
|
|
{
|
|
|
|
|
sol::object& prevScript = mScripts[*(prevIt++)].mInterface;
|
|
|
|
|
sol::object prevInterfaceName = getFieldOrNil(prevScript, INTERFACE_NAME);
|
|
|
|
|
if (prevInterfaceName != sol::nil && prevInterfaceName.as<std::string_view>() == interfaceName)
|
|
|
|
|
{
|
|
|
|
|
mPublicInterfaces[interfaceName] = getFieldOrNil(prevScript, INTERFACE);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
next = &otherScript;
|
|
|
|
|
nextId = otherId;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sol::object engineHandlers = getFieldOrNil(script, ENGINE_HANDLERS);
|
|
|
|
|
if (engineHandlers != sol::nil)
|
|
|
|
|
if (prev && script.mOnOverride)
|
|
|
|
|
{
|
|
|
|
|
try { LuaUtil::call(*script.mOnOverride, *prev->mInterface); }
|
|
|
|
|
catch (std::exception& e) { printError(scriptId, "onInterfaceOverride failed", e); }
|
|
|
|
|
}
|
|
|
|
|
if (next && next->mOnOverride)
|
|
|
|
|
{
|
|
|
|
|
for (auto& [key, value] : sol::table(engineHandlers))
|
|
|
|
|
try { LuaUtil::call(*next->mOnOverride, *script.mInterface); }
|
|
|
|
|
catch (std::exception& e) { printError(nextId, "onInterfaceOverride failed", e); }
|
|
|
|
|
}
|
|
|
|
|
if (next == nullptr)
|
|
|
|
|
mPublicInterfaces[script.mInterfaceName] = *script.mInterface;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::removeInterface(int scriptId, const Script& script)
|
|
|
|
|
{
|
|
|
|
|
assert(script.mInterface);
|
|
|
|
|
const Script* prev = nullptr;
|
|
|
|
|
const Script* next = nullptr;
|
|
|
|
|
int nextId = 0;
|
|
|
|
|
for (const auto& [otherId, otherScript] : mScripts)
|
|
|
|
|
{
|
|
|
|
|
if (scriptId == otherId || script.mInterfaceName != otherScript.mInterfaceName)
|
|
|
|
|
continue;
|
|
|
|
|
if (otherId < scriptId)
|
|
|
|
|
prev = &otherScript;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
std::string_view handlerName = key.as<std::string_view>();
|
|
|
|
|
auto handlerIter = mEngineHandlers.find(handlerName);
|
|
|
|
|
if (handlerIter == mEngineHandlers.end())
|
|
|
|
|
continue;
|
|
|
|
|
std::vector<sol::protected_function>& list = handlerIter->second->mList;
|
|
|
|
|
list.erase(std::find(list.begin(), list.end(), value.as<sol::protected_function>()));
|
|
|
|
|
next = &otherScript;
|
|
|
|
|
nextId = otherId;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sol::object eventHandlers = getFieldOrNil(script, EVENT_HANDLERS);
|
|
|
|
|
if (eventHandlers != sol::nil)
|
|
|
|
|
if (next)
|
|
|
|
|
{
|
|
|
|
|
for (auto& [key, value] : sol::table(eventHandlers))
|
|
|
|
|
if (next->mOnOverride)
|
|
|
|
|
{
|
|
|
|
|
EventHandlerList& list = mEventHandlers.find(key.as<std::string_view>())->second;
|
|
|
|
|
list.erase(std::find(list.begin(), list.end(), value.as<sol::protected_function>()));
|
|
|
|
|
sol::object prevInterface = sol::nil;
|
|
|
|
|
if (prev)
|
|
|
|
|
prevInterface = *prev->mInterface;
|
|
|
|
|
try { LuaUtil::call(*next->mOnOverride, prevInterface); }
|
|
|
|
|
catch (std::exception& e) { printError(nextId, "onInterfaceOverride failed", e); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mScripts.erase(scriptIter);
|
|
|
|
|
mScriptOrder.erase(std::find(mScriptOrder.begin(), mScriptOrder.end(), path));
|
|
|
|
|
return true;
|
|
|
|
|
else if (prev)
|
|
|
|
|
mPublicInterfaces[script.mInterfaceName] = *prev->mInterface;
|
|
|
|
|
else
|
|
|
|
|
mPublicInterfaces[script.mInterfaceName] = sol::nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::parseEventHandlers(sol::table handlers, std::string_view scriptPath)
|
|
|
|
|
void ScriptsContainer::insertHandler(std::vector<Handler>& list, int scriptId, sol::function fn)
|
|
|
|
|
{
|
|
|
|
|
for (auto& [key, value] : handlers)
|
|
|
|
|
list.emplace_back();
|
|
|
|
|
int pos = list.size() - 1;
|
|
|
|
|
while (pos > 0 && list[pos - 1].mScriptId > scriptId)
|
|
|
|
|
{
|
|
|
|
|
std::string_view eventName = key.as<std::string_view>();
|
|
|
|
|
auto it = mEventHandlers.find(eventName);
|
|
|
|
|
if (it == mEventHandlers.end())
|
|
|
|
|
it = mEventHandlers.insert({std::string(eventName), EventHandlerList()}).first;
|
|
|
|
|
it->second.push_back(value);
|
|
|
|
|
list[pos] = std::move(list[pos - 1]);
|
|
|
|
|
pos--;
|
|
|
|
|
}
|
|
|
|
|
list[pos].mScriptId = scriptId;
|
|
|
|
|
list[pos].mFn = std::move(fn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::parseEngineHandlers(sol::table handlers, std::string_view scriptPath)
|
|
|
|
|
void ScriptsContainer::removeHandler(std::vector<Handler>& list, int scriptId)
|
|
|
|
|
{
|
|
|
|
|
for (auto& [key, value] : handlers)
|
|
|
|
|
{
|
|
|
|
|
std::string_view handlerName = key.as<std::string_view>();
|
|
|
|
|
if (handlerName == HANDLER_LOAD || handlerName == HANDLER_SAVE)
|
|
|
|
|
continue; // save and load are handled separately
|
|
|
|
|
auto it = mEngineHandlers.find(handlerName);
|
|
|
|
|
if (it == mEngineHandlers.end())
|
|
|
|
|
Log(Debug::Error) << "Not supported handler '" << handlerName << "' in " << mNamePrefix << "[" << scriptPath << "]";
|
|
|
|
|
else
|
|
|
|
|
it->second->mList.push_back(value);
|
|
|
|
|
}
|
|
|
|
|
list.erase(std::remove_if(list.begin(), list.end(),
|
|
|
|
|
[scriptId](const Handler& h){ return h.mScriptId == scriptId; }),
|
|
|
|
|
list.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::receiveEvent(std::string_view eventName, std::string_view eventData)
|
|
|
|
@ -191,13 +284,14 @@ namespace LuaUtil
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sol::object res = LuaUtil::call(list[i], data);
|
|
|
|
|
sol::object res = LuaUtil::call(list[i].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 << " eventHandler[" << eventName << "] failed. " << e.what();
|
|
|
|
|
Log(Debug::Error) << mNamePrefix << "[" << scriptPath(list[i].mScriptId)
|
|
|
|
|
<< "] eventHandler[" << eventName << "] failed. " << e.what();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@ -208,9 +302,19 @@ namespace LuaUtil
|
|
|
|
|
mEngineHandlers[h->mName] = h;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::callOnInit(int scriptId, const sol::function& onInit)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
const std::string& data = mLua.getConfiguration()[scriptId].mInitializationData;
|
|
|
|
|
LuaUtil::call(onInit, deserialize(mLua.sol(), data, mSerializer));
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) { printError(scriptId, "onInit failed", e); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::save(ESM::LuaScripts& data)
|
|
|
|
|
{
|
|
|
|
|
std::map<std::string, std::vector<ESM::LuaTimer>> timers;
|
|
|
|
|
std::map<int, std::vector<ESM::LuaTimer>> timers;
|
|
|
|
|
auto saveTimerFn = [&](const Timer& timer, TimeUnit timeUnit)
|
|
|
|
|
{
|
|
|
|
|
if (!timer.mSerializable)
|
|
|
|
@ -220,78 +324,85 @@ namespace LuaUtil
|
|
|
|
|
savedTimer.mUnit = timeUnit;
|
|
|
|
|
savedTimer.mCallbackName = std::get<std::string>(timer.mCallback);
|
|
|
|
|
savedTimer.mCallbackArgument = timer.mSerializedArg;
|
|
|
|
|
if (timers.count(timer.mScript) == 0)
|
|
|
|
|
timers[timer.mScript] = {};
|
|
|
|
|
timers[timer.mScript].push_back(std::move(savedTimer));
|
|
|
|
|
timers[timer.mScriptId].push_back(std::move(savedTimer));
|
|
|
|
|
};
|
|
|
|
|
for (const Timer& timer : mSecondsTimersQueue)
|
|
|
|
|
saveTimerFn(timer, TimeUnit::SECONDS);
|
|
|
|
|
for (const Timer& timer : mHoursTimersQueue)
|
|
|
|
|
saveTimerFn(timer, TimeUnit::HOURS);
|
|
|
|
|
data.mScripts.clear();
|
|
|
|
|
for (const std::string& path : mScriptOrder)
|
|
|
|
|
for (auto& [scriptId, script] : mScripts)
|
|
|
|
|
{
|
|
|
|
|
ESM::LuaScript savedScript;
|
|
|
|
|
savedScript.mScriptPath = path;
|
|
|
|
|
sol::object handler = getFieldOrNil(mScripts[path].mInterface, ENGINE_HANDLERS, HANDLER_SAVE);
|
|
|
|
|
if (handler != sol::nil)
|
|
|
|
|
savedScript.mScriptPath = script.mHiddenData.get<ScriptId>(ScriptId::KEY).mPath;
|
|
|
|
|
if (script.mOnSave)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sol::object state = LuaUtil::call(handler);
|
|
|
|
|
sol::object state = LuaUtil::call(*script.mOnSave);
|
|
|
|
|
savedScript.mData = serialize(state, mSerializer);
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e)
|
|
|
|
|
{
|
|
|
|
|
Log(Debug::Error) << mNamePrefix << "[" << path << "] onSave failed: " << e.what();
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) { printError(scriptId, "onSave failed", e); }
|
|
|
|
|
}
|
|
|
|
|
auto timersIt = timers.find(path);
|
|
|
|
|
auto timersIt = timers.find(scriptId);
|
|
|
|
|
if (timersIt != timers.end())
|
|
|
|
|
savedScript.mTimers = std::move(timersIt->second);
|
|
|
|
|
data.mScripts.push_back(std::move(savedScript));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::load(const ESM::LuaScripts& data, bool resetScriptList)
|
|
|
|
|
void ScriptsContainer::load(const ESM::LuaScripts& data)
|
|
|
|
|
{
|
|
|
|
|
std::map<std::string, Script> scriptsWithoutSavedData;
|
|
|
|
|
if (resetScriptList)
|
|
|
|
|
removeAllScripts();
|
|
|
|
|
const ScriptsConfiguration& cfg = mLua.getConfiguration();
|
|
|
|
|
|
|
|
|
|
std::map<int, const ESM::LuaScript*> scripts;
|
|
|
|
|
for (int scriptId : mLua.getConfiguration().getListByFlag(mAutoStartMode))
|
|
|
|
|
scripts[scriptId] = nullptr;
|
|
|
|
|
for (const ESM::LuaScript& s : data.mScripts)
|
|
|
|
|
{
|
|
|
|
|
removeAllScripts();
|
|
|
|
|
for (const ESM::LuaScript& script : data.mScripts)
|
|
|
|
|
addNewScript(script.mScriptPath);
|
|
|
|
|
std::optional<int> scriptId = cfg.findId(s.mScriptPath);
|
|
|
|
|
if (!scriptId)
|
|
|
|
|
{
|
|
|
|
|
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; script not registered";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!(cfg[*scriptId].mFlags & (ESM::LuaScriptCfg::sCustom | mAutoStartMode)))
|
|
|
|
|
{
|
|
|
|
|
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; this script is not allowed here";
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
scripts[*scriptId] = &s;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
scriptsWithoutSavedData = mScripts;
|
|
|
|
|
mSecondsTimersQueue.clear();
|
|
|
|
|
mHoursTimersQueue.clear();
|
|
|
|
|
for (const ESM::LuaScript& script : data.mScripts)
|
|
|
|
|
|
|
|
|
|
for (const auto& [scriptId, savedScript] : scripts)
|
|
|
|
|
{
|
|
|
|
|
auto iter = mScripts.find(script.mScriptPath);
|
|
|
|
|
if (iter == mScripts.end())
|
|
|
|
|
std::optional<sol::function> onInit, onLoad;
|
|
|
|
|
if (!addScript(scriptId, onInit, onLoad))
|
|
|
|
|
continue;
|
|
|
|
|
scriptsWithoutSavedData.erase(iter->first);
|
|
|
|
|
iter->second.mHiddenData.get<sol::table>(TEMPORARY_TIMER_CALLBACKS).clear();
|
|
|
|
|
try
|
|
|
|
|
if (savedScript == nullptr)
|
|
|
|
|
{
|
|
|
|
|
sol::object handler = getFieldOrNil(iter->second.mInterface, ENGINE_HANDLERS, HANDLER_LOAD);
|
|
|
|
|
if (handler != sol::nil)
|
|
|
|
|
{
|
|
|
|
|
sol::object state = deserialize(mLua.sol(), script.mData, mSerializer);
|
|
|
|
|
LuaUtil::call(handler, state);
|
|
|
|
|
}
|
|
|
|
|
if (onInit)
|
|
|
|
|
callOnInit(scriptId, *onInit);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e)
|
|
|
|
|
if (onLoad)
|
|
|
|
|
{
|
|
|
|
|
Log(Debug::Error) << mNamePrefix << "[" << script.mScriptPath << "] onLoad failed: " << e.what();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sol::object state = deserialize(mLua.sol(), savedScript->mData, mSerializer);
|
|
|
|
|
sol::object initializationData =
|
|
|
|
|
deserialize(mLua.sol(), mLua.getConfiguration()[scriptId].mInitializationData, mSerializer);
|
|
|
|
|
LuaUtil::call(*onLoad, state, initializationData);
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) { printError(scriptId, "onLoad failed", e); }
|
|
|
|
|
}
|
|
|
|
|
for (const ESM::LuaTimer& savedTimer : script.mTimers)
|
|
|
|
|
for (const ESM::LuaTimer& savedTimer : savedScript->mTimers)
|
|
|
|
|
{
|
|
|
|
|
Timer timer;
|
|
|
|
|
timer.mCallback = savedTimer.mCallbackName;
|
|
|
|
|
timer.mSerializable = true;
|
|
|
|
|
timer.mScript = script.mScriptPath;
|
|
|
|
|
timer.mScriptId = scriptId;
|
|
|
|
|
timer.mTime = savedTimer.mTime;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
@ -306,24 +417,10 @@ namespace LuaUtil
|
|
|
|
|
else
|
|
|
|
|
mSecondsTimersQueue.push_back(std::move(timer));
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e)
|
|
|
|
|
{
|
|
|
|
|
Log(Debug::Error) << mNamePrefix << "[" << script.mScriptPath << "] can not load timer: " << e.what();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for (auto& [path, script] : scriptsWithoutSavedData)
|
|
|
|
|
{
|
|
|
|
|
script.mHiddenData.get<sol::table>(TEMPORARY_TIMER_CALLBACKS).clear();
|
|
|
|
|
sol::object handler = getFieldOrNil(script.mInterface, ENGINE_HANDLERS, HANDLER_LOAD);
|
|
|
|
|
if (handler == sol::nil)
|
|
|
|
|
continue;
|
|
|
|
|
try { LuaUtil::call(handler); }
|
|
|
|
|
catch (std::exception& e)
|
|
|
|
|
{
|
|
|
|
|
Log(Debug::Error) << mNamePrefix << "[" << path << "] onLoad failed: " << e.what();
|
|
|
|
|
catch (std::exception& e) { printError(scriptId, "can not load timer", e); }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::make_heap(mSecondsTimersQueue.begin(), mSecondsTimersQueue.end());
|
|
|
|
|
std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end());
|
|
|
|
|
}
|
|
|
|
@ -334,12 +431,13 @@ namespace LuaUtil
|
|
|
|
|
script.mHiddenData[ScriptId::KEY] = sol::nil;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
script.mHiddenData[ScriptId::KEY] = sol::nil;
|
|
|
|
|
mScripts.clear();
|
|
|
|
|
mScriptOrder.clear();
|
|
|
|
|
for (auto& [_, handlers] : mEngineHandlers)
|
|
|
|
|
handlers->mList.clear();
|
|
|
|
|
mEventHandlers.clear();
|
|
|
|
@ -351,17 +449,17 @@ namespace LuaUtil
|
|
|
|
|
mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sol::table ScriptsContainer::getHiddenData(const std::string& scriptPath)
|
|
|
|
|
sol::table ScriptsContainer::getHiddenData(int scriptId)
|
|
|
|
|
{
|
|
|
|
|
auto it = mScripts.find(scriptPath);
|
|
|
|
|
auto it = mScripts.find(scriptId);
|
|
|
|
|
if (it == mScripts.end())
|
|
|
|
|
throw std::logic_error("ScriptsContainer::getHiddenData: script doesn't exist");
|
|
|
|
|
return it->second.mHiddenData;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::registerTimerCallback(const std::string& scriptPath, std::string_view callbackName, sol::function callback)
|
|
|
|
|
void ScriptsContainer::registerTimerCallback(int scriptId, std::string_view callbackName, sol::function callback)
|
|
|
|
|
{
|
|
|
|
|
getHiddenData(scriptPath)[REGISTERED_TIMER_CALLBACKS][callbackName] = std::move(callback);
|
|
|
|
|
getHiddenData(scriptId)[REGISTERED_TIMER_CALLBACKS][callbackName] = std::move(callback);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::insertTimer(std::vector<Timer>& timerQueue, Timer&& t)
|
|
|
|
@ -370,12 +468,12 @@ namespace LuaUtil
|
|
|
|
|
std::push_heap(timerQueue.begin(), timerQueue.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::setupSerializableTimer(TimeUnit timeUnit, double time, const std::string& scriptPath,
|
|
|
|
|
void ScriptsContainer::setupSerializableTimer(TimeUnit timeUnit, double time, int scriptId,
|
|
|
|
|
std::string_view callbackName, sol::object callbackArg)
|
|
|
|
|
{
|
|
|
|
|
Timer t;
|
|
|
|
|
t.mCallback = std::string(callbackName);
|
|
|
|
|
t.mScript = scriptPath;
|
|
|
|
|
t.mScriptId = scriptId;
|
|
|
|
|
t.mSerializable = true;
|
|
|
|
|
t.mTime = time;
|
|
|
|
|
t.mArg = callbackArg;
|
|
|
|
@ -383,15 +481,15 @@ namespace LuaUtil
|
|
|
|
|
insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::setupUnsavableTimer(TimeUnit timeUnit, double time, const std::string& scriptPath, sol::function callback)
|
|
|
|
|
void ScriptsContainer::setupUnsavableTimer(TimeUnit timeUnit, double time, int scriptId, sol::function callback)
|
|
|
|
|
{
|
|
|
|
|
Timer t;
|
|
|
|
|
t.mScript = scriptPath;
|
|
|
|
|
t.mScriptId = scriptId;
|
|
|
|
|
t.mSerializable = false;
|
|
|
|
|
t.mTime = time;
|
|
|
|
|
|
|
|
|
|
t.mCallback = mTemporaryCallbackCounter;
|
|
|
|
|
getHiddenData(scriptPath)[TEMPORARY_TIMER_CALLBACKS][mTemporaryCallbackCounter] = std::move(callback);
|
|
|
|
|
getHiddenData(scriptId)[TEMPORARY_TIMER_CALLBACKS][mTemporaryCallbackCounter] = std::move(callback);
|
|
|
|
|
mTemporaryCallbackCounter++;
|
|
|
|
|
|
|
|
|
|
insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t));
|
|
|
|
@ -401,7 +499,7 @@ namespace LuaUtil
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
sol::table data = getHiddenData(t.mScript);
|
|
|
|
|
sol::table data = getHiddenData(t.mScriptId);
|
|
|
|
|
if (t.mSerializable)
|
|
|
|
|
{
|
|
|
|
|
const std::string& callbackName = std::get<std::string>(t.mCallback);
|
|
|
|
@ -421,10 +519,7 @@ namespace LuaUtil
|
|
|
|
|
callbacks[id] = sol::nil;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e)
|
|
|
|
|
{
|
|
|
|
|
Log(Debug::Error) << mNamePrefix << "[" << t.mScript << "] callTimer failed: " << e.what();
|
|
|
|
|
}
|
|
|
|
|
catch (std::exception& e) { printError(t.mScriptId, "callTimer failed", e); }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void ScriptsContainer::updateTimerQueue(std::vector<Timer>& timerQueue, double time)
|
|
|
|
|