From a65f8ebbc611667e71b10d10d1fcafc9d6151599 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Mon, 11 Apr 2022 01:04:55 +0200 Subject: [PATCH] Reorganize delayed Lua actions --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/actions.cpp | 180 --------------------------- apps/openmw/mwlua/actions.hpp | 94 -------------- apps/openmw/mwlua/luamanagerimp.cpp | 46 +++++++ apps/openmw/mwlua/luamanagerimp.hpp | 26 +++- apps/openmw/mwlua/objectbindings.cpp | 80 ++++++++++++ apps/openmw/mwlua/stats.cpp | 20 +++ apps/openmw/mwlua/types/actor.cpp | 97 ++++++++++++++- apps/openmw/mwlua/uibindings.cpp | 5 +- 9 files changed, 267 insertions(+), 283 deletions(-) delete mode 100644 apps/openmw/mwlua/actions.cpp delete mode 100644 apps/openmw/mwlua/actions.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3d6469fa33..f67c991958 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -57,7 +57,7 @@ add_openmw_dir (mwscript ) add_openmw_dir (mwlua - luamanagerimp actions object worldview userdataserializer eventqueue + luamanagerimp object worldview userdataserializer eventqueue luabindings localscripts playerscripts objectbindings cellbindings asyncbindings settingsbindings camerabindings uibindings inputbindings nearbybindings stats types/types types/door types/actor types/container types/weapon types/npc diff --git a/apps/openmw/mwlua/actions.cpp b/apps/openmw/mwlua/actions.cpp deleted file mode 100644 index 0934b92226..0000000000 --- a/apps/openmw/mwlua/actions.cpp +++ /dev/null @@ -1,180 +0,0 @@ -#include "actions.hpp" - -#include "localscripts.hpp" - -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -namespace MWLua -{ - Action::Action(LuaUtil::LuaState* state) - { - static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); - if (luaDebug) - mCallerTraceback = state->debugTraceback(); - } - - void Action::safeApply(WorldView& w) const - { - try - { - apply(w); - } - catch (const std::exception& e) - { - Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); - - if (mCallerTraceback.empty()) - Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; - else - Log(Debug::Error) << "Caller " << mCallerTraceback; - } - } - - void TeleportAction::apply(WorldView& worldView) const - { - MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); - if (!cell) - throw std::runtime_error(std::string("cell not found: '") + mCell + "'"); - - MWBase::World* world = MWBase::Environment::get().getWorld(); - MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); - const MWWorld::Class& cls = obj.getClass(); - bool isPlayer = obj == world->getPlayerPtr(); - if (cls.isActor()) - cls.getCreatureStats(obj).land(isPlayer); - if (isPlayer) - { - ESM::Position esmPos; - static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); - std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); - std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); - world->getPlayer().setTeleported(true); - if (cell->isExterior()) - world->changeToExteriorCell(esmPos, true); - else - world->changeToInteriorCell(mCell, esmPos, true); - } - else - { - MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); - world->rotateObject(newObj, mRot); - } - } - - void SetEquipmentAction::apply(WorldView& worldView) const - { - MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false); - MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); - std::array usedSlots; - std::fill(usedSlots.begin(), usedSlots.end(), false); - - static constexpr int anySlot = -1; - auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool - { - auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); - MWWorld::Ptr itemPtr; - if (std::holds_alternative(item)) - { - itemPtr = worldView.getObjectRegistry()->getPtr(std::get(item), false); - if (old_it != store.end() && *old_it == itemPtr) - return true; // already equipped - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 || - itemPtr.getContainerStore() != static_cast(&store)) - { - Log(Debug::Warning) << "Object" << idToString(std::get(item)) << " is not in inventory"; - return false; - } - } - else - { - const std::string& recordId = std::get(item); - if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) - return true; // already equipped - itemPtr = store.search(recordId); - if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) - { - Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; - return false; - } - } - - auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); - bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end(); - if (!requestedSlotIsAllowed) - { - auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); - if (firstAllowed == allowedSlots.end()) - { - Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); - return false; - } - slot = *firstAllowed; - } - - // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. - MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); - if (it == store.end()) // should never happen - throw std::logic_error("Item not found in container"); - - store.equip(slot, it, actor); - return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed - }; - - for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) - { - auto old_it = store.getSlot(slot); - auto new_it = mEquipment.find(slot); - if (new_it == mEquipment.end()) - { - if (old_it != store.end()) - store.unequipSlot(slot, actor); - continue; - } - if (tryEquipToSlot(slot, new_it->second)) - usedSlots[slot] = true; - } - for (const auto& [slot, item] : mEquipment) - if (slot >= MWWorld::InventoryStore::Slots) - tryEquipToSlot(anySlot, item); - } - - void ActivateAction::apply(WorldView& worldView) const - { - MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true); - if (object.isEmpty()) - throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); - MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true); - if (actor.isEmpty()) - throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); - - MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); - std::unique_ptr action = object.getClass().activate(object, actor); - action->execute(actor); - } - - std::string ActivateAction::toString() const - { - return std::string("ActivateAction object=") + idToString(mObject) + - std::string(" actor=") + idToString(mActor); - } - - void StatUpdateAction::apply(WorldView& worldView) const - { - LObject obj(mId, worldView.getObjectRegistry()); - LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); - if (scripts) - scripts->applyStatsCache(); - } -} diff --git a/apps/openmw/mwlua/actions.hpp b/apps/openmw/mwlua/actions.hpp deleted file mode 100644 index 30211b6e53..0000000000 --- a/apps/openmw/mwlua/actions.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef MWLUA_ACTIONS_H -#define MWLUA_ACTIONS_H - -#include - -#include "object.hpp" -#include "worldview.hpp" - -namespace LuaUtil -{ - class LuaState; -} - -namespace MWLua -{ - - // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), - // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited - // from MWLua::Action. - - class Action - { - public: - Action(LuaUtil::LuaState* state); - virtual ~Action() {} - - void safeApply(WorldView&) const; - virtual void apply(WorldView&) const = 0; - virtual std::string toString() const = 0; - - private: - std::string mCallerTraceback; - }; - - class TeleportAction final : public Action - { - public: - TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) - : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} - - void apply(WorldView&) const override; - std::string toString() const override { return "TeleportAction"; } - - private: - ObjectId mObject; - std::string mCell; - osg::Vec3f mPos; - osg::Vec3f mRot; - }; - - class SetEquipmentAction final : public Action - { - public: - using Item = std::variant; // recordId or ObjectId - using Equipment = std::map; // slot to item - - SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment) - : Action(state), mActor(actor), mEquipment(std::move(equipment)) {} - - void apply(WorldView&) const override; - std::string toString() const override { return "SetEquipmentAction"; } - - private: - ObjectId mActor; - Equipment mEquipment; - }; - - class ActivateAction final : public Action - { - public: - ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) - : Action(state), mObject(object), mActor(actor) {} - - void apply(WorldView&) const override; - std::string toString() const override; - - private: - ObjectId mObject; - ObjectId mActor; - }; - - class StatUpdateAction final : public Action - { - ObjectId mId; - public: - StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} - - void apply(WorldView& worldView) const override; - - std::string toString() const override { return "StatUpdateAction"; } - }; -} - -#endif // MWLUA_ACTIONS_H diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index e2037049d8..692b089bdb 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -501,4 +501,50 @@ namespace MWLua scripts->receiveEngineEvent(LocalScripts::OnActive()); } + LuaManager::Action::Action(LuaUtil::LuaState* state) + { + static const bool luaDebug = Settings::Manager::getBool("lua debug", "Lua"); + if (luaDebug) + mCallerTraceback = state->debugTraceback(); + } + + void LuaManager::Action::safeApply(WorldView& w) const + { + try + { + apply(w); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Error in " << this->toString() << ": " << e.what(); + + if (mCallerTraceback.empty()) + Log(Debug::Error) << "Set 'lua_debug=true' in settings.cfg to enable action tracebacks"; + else + Log(Debug::Error) << "Caller " << mCallerTraceback; + } + } + + namespace + { + class FunctionAction final : public LuaManager::Action + { + public: + FunctionAction(LuaUtil::LuaState* state, std::function fn, std::string_view name) + : Action(state), mFn(std::move(fn)), mName(name) {} + + void apply(WorldView&) const override { mFn(); } + std::string toString() const override { return "FunctionAction " + mName; } + + private: + std::function mFn; + std::string mName; + }; + } + + void LuaManager::addAction(std::function action, std::string_view name) + { + mActionQueue.push_back(std::make_unique(&mLua, std::move(action), name)); + } + } diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index e6f5af78be..43f5d9c3b6 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -12,7 +12,6 @@ #include "../mwbase/luamanager.hpp" -#include "actions.hpp" #include "object.hpp" #include "eventqueue.hpp" #include "globalscripts.hpp" @@ -60,9 +59,28 @@ namespace MWLua // Used only in Lua bindings void addCustomLocalScript(const MWWorld::Ptr&, int scriptId); - void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } - void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } void addUIMessage(std::string_view message) { mUIMessages.emplace_back(message); } + + // Some changes to the game world can not be done from the scripting thread (because it runs in parallel with OSG Cull), + // so we need to queue it and apply from the main thread. All such changes should be implemented as classes inherited + // from MWLua::Action. + class Action + { + public: + Action(LuaUtil::LuaState* state); + virtual ~Action() {} + + void safeApply(WorldView&) const; + virtual void apply(WorldView&) const = 0; + virtual std::string toString() const = 0; + + private: + std::string mCallerTraceback; + }; + + void addAction(std::function action, std::string_view name = ""); + void addAction(std::unique_ptr&& action) { mActionQueue.push_back(std::move(action)); } + void addTeleportPlayerAction(std::unique_ptr&& action) { mTeleportPlayerAction = std::move(action); } // Saving void write(ESM::ESMWriter& writer, Loading::Listener& progress) override; @@ -149,7 +167,7 @@ namespace MWLua // Queued actions that should be done in main thread. Processed by applyQueuedChanges(). std::vector> mActionQueue; - std::unique_ptr mTeleportPlayerAction; + std::unique_ptr mTeleportPlayerAction; std::vector mUIMessages; LuaUtil::LuaStorage mGlobalStorage{mLua.sol()}; diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 08435583df..0bf3fc99c5 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -2,9 +2,12 @@ #include +#include "../mwworld/action.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/inventorystore.hpp" +#include "../mwworld/player.hpp" #include "eventqueue.hpp" #include "luamanagerimp.hpp" @@ -31,6 +34,83 @@ namespace MWLua namespace { + class TeleportAction final : public LuaManager::Action + { + public: + TeleportAction(LuaUtil::LuaState* state, ObjectId object, std::string cell, const osg::Vec3f& pos, const osg::Vec3f& rot) + : Action(state), mObject(object), mCell(std::move(cell)), mPos(pos), mRot(rot) {} + + void apply(WorldView& worldView) const override + { + MWWorld::CellStore* cell = worldView.findCell(mCell, mPos); + if (!cell) + throw std::runtime_error(std::string("cell not found: '") + mCell + "'"); + + MWBase::World* world = MWBase::Environment::get().getWorld(); + MWWorld::Ptr obj = worldView.getObjectRegistry()->getPtr(mObject, false); + const MWWorld::Class& cls = obj.getClass(); + bool isPlayer = obj == world->getPlayerPtr(); + if (cls.isActor()) + cls.getCreatureStats(obj).land(isPlayer); + if (isPlayer) + { + ESM::Position esmPos; + static_assert(sizeof(esmPos) == sizeof(osg::Vec3f) * 2); + std::memcpy(esmPos.pos, &mPos, sizeof(osg::Vec3f)); + std::memcpy(esmPos.rot, &mRot, sizeof(osg::Vec3f)); + world->getPlayer().setTeleported(true); + if (cell->isExterior()) + world->changeToExteriorCell(esmPos, true); + else + world->changeToInteriorCell(mCell, esmPos, true); + } + else + { + MWWorld::Ptr newObj = world->moveObject(obj, cell, mPos); + world->rotateObject(newObj, mRot); + } + } + + std::string toString() const override { return "TeleportAction"; } + + private: + ObjectId mObject; + std::string mCell; + osg::Vec3f mPos; + osg::Vec3f mRot; + }; + + class ActivateAction final : public LuaManager::Action + { + public: + ActivateAction(LuaUtil::LuaState* state, ObjectId object, ObjectId actor) + : Action(state), mObject(object), mActor(actor) {} + + void apply(WorldView& worldView) const override + { + MWWorld::Ptr object = worldView.getObjectRegistry()->getPtr(mObject, true); + if (object.isEmpty()) + throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); + MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, true); + if (actor.isEmpty()) + throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); + + MWBase::Environment::get().getLuaManager()->objectActivated(object, actor); + std::unique_ptr action = object.getClass().activate(object, actor); + action->execute(actor); + } + + std::string toString() const override + { + return std::string("ActivateAction object=") + idToString(mObject) + + std::string(" actor=") + idToString(mActor); + } + + private: + ObjectId mObject; + ObjectId mActor; + }; + template using Cell = std::conditional_t, LCell, GCell>; diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 36d7cf6538..f81293513d 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -79,6 +79,26 @@ namespace namespace MWLua { + namespace + { + class StatUpdateAction final : public LuaManager::Action + { + ObjectId mId; + public: + StatUpdateAction(LuaUtil::LuaState* state, ObjectId id) : Action(state), mId(id) {} + + void apply(WorldView& worldView) const override + { + LObject obj(mId, worldView.getObjectRegistry()); + LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + if (scripts) + scripts->applyStatsCache(); + } + + std::string toString() const override { return "StatUpdateAction"; } + }; + } + class LevelStat { StatObject mObject; diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 335ba517d5..98ff5148d5 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -6,7 +6,6 @@ #include #include -#include "../actions.hpp" #include "../luabindings.hpp" #include "../localscripts.hpp" #include "../luamanagerimp.hpp" @@ -14,6 +13,102 @@ namespace MWLua { + namespace + { + class SetEquipmentAction final : public LuaManager::Action + { + public: + using Item = std::variant; // recordId or ObjectId + using Equipment = std::map; // slot to item + + SetEquipmentAction(LuaUtil::LuaState* state, ObjectId actor, Equipment equipment) + : Action(state), mActor(actor), mEquipment(std::move(equipment)) {} + + void apply(WorldView& worldView) const override + { + MWWorld::Ptr actor = worldView.getObjectRegistry()->getPtr(mActor, false); + MWWorld::InventoryStore& store = actor.getClass().getInventoryStore(actor); + std::array usedSlots; + std::fill(usedSlots.begin(), usedSlots.end(), false); + + static constexpr int anySlot = -1; + auto tryEquipToSlot = [&actor, &store, &usedSlots, &worldView](int slot, const Item& item) -> bool + { + auto old_it = slot != anySlot ? store.getSlot(slot) : store.end(); + MWWorld::Ptr itemPtr; + if (std::holds_alternative(item)) + { + itemPtr = worldView.getObjectRegistry()->getPtr(std::get(item), false); + if (old_it != store.end() && *old_it == itemPtr) + return true; // already equipped + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0 || + itemPtr.getContainerStore() != static_cast(&store)) + { + Log(Debug::Warning) << "Object" << idToString(std::get(item)) << " is not in inventory"; + return false; + } + } + else + { + const std::string& recordId = std::get(item); + if (old_it != store.end() && old_it->getCellRef().getRefId() == recordId) + return true; // already equipped + itemPtr = store.search(recordId); + if (itemPtr.isEmpty() || itemPtr.getRefData().getCount() == 0) + { + Log(Debug::Warning) << "There is no object with recordId='" << recordId << "' in inventory"; + return false; + } + } + + auto [allowedSlots, _] = itemPtr.getClass().getEquipmentSlots(itemPtr); + bool requestedSlotIsAllowed = std::find(allowedSlots.begin(), allowedSlots.end(), slot) != allowedSlots.end(); + if (!requestedSlotIsAllowed) + { + auto firstAllowed = std::find_if(allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); + if (firstAllowed == allowedSlots.end()) + { + Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); + return false; + } + slot = *firstAllowed; + } + + // TODO: Refactor InventoryStore to accept Ptr and get rid of this linear search. + MWWorld::ContainerStoreIterator it = std::find(store.begin(), store.end(), itemPtr); + if (it == store.end()) // should never happen + throw std::logic_error("Item not found in container"); + + store.equip(slot, it, actor); + return requestedSlotIsAllowed; // return true if equipped to requested slot and false if slot was changed + }; + + for (int slot = 0; slot < MWWorld::InventoryStore::Slots; ++slot) + { + auto old_it = store.getSlot(slot); + auto new_it = mEquipment.find(slot); + if (new_it == mEquipment.end()) + { + if (old_it != store.end()) + store.unequipSlot(slot, actor); + continue; + } + if (tryEquipToSlot(slot, new_it->second)) + usedSlots[slot] = true; + } + for (const auto& [slot, item] : mEquipment) + if (slot >= MWWorld::InventoryStore::Slots) + tryEquipToSlot(anySlot, item); + } + + std::string toString() const override { return "SetEquipmentAction"; } + + private: + ObjectId mActor; + Equipment mEquipment; + }; + } + using SelfObject = LocalScripts::SelfObject; void addActorBindings(sol::table actor, const Context& context) diff --git a/apps/openmw/mwlua/uibindings.cpp b/apps/openmw/mwlua/uibindings.cpp index fdad57b13d..f82dd3db00 100644 --- a/apps/openmw/mwlua/uibindings.cpp +++ b/apps/openmw/mwlua/uibindings.cpp @@ -10,14 +10,13 @@ #include #include "context.hpp" -#include "actions.hpp" #include "luamanagerimp.hpp" namespace MWLua { namespace { - class UiAction final : public Action + class UiAction final : public LuaManager::Action { public: enum Type @@ -81,7 +80,7 @@ namespace MWLua std::shared_ptr mElement; }; - class InsertLayerAction final : public Action + class InsertLayerAction final : public LuaManager::Action { public: InsertLayerAction(std::string_view name, size_t index,