diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 2da93de653..7794500edf 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -59,8 +59,8 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwlua - luamanagerimp object worldview userdataserializer luaevents objectvariant - luabindings localscripts playerscripts objectbindings cellbindings + luamanagerimp object worldview userdataserializer luaevents engineevents objectvariant + context globalscripts localscripts playerscripts luabindings objectbindings cellbindings camerabindings uibindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/actor types/container types/weapon types/npc types/creature types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing worker diff --git a/apps/openmw/mwlua/engineevents.cpp b/apps/openmw/mwlua/engineevents.cpp new file mode 100644 index 0000000000..f29aa79b9a --- /dev/null +++ b/apps/openmw/mwlua/engineevents.cpp @@ -0,0 +1,105 @@ +#include "engineevents.hpp" + +#include +#include + +#include "../mwbase/environment.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/worldmodel.hpp" + +#include "globalscripts.hpp" +#include "localscripts.hpp" +#include "object.hpp" + +namespace MWLua +{ + + class EngineEvents::Visitor + { + public: + explicit Visitor(GlobalScripts* globalScripts) + : mGlobalScripts(globalScripts) + { + } + + void operator()(const OnNewGame&) const { mGlobalScripts->newGameStarted(); } + + void operator()(const OnActive& event) const + { + MWWorld::Ptr ptr = getPtr(event.mObject); + if (ptr.isEmpty()) + return; + if (ptr.getCellRef().getRefId() == "player") + mGlobalScripts->playerAdded(GObject(ptr)); + else + { + mGlobalScripts->objectActive(GObject(ptr)); + const MWWorld::Class& objClass = ptr.getClass(); + if (objClass.isActor()) + mGlobalScripts->actorActive(GObject(ptr)); + if (objClass.isItem(ptr)) + mGlobalScripts->itemActive(GObject(ptr)); + } + if (auto* scripts = getLocalScripts(ptr)) + scripts->setActive(true); + } + + void operator()(const OnInactive& event) const + { + if (auto* scripts = getLocalScripts(event.mObject)) + scripts->setActive(false); + } + + void operator()(const OnActivate& event) const + { + MWWorld::Ptr obj = getPtr(event.mObject); + MWWorld::Ptr actor = getPtr(event.mActor); + if (actor.isEmpty() || obj.isEmpty()) + return; + if (auto* scripts = getLocalScripts(obj)) + scripts->onActivated(LObject(actor)); + } + + void operator()(const OnConsume& event) const + { + MWWorld::Ptr actor = getPtr(event.mActor); + MWWorld::Ptr consumable = getPtr(event.mConsumable); + if (actor.isEmpty() || consumable.isEmpty()) + return; + if (auto* scripts = getLocalScripts(actor)) + scripts->onConsume(LObject(consumable)); + } + + private: + MWWorld::Ptr getPtr(const ESM::RefNum& id) const + { + MWWorld::Ptr res = mWorldModel->getPtr(id); + if (res.isEmpty() && mLuaDebug) + Log(Debug::Verbose) << "Can not find object" << id.toString() << " when calling engine hanglers"; + return res; + } + + LocalScripts* getLocalScripts(const MWWorld::Ptr& ptr) const + { + if (ptr.isEmpty()) + return nullptr; + else + return ptr.getRefData().getLuaScripts(); + } + + LocalScripts* getLocalScripts(const ESM::RefNum& id) const { return getLocalScripts(getPtr(id)); } + + GlobalScripts* mGlobalScripts; + bool mLuaDebug = Settings::Manager::getBool("lua debug", "Lua"); + MWWorld::WorldModel* mWorldModel = MWBase::Environment::get().getWorldModel(); + }; + + void EngineEvents::callEngineHandlers() + { + Visitor vis(mGlobalScripts); + for (const Event& event : mQueue) + std::visit(vis, event); + mQueue.clear(); + } + +} diff --git a/apps/openmw/mwlua/engineevents.hpp b/apps/openmw/mwlua/engineevents.hpp new file mode 100644 index 0000000000..f6d21b509a --- /dev/null +++ b/apps/openmw/mwlua/engineevents.hpp @@ -0,0 +1,56 @@ +#ifndef MWLUA_ENGINEEVENTS_H +#define MWLUA_ENGINEEVENTS_H + +#include + +#include // defines RefNum that is used as a unique id + +namespace MWLua +{ + class GlobalScripts; + + class EngineEvents + { + public: + explicit EngineEvents(GlobalScripts* globalScripts) + : mGlobalScripts(globalScripts) + { + } + + struct OnNewGame + { + }; + struct OnActive + { + ESM::RefNum mObject; + }; + struct OnInactive + { + ESM::RefNum mObject; + }; + struct OnActivate + { + ESM::RefNum mActor; + ESM::RefNum mObject; + }; + struct OnConsume + { + ESM::RefNum mActor; + ESM::RefNum mConsumable; + }; + using Event = std::variant; + + void clear() { mQueue.clear(); } + void addToQueue(Event e) { mQueue.push_back(std::move(e)); } + void callEngineHandlers(); + + private: + class Visitor; + + GlobalScripts* mGlobalScripts; + std::vector mQueue; + }; + +} + +#endif // MWLUA_ENGINEEVENTS_H diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 1b9984fc66..fb4e5e2ade 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -206,32 +206,13 @@ namespace MWLua { &mOnActiveHandlers, &mOnInactiveHandlers, &mOnConsumeHandlers, &mOnActivatedHandlers }); } - void LocalScripts::receiveEngineEvent(const EngineEvent& event) + void LocalScripts::setActive(bool active) { - std::visit( - [this](auto&& arg) { - using EventT = std::decay_t; - if constexpr (std::is_same_v) - { - mData.mIsActive = true; - callEngineHandlers(mOnActiveHandlers); - } - else if constexpr (std::is_same_v) - { - mData.mIsActive = false; - callEngineHandlers(mOnInactiveHandlers); - } - else if constexpr (std::is_same_v) - { - callEngineHandlers(mOnActivatedHandlers, arg.mActivatingActor); - } - else - { - static_assert(std::is_same_v); - callEngineHandlers(mOnConsumeHandlers, arg.mConsumable); - } - }, - event); + mData.mIsActive = active; + if (active) + callEngineHandlers(mOnActiveHandlers); + else + callEngineHandlers(mOnInactiveHandlers); } void LocalScripts::applyStatsCache() diff --git a/apps/openmw/mwlua/localscripts.hpp b/apps/openmw/mwlua/localscripts.hpp index bad7300849..aa22ec0424 100644 --- a/apps/openmw/mwlua/localscripts.hpp +++ b/apps/openmw/mwlua/localscripts.hpp @@ -23,11 +23,6 @@ namespace MWLua public: using Setter = void (*)(int, std::string_view, const MWWorld::Ptr&, const sol::object&); - private: - Setter mSetter; // Function that updates a stat's property - int mIndex; // Optional index to disambiguate the stat - std::string_view mProp; // Name of the stat's property - public: CachedStat(Setter setter, int index, std::string_view prop) : mSetter(setter) , mIndex(index) @@ -44,6 +39,11 @@ namespace MWLua { return std::tie(mSetter, mIndex, mProp) < std::tie(other.mSetter, other.mIndex, other.mProp); } + + private: + Setter mSetter; // Function that updates a stat's property + int mIndex; // Optional index to disambiguate the stat + std::string_view mProp; // Name of the stat's property }; SelfObject(const LObject& obj) @@ -65,23 +65,9 @@ namespace MWLua MWBase::LuaManager::ActorControls* getActorControls() { return &mData.mControls; } const MWWorld::Ptr& getPtr() const { return mData.ptr(); } - struct OnActive - { - }; - struct OnInactive - { - }; - struct OnActivated - { - LObject mActivatingActor; - }; - struct OnConsume - { - LObject mConsumable; - }; - using EngineEvent = std::variant; - - void receiveEngineEvent(const EngineEvent&); + void setActive(bool active); + void onConsume(const LObject& consumable) { callEngineHandlers(mOnConsumeHandlers, consumable); } + void onActivated(const LObject& actor) { callEngineHandlers(mOnActivatedHandlers, actor); } void applyStatsCache(); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 2b3c900959..43dd3fbfc1 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -136,7 +136,6 @@ namespace MWLua void LuaManager::update() { - static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); static const int gcStepCount = Settings::Manager::getInt("gc steps per frame", "Lua"); if (gcStepCount > 0) lua_gc(mLua.sol(), LUA_GCSTEP, gcStepCount); @@ -177,6 +176,7 @@ namespace MWLua scripts->processTimers(simulationTime, gameTime); } + // Run event handlers for events that were sent before `finalizeEventBatch`. mLuaEvents.callEventHandlers(); // Run queued callbacks @@ -184,63 +184,14 @@ namespace MWLua c.mCallback.tryCall(c.mArg); mQueuedCallbacks.clear(); - // Engine handlers in local scripts - for (const LocalEngineEvent& e : mLocalEngineEvents) - { - LObject obj(e.mDest); - const MWWorld::Ptr& ptr = obj.ptrOrNull(); - if (ptr.isEmpty()) - { - if (luaDebug) - Log(Debug::Verbose) << "Can not call engine handlers: object" << e.mDest.toString() - << " is not found"; - continue; - } - LocalScripts* scripts = ptr.getRefData().getLuaScripts(); - if (scripts) - scripts->receiveEngineEvent(e.mEvent); - } - mLocalEngineEvents.clear(); - + // Run engine handlers + mEngineEvents.callEngineHandlers(); if (!mWorldView.isPaused()) { for (LocalScripts* scripts : mActiveLocalScripts) scripts->update(frameDuration); - } - - // Engine handlers in global scripts - if (mPlayerChanged) - { - mPlayerChanged = false; - mGlobalScripts.playerAdded(GObject(getId(mPlayer))); - } - if (mNewGameStarted) - { - mNewGameStarted = false; - mGlobalScripts.newGameStarted(); - } - - for (ObjectId id : mObjectAddedEvents) - { - GObject obj(id); - const MWWorld::Ptr& ptr = obj.ptrOrNull(); - if (!ptr.isEmpty()) - { - mGlobalScripts.objectActive(obj); - const MWWorld::Class& objClass = ptr.getClass(); - if (objClass.isActor()) - mGlobalScripts.actorActive(obj); - if (objClass.isItem(ptr)) - mGlobalScripts.itemActive(obj); - } - else if (luaDebug) - Log(Debug::Verbose) << "Could not resolve a Lua object added event: object" << id.toString() - << " is already removed"; - } - mObjectAddedEvents.clear(); - - if (!mWorldView.isPaused()) mGlobalScripts.update(frameDuration); + } } void LuaManager::synchronizedUpdate() @@ -286,11 +237,8 @@ namespace MWLua MWBase::Environment::get().getWorld()->getPostProcessor()->disableDynamicShaders(); mActiveLocalScripts.clear(); mLuaEvents.clear(); + mEngineEvents.clear(); mInputEvents.clear(); - mObjectAddedEvents.clear(); - mLocalEngineEvents.clear(); - mNewGameStarted = false; - mPlayerChanged = false; mWorldView.clear(); mGlobalScripts.removeAllScripts(); mGlobalScriptsStarted = false; @@ -321,16 +269,15 @@ namespace MWLua localScripts->addAutoStartedScripts(); } mActiveLocalScripts.insert(localScripts); - mLocalEngineEvents.push_back({ getId(ptr), LocalScripts::OnActive{} }); - mPlayerChanged = true; + mEngineEvents.addToQueue(EngineEvents::OnActive{ getId(ptr) }); } void LuaManager::newGameStarted() { - mNewGameStarted = true; mInputEvents.clear(); mGlobalScripts.addAutoStartedScripts(); mGlobalScriptsStarted = true; + mEngineEvents.addToQueue(EngineEvents::OnNewGame{}); } void LuaManager::gameLoaded() @@ -343,26 +290,16 @@ namespace MWLua void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mWorldView.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. + mEngineEvents.addToQueue(EngineEvents::OnActive{ getId(ptr) }); - LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); - if (!localScripts) + if (!ptr.getRefData().getLuaScripts()) { LuaUtil::ScriptIdsWithInitializationData autoStartConf = mConfiguration.getLocalConf(getLiveCellRefType(ptr.mRef), ptr.getCellRef().getRefId(), getId(ptr)); + // TODO: put to a queue and apply `addAutoStartedScripts` on next `update()` if (!autoStartConf.empty()) - { - localScripts = createLocalScripts(ptr, std::move(autoStartConf)); - localScripts->addAutoStartedScripts(); // TODO: put to a queue and apply on next `update()` - } + createLocalScripts(ptr, std::move(autoStartConf))->addAutoStartedScripts(); } - if (localScripts) - { - mActiveLocalScripts.insert(localScripts); - mLocalEngineEvents.push_back({ getId(ptr), LocalScripts::OnActive{} }); - } - - if (ptr != mPlayer) - mObjectAddedEvents.push_back(getId(ptr)); } void LuaManager::objectRemovedFromScene(const MWWorld::Ptr& ptr) @@ -373,21 +310,10 @@ namespace MWLua { mActiveLocalScripts.erase(localScripts); if (!MWBase::Environment::get().getWorldModel()->getPtr(getId(ptr)).isEmpty()) - mLocalEngineEvents.push_back({ getId(ptr), LocalScripts::OnInactive{} }); + mEngineEvents.addToQueue(EngineEvents::OnInactive{ getId(ptr) }); } } - void LuaManager::itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) - { - MWBase::Environment::get().getWorldModel()->registerPtr(consumable); - mLocalEngineEvents.push_back({ getId(actor), LocalScripts::OnConsume{ LObject(getId(consumable)) } }); - } - - void LuaManager::objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) - { - mLocalEngineEvents.push_back({ getId(object), LocalScripts::OnActivated{ LObject(getId(actor)) } }); - } - MWBase::LuaManager::ActorControls* LuaManager::getActorControls(const MWWorld::Ptr& ptr) const { LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); @@ -529,7 +455,7 @@ namespace MWLua scripts->load(data); } for (LocalScripts* scripts : mActiveLocalScripts) - scripts->receiveEngineEvent(LocalScripts::OnActive()); + scripts->setActive(true); } void LuaManager::handleConsoleCommand( diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index 3adf97ab6f..717dfe4af9 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -14,6 +14,7 @@ #include "../mwbase/luamanager.hpp" +#include "engineevents.hpp" #include "globalscripts.hpp" #include "localscripts.hpp" #include "luaevents.hpp" @@ -64,8 +65,14 @@ namespace MWLua void objectAddedToScene(const MWWorld::Ptr& ptr) override; void objectRemovedFromScene(const MWWorld::Ptr& ptr) override; void inputEvent(const InputEvent& event) override { mInputEvents.push_back(event); } - void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) override; - void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override; + void itemConsumed(const MWWorld::Ptr& consumable, const MWWorld::Ptr& actor) override + { + mEngineEvents.addToQueue(EngineEvents::OnConsume{ getId(actor), getId(consumable) }); + } + void objectActivated(const MWWorld::Ptr& object, const MWWorld::Ptr& actor) override + { + mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); + } MWBase::LuaManager::ActorControls* getActorControls(const MWWorld::Ptr&) const override; @@ -163,11 +170,11 @@ namespace MWLua std::set mActiveLocalScripts; WorldView mWorldView; - bool mPlayerChanged = false; - bool mNewGameStarted = false; MWWorld::Ptr mPlayer; LuaEvents mLuaEvents{ &mGlobalScripts }; + EngineEvents mEngineEvents{ &mGlobalScripts }; + std::vector mInputEvents; std::unique_ptr mGlobalSerializer; std::unique_ptr mLocalSerializer; @@ -176,9 +183,6 @@ namespace MWLua std::unique_ptr mGlobalLoader; std::unique_ptr mLocalLoader; - std::vector mInputEvents; - std::vector mObjectAddedEvents; - struct CallbackWithData { LuaUtil::Callback mCallback; @@ -186,13 +190,6 @@ namespace MWLua }; std::vector mQueuedCallbacks; - struct LocalEngineEvent - { - ObjectId mDest; - LocalScripts::EngineEvent mEvent; - }; - std::vector mLocalEngineEvents; - // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). std::vector> mActionQueue; std::unique_ptr mTeleportPlayerAction; diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp index 121def13bd..a0826474c5 100644 --- a/apps/openmw/mwworld/ptr.cpp +++ b/apps/openmw/mwworld/ptr.cpp @@ -21,6 +21,13 @@ namespace MWWorld return res; } + SafePtr::SafePtr(const Ptr& ptr) + : mId(ptr.getCellRef().getRefNum()) + , mPtr(ptr) + , mLastUpdate(MWBase::Environment::get().getWorldModel()->getPtrIndexUpdateCounter()) + { + } + std::string SafePtr::toString() const { update(); diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 4299130aa8..209d7d9256 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -159,10 +159,7 @@ namespace MWWorld : mId(id) { } - explicit SafePtr(const Ptr& ptr) - : SafePtr(ptr.getCellRef().getRefNum()) - { - } + explicit SafePtr(const Ptr& ptr); virtual ~SafePtr() = default; Id id() const { return mId; }