mirror of
https://github.com/OpenMW/openmw.git
synced 2025-04-27 06:36:49 +00:00
Redesign LuaUtil::ScriptsContainer to work with ScriptsConfiguration
This commit is contained in:
parent
33d71be81f
commit
9adc190209
22 changed files with 638 additions and 466 deletions
|
@ -30,6 +30,7 @@ namespace MWBase
|
||||||
virtual ~LuaManager() = default;
|
virtual ~LuaManager() = default;
|
||||||
|
|
||||||
virtual void newGameStarted() = 0;
|
virtual void newGameStarted() = 0;
|
||||||
|
virtual void gameLoaded() = 0;
|
||||||
virtual void registerObject(const MWWorld::Ptr& ptr) = 0;
|
virtual void registerObject(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0;
|
virtual void deregisterObject(const MWWorld::Ptr& ptr) = 0;
|
||||||
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
|
virtual void objectAddedToScene(const MWWorld::Ptr& ptr) = 0;
|
||||||
|
|
|
@ -23,7 +23,7 @@ namespace MWLua
|
||||||
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
|
sol::usertype<AsyncPackageId> api = context.mLua->sol().new_usertype<AsyncPackageId>("AsyncPackage");
|
||||||
api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback)
|
api["registerTimerCallback"] = [](const AsyncPackageId& asyncId, std::string_view name, sol::function callback)
|
||||||
{
|
{
|
||||||
asyncId.mContainer->registerTimerCallback(asyncId.mScript, name, std::move(callback));
|
asyncId.mContainer->registerTimerCallback(asyncId.mScriptId, name, std::move(callback));
|
||||||
return TimerCallback{asyncId, std::string(name)};
|
return TimerCallback{asyncId, std::string(name)};
|
||||||
};
|
};
|
||||||
api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
api["newTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
||||||
|
@ -31,24 +31,24 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
callback.mAsyncId.mContainer->setupSerializableTimer(
|
callback.mAsyncId.mContainer->setupSerializableTimer(
|
||||||
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay,
|
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay,
|
||||||
callback.mAsyncId.mScript, callback.mName, std::move(callbackArg));
|
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
|
||||||
};
|
};
|
||||||
api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
api["newTimerInHours"] = [world=context.mWorldView](const AsyncPackageId&, double delay,
|
||||||
const TimerCallback& callback, sol::object callbackArg)
|
const TimerCallback& callback, sol::object callbackArg)
|
||||||
{
|
{
|
||||||
callback.mAsyncId.mContainer->setupSerializableTimer(
|
callback.mAsyncId.mContainer->setupSerializableTimer(
|
||||||
TimeUnit::HOURS, world->getGameTimeInHours() + delay,
|
TimeUnit::HOURS, world->getGameTimeInHours() + delay,
|
||||||
callback.mAsyncId.mScript, callback.mName, std::move(callbackArg));
|
callback.mAsyncId.mScriptId, callback.mName, std::move(callbackArg));
|
||||||
};
|
};
|
||||||
api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
|
api["newUnsavableTimerInSeconds"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
|
||||||
{
|
{
|
||||||
asyncId.mContainer->setupUnsavableTimer(
|
asyncId.mContainer->setupUnsavableTimer(
|
||||||
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScript, std::move(callback));
|
TimeUnit::SECONDS, world->getGameTimeInSeconds() + delay, asyncId.mScriptId, std::move(callback));
|
||||||
};
|
};
|
||||||
api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
|
api["newUnsavableTimerInHours"] = [world=context.mWorldView](const AsyncPackageId& asyncId, double delay, sol::function callback)
|
||||||
{
|
{
|
||||||
asyncId.mContainer->setupUnsavableTimer(
|
asyncId.mContainer->setupUnsavableTimer(
|
||||||
TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScript, std::move(callback));
|
TimeUnit::HOURS, world->getGameTimeInHours() + delay, asyncId.mScriptId, std::move(callback));
|
||||||
};
|
};
|
||||||
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn)
|
api["callback"] = [](const AsyncPackageId& asyncId, sol::function fn)
|
||||||
{
|
{
|
||||||
|
@ -59,7 +59,7 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY];
|
LuaUtil::ScriptsContainer::ScriptId id = hiddenData[LuaUtil::ScriptsContainer::ScriptId::KEY];
|
||||||
hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString();
|
hiddenData[Callback::SCRIPT_NAME_KEY] = id.toString();
|
||||||
return AsyncPackageId{id.mContainer, id.mPath, hiddenData};
|
return AsyncPackageId{id.mContainer, id.mIndex, hiddenData};
|
||||||
};
|
};
|
||||||
return sol::make_object(context.mLua->sol(), initializer);
|
return sol::make_object(context.mLua->sol(), initializer);
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,8 @@ namespace MWLua
|
||||||
class GlobalScripts : public LuaUtil::ScriptsContainer
|
class GlobalScripts : public LuaUtil::ScriptsContainer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
GlobalScripts(LuaUtil::LuaState* lua) : LuaUtil::ScriptsContainer(lua, "Global")
|
GlobalScripts(LuaUtil::LuaState* lua) :
|
||||||
|
LuaUtil::ScriptsContainer(lua, "Global", ESM::LuaScriptCfg::sGlobal)
|
||||||
{
|
{
|
||||||
registerEngineHandlers({&mActorActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers});
|
registerEngineHandlers({&mActorActiveHandlers, &mNewGameHandlers, &mPlayerAddedHandlers});
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,14 +82,14 @@ namespace MWLua
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj)
|
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode)
|
||||||
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())), mData(obj)
|
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id()), autoStartMode), mData(obj)
|
||||||
{
|
{
|
||||||
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
|
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));
|
||||||
registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers});
|
registerEngineHandlers({&mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers});
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalScripts::receiveEngineEvent(const EngineEvent& event, ObjectRegistry*)
|
void LocalScripts::receiveEngineEvent(const EngineEvent& event)
|
||||||
{
|
{
|
||||||
std::visit([this](auto&& arg)
|
std::visit([this](auto&& arg)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void initializeSelfPackage(const Context&);
|
static void initializeSelfPackage(const Context&);
|
||||||
LocalScripts(LuaUtil::LuaState* lua, const LObject& obj);
|
LocalScripts(LuaUtil::LuaState* lua, const LObject& obj, ESM::LuaScriptCfg::Flags autoStartMode);
|
||||||
|
|
||||||
MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; }
|
MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; }
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ namespace MWLua
|
||||||
};
|
};
|
||||||
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume>;
|
using EngineEvent = std::variant<OnActive, OnInactive, OnConsume>;
|
||||||
|
|
||||||
void receiveEngineEvent(const EngineEvent&, ObjectRegistry*);
|
void receiveEngineEvent(const EngineEvent&);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SelfObject mData;
|
SelfObject mData;
|
||||||
|
|
|
@ -25,7 +25,7 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
auto* lua = context.mLua;
|
auto* lua = context.mLua;
|
||||||
sol::table api(lua->sol(), sol::create);
|
sol::table api(lua->sol(), sol::create);
|
||||||
api["API_REVISION"] = 7;
|
api["API_REVISION"] = 8;
|
||||||
api["quit"] = [lua]()
|
api["quit"] = [lua]()
|
||||||
{
|
{
|
||||||
std::string traceback = lua->sol()["debug"]["traceback"]().get<std::string>();
|
std::string traceback = lua->sol()["debug"]["traceback"]().get<std::string>();
|
||||||
|
|
|
@ -48,7 +48,7 @@ namespace MWLua
|
||||||
struct AsyncPackageId
|
struct AsyncPackageId
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer* mContainer;
|
LuaUtil::ScriptsContainer* mContainer;
|
||||||
std::string mScript;
|
int mScriptId;
|
||||||
sol::table mHiddenData;
|
sol::table mHiddenData;
|
||||||
};
|
};
|
||||||
sol::function getAsyncPackageInitializer(const Context&);
|
sol::function getAsyncPackageInitializer(const Context&);
|
||||||
|
|
|
@ -7,7 +7,6 @@
|
||||||
#include <components/esm/luascripts.hpp>
|
#include <components/esm/luascripts.hpp>
|
||||||
|
|
||||||
#include <components/lua/utilpackage.hpp>
|
#include <components/lua/utilpackage.hpp>
|
||||||
#include <components/lua/omwscriptsparser.hpp>
|
|
||||||
|
|
||||||
#include "../mwbase/windowmanager.hpp"
|
#include "../mwbase/windowmanager.hpp"
|
||||||
|
|
||||||
|
@ -20,10 +19,10 @@
|
||||||
namespace MWLua
|
namespace MWLua
|
||||||
{
|
{
|
||||||
|
|
||||||
LuaManager::LuaManager(const VFS::Manager* vfs, const std::vector<std::string>& scriptLists) : mLua(vfs)
|
LuaManager::LuaManager(const VFS::Manager* vfs, std::vector<std::string> OMWScriptsFiles) :
|
||||||
|
mVFS(vfs), mOMWScriptsFiles(std::move(OMWScriptsFiles)), mLua(vfs, &mConfiguration)
|
||||||
{
|
{
|
||||||
Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion();
|
Log(Debug::Info) << "Lua version: " << LuaUtil::getLuaVersion();
|
||||||
mGlobalScriptList = LuaUtil::parseOMWScriptsFiles(vfs, scriptLists);
|
|
||||||
|
|
||||||
mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry());
|
mGlobalSerializer = createUserdataSerializer(false, mWorldView.getObjectRegistry());
|
||||||
mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry());
|
mLocalSerializer = createUserdataSerializer(true, mWorldView.getObjectRegistry());
|
||||||
|
@ -33,6 +32,30 @@ namespace MWLua
|
||||||
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LuaManager::initConfiguration()
|
||||||
|
{
|
||||||
|
ESM::LuaScriptsCfg cfg;
|
||||||
|
for (const std::string& file : mOMWScriptsFiles)
|
||||||
|
{
|
||||||
|
if (!Misc::StringUtils::endsWith(file, ".omwscripts"))
|
||||||
|
{
|
||||||
|
Log(Debug::Error) << "Script list should have suffix '.omwscripts', got: '" << file << "'";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::string content(std::istreambuf_iterator<char>(*mVFS->get(file)), {});
|
||||||
|
LuaUtil::parseOMWScripts(cfg, content);
|
||||||
|
}
|
||||||
|
catch (std::exception& e) { Log(Debug::Error) << e.what(); }
|
||||||
|
}
|
||||||
|
// TODO: Add data from content files
|
||||||
|
mConfiguration.init(cfg);
|
||||||
|
Log(Debug::Verbose) << "Lua scripts configuration (" << mConfiguration.size() << " scripts):";
|
||||||
|
for (size_t i = 0; i < mConfiguration.size(); ++i)
|
||||||
|
Log(Debug::Verbose) << "#" << i << " " << LuaUtil::scriptCfgToString(mConfiguration[i]);
|
||||||
|
}
|
||||||
|
|
||||||
void LuaManager::init()
|
void LuaManager::init()
|
||||||
{
|
{
|
||||||
Context context;
|
Context context;
|
||||||
|
@ -67,10 +90,7 @@ namespace MWLua
|
||||||
mLocalSettingsPackage = initLocalSettingsPackage(localContext);
|
mLocalSettingsPackage = initLocalSettingsPackage(localContext);
|
||||||
mPlayerSettingsPackage = initPlayerSettingsPackage(localContext);
|
mPlayerSettingsPackage = initPlayerSettingsPackage(localContext);
|
||||||
|
|
||||||
mInputEvents.clear();
|
initConfiguration();
|
||||||
for (const std::string& path : mGlobalScriptList)
|
|
||||||
if (mGlobalScripts.addNewScript(path))
|
|
||||||
Log(Debug::Info) << "Global script started: " << path;
|
|
||||||
mInitialized = true;
|
mInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,7 +180,7 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
|
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts();
|
||||||
if (scripts)
|
if (scripts)
|
||||||
scripts->receiveEngineEvent(e.mEvent, objectRegistry);
|
scripts->receiveEngineEvent(e.mEvent);
|
||||||
}
|
}
|
||||||
mLocalEngineEvents.clear();
|
mLocalEngineEvents.clear();
|
||||||
|
|
||||||
|
@ -173,6 +193,11 @@ namespace MWLua
|
||||||
mPlayerChanged = false;
|
mPlayerChanged = false;
|
||||||
mGlobalScripts.playerAdded(GObject(getId(mPlayer), objectRegistry));
|
mGlobalScripts.playerAdded(GObject(getId(mPlayer), objectRegistry));
|
||||||
}
|
}
|
||||||
|
if (mNewGameStarted)
|
||||||
|
{
|
||||||
|
mNewGameStarted = false;
|
||||||
|
mGlobalScripts.newGameStarted();
|
||||||
|
}
|
||||||
|
|
||||||
for (ObjectId id : mActorAddedEvents)
|
for (ObjectId id : mActorAddedEvents)
|
||||||
mGlobalScripts.actorActive(GObject(id, objectRegistry));
|
mGlobalScripts.actorActive(GObject(id, objectRegistry));
|
||||||
|
@ -205,8 +230,11 @@ namespace MWLua
|
||||||
mInputEvents.clear();
|
mInputEvents.clear();
|
||||||
mActorAddedEvents.clear();
|
mActorAddedEvents.clear();
|
||||||
mLocalEngineEvents.clear();
|
mLocalEngineEvents.clear();
|
||||||
|
mNewGameStarted = false;
|
||||||
mPlayerChanged = false;
|
mPlayerChanged = false;
|
||||||
mWorldView.clear();
|
mWorldView.clear();
|
||||||
|
mGlobalScripts.removeAllScripts();
|
||||||
|
mGlobalScriptsStarted = false;
|
||||||
if (!mPlayer.isEmpty())
|
if (!mPlayer.isEmpty())
|
||||||
{
|
{
|
||||||
mPlayer.getCellRef().unsetRefNum();
|
mPlayer.getCellRef().unsetRefNum();
|
||||||
|
@ -225,17 +253,38 @@ namespace MWLua
|
||||||
mPlayer = ptr;
|
mPlayer = ptr;
|
||||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||||
if (!localScripts)
|
if (!localScripts)
|
||||||
localScripts = createLocalScripts(ptr);
|
localScripts = createLocalScripts(ptr, ESM::LuaScriptCfg::sPlayer);
|
||||||
mActiveLocalScripts.insert(localScripts);
|
mActiveLocalScripts.insert(localScripts);
|
||||||
mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}});
|
mLocalEngineEvents.push_back({getId(ptr), LocalScripts::OnActive{}});
|
||||||
mPlayerChanged = true;
|
mPlayerChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LuaManager::newGameStarted()
|
||||||
|
{
|
||||||
|
mNewGameStarted = true;
|
||||||
|
mInputEvents.clear();
|
||||||
|
mGlobalScripts.addAutoStartedScripts();
|
||||||
|
mGlobalScriptsStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LuaManager::gameLoaded()
|
||||||
|
{
|
||||||
|
if (!mGlobalScriptsStarted)
|
||||||
|
mGlobalScripts.addAutoStartedScripts();
|
||||||
|
mGlobalScriptsStarted = true;
|
||||||
|
}
|
||||||
|
|
||||||
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
|
void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr)
|
||||||
{
|
{
|
||||||
mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
|
mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet.
|
||||||
|
|
||||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||||
|
if (!localScripts)
|
||||||
|
{
|
||||||
|
ESM::LuaScriptCfg::Flags flag = getLuaScriptFlag(ptr);
|
||||||
|
if (!mConfiguration.getListByFlag(flag).empty())
|
||||||
|
localScripts = createLocalScripts(ptr, flag); // TODO: put to a queue and apply on next `update()`
|
||||||
|
}
|
||||||
if (localScripts)
|
if (localScripts)
|
||||||
{
|
{
|
||||||
mActiveLocalScripts.insert(localScripts);
|
mActiveLocalScripts.insert(localScripts);
|
||||||
|
@ -281,26 +330,26 @@ namespace MWLua
|
||||||
return localScripts->getActorControls();
|
return localScripts->getActorControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
void LuaManager::addLocalScript(const MWWorld::Ptr& ptr, const std::string& scriptPath)
|
void LuaManager::addCustomLocalScript(const MWWorld::Ptr& ptr, int scriptId)
|
||||||
{
|
{
|
||||||
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||||
if (!localScripts)
|
if (!localScripts)
|
||||||
{
|
{
|
||||||
localScripts = createLocalScripts(ptr);
|
localScripts = createLocalScripts(ptr, getLuaScriptFlag(ptr));
|
||||||
if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell()))
|
if (ptr.isInCell() && MWBase::Environment::get().getWorld()->isCellActive(ptr.getCell()))
|
||||||
mActiveLocalScripts.insert(localScripts);
|
mActiveLocalScripts.insert(localScripts);
|
||||||
}
|
}
|
||||||
localScripts->addNewScript(scriptPath);
|
localScripts->addCustomScript(scriptId);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr)
|
LocalScripts* LuaManager::createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags flag)
|
||||||
{
|
{
|
||||||
assert(mInitialized);
|
assert(mInitialized);
|
||||||
|
assert(flag != ESM::LuaScriptCfg::sGlobal);
|
||||||
std::shared_ptr<LocalScripts> scripts;
|
std::shared_ptr<LocalScripts> scripts;
|
||||||
// When loading a game, it can be called before LuaManager::setPlayer,
|
if (flag == ESM::LuaScriptCfg::sPlayer)
|
||||||
// so we can't just check ptr == mPlayer here.
|
|
||||||
if (ptr.getCellRef().getRefIdRef() == "player")
|
|
||||||
{
|
{
|
||||||
|
assert(ptr.getCellRef().getRefIdRef() == "player");
|
||||||
scripts = std::make_shared<PlayerScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
scripts = std::make_shared<PlayerScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
||||||
scripts->addPackage("openmw.ui", mUserInterfacePackage);
|
scripts->addPackage("openmw.ui", mUserInterfacePackage);
|
||||||
scripts->addPackage("openmw.camera", mCameraPackage);
|
scripts->addPackage("openmw.camera", mCameraPackage);
|
||||||
|
@ -309,11 +358,12 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
scripts = std::make_shared<LocalScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()));
|
scripts = std::make_shared<LocalScripts>(&mLua, LObject(getId(ptr), mWorldView.getObjectRegistry()), flag);
|
||||||
scripts->addPackage("openmw.settings", mLocalSettingsPackage);
|
scripts->addPackage("openmw.settings", mLocalSettingsPackage);
|
||||||
}
|
}
|
||||||
scripts->addPackage("openmw.nearby", mNearbyPackage);
|
scripts->addPackage("openmw.nearby", mNearbyPackage);
|
||||||
scripts->setSerializer(mLocalSerializer.get());
|
scripts->setSerializer(mLocalSerializer.get());
|
||||||
|
scripts->addAutoStartedScripts();
|
||||||
|
|
||||||
MWWorld::RefData& refData = ptr.getRefData();
|
MWWorld::RefData& refData = ptr.getRefData();
|
||||||
refData.setLuaScripts(std::move(scripts));
|
refData.setLuaScripts(std::move(scripts));
|
||||||
|
@ -344,8 +394,9 @@ namespace MWLua
|
||||||
loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get());
|
loadEvents(mLua.sol(), reader, mGlobalEvents, mLocalEvents, mContentFileMapping, mGlobalLoader.get());
|
||||||
|
|
||||||
mGlobalScripts.setSerializer(mGlobalLoader.get());
|
mGlobalScripts.setSerializer(mGlobalLoader.get());
|
||||||
mGlobalScripts.load(globalScripts, false);
|
mGlobalScripts.load(globalScripts);
|
||||||
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
mGlobalScripts.setSerializer(mGlobalSerializer.get());
|
||||||
|
mGlobalScriptsStarted = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data)
|
void LuaManager::saveLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScripts& data)
|
||||||
|
@ -366,10 +417,10 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
|
|
||||||
mWorldView.getObjectRegistry()->registerPtr(ptr);
|
mWorldView.getObjectRegistry()->registerPtr(ptr);
|
||||||
LocalScripts* scripts = createLocalScripts(ptr);
|
LocalScripts* scripts = createLocalScripts(ptr, getLuaScriptFlag(ptr));
|
||||||
|
|
||||||
scripts->setSerializer(mLocalLoader.get());
|
scripts->setSerializer(mLocalLoader.get());
|
||||||
scripts->load(data, true);
|
scripts->load(data);
|
||||||
scripts->setSerializer(mLocalSerializer.get());
|
scripts->setSerializer(mLocalSerializer.get());
|
||||||
|
|
||||||
// LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered.
|
// LiveCellRef is usually copied after loading, so this Ptr will become invalid and should be deregistered.
|
||||||
|
@ -380,15 +431,12 @@ namespace MWLua
|
||||||
{
|
{
|
||||||
Log(Debug::Info) << "Reload Lua";
|
Log(Debug::Info) << "Reload Lua";
|
||||||
mLua.dropScriptCache();
|
mLua.dropScriptCache();
|
||||||
|
initConfiguration();
|
||||||
|
|
||||||
{ // Reload global scripts
|
{ // Reload global scripts
|
||||||
ESM::LuaScripts data;
|
ESM::LuaScripts data;
|
||||||
mGlobalScripts.save(data);
|
mGlobalScripts.save(data);
|
||||||
mGlobalScripts.removeAllScripts();
|
mGlobalScripts.load(data);
|
||||||
for (const std::string& path : mGlobalScriptList)
|
|
||||||
if (mGlobalScripts.addNewScript(path))
|
|
||||||
Log(Debug::Info) << "Global script restarted: " << path;
|
|
||||||
mGlobalScripts.load(data, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [id, ptr] : mWorldView.getObjectRegistry()->mObjectMapping)
|
for (const auto& [id, ptr] : mWorldView.getObjectRegistry()->mObjectMapping)
|
||||||
|
@ -398,7 +446,7 @@ namespace MWLua
|
||||||
continue;
|
continue;
|
||||||
ESM::LuaScripts data;
|
ESM::LuaScripts data;
|
||||||
scripts->save(data);
|
scripts->save(data);
|
||||||
scripts->load(data, true);
|
scripts->load(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,9 +35,9 @@ namespace MWLua
|
||||||
class LuaManager : public MWBase::LuaManager
|
class LuaManager : public MWBase::LuaManager
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LuaManager(const VFS::Manager* vfs, const std::vector<std::string>& globalScriptLists);
|
LuaManager(const VFS::Manager* vfs, std::vector<std::string> OMWScriptsFiles);
|
||||||
|
|
||||||
// Called by engine.cpp when environment is fully initialized.
|
// Called by engine.cpp when the environment is fully initialized.
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
// Called by engine.cpp every frame. For performance reasons it works in a separate
|
// Called by engine.cpp every frame. For performance reasons it works in a separate
|
||||||
|
@ -49,7 +49,8 @@ namespace MWLua
|
||||||
|
|
||||||
// Available everywhere through the MWBase::LuaManager interface.
|
// Available everywhere through the MWBase::LuaManager interface.
|
||||||
// LuaManager queues these events and propagates to scripts on the next `update` call.
|
// LuaManager queues these events and propagates to scripts on the next `update` call.
|
||||||
void newGameStarted() override { mGlobalScripts.newGameStarted(); }
|
void newGameStarted() override;
|
||||||
|
void gameLoaded() override;
|
||||||
void objectAddedToScene(const MWWorld::Ptr& ptr) override;
|
void objectAddedToScene(const MWWorld::Ptr& ptr) override;
|
||||||
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
|
void objectRemovedFromScene(const MWWorld::Ptr& ptr) override;
|
||||||
void registerObject(const MWWorld::Ptr& ptr) override;
|
void registerObject(const MWWorld::Ptr& ptr) override;
|
||||||
|
@ -62,8 +63,8 @@ namespace MWLua
|
||||||
void clear() override; // should be called before loading game or starting a new game to reset internal state.
|
void clear() override; // should be called before loading game or starting a new game to reset internal state.
|
||||||
void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear".
|
void setupPlayer(const MWWorld::Ptr& ptr) override; // Should be called once after each "clear".
|
||||||
|
|
||||||
// Used only in luabindings
|
// Used only in Lua bindings
|
||||||
void addLocalScript(const MWWorld::Ptr&, const std::string& scriptPath);
|
void addCustomLocalScript(const MWWorld::Ptr&, int scriptId);
|
||||||
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
|
void addAction(std::unique_ptr<Action>&& action) { mActionQueue.push_back(std::move(action)); }
|
||||||
void addTeleportPlayerAction(std::unique_ptr<TeleportAction>&& action) { mTeleportPlayerAction = std::move(action); }
|
void addTeleportPlayerAction(std::unique_ptr<TeleportAction>&& action) { mTeleportPlayerAction = std::move(action); }
|
||||||
void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); }
|
void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); }
|
||||||
|
@ -93,9 +94,15 @@ namespace MWLua
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr);
|
void initConfiguration();
|
||||||
|
LocalScripts* createLocalScripts(const MWWorld::Ptr& ptr, ESM::LuaScriptCfg::Flags);
|
||||||
|
|
||||||
|
const VFS::Manager* mVFS;
|
||||||
|
const std::vector<std::string> mOMWScriptsFiles;
|
||||||
|
|
||||||
bool mInitialized = false;
|
bool mInitialized = false;
|
||||||
|
bool mGlobalScriptsStarted = false;
|
||||||
|
LuaUtil::ScriptsConfiguration mConfiguration;
|
||||||
LuaUtil::LuaState mLua;
|
LuaUtil::LuaState mLua;
|
||||||
sol::table mNearbyPackage;
|
sol::table mNearbyPackage;
|
||||||
sol::table mUserInterfacePackage;
|
sol::table mUserInterfacePackage;
|
||||||
|
@ -104,12 +111,12 @@ namespace MWLua
|
||||||
sol::table mLocalSettingsPackage;
|
sol::table mLocalSettingsPackage;
|
||||||
sol::table mPlayerSettingsPackage;
|
sol::table mPlayerSettingsPackage;
|
||||||
|
|
||||||
std::vector<std::string> mGlobalScriptList;
|
|
||||||
GlobalScripts mGlobalScripts{&mLua};
|
GlobalScripts mGlobalScripts{&mLua};
|
||||||
std::set<LocalScripts*> mActiveLocalScripts;
|
std::set<LocalScripts*> mActiveLocalScripts;
|
||||||
WorldView mWorldView;
|
WorldView mWorldView;
|
||||||
|
|
||||||
bool mPlayerChanged = false;
|
bool mPlayerChanged = false;
|
||||||
|
bool mNewGameStarted = false;
|
||||||
MWWorld::Ptr mPlayer;
|
MWWorld::Ptr mPlayer;
|
||||||
|
|
||||||
GlobalEventQueue mGlobalEvents;
|
GlobalEventQueue mGlobalEvents;
|
||||||
|
|
|
@ -140,9 +140,43 @@ namespace MWLua
|
||||||
|
|
||||||
if constexpr (std::is_same_v<ObjectT, GObject>)
|
if constexpr (std::is_same_v<ObjectT, GObject>)
|
||||||
{ // Only for global scripts
|
{ // Only for global scripts
|
||||||
objectT["addScript"] = [luaManager=context.mLuaManager](const GObject& object, const std::string& path)
|
objectT["addScript"] = [lua=context.mLua, luaManager=context.mLuaManager](const GObject& object, std::string_view path)
|
||||||
{
|
{
|
||||||
luaManager->addLocalScript(object.ptr(), path);
|
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
|
||||||
|
std::optional<int> scriptId = cfg.findId(path);
|
||||||
|
if (!scriptId)
|
||||||
|
throw std::runtime_error("Unknown script: " + std::string(path));
|
||||||
|
if (!(cfg[*scriptId].mFlags & ESM::LuaScriptCfg::sCustom))
|
||||||
|
throw std::runtime_error("Script without CUSTOM tag can not be added dynamically: " + std::string(path));
|
||||||
|
luaManager->addCustomLocalScript(object.ptr(), *scriptId);
|
||||||
|
};
|
||||||
|
objectT["hasScript"] = [lua=context.mLua](const GObject& object, std::string_view path)
|
||||||
|
{
|
||||||
|
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
|
||||||
|
std::optional<int> scriptId = cfg.findId(path);
|
||||||
|
if (!scriptId)
|
||||||
|
return false;
|
||||||
|
MWWorld::Ptr ptr = object.ptr();
|
||||||
|
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||||
|
if (localScripts)
|
||||||
|
return localScripts->hasScript(*scriptId);
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
objectT["removeScript"] = [lua=context.mLua](const GObject& object, std::string_view path)
|
||||||
|
{
|
||||||
|
const LuaUtil::ScriptsConfiguration& cfg = lua->getConfiguration();
|
||||||
|
std::optional<int> scriptId = cfg.findId(path);
|
||||||
|
if (!scriptId)
|
||||||
|
throw std::runtime_error("Unknown script: " + std::string(path));
|
||||||
|
MWWorld::Ptr ptr = object.ptr();
|
||||||
|
LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
|
||||||
|
if (!localScripts || !localScripts->hasScript(*scriptId))
|
||||||
|
throw std::runtime_error("There is no script " + std::string(path) + " on " + ptrToString(ptr));
|
||||||
|
ESM::LuaScriptCfg::Flags flags = cfg[*scriptId].mFlags;
|
||||||
|
if ((flags & (localScripts->getAutoStartMode() | ESM::LuaScriptCfg::sCustom)) != ESM::LuaScriptCfg::sCustom)
|
||||||
|
throw std::runtime_error("Autostarted script can not be removed: " + std::string(path));
|
||||||
|
localScripts->removeScript(*scriptId);
|
||||||
};
|
};
|
||||||
|
|
||||||
objectT["teleport"] = [luaManager=context.mLuaManager](const GObject& object, std::string_view cell,
|
objectT["teleport"] = [luaManager=context.mLuaManager](const GObject& object, std::string_view cell,
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace MWLua
|
||||||
class PlayerScripts : public LocalScripts
|
class PlayerScripts : public LocalScripts
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj)
|
PlayerScripts(LuaUtil::LuaState* lua, const LObject& obj) : LocalScripts(lua, obj, ESM::LuaScriptCfg::sPlayer)
|
||||||
{
|
{
|
||||||
registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers,
|
registerEngineHandlers({&mKeyPressHandlers, &mKeyReleaseHandlers,
|
||||||
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers,
|
&mControllerButtonPressHandlers, &mControllerButtonReleaseHandlers,
|
||||||
|
|
|
@ -558,6 +558,8 @@ void MWState::StateManager::loadGame (const Character *character, const std::str
|
||||||
// Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag.
|
// Since we passed "changeEvent=false" to changeCell, we shouldn't have triggered the cell change flag.
|
||||||
// But make sure the flag is cleared anyway in case it was set from an earlier game.
|
// But make sure the flag is cleared anyway in case it was set from an earlier game.
|
||||||
MWBase::Environment::get().getWorld()->markCellAsUnchanged();
|
MWBase::Environment::get().getWorld()->markCellAsUnchanged();
|
||||||
|
|
||||||
|
MWBase::Environment::get().getLuaManager()->gameLoaded();
|
||||||
}
|
}
|
||||||
catch (const std::exception& e)
|
catch (const std::exception& e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -58,7 +58,8 @@ return {
|
||||||
{"invalid.lua", &invalidScriptFile}
|
{"invalid.lua", &invalidScriptFile}
|
||||||
});
|
});
|
||||||
|
|
||||||
LuaUtil::LuaState mLua{mVFS.get()};
|
LuaUtil::ScriptsConfiguration mCfg;
|
||||||
|
LuaUtil::LuaState mLua{mVFS.get(), &mCfg};
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(LuaStateTest, Sandbox)
|
TEST_F(LuaStateTest, Sandbox)
|
||||||
|
@ -148,7 +149,7 @@ return {
|
||||||
|
|
||||||
TEST_F(LuaStateTest, ProvideAPI)
|
TEST_F(LuaStateTest, ProvideAPI)
|
||||||
{
|
{
|
||||||
LuaUtil::LuaState lua(mVFS.get());
|
LuaUtil::LuaState lua(mVFS.get(), &mCfg);
|
||||||
|
|
||||||
sol::table api1 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api1"));
|
sol::table api1 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api1"));
|
||||||
sol::table api2 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api2"));
|
sol::table api2 = LuaUtil::makeReadOnly(lua.sol().create_table_with("name", "api2"));
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
#include "gmock/gmock.h"
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include <components/lua/omwscriptsparser.hpp>
|
|
||||||
|
|
||||||
#include "testing_util.hpp"
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
using namespace testing;
|
|
||||||
|
|
||||||
TestFile file1(
|
|
||||||
"#comment.lua\n"
|
|
||||||
"\n"
|
|
||||||
"script1.lua\n"
|
|
||||||
"some mod/Some Script.lua"
|
|
||||||
);
|
|
||||||
TestFile file2(
|
|
||||||
"#comment.lua\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"script2.lua\r\n"
|
|
||||||
"some other mod/Some Script.lua\r"
|
|
||||||
);
|
|
||||||
TestFile emptyFile("");
|
|
||||||
TestFile invalidFile("Invalid file");
|
|
||||||
|
|
||||||
struct OMWScriptsParserTest : Test
|
|
||||||
{
|
|
||||||
std::unique_ptr<VFS::Manager> mVFS = createTestVFS({
|
|
||||||
{"file1.omwscripts", &file1},
|
|
||||||
{"file2.omwscripts", &file2},
|
|
||||||
{"empty.omwscripts", &emptyFile},
|
|
||||||
{"invalid.lua", &file1},
|
|
||||||
{"invalid.omwscripts", &invalidFile},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_F(OMWScriptsParserTest, Basic)
|
|
||||||
{
|
|
||||||
internal::CaptureStdout();
|
|
||||||
std::vector<std::string> res = LuaUtil::parseOMWScriptsFiles(
|
|
||||||
mVFS.get(), {"file2.omwscripts", "empty.omwscripts", "file1.omwscripts"});
|
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(), "");
|
|
||||||
EXPECT_THAT(res, ElementsAre("script2.lua", "some other mod/Some Script.lua",
|
|
||||||
"script1.lua", "some mod/Some Script.lua"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(OMWScriptsParserTest, InvalidFiles)
|
|
||||||
{
|
|
||||||
internal::CaptureStdout();
|
|
||||||
std::vector<std::string> res = LuaUtil::parseOMWScriptsFiles(
|
|
||||||
mVFS.get(), {"invalid.lua", "invalid.omwscripts"});
|
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(),
|
|
||||||
"Script list should have suffix '.omwscripts', got: 'invalid.lua'\n"
|
|
||||||
"Lua script should have suffix '.lua', got: 'Invalid file'\n");
|
|
||||||
EXPECT_THAT(res, ElementsAre());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -18,7 +18,10 @@ namespace
|
||||||
|
|
||||||
TestFile testScript(R"X(
|
TestFile testScript(R"X(
|
||||||
return {
|
return {
|
||||||
engineHandlers = { onUpdate = function(dt) print(' update ' .. tostring(dt)) end },
|
engineHandlers = {
|
||||||
|
onUpdate = function(dt) print(' update ' .. tostring(dt)) end,
|
||||||
|
onLoad = function() print('load') end,
|
||||||
|
},
|
||||||
eventHandlers = {
|
eventHandlers = {
|
||||||
Event1 = function(eventData) print(' event1 ' .. tostring(eventData.x)) end,
|
Event1 = function(eventData) print(' event1 ' .. tostring(eventData.x)) end,
|
||||||
Event2 = function(eventData) print(' event2 ' .. tostring(eventData.x)) end,
|
Event2 = function(eventData) print(' event2 ' .. tostring(eventData.x)) end,
|
||||||
|
@ -75,15 +78,25 @@ return {
|
||||||
)X");
|
)X");
|
||||||
|
|
||||||
TestFile overrideInterfaceScript(R"X(
|
TestFile overrideInterfaceScript(R"X(
|
||||||
local old = require('openmw.interfaces').TestInterface
|
local old = nil
|
||||||
|
local interface = {
|
||||||
|
fn = function(x)
|
||||||
|
print('NEW FN', x)
|
||||||
|
old.fn(x)
|
||||||
|
end,
|
||||||
|
value,
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
interfaceName = "TestInterface",
|
interfaceName = "TestInterface",
|
||||||
interface = {
|
interface = interface,
|
||||||
fn = function(x)
|
engineHandlers = {
|
||||||
print('NEW FN', x)
|
onInit = function() print('init') end,
|
||||||
old.fn(x)
|
onLoad = function() print('load') end,
|
||||||
end,
|
onInterfaceOverride = function(oldInterface)
|
||||||
value = old.value + 1
|
print('override')
|
||||||
|
old = oldInterface
|
||||||
|
interface.value = oldInterface.value + 1
|
||||||
|
end
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)X");
|
)X");
|
||||||
|
@ -115,7 +128,25 @@ return {
|
||||||
{"useInterface.lua", &useInterfaceScript},
|
{"useInterface.lua", &useInterfaceScript},
|
||||||
});
|
});
|
||||||
|
|
||||||
LuaUtil::LuaState mLua{mVFS.get()};
|
LuaUtil::ScriptsConfiguration mCfg;
|
||||||
|
LuaUtil::LuaState mLua{mVFS.get(), &mCfg};
|
||||||
|
|
||||||
|
LuaScriptsContainerTest()
|
||||||
|
{
|
||||||
|
ESM::LuaScriptsCfg cfg;
|
||||||
|
cfg.mScripts.push_back({"invalid.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||||
|
cfg.mScripts.push_back({"incorrect.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||||
|
cfg.mScripts.push_back({"empty.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||||
|
cfg.mScripts.push_back({"test1.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||||
|
cfg.mScripts.push_back({"stopEvent.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||||
|
cfg.mScripts.push_back({"test2.lua", "", ESM::LuaScriptCfg::sCustom});
|
||||||
|
cfg.mScripts.push_back({"loadSave1.lua", "", ESM::LuaScriptCfg::sNPC});
|
||||||
|
cfg.mScripts.push_back({"loadSave2.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sNPC});
|
||||||
|
cfg.mScripts.push_back({"testInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer});
|
||||||
|
cfg.mScripts.push_back({"overrideInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer});
|
||||||
|
cfg.mScripts.push_back({"useInterface.lua", "", ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sPlayer});
|
||||||
|
mCfg.init(std::move(cfg));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(LuaScriptsContainerTest, VerifyStructure)
|
TEST_F(LuaScriptsContainerTest, VerifyStructure)
|
||||||
|
@ -123,21 +154,21 @@ return {
|
||||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
||||||
{
|
{
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
EXPECT_FALSE(scripts.addNewScript("invalid.lua"));
|
EXPECT_FALSE(scripts.addCustomScript(*mCfg.findId("invalid.lua")));
|
||||||
std::string output = testing::internal::GetCapturedStdout();
|
std::string output = testing::internal::GetCapturedStdout();
|
||||||
EXPECT_THAT(output, HasSubstr("Can't start Test[invalid.lua]"));
|
EXPECT_THAT(output, HasSubstr("Can't start Test[invalid.lua]"));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
EXPECT_TRUE(scripts.addNewScript("incorrect.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("incorrect.lua")));
|
||||||
std::string output = testing::internal::GetCapturedStdout();
|
std::string output = testing::internal::GetCapturedStdout();
|
||||||
EXPECT_THAT(output, HasSubstr("Not supported handler 'incorrectHandler' in Test[incorrect.lua]"));
|
EXPECT_THAT(output, HasSubstr("Not supported handler 'incorrectHandler' in Test[incorrect.lua]"));
|
||||||
EXPECT_THAT(output, HasSubstr("Not supported section 'incorrectSection' in Test[incorrect.lua]"));
|
EXPECT_THAT(output, HasSubstr("Not supported section 'incorrectSection' in Test[incorrect.lua]"));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
EXPECT_TRUE(scripts.addNewScript("empty.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("empty.lua")));
|
||||||
EXPECT_FALSE(scripts.addNewScript("empty.lua")); // already present
|
EXPECT_FALSE(scripts.addCustomScript(*mCfg.findId("empty.lua"))); // already present
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(), "");
|
EXPECT_EQ(internal::GetCapturedStdout(), "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -146,9 +177,9 @@ return {
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
EXPECT_TRUE(scripts.addNewScript("test1.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua")));
|
||||||
EXPECT_TRUE(scripts.addNewScript("stopEvent.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua")));
|
||||||
EXPECT_TRUE(scripts.addNewScript("test2.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua")));
|
||||||
scripts.update(1.5f);
|
scripts.update(1.5f);
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\t update 1.5\n"
|
EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\t update 1.5\n"
|
||||||
"Test[test2.lua]:\t update 1.5\n");
|
"Test[test2.lua]:\t update 1.5\n");
|
||||||
|
@ -157,9 +188,9 @@ return {
|
||||||
TEST_F(LuaScriptsContainerTest, CallEvent)
|
TEST_F(LuaScriptsContainerTest, CallEvent)
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
||||||
EXPECT_TRUE(scripts.addNewScript("test1.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua")));
|
||||||
EXPECT_TRUE(scripts.addNewScript("stopEvent.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua")));
|
||||||
EXPECT_TRUE(scripts.addNewScript("test2.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua")));
|
||||||
|
|
||||||
std::string X0 = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5));
|
std::string X0 = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5));
|
||||||
std::string X1 = LuaUtil::serialize(mLua.sol().create_table_with("x", 1.5));
|
std::string X1 = LuaUtil::serialize(mLua.sol().create_table_with("x", 1.5));
|
||||||
|
@ -204,9 +235,9 @@ return {
|
||||||
TEST_F(LuaScriptsContainerTest, RemoveScript)
|
TEST_F(LuaScriptsContainerTest, RemoveScript)
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
||||||
EXPECT_TRUE(scripts.addNewScript("test1.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test1.lua")));
|
||||||
EXPECT_TRUE(scripts.addNewScript("stopEvent.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("stopEvent.lua")));
|
||||||
EXPECT_TRUE(scripts.addNewScript("test2.lua"));
|
EXPECT_TRUE(scripts.addCustomScript(*mCfg.findId("test2.lua")));
|
||||||
std::string X = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5));
|
std::string X = LuaUtil::serialize(mLua.sol().create_table_with("x", 0.5));
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -221,8 +252,10 @@ return {
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
EXPECT_TRUE(scripts.removeScript("stopEvent.lua"));
|
int stopEventScriptId = *mCfg.findId("stopEvent.lua");
|
||||||
EXPECT_FALSE(scripts.removeScript("stopEvent.lua")); // already removed
|
EXPECT_TRUE(scripts.hasScript(stopEventScriptId));
|
||||||
|
scripts.removeScript(stopEventScriptId);
|
||||||
|
EXPECT_FALSE(scripts.hasScript(stopEventScriptId));
|
||||||
scripts.update(1.5f);
|
scripts.update(1.5f);
|
||||||
scripts.receiveEvent("Event1", X);
|
scripts.receiveEvent("Event1", X);
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(),
|
EXPECT_EQ(internal::GetCapturedStdout(),
|
||||||
|
@ -233,7 +266,7 @@ return {
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
EXPECT_TRUE(scripts.removeScript("test1.lua"));
|
scripts.removeScript(*mCfg.findId("test1.lua"));
|
||||||
scripts.update(1.5f);
|
scripts.update(1.5f);
|
||||||
scripts.receiveEvent("Event1", X);
|
scripts.receiveEvent("Event1", X);
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(),
|
EXPECT_EQ(internal::GetCapturedStdout(),
|
||||||
|
@ -242,17 +275,41 @@ return {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LuaScriptsContainerTest, Interface)
|
TEST_F(LuaScriptsContainerTest, AutoStart)
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts(&mLua, "Test", ESM::LuaScriptCfg::sPlayer);
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
EXPECT_TRUE(scripts.addNewScript("testInterface.lua"));
|
scripts.addAutoStartedScripts();
|
||||||
EXPECT_TRUE(scripts.addNewScript("overrideInterface.lua"));
|
|
||||||
EXPECT_TRUE(scripts.addNewScript("useInterface.lua"));
|
|
||||||
scripts.update(1.5f);
|
|
||||||
EXPECT_TRUE(scripts.removeScript("overrideInterface.lua"));
|
|
||||||
scripts.update(1.5f);
|
scripts.update(1.5f);
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(),
|
EXPECT_EQ(internal::GetCapturedStdout(),
|
||||||
|
"Test[overrideInterface.lua]:\toverride\n"
|
||||||
|
"Test[overrideInterface.lua]:\tinit\n"
|
||||||
|
"Test[overrideInterface.lua]:\tNEW FN\t4.5\n"
|
||||||
|
"Test[testInterface.lua]:\tFN\t4.5\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LuaScriptsContainerTest, Interface)
|
||||||
|
{
|
||||||
|
LuaUtil::ScriptsContainer scripts(&mLua, "Test", ESM::LuaScriptCfg::sCreature);
|
||||||
|
int addIfaceId = *mCfg.findId("testInterface.lua");
|
||||||
|
int overrideIfaceId = *mCfg.findId("overrideInterface.lua");
|
||||||
|
int useIfaceId = *mCfg.findId("useInterface.lua");
|
||||||
|
|
||||||
|
testing::internal::CaptureStdout();
|
||||||
|
scripts.addAutoStartedScripts();
|
||||||
|
scripts.update(1.5f);
|
||||||
|
EXPECT_EQ(internal::GetCapturedStdout(), "");
|
||||||
|
|
||||||
|
testing::internal::CaptureStdout();
|
||||||
|
EXPECT_TRUE(scripts.addCustomScript(addIfaceId));
|
||||||
|
EXPECT_TRUE(scripts.addCustomScript(overrideIfaceId));
|
||||||
|
EXPECT_TRUE(scripts.addCustomScript(useIfaceId));
|
||||||
|
scripts.update(1.5f);
|
||||||
|
scripts.removeScript(overrideIfaceId);
|
||||||
|
scripts.update(1.5f);
|
||||||
|
EXPECT_EQ(internal::GetCapturedStdout(),
|
||||||
|
"Test[overrideInterface.lua]:\toverride\n"
|
||||||
|
"Test[overrideInterface.lua]:\tinit\n"
|
||||||
"Test[overrideInterface.lua]:\tNEW FN\t4.5\n"
|
"Test[overrideInterface.lua]:\tNEW FN\t4.5\n"
|
||||||
"Test[testInterface.lua]:\tFN\t4.5\n"
|
"Test[testInterface.lua]:\tFN\t4.5\n"
|
||||||
"Test[testInterface.lua]:\tFN\t3.5\n");
|
"Test[testInterface.lua]:\tFN\t3.5\n");
|
||||||
|
@ -260,16 +317,12 @@ return {
|
||||||
|
|
||||||
TEST_F(LuaScriptsContainerTest, LoadSave)
|
TEST_F(LuaScriptsContainerTest, LoadSave)
|
||||||
{
|
{
|
||||||
LuaUtil::ScriptsContainer scripts1(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts1(&mLua, "Test", ESM::LuaScriptCfg::sNPC);
|
||||||
LuaUtil::ScriptsContainer scripts2(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts2(&mLua, "Test", ESM::LuaScriptCfg::sNPC);
|
||||||
LuaUtil::ScriptsContainer scripts3(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts3(&mLua, "Test", ESM::LuaScriptCfg::sPlayer);
|
||||||
|
|
||||||
EXPECT_TRUE(scripts1.addNewScript("loadSave1.lua"));
|
scripts1.addAutoStartedScripts();
|
||||||
EXPECT_TRUE(scripts1.addNewScript("test1.lua"));
|
EXPECT_TRUE(scripts1.addCustomScript(*mCfg.findId("test1.lua")));
|
||||||
EXPECT_TRUE(scripts1.addNewScript("loadSave2.lua"));
|
|
||||||
|
|
||||||
EXPECT_TRUE(scripts3.addNewScript("test2.lua"));
|
|
||||||
EXPECT_TRUE(scripts3.addNewScript("loadSave2.lua"));
|
|
||||||
|
|
||||||
scripts1.receiveEvent("Set", LuaUtil::serialize(mLua.sol().create_table_with(
|
scripts1.receiveEvent("Set", LuaUtil::serialize(mLua.sol().create_table_with(
|
||||||
"n", 1,
|
"n", 1,
|
||||||
|
@ -282,23 +335,30 @@ return {
|
||||||
|
|
||||||
ESM::LuaScripts data;
|
ESM::LuaScripts data;
|
||||||
scripts1.save(data);
|
scripts1.save(data);
|
||||||
scripts2.load(data, true);
|
|
||||||
scripts3.load(data, false);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
|
scripts2.load(data);
|
||||||
scripts2.receiveEvent("Print", "");
|
scripts2.receiveEvent("Print", "");
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(),
|
EXPECT_EQ(internal::GetCapturedStdout(),
|
||||||
|
"Test[test1.lua]:\tload\n"
|
||||||
"Test[loadSave2.lua]:\t0.5\t3.5\n"
|
"Test[loadSave2.lua]:\t0.5\t3.5\n"
|
||||||
"Test[test1.lua]:\tprint\n"
|
"Test[loadSave1.lua]:\t2.5\t1.5\n"
|
||||||
"Test[loadSave1.lua]:\t2.5\t1.5\n");
|
"Test[test1.lua]:\tprint\n");
|
||||||
|
EXPECT_FALSE(scripts2.hasScript(*mCfg.findId("testInterface.lua")));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
|
scripts3.load(data);
|
||||||
scripts3.receiveEvent("Print", "");
|
scripts3.receiveEvent("Print", "");
|
||||||
EXPECT_EQ(internal::GetCapturedStdout(),
|
EXPECT_EQ(internal::GetCapturedStdout(),
|
||||||
|
"Ignoring Test[loadSave1.lua]; this script is not allowed here\n"
|
||||||
|
"Test[test1.lua]:\tload\n"
|
||||||
|
"Test[overrideInterface.lua]:\toverride\n"
|
||||||
|
"Test[overrideInterface.lua]:\tinit\n"
|
||||||
"Test[loadSave2.lua]:\t0.5\t3.5\n"
|
"Test[loadSave2.lua]:\t0.5\t3.5\n"
|
||||||
"Test[test2.lua]:\tprint\n");
|
"Test[test1.lua]:\tprint\n");
|
||||||
|
EXPECT_TRUE(scripts3.hasScript(*mCfg.findId("testInterface.lua")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,8 +366,13 @@ return {
|
||||||
{
|
{
|
||||||
using TimeUnit = LuaUtil::ScriptsContainer::TimeUnit;
|
using TimeUnit = LuaUtil::ScriptsContainer::TimeUnit;
|
||||||
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
LuaUtil::ScriptsContainer scripts(&mLua, "Test");
|
||||||
EXPECT_TRUE(scripts.addNewScript("test1.lua"));
|
int test1Id = *mCfg.findId("test1.lua");
|
||||||
EXPECT_TRUE(scripts.addNewScript("test2.lua"));
|
int test2Id = *mCfg.findId("test2.lua");
|
||||||
|
|
||||||
|
testing::internal::CaptureStdout();
|
||||||
|
EXPECT_TRUE(scripts.addCustomScript(test1Id));
|
||||||
|
EXPECT_TRUE(scripts.addCustomScript(test2Id));
|
||||||
|
EXPECT_EQ(internal::GetCapturedStdout(), "");
|
||||||
|
|
||||||
int counter1 = 0, counter2 = 0, counter3 = 0, counter4 = 0;
|
int counter1 = 0, counter2 = 0, counter3 = 0, counter4 = 0;
|
||||||
sol::function fn1 = sol::make_object(mLua.sol(), [&]() { counter1++; });
|
sol::function fn1 = sol::make_object(mLua.sol(), [&]() { counter1++; });
|
||||||
|
@ -315,25 +380,25 @@ return {
|
||||||
sol::function fn3 = sol::make_object(mLua.sol(), [&](int d) { counter3 += d; });
|
sol::function fn3 = sol::make_object(mLua.sol(), [&](int d) { counter3 += d; });
|
||||||
sol::function fn4 = sol::make_object(mLua.sol(), [&](int d) { counter4 += d; });
|
sol::function fn4 = sol::make_object(mLua.sol(), [&](int d) { counter4 += d; });
|
||||||
|
|
||||||
scripts.registerTimerCallback("test1.lua", "A", fn3);
|
scripts.registerTimerCallback(test1Id, "A", fn3);
|
||||||
scripts.registerTimerCallback("test1.lua", "B", fn4);
|
scripts.registerTimerCallback(test1Id, "B", fn4);
|
||||||
scripts.registerTimerCallback("test2.lua", "B", fn3);
|
scripts.registerTimerCallback(test2Id, "B", fn3);
|
||||||
scripts.registerTimerCallback("test2.lua", "A", fn4);
|
scripts.registerTimerCallback(test2Id, "A", fn4);
|
||||||
|
|
||||||
scripts.processTimers(1, 2);
|
scripts.processTimers(1, 2);
|
||||||
|
|
||||||
scripts.setupSerializableTimer(TimeUnit::SECONDS, 10, "test1.lua", "B", sol::make_object(mLua.sol(), 3));
|
scripts.setupSerializableTimer(TimeUnit::SECONDS, 10, test1Id, "B", sol::make_object(mLua.sol(), 3));
|
||||||
scripts.setupSerializableTimer(TimeUnit::HOURS, 10, "test2.lua", "B", sol::make_object(mLua.sol(), 4));
|
scripts.setupSerializableTimer(TimeUnit::HOURS, 10, test2Id, "B", sol::make_object(mLua.sol(), 4));
|
||||||
scripts.setupSerializableTimer(TimeUnit::SECONDS, 5, "test1.lua", "A", sol::make_object(mLua.sol(), 1));
|
scripts.setupSerializableTimer(TimeUnit::SECONDS, 5, test1Id, "A", sol::make_object(mLua.sol(), 1));
|
||||||
scripts.setupSerializableTimer(TimeUnit::HOURS, 5, "test2.lua", "A", sol::make_object(mLua.sol(), 2));
|
scripts.setupSerializableTimer(TimeUnit::HOURS, 5, test2Id, "A", sol::make_object(mLua.sol(), 2));
|
||||||
scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, "test1.lua", "A", sol::make_object(mLua.sol(), 10));
|
scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, test1Id, "A", sol::make_object(mLua.sol(), 10));
|
||||||
scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, "test1.lua", "B", sol::make_object(mLua.sol(), 20));
|
scripts.setupSerializableTimer(TimeUnit::SECONDS, 15, test1Id, "B", sol::make_object(mLua.sol(), 20));
|
||||||
|
|
||||||
scripts.setupUnsavableTimer(TimeUnit::SECONDS, 10, "test2.lua", fn2);
|
scripts.setupUnsavableTimer(TimeUnit::SECONDS, 10, test2Id, fn2);
|
||||||
scripts.setupUnsavableTimer(TimeUnit::HOURS, 10, "test1.lua", fn2);
|
scripts.setupUnsavableTimer(TimeUnit::HOURS, 10, test1Id, fn2);
|
||||||
scripts.setupUnsavableTimer(TimeUnit::SECONDS, 5, "test2.lua", fn1);
|
scripts.setupUnsavableTimer(TimeUnit::SECONDS, 5, test2Id, fn1);
|
||||||
scripts.setupUnsavableTimer(TimeUnit::HOURS, 5, "test1.lua", fn1);
|
scripts.setupUnsavableTimer(TimeUnit::HOURS, 5, test1Id, fn1);
|
||||||
scripts.setupUnsavableTimer(TimeUnit::SECONDS, 15, "test2.lua", fn1);
|
scripts.setupUnsavableTimer(TimeUnit::SECONDS, 15, test2Id, fn1);
|
||||||
|
|
||||||
EXPECT_EQ(counter1, 0);
|
EXPECT_EQ(counter1, 0);
|
||||||
EXPECT_EQ(counter3, 0);
|
EXPECT_EQ(counter3, 0);
|
||||||
|
@ -358,10 +423,12 @@ return {
|
||||||
EXPECT_EQ(counter3, 5);
|
EXPECT_EQ(counter3, 5);
|
||||||
EXPECT_EQ(counter4, 5);
|
EXPECT_EQ(counter4, 5);
|
||||||
|
|
||||||
|
testing::internal::CaptureStdout();
|
||||||
ESM::LuaScripts data;
|
ESM::LuaScripts data;
|
||||||
scripts.save(data);
|
scripts.save(data);
|
||||||
scripts.load(data, true);
|
scripts.load(data);
|
||||||
scripts.registerTimerCallback("test1.lua", "B", fn4);
|
scripts.registerTimerCallback(test1Id, "B", fn4);
|
||||||
|
EXPECT_EQ(internal::GetCapturedStdout(), "Test[test1.lua]:\tload\nTest[test2.lua]:\tload\n");
|
||||||
|
|
||||||
testing::internal::CaptureStdout();
|
testing::internal::CaptureStdout();
|
||||||
scripts.processTimers(20, 20);
|
scripts.processTimers(20, 20);
|
||||||
|
|
|
@ -29,7 +29,7 @@ endif (GIT_CHECKOUT)
|
||||||
# source files
|
# source files
|
||||||
|
|
||||||
add_component_dir (lua
|
add_component_dir (lua
|
||||||
luastate scriptscontainer utilpackage serialization omwscriptsparser
|
luastate scriptscontainer utilpackage serialization configuration
|
||||||
)
|
)
|
||||||
|
|
||||||
add_component_dir (settings
|
add_component_dir (settings
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace LuaUtil
|
||||||
"type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "getmetatable", "setmetatable"};
|
"type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "getmetatable", "setmetatable"};
|
||||||
static const std::string safePackages[] = {"coroutine", "math", "string", "table"};
|
static const std::string safePackages[] = {"coroutine", "math", "string", "table"};
|
||||||
|
|
||||||
LuaState::LuaState(const VFS::Manager* vfs) : mVFS(vfs)
|
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf) : mConf(conf), mVFS(vfs)
|
||||||
{
|
{
|
||||||
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math,
|
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math,
|
||||||
sol::lib::string, sol::lib::table, sol::lib::debug);
|
sol::lib::string, sol::lib::table, sol::lib::debug);
|
||||||
|
@ -95,12 +95,11 @@ namespace LuaUtil
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LuaState::addCommonPackage(const std::string& packageName, const sol::object& package)
|
void LuaState::addCommonPackage(std::string packageName, sol::object package)
|
||||||
{
|
{
|
||||||
if (package.is<sol::function>())
|
if (!package.is<sol::function>())
|
||||||
mCommonPackages[packageName] = package;
|
package = makeReadOnly(std::move(package));
|
||||||
else
|
mCommonPackages.emplace(std::move(packageName), std::move(package));
|
||||||
mCommonPackages[packageName] = makeReadOnly(package);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::protected_function_result LuaState::runInNewSandbox(
|
sol::protected_function_result LuaState::runInNewSandbox(
|
||||||
|
@ -148,7 +147,7 @@ namespace LuaUtil
|
||||||
return std::move(res);
|
return std::move(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
sol::protected_function LuaState::loadScript(const std::string& path)
|
sol::function LuaState::loadScript(const std::string& path)
|
||||||
{
|
{
|
||||||
auto iter = mCompiledScripts.find(path);
|
auto iter = mCompiledScripts.find(path);
|
||||||
if (iter != mCompiledScripts.end())
|
if (iter != mCompiledScripts.end())
|
||||||
|
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
#include <components/vfs/manager.hpp>
|
#include <components/vfs/manager.hpp>
|
||||||
|
|
||||||
|
#include "configuration.hpp"
|
||||||
|
|
||||||
namespace LuaUtil
|
namespace LuaUtil
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -22,12 +24,12 @@ namespace LuaUtil
|
||||||
// - Access to common read-only resources from different sandboxes;
|
// - Access to common read-only resources from different sandboxes;
|
||||||
// - Replace standard `require` with a safe version that allows to search
|
// - Replace standard `require` with a safe version that allows to search
|
||||||
// Lua libraries (only source, no dll's) in the virtual filesystem;
|
// Lua libraries (only source, no dll's) in the virtual filesystem;
|
||||||
// - Make `print` to add the script name to the every message and
|
// - Make `print` to add the script name to every message and
|
||||||
// write to Log rather than directly to stdout;
|
// write to the Log rather than directly to stdout;
|
||||||
class LuaState
|
class LuaState
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit LuaState(const VFS::Manager* vfs);
|
explicit LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf);
|
||||||
~LuaState();
|
~LuaState();
|
||||||
|
|
||||||
// Returns underlying sol::state.
|
// Returns underlying sol::state.
|
||||||
|
@ -40,7 +42,7 @@ namespace LuaUtil
|
||||||
// The package can be either a sol::table with an API or a sol::function. If it is a function,
|
// The package can be either a sol::table with an API or a sol::function. If it is a function,
|
||||||
// it will be evaluated (once per sandbox) the first time when requested. If the package
|
// it will be evaluated (once per sandbox) the first time when requested. If the package
|
||||||
// is a table, then `makeReadOnly` is applied to it automatically (but not to other tables it contains).
|
// is a table, then `makeReadOnly` is applied to it automatically (but not to other tables it contains).
|
||||||
void addCommonPackage(const std::string& packageName, const sol::object& package);
|
void addCommonPackage(std::string packageName, sol::object package);
|
||||||
|
|
||||||
// Creates a new sandbox, runs a script, and returns the result
|
// Creates a new sandbox, runs a script, and returns the result
|
||||||
// (the result is expected to be an interface of the script).
|
// (the result is expected to be an interface of the script).
|
||||||
|
@ -58,14 +60,17 @@ namespace LuaUtil
|
||||||
|
|
||||||
void dropScriptCache() { mCompiledScripts.clear(); }
|
void dropScriptCache() { mCompiledScripts.clear(); }
|
||||||
|
|
||||||
|
const ScriptsConfiguration& getConfiguration() const { return *mConf; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static sol::protected_function_result throwIfError(sol::protected_function_result&&);
|
static sol::protected_function_result throwIfError(sol::protected_function_result&&);
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
friend sol::protected_function_result call(sol::protected_function fn, Args&&... args);
|
friend sol::protected_function_result call(const sol::protected_function& fn, Args&&... args);
|
||||||
|
|
||||||
sol::protected_function loadScript(const std::string& path);
|
sol::function loadScript(const std::string& path);
|
||||||
|
|
||||||
sol::state mLua;
|
sol::state mLua;
|
||||||
|
const ScriptsConfiguration* mConf;
|
||||||
sol::table mSandboxEnv;
|
sol::table mSandboxEnv;
|
||||||
std::map<std::string, sol::bytecode> mCompiledScripts;
|
std::map<std::string, sol::bytecode> mCompiledScripts;
|
||||||
std::map<std::string, sol::object> mCommonPackages;
|
std::map<std::string, sol::object> mCommonPackages;
|
||||||
|
@ -75,7 +80,7 @@ namespace LuaUtil
|
||||||
// Should be used for every call of every Lua function.
|
// 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
|
// It is a workaround for a bug in `sol`. See https://github.com/ThePhD/sol2/issues/1078
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
sol::protected_function_result call(sol::protected_function fn, Args&&... args)
|
sol::protected_function_result call(const sol::protected_function& fn, Args&&... args)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -101,7 +106,7 @@ namespace LuaUtil
|
||||||
std::string toString(const sol::object&);
|
std::string toString(const sol::object&);
|
||||||
|
|
||||||
// Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata.
|
// Makes a table read only (when accessed from Lua) by wrapping it with an empty userdata.
|
||||||
// Needed to forbid any changes in common resources that can accessed from different sandboxes.
|
// Needed to forbid any changes in common resources that can be accessed from different sandboxes.
|
||||||
sol::table makeReadOnly(sol::table);
|
sol::table makeReadOnly(sol::table);
|
||||||
sol::table getMutableFromReadOnly(const sol::userdata&);
|
sol::table getMutableFromReadOnly(const sol::userdata&);
|
||||||
|
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
#include "omwscriptsparser.hpp"
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include <components/debug/debuglog.hpp>
|
|
||||||
|
|
||||||
std::vector<std::string> LuaUtil::parseOMWScriptsFiles(const VFS::Manager* vfs, const std::vector<std::string>& scriptLists)
|
|
||||||
{
|
|
||||||
auto endsWith = [](std::string_view s, std::string_view suffix)
|
|
||||||
{
|
|
||||||
return s.size() >= suffix.size() && std::equal(suffix.rbegin(), suffix.rend(), s.rbegin());
|
|
||||||
};
|
|
||||||
std::vector<std::string> res;
|
|
||||||
for (const std::string& scriptListFile : scriptLists)
|
|
||||||
{
|
|
||||||
if (!endsWith(scriptListFile, ".omwscripts"))
|
|
||||||
{
|
|
||||||
Log(Debug::Error) << "Script list should have suffix '.omwscripts', got: '" << scriptListFile << "'";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
std::string content(std::istreambuf_iterator<char>(*vfs->get(scriptListFile)), {});
|
|
||||||
std::string_view view(content);
|
|
||||||
while (!view.empty())
|
|
||||||
{
|
|
||||||
size_t pos = 0;
|
|
||||||
while (pos < view.size() && view[pos] != '\n')
|
|
||||||
pos++;
|
|
||||||
std::string_view line = view.substr(0, pos);
|
|
||||||
view = view.substr(std::min(pos + 1, view.size()));
|
|
||||||
if (!line.empty() && line.back() == '\r')
|
|
||||||
line = line.substr(0, pos - 1);
|
|
||||||
// Lines starting with '#' are comments.
|
|
||||||
// TODO: Maybe make the parser more robust. It is a bit inconsistent that 'path/#to/file.lua'
|
|
||||||
// is a valid path, but '#path/to/file.lua' is considered as a comment and ignored.
|
|
||||||
if (line.empty() || line[0] == '#')
|
|
||||||
continue;
|
|
||||||
if (endsWith(line, ".lua"))
|
|
||||||
res.push_back(std::string(line));
|
|
||||||
else
|
|
||||||
Log(Debug::Error) << "Lua script should have suffix '.lua', got: '" << line.substr(0, 300) << "'";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
#ifndef COMPONENTS_LUA_OMWSCRIPTSPARSER_H
|
|
||||||
#define COMPONENTS_LUA_OMWSCRIPTSPARSER_H
|
|
||||||
|
|
||||||
#include <components/vfs/manager.hpp>
|
|
||||||
|
|
||||||
namespace LuaUtil
|
|
||||||
{
|
|
||||||
|
|
||||||
// Parses list of `*.omwscripts` files.
|
|
||||||
std::vector<std::string> parseOMWScriptsFiles(const VFS::Manager* vfs, const std::vector<std::string>& scriptLists);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // COMPONENTS_LUA_OMWSCRIPTSPARSER_H
|
|
|
@ -10,8 +10,10 @@ namespace LuaUtil
|
||||||
static constexpr std::string_view INTERFACE_NAME = "interfaceName";
|
static constexpr std::string_view INTERFACE_NAME = "interfaceName";
|
||||||
static constexpr std::string_view INTERFACE = "interface";
|
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_SAVE = "onSave";
|
||||||
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 REGISTERED_TIMER_CALLBACKS = "_timers";
|
static constexpr std::string_view REGISTERED_TIMER_CALLBACKS = "_timers";
|
||||||
static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers";
|
static constexpr std::string_view TEMPORARY_TIMER_CALLBACKS = "_temp_timers";
|
||||||
|
@ -25,147 +27,238 @@ namespace LuaUtil
|
||||||
return res;
|
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});
|
registerEngineHandlers({&mUpdateHandlers});
|
||||||
mPublicInterfaces = sol::table(lua->sol(), sol::create);
|
mPublicInterfaces = sol::table(lua->sol(), sol::create);
|
||||||
addPackage("openmw.interfaces", mPublicInterfaces);
|
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
|
return false; // already present
|
||||||
|
|
||||||
|
const std::string& path = scriptPath(scriptId);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sol::table hiddenData(mLua.sol(), sol::create);
|
Script& script = mScripts[scriptId];
|
||||||
hiddenData[ScriptId::KEY] = ScriptId{this, path};
|
script.mHiddenData = mLua.newTable();
|
||||||
hiddenData[REGISTERED_TIMER_CALLBACKS] = mLua.newTable();
|
script.mHiddenData[ScriptId::KEY] = ScriptId{this, scriptId, path};
|
||||||
hiddenData[TEMPORARY_TIMER_CALLBACKS] = mLua.newTable();
|
script.mHiddenData[REGISTERED_TIMER_CALLBACKS] = mLua.newTable();
|
||||||
mScripts[path].mHiddenData = hiddenData;
|
script.mHiddenData[TEMPORARY_TIMER_CALLBACKS] = mLua.newTable();
|
||||||
sol::object script = mLua.runInNewSandbox(path, mNamePrefix, API, hiddenData);
|
sol::object scriptOutput = mLua.runInNewSandbox(path, mNamePrefix, mAPI, script.mHiddenData);
|
||||||
std::string interfaceName = "";
|
if (scriptOutput == sol::nil)
|
||||||
sol::object publicInterface = sol::nil;
|
return true;
|
||||||
if (script != sol::nil)
|
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>();
|
std::string_view handlerName = key.as<std::string_view>();
|
||||||
if (sectionName == ENGINE_HANDLERS)
|
if (handlerName == HANDLER_INIT)
|
||||||
parseEngineHandlers(value, path);
|
onInit = sol::function(fn);
|
||||||
else if (sectionName == EVENT_HANDLERS)
|
else if (handlerName == HANDLER_LOAD)
|
||||||
parseEventHandlers(value, path);
|
onLoad = sol::function(fn);
|
||||||
else if (sectionName == INTERFACE_NAME)
|
else if (handlerName == HANDLER_SAVE)
|
||||||
interfaceName = value.as<std::string>();
|
script.mOnSave = sol::function(fn);
|
||||||
else if (sectionName == INTERFACE)
|
else if (handlerName == HANDLER_INTERFACE_OVERRIDE)
|
||||||
publicInterface = value.as<sol::table>();
|
script.mOnOverride = sol::function(fn);
|
||||||
else
|
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'";
|
Log(Debug::Error) << mNamePrefix << "[" << path << "]: 'interfaceName' should always be used together with 'interface'";
|
||||||
else if (!interfaceName.empty())
|
script.mInterfaceName.clear();
|
||||||
script.as<sol::table>()[INTERFACE] = mPublicInterfaces[interfaceName] = makeReadOnly(publicInterface);
|
script.mInterface = sol::nil;
|
||||||
mScriptOrder.push_back(path);
|
}
|
||||||
mScripts[path].mInterface = std::move(script);
|
else if (script.mInterface)
|
||||||
|
{
|
||||||
|
script.mInterface = makeReadOnly(*script.mInterface);
|
||||||
|
insertInterface(scriptId, script);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
mScripts.erase(path);
|
mScripts.erase(scriptId);
|
||||||
Log(Debug::Error) << "Can't start " << mNamePrefix << "[" << path << "]; " << e.what();
|
Log(Debug::Error) << "Can't start " << mNamePrefix << "[" << path << "]; " << e.what();
|
||||||
return false;
|
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())
|
if (scriptIter == mScripts.end())
|
||||||
return false; // no such script
|
return; // no such script
|
||||||
scriptIter->second.mHiddenData[ScriptId::KEY] = sol::nil;
|
Script& script = scriptIter->second;
|
||||||
sol::object& script = scriptIter->second.mInterface;
|
if (script.mInterface)
|
||||||
if (getFieldOrNil(script, INTERFACE_NAME) != sol::nil)
|
removeInterface(scriptId, script);
|
||||||
{
|
script.mHiddenData[ScriptId::KEY] = sol::nil;
|
||||||
std::string_view interfaceName = getFieldOrNil(script, INTERFACE_NAME).as<std::string_view>();
|
|
||||||
if (mPublicInterfaces[interfaceName] == getFieldOrNil(script, INTERFACE))
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sol::object engineHandlers = getFieldOrNil(script, ENGINE_HANDLERS);
|
|
||||||
if (engineHandlers != sol::nil)
|
|
||||||
{
|
|
||||||
for (auto& [key, value] : sol::table(engineHandlers))
|
|
||||||
{
|
|
||||||
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>()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sol::object eventHandlers = getFieldOrNil(script, EVENT_HANDLERS);
|
|
||||||
if (eventHandlers != sol::nil)
|
|
||||||
{
|
|
||||||
for (auto& [key, value] : sol::table(eventHandlers))
|
|
||||||
{
|
|
||||||
EventHandlerList& list = mEventHandlers.find(key.as<std::string_view>())->second;
|
|
||||||
list.erase(std::find(list.begin(), list.end(), value.as<sol::protected_function>()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mScripts.erase(scriptIter);
|
mScripts.erase(scriptIter);
|
||||||
mScriptOrder.erase(std::find(mScriptOrder.begin(), mScriptOrder.end(), path));
|
for (auto& [_, handlers] : mEngineHandlers)
|
||||||
return true;
|
removeHandler(handlers->mList, scriptId);
|
||||||
|
for (auto& [_, handlers] : mEventHandlers)
|
||||||
|
removeHandler(handlers, scriptId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptsContainer::parseEventHandlers(sol::table handlers, std::string_view scriptPath)
|
void ScriptsContainer::insertInterface(int scriptId, const Script& script)
|
||||||
{
|
{
|
||||||
for (auto& [key, value] : handlers)
|
assert(script.mInterface);
|
||||||
|
const Script* prev = nullptr;
|
||||||
|
const Script* next = nullptr;
|
||||||
|
int nextId = 0;
|
||||||
|
for (const auto& [otherId, otherScript] : mScripts)
|
||||||
{
|
{
|
||||||
std::string_view eventName = key.as<std::string_view>();
|
if (scriptId == otherId || script.mInterfaceName != otherScript.mInterfaceName)
|
||||||
auto it = mEventHandlers.find(eventName);
|
continue;
|
||||||
if (it == mEventHandlers.end())
|
if (otherId < scriptId)
|
||||||
it = mEventHandlers.insert({std::string(eventName), EventHandlerList()}).first;
|
prev = &otherScript;
|
||||||
it->second.push_back(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptsContainer::parseEngineHandlers(sol::table handlers, std::string_view scriptPath)
|
|
||||||
{
|
|
||||||
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
|
else
|
||||||
it->second->mList.push_back(value);
|
{
|
||||||
|
next = &otherScript;
|
||||||
|
nextId = otherId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (prev && script.mOnOverride)
|
||||||
|
{
|
||||||
|
try { LuaUtil::call(*script.mOnOverride, *prev->mInterface); }
|
||||||
|
catch (std::exception& e) { printError(scriptId, "onInterfaceOverride failed", e); }
|
||||||
|
}
|
||||||
|
if (next && next->mOnOverride)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
{
|
||||||
|
next = &otherScript;
|
||||||
|
nextId = otherId;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next)
|
||||||
|
{
|
||||||
|
if (next->mOnOverride)
|
||||||
|
{
|
||||||
|
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); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (prev)
|
||||||
|
mPublicInterfaces[script.mInterfaceName] = *prev->mInterface;
|
||||||
|
else
|
||||||
|
mPublicInterfaces[script.mInterfaceName] = sol::nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptsContainer::insertHandler(std::vector<Handler>& list, int scriptId, sol::function fn)
|
||||||
|
{
|
||||||
|
list.emplace_back();
|
||||||
|
int pos = list.size() - 1;
|
||||||
|
while (pos > 0 && list[pos - 1].mScriptId > scriptId)
|
||||||
|
{
|
||||||
|
list[pos] = std::move(list[pos - 1]);
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
list[pos].mScriptId = scriptId;
|
||||||
|
list[pos].mFn = std::move(fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScriptsContainer::removeHandler(std::vector<Handler>& list, int scriptId)
|
||||||
|
{
|
||||||
|
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)
|
void ScriptsContainer::receiveEvent(std::string_view eventName, std::string_view eventData)
|
||||||
|
@ -191,13 +284,14 @@ namespace LuaUtil
|
||||||
{
|
{
|
||||||
try
|
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>())
|
if (res != sol::nil && !res.as<bool>())
|
||||||
break; // Skip other handlers if 'false' was returned.
|
break; // Skip other handlers if 'false' was returned.
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
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;
|
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)
|
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)
|
auto saveTimerFn = [&](const Timer& timer, TimeUnit timeUnit)
|
||||||
{
|
{
|
||||||
if (!timer.mSerializable)
|
if (!timer.mSerializable)
|
||||||
|
@ -220,78 +324,85 @@ namespace LuaUtil
|
||||||
savedTimer.mUnit = timeUnit;
|
savedTimer.mUnit = timeUnit;
|
||||||
savedTimer.mCallbackName = std::get<std::string>(timer.mCallback);
|
savedTimer.mCallbackName = std::get<std::string>(timer.mCallback);
|
||||||
savedTimer.mCallbackArgument = timer.mSerializedArg;
|
savedTimer.mCallbackArgument = timer.mSerializedArg;
|
||||||
if (timers.count(timer.mScript) == 0)
|
timers[timer.mScriptId].push_back(std::move(savedTimer));
|
||||||
timers[timer.mScript] = {};
|
|
||||||
timers[timer.mScript].push_back(std::move(savedTimer));
|
|
||||||
};
|
};
|
||||||
for (const Timer& timer : mSecondsTimersQueue)
|
for (const Timer& timer : mSecondsTimersQueue)
|
||||||
saveTimerFn(timer, TimeUnit::SECONDS);
|
saveTimerFn(timer, TimeUnit::SECONDS);
|
||||||
for (const Timer& timer : mHoursTimersQueue)
|
for (const Timer& timer : mHoursTimersQueue)
|
||||||
saveTimerFn(timer, TimeUnit::HOURS);
|
saveTimerFn(timer, TimeUnit::HOURS);
|
||||||
data.mScripts.clear();
|
data.mScripts.clear();
|
||||||
for (const std::string& path : mScriptOrder)
|
for (auto& [scriptId, script] : mScripts)
|
||||||
{
|
{
|
||||||
ESM::LuaScript savedScript;
|
ESM::LuaScript savedScript;
|
||||||
savedScript.mScriptPath = path;
|
savedScript.mScriptPath = script.mHiddenData.get<ScriptId>(ScriptId::KEY).mPath;
|
||||||
sol::object handler = getFieldOrNil(mScripts[path].mInterface, ENGINE_HANDLERS, HANDLER_SAVE);
|
if (script.mOnSave)
|
||||||
if (handler != sol::nil)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sol::object state = LuaUtil::call(handler);
|
sol::object state = LuaUtil::call(*script.mOnSave);
|
||||||
savedScript.mData = serialize(state, mSerializer);
|
savedScript.mData = serialize(state, mSerializer);
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e) { printError(scriptId, "onSave failed", e); }
|
||||||
{
|
|
||||||
Log(Debug::Error) << mNamePrefix << "[" << path << "] onSave failed: " << e.what();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
auto timersIt = timers.find(path);
|
auto timersIt = timers.find(scriptId);
|
||||||
if (timersIt != timers.end())
|
if (timersIt != timers.end())
|
||||||
savedScript.mTimers = std::move(timersIt->second);
|
savedScript.mTimers = std::move(timersIt->second);
|
||||||
data.mScripts.push_back(std::move(savedScript));
|
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;
|
removeAllScripts();
|
||||||
if (resetScriptList)
|
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();
|
std::optional<int> scriptId = cfg.findId(s.mScriptPath);
|
||||||
for (const ESM::LuaScript& script : data.mScripts)
|
if (!scriptId)
|
||||||
addNewScript(script.mScriptPath);
|
{
|
||||||
}
|
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; script not registered";
|
||||||
else
|
|
||||||
scriptsWithoutSavedData = mScripts;
|
|
||||||
mSecondsTimersQueue.clear();
|
|
||||||
mHoursTimersQueue.clear();
|
|
||||||
for (const ESM::LuaScript& script : data.mScripts)
|
|
||||||
{
|
|
||||||
auto iter = mScripts.find(script.mScriptPath);
|
|
||||||
if (iter == mScripts.end())
|
|
||||||
continue;
|
continue;
|
||||||
scriptsWithoutSavedData.erase(iter->first);
|
}
|
||||||
iter->second.mHiddenData.get<sol::table>(TEMPORARY_TIMER_CALLBACKS).clear();
|
if (!(cfg[*scriptId].mFlags & (ESM::LuaScriptCfg::sCustom | mAutoStartMode)))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
sol::object handler = getFieldOrNil(iter->second.mInterface, ENGINE_HANDLERS, HANDLER_LOAD);
|
Log(Debug::Verbose) << "Ignoring " << mNamePrefix << "[" << s.mScriptPath << "]; this script is not allowed here";
|
||||||
if (handler != sol::nil)
|
continue;
|
||||||
|
}
|
||||||
|
scripts[*scriptId] = &s;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [scriptId, savedScript] : scripts)
|
||||||
|
{
|
||||||
|
std::optional<sol::function> onInit, onLoad;
|
||||||
|
if (!addScript(scriptId, onInit, onLoad))
|
||||||
|
continue;
|
||||||
|
if (savedScript == nullptr)
|
||||||
|
{
|
||||||
|
if (onInit)
|
||||||
|
callOnInit(scriptId, *onInit);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (onLoad)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
sol::object state = deserialize(mLua.sol(), script.mData, mSerializer);
|
sol::object state = deserialize(mLua.sol(), savedScript->mData, mSerializer);
|
||||||
LuaUtil::call(handler, state);
|
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); }
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
for (const ESM::LuaTimer& savedTimer : savedScript->mTimers)
|
||||||
{
|
|
||||||
Log(Debug::Error) << mNamePrefix << "[" << script.mScriptPath << "] onLoad failed: " << e.what();
|
|
||||||
}
|
|
||||||
for (const ESM::LuaTimer& savedTimer : script.mTimers)
|
|
||||||
{
|
{
|
||||||
Timer timer;
|
Timer timer;
|
||||||
timer.mCallback = savedTimer.mCallbackName;
|
timer.mCallback = savedTimer.mCallbackName;
|
||||||
timer.mSerializable = true;
|
timer.mSerializable = true;
|
||||||
timer.mScript = script.mScriptPath;
|
timer.mScriptId = scriptId;
|
||||||
timer.mTime = savedTimer.mTime;
|
timer.mTime = savedTimer.mTime;
|
||||||
|
|
||||||
try
|
try
|
||||||
|
@ -306,24 +417,10 @@ namespace LuaUtil
|
||||||
else
|
else
|
||||||
mSecondsTimersQueue.push_back(std::move(timer));
|
mSecondsTimersQueue.push_back(std::move(timer));
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e) { printError(scriptId, "can not load timer", 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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::make_heap(mSecondsTimersQueue.begin(), mSecondsTimersQueue.end());
|
std::make_heap(mSecondsTimersQueue.begin(), mSecondsTimersQueue.end());
|
||||||
std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end());
|
std::make_heap(mHoursTimersQueue.begin(), mHoursTimersQueue.end());
|
||||||
}
|
}
|
||||||
|
@ -334,12 +431,13 @@ namespace LuaUtil
|
||||||
script.mHiddenData[ScriptId::KEY] = sol::nil;
|
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()
|
void ScriptsContainer::removeAllScripts()
|
||||||
{
|
{
|
||||||
for (auto& [_, script] : mScripts)
|
for (auto& [_, script] : mScripts)
|
||||||
script.mHiddenData[ScriptId::KEY] = sol::nil;
|
script.mHiddenData[ScriptId::KEY] = sol::nil;
|
||||||
mScripts.clear();
|
mScripts.clear();
|
||||||
mScriptOrder.clear();
|
|
||||||
for (auto& [_, handlers] : mEngineHandlers)
|
for (auto& [_, handlers] : mEngineHandlers)
|
||||||
handlers->mList.clear();
|
handlers->mList.clear();
|
||||||
mEventHandlers.clear();
|
mEventHandlers.clear();
|
||||||
|
@ -351,17 +449,17 @@ namespace LuaUtil
|
||||||
mPublicInterfaces[sol::meta_function::index] = mPublicInterfaces;
|
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())
|
if (it == mScripts.end())
|
||||||
throw std::logic_error("ScriptsContainer::getHiddenData: script doesn't exist");
|
throw std::logic_error("ScriptsContainer::getHiddenData: script doesn't exist");
|
||||||
return it->second.mHiddenData;
|
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)
|
void ScriptsContainer::insertTimer(std::vector<Timer>& timerQueue, Timer&& t)
|
||||||
|
@ -370,12 +468,12 @@ namespace LuaUtil
|
||||||
std::push_heap(timerQueue.begin(), timerQueue.end());
|
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)
|
std::string_view callbackName, sol::object callbackArg)
|
||||||
{
|
{
|
||||||
Timer t;
|
Timer t;
|
||||||
t.mCallback = std::string(callbackName);
|
t.mCallback = std::string(callbackName);
|
||||||
t.mScript = scriptPath;
|
t.mScriptId = scriptId;
|
||||||
t.mSerializable = true;
|
t.mSerializable = true;
|
||||||
t.mTime = time;
|
t.mTime = time;
|
||||||
t.mArg = callbackArg;
|
t.mArg = callbackArg;
|
||||||
|
@ -383,15 +481,15 @@ namespace LuaUtil
|
||||||
insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t));
|
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;
|
Timer t;
|
||||||
t.mScript = scriptPath;
|
t.mScriptId = scriptId;
|
||||||
t.mSerializable = false;
|
t.mSerializable = false;
|
||||||
t.mTime = time;
|
t.mTime = time;
|
||||||
|
|
||||||
t.mCallback = mTemporaryCallbackCounter;
|
t.mCallback = mTemporaryCallbackCounter;
|
||||||
getHiddenData(scriptPath)[TEMPORARY_TIMER_CALLBACKS][mTemporaryCallbackCounter] = std::move(callback);
|
getHiddenData(scriptId)[TEMPORARY_TIMER_CALLBACKS][mTemporaryCallbackCounter] = std::move(callback);
|
||||||
mTemporaryCallbackCounter++;
|
mTemporaryCallbackCounter++;
|
||||||
|
|
||||||
insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t));
|
insertTimer(timeUnit == TimeUnit::HOURS ? mHoursTimersQueue : mSecondsTimersQueue, std::move(t));
|
||||||
|
@ -401,7 +499,7 @@ namespace LuaUtil
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
sol::table data = getHiddenData(t.mScript);
|
sol::table data = getHiddenData(t.mScriptId);
|
||||||
if (t.mSerializable)
|
if (t.mSerializable)
|
||||||
{
|
{
|
||||||
const std::string& callbackName = std::get<std::string>(t.mCallback);
|
const std::string& callbackName = std::get<std::string>(t.mCallback);
|
||||||
|
@ -421,10 +519,7 @@ namespace LuaUtil
|
||||||
callbacks[id] = sol::nil;
|
callbacks[id] = sol::nil;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (std::exception& e)
|
catch (std::exception& e) { printError(t.mScriptId, "callTimer failed", e); }
|
||||||
{
|
|
||||||
Log(Debug::Error) << mNamePrefix << "[" << t.mScript << "] callTimer failed: " << e.what();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptsContainer::updateTimerQueue(std::vector<Timer>& timerQueue, double time)
|
void ScriptsContainer::updateTimerQueue(std::vector<Timer>& timerQueue, double time)
|
||||||
|
|
|
@ -17,7 +17,7 @@ namespace LuaUtil
|
||||||
// ScriptsContainer is a base class for all scripts containers (LocalScripts,
|
// ScriptsContainer is a base class for all scripts containers (LocalScripts,
|
||||||
// GlobalScripts, PlayerScripts, etc). Each script runs in a separate sandbox.
|
// GlobalScripts, PlayerScripts, etc). Each script runs in a separate sandbox.
|
||||||
// Scripts from different containers can interact to each other only via events.
|
// Scripts from different containers can interact to each other only via events.
|
||||||
// Scripts within one container can interact via interfaces (not implemented yet).
|
// Scripts within one container can interact via interfaces.
|
||||||
// All scripts from one container have the same set of API packages available.
|
// All scripts from one container have the same set of API packages available.
|
||||||
//
|
//
|
||||||
// Each script should return a table in a specific format that describes its
|
// Each script should return a table in a specific format that describes its
|
||||||
|
@ -42,11 +42,12 @@ namespace LuaUtil
|
||||||
// -- An error is printed if unknown handler is specified.
|
// -- An error is printed if unknown handler is specified.
|
||||||
// engineHandlers = {
|
// engineHandlers = {
|
||||||
// onUpdate = update,
|
// onUpdate = update,
|
||||||
|
// onInit = function(initData) ... end, -- used when the script is just created (not loaded)
|
||||||
// onSave = function() return ... end,
|
// onSave = function() return ... end,
|
||||||
// onLoad = function(state) ... end, -- "state" is the data that was earlier returned by onSave
|
// onLoad = function(state, initData) ... end, -- "state" is the data that was earlier returned by onSave
|
||||||
//
|
//
|
||||||
// -- Works only if ScriptsContainer::registerEngineHandler is overloaded in a child class
|
// -- Works only if a child class has passed a EngineHandlerList
|
||||||
// -- and explicitly supports 'onSomethingElse'
|
// -- for 'onSomethingElse' to ScriptsContainer::registerEngineHandlers.
|
||||||
// onSomethingElse = function() print("something else") end
|
// onSomethingElse = function() print("something else") end
|
||||||
// },
|
// },
|
||||||
//
|
//
|
||||||
|
@ -65,30 +66,36 @@ namespace LuaUtil
|
||||||
constexpr static std::string_view KEY = "_id";
|
constexpr static std::string_view KEY = "_id";
|
||||||
|
|
||||||
ScriptsContainer* mContainer;
|
ScriptsContainer* mContainer;
|
||||||
std::string mPath;
|
int mIndex; // index in LuaUtil::ScriptsConfiguration
|
||||||
|
std::string mPath; // path to the script source in VFS
|
||||||
|
|
||||||
std::string toString() const;
|
std::string toString() const;
|
||||||
};
|
};
|
||||||
using TimeUnit = ESM::LuaTimer::TimeUnit;
|
using TimeUnit = ESM::LuaTimer::TimeUnit;
|
||||||
|
|
||||||
// `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print` output.
|
// `namePrefix` is a common prefix for all scripts in the container. Used in logs for error messages and `print` output.
|
||||||
ScriptsContainer(LuaUtil::LuaState* lua, std::string_view namePrefix);
|
// `autoStartMode` specifies the list of scripts that should be autostarted in this container; the list itself is
|
||||||
|
// stored in ScriptsConfiguration: lua->getConfiguration().getListByFlag(autoStartMode).
|
||||||
|
ScriptsContainer(LuaState* lua, std::string_view namePrefix, ESM::LuaScriptCfg::Flags autoStartMode = 0);
|
||||||
|
|
||||||
ScriptsContainer(const ScriptsContainer&) = delete;
|
ScriptsContainer(const ScriptsContainer&) = delete;
|
||||||
ScriptsContainer(ScriptsContainer&&) = delete;
|
ScriptsContainer(ScriptsContainer&&) = delete;
|
||||||
virtual ~ScriptsContainer();
|
virtual ~ScriptsContainer();
|
||||||
|
|
||||||
|
ESM::LuaScriptCfg::Flags getAutoStartMode() const { return mAutoStartMode; }
|
||||||
|
|
||||||
// Adds package that will be available (via `require`) for all scripts in the container.
|
// Adds package that will be available (via `require`) for all scripts in the container.
|
||||||
// Automatically applies LuaUtil::makeReadOnly to the package.
|
// Automatically applies LuaUtil::makeReadOnly to the package.
|
||||||
void addPackage(const std::string& packageName, sol::object package);
|
void addPackage(std::string packageName, sol::object package);
|
||||||
|
|
||||||
// Finds a file with given path in the virtual file system, starts as a new script, and adds it to the container.
|
// Gets script with given id from ScriptsConfiguration, finds the source in the virtual file system, starts as a new script,
|
||||||
// Returns `true` if the script was successfully added. Otherwise prints an error message and returns `false`.
|
// adds it to the container, and calls onInit for this script. Returns `true` if the script was successfully added.
|
||||||
// `false` can be returned if either file not found or has syntax errors or such script already exists in the container.
|
// The script should have CUSTOM flag. If the flag is not set, or file not found, or has syntax errors, returns false.
|
||||||
bool addNewScript(const std::string& path);
|
// If such script already exists in the container, then also returns false.
|
||||||
|
bool addCustomScript(int scriptId);
|
||||||
|
|
||||||
// Removes script. Returns `true` if it was successfully removed.
|
bool hasScript(int scriptId) const { return mScripts.count(scriptId) != 0; }
|
||||||
bool removeScript(const std::string& path);
|
void removeScript(int scriptId);
|
||||||
void removeAllScripts();
|
|
||||||
|
|
||||||
// Processes timers. gameSeconds and gameHours are time (in seconds and in game hours) passed from the game start.
|
// Processes timers. gameSeconds and gameHours are time (in seconds and in game hours) passed from the game start.
|
||||||
void processTimers(double gameSeconds, double gameHours);
|
void processTimers(double gameSeconds, double gameHours);
|
||||||
|
@ -107,22 +114,22 @@ namespace LuaUtil
|
||||||
// only built-in types and types from util package can be serialized.
|
// only built-in types and types from util package can be serialized.
|
||||||
void setSerializer(const UserdataSerializer* serializer) { mSerializer = serializer; }
|
void setSerializer(const UserdataSerializer* serializer) { mSerializer = serializer; }
|
||||||
|
|
||||||
|
// Starts scripts according to `autoStartMode` and calls `onInit` for them. Not needed if `load` is used.
|
||||||
|
void addAutoStartedScripts();
|
||||||
|
|
||||||
|
// Removes all scripts including the auto started.
|
||||||
|
void removeAllScripts();
|
||||||
|
|
||||||
// Calls engineHandler "onSave" for every script and saves the list of the scripts with serialized data to ESM::LuaScripts.
|
// Calls engineHandler "onSave" for every script and saves the list of the scripts with serialized data to ESM::LuaScripts.
|
||||||
void save(ESM::LuaScripts&);
|
void save(ESM::LuaScripts&);
|
||||||
|
|
||||||
// Calls engineHandler "onLoad" for every script with given data.
|
// Removes all scripts; starts scripts according to `autoStartMode` and
|
||||||
// If resetScriptList=true, then removes all currently active scripts and runs the scripts that were saved in ESM::LuaScripts.
|
// loads the savedScripts. Runs "onLoad" for each script.
|
||||||
// If resetScriptList=false, then list of running scripts is not changed, only engineHandlers "onLoad" are called.
|
void load(const ESM::LuaScripts& savedScripts);
|
||||||
void load(const ESM::LuaScripts&, bool resetScriptList);
|
|
||||||
|
|
||||||
// Returns the hidden data of a script.
|
|
||||||
// Each script has a corresponding "hidden data" - a lua table that is not accessible from the script itself,
|
|
||||||
// but can be used by built-in packages. It contains ScriptId and can contain any arbitrary data.
|
|
||||||
sol::table getHiddenData(const std::string& scriptPath);
|
|
||||||
|
|
||||||
// Callbacks for serializable timers should be registered in advance.
|
// Callbacks for serializable timers should be registered in advance.
|
||||||
// The script with the given path should already present in the container.
|
// The script with the given path should already present in the container.
|
||||||
void registerTimerCallback(const std::string& scriptPath, std::string_view callbackName, sol::function callback);
|
void registerTimerCallback(int scriptId, std::string_view callbackName, sol::function callback);
|
||||||
|
|
||||||
// Sets up a timer, that can be automatically saved and loaded.
|
// Sets up a timer, that can be automatically saved and loaded.
|
||||||
// timeUnit - game seconds (TimeUnit::Seconds) or game hours (TimeUnit::Hours).
|
// timeUnit - game seconds (TimeUnit::Seconds) or game hours (TimeUnit::Hours).
|
||||||
|
@ -130,18 +137,24 @@ namespace LuaUtil
|
||||||
// scriptPath - script path in VFS is used as script id. The script with the given path should already present in the container.
|
// scriptPath - script path in VFS is used as script id. The script with the given path should already present in the container.
|
||||||
// callbackName - callback (should be registered in advance) for this timer.
|
// callbackName - callback (should be registered in advance) for this timer.
|
||||||
// callbackArg - parameter for the callback (should be serializable).
|
// callbackArg - parameter for the callback (should be serializable).
|
||||||
void setupSerializableTimer(TimeUnit timeUnit, double time, const std::string& scriptPath,
|
void setupSerializableTimer(TimeUnit timeUnit, double time, int scriptId,
|
||||||
std::string_view callbackName, sol::object callbackArg);
|
std::string_view callbackName, sol::object callbackArg);
|
||||||
|
|
||||||
// Creates a timer. `callback` is an arbitrary Lua function. This type of timers is called "unsavable"
|
// Creates a timer. `callback` is an arbitrary Lua function. This type of timers is called "unsavable"
|
||||||
// because it can not be stored in saves. I.e. loading a saved game will not fully restore the state.
|
// because it can not be stored in saves. I.e. loading a saved game will not fully restore the state.
|
||||||
void setupUnsavableTimer(TimeUnit timeUnit, double time, const std::string& scriptPath, sol::function callback);
|
void setupUnsavableTimer(TimeUnit timeUnit, double time, int scriptId, sol::function callback);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
struct Handler
|
||||||
|
{
|
||||||
|
int mScriptId;
|
||||||
|
sol::function mFn;
|
||||||
|
};
|
||||||
|
|
||||||
struct EngineHandlerList
|
struct EngineHandlerList
|
||||||
{
|
{
|
||||||
std::string_view mName;
|
std::string_view mName;
|
||||||
std::vector<sol::protected_function> mList;
|
std::vector<Handler> mList;
|
||||||
|
|
||||||
// "name" must be string literal
|
// "name" must be string literal
|
||||||
explicit EngineHandlerList(std::string_view name) : mName(name) {}
|
explicit EngineHandlerList(std::string_view name) : mName(name) {}
|
||||||
|
@ -151,12 +164,13 @@ namespace LuaUtil
|
||||||
template <typename... Args>
|
template <typename... Args>
|
||||||
void callEngineHandlers(EngineHandlerList& handlers, const Args&... args)
|
void callEngineHandlers(EngineHandlerList& handlers, const Args&... args)
|
||||||
{
|
{
|
||||||
for (sol::protected_function& handler : handlers.mList)
|
for (Handler& handler : handlers.mList)
|
||||||
{
|
{
|
||||||
try { LuaUtil::call(handler, args...); }
|
try { LuaUtil::call(handler.mFn, args...); }
|
||||||
catch (std::exception& e)
|
catch (std::exception& e)
|
||||||
{
|
{
|
||||||
Log(Debug::Error) << mNamePrefix << " " << handlers.mName << " failed. " << e.what();
|
Log(Debug::Error) << mNamePrefix << "[" << scriptPath(handler.mScriptId) << "] "
|
||||||
|
<< handlers.mName << " failed. " << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,34 +185,49 @@ namespace LuaUtil
|
||||||
private:
|
private:
|
||||||
struct Script
|
struct Script
|
||||||
{
|
{
|
||||||
sol::object mInterface; // returned value of the script (sol::table or nil)
|
std::optional<sol::function> mOnSave;
|
||||||
|
std::optional<sol::function> mOnOverride;
|
||||||
|
std::optional<sol::table> mInterface;
|
||||||
|
std::string mInterfaceName;
|
||||||
sol::table mHiddenData;
|
sol::table mHiddenData;
|
||||||
};
|
};
|
||||||
struct Timer
|
struct Timer
|
||||||
{
|
{
|
||||||
double mTime;
|
double mTime;
|
||||||
bool mSerializable;
|
bool mSerializable;
|
||||||
std::string mScript;
|
int mScriptId;
|
||||||
std::variant<std::string, int64_t> mCallback; // string if serializable, integer otherwise
|
std::variant<std::string, int64_t> mCallback; // string if serializable, integer otherwise
|
||||||
sol::object mArg;
|
sol::object mArg;
|
||||||
std::string mSerializedArg;
|
std::string mSerializedArg;
|
||||||
|
|
||||||
bool operator<(const Timer& t) const { return mTime > t.mTime; }
|
bool operator<(const Timer& t) const { return mTime > t.mTime; }
|
||||||
};
|
};
|
||||||
using EventHandlerList = std::vector<sol::protected_function>;
|
using EventHandlerList = std::vector<Handler>;
|
||||||
|
|
||||||
void parseEngineHandlers(sol::table handlers, std::string_view scriptPath);
|
// Add to container without calling onInit/onLoad.
|
||||||
void parseEventHandlers(sol::table handlers, std::string_view scriptPath);
|
bool addScript(int scriptId, std::optional<sol::function>& onInit, std::optional<sol::function>& onLoad);
|
||||||
|
|
||||||
|
// Returns the hidden data of a script.
|
||||||
|
// Each script has a corresponding "hidden data" - a lua table that is not accessible from the script itself,
|
||||||
|
// but can be used by built-in packages. It contains ScriptId and can contain any arbitrary data.
|
||||||
|
sol::table getHiddenData(int scriptId);
|
||||||
|
|
||||||
|
void printError(int scriptId, std::string_view msg, const std::exception& e);
|
||||||
|
const std::string& scriptPath(int scriptId) const { return mLua.getConfiguration()[scriptId].mScriptPath; }
|
||||||
|
void callOnInit(int scriptId, const sol::function& onInit);
|
||||||
void callTimer(const Timer& t);
|
void callTimer(const Timer& t);
|
||||||
void updateTimerQueue(std::vector<Timer>& timerQueue, double time);
|
void updateTimerQueue(std::vector<Timer>& timerQueue, double time);
|
||||||
static void insertTimer(std::vector<Timer>& timerQueue, Timer&& t);
|
static void insertTimer(std::vector<Timer>& timerQueue, Timer&& t);
|
||||||
|
static void insertHandler(std::vector<Handler>& list, int scriptId, sol::function fn);
|
||||||
|
static void removeHandler(std::vector<Handler>& list, int scriptId);
|
||||||
|
void insertInterface(int scriptId, const Script& script);
|
||||||
|
void removeInterface(int scriptId, const Script& script);
|
||||||
|
|
||||||
|
ESM::LuaScriptCfg::Flags mAutoStartMode;
|
||||||
const UserdataSerializer* mSerializer = nullptr;
|
const UserdataSerializer* mSerializer = nullptr;
|
||||||
std::map<std::string, sol::object> API;
|
std::map<std::string, sol::object> mAPI;
|
||||||
|
|
||||||
std::vector<std::string> mScriptOrder;
|
std::map<int, Script> mScripts;
|
||||||
std::map<std::string, Script> mScripts;
|
|
||||||
sol::table mPublicInterfaces;
|
sol::table mPublicInterfaces;
|
||||||
|
|
||||||
EngineHandlerList mUpdateHandlers{"onUpdate"};
|
EngineHandlerList mUpdateHandlers{"onUpdate"};
|
||||||
|
|
Loading…
Reference in a new issue