diff --git a/apps/openmw/mwlua/localscripts.cpp b/apps/openmw/mwlua/localscripts.cpp index 883afb5328..e3e46d453f 100644 --- a/apps/openmw/mwlua/localscripts.cpp +++ b/apps/openmw/mwlua/localscripts.cpp @@ -198,7 +198,7 @@ namespace MWLua } LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj) - : LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())) + : LuaUtil::ScriptsContainer(lua, "L" + obj.id().toString()) , mData(obj) { this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 9bbd63ac25..6ab9a27581 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -187,11 +187,12 @@ namespace MWLua for (LocalEvent& e : localEvents) { LObject obj(e.mDest); - LocalScripts* scripts = obj.isValid() ? obj.ptr().getRefData().getLuaScripts() : nullptr; + const MWWorld::Ptr& ptr = obj.ptrOrNull(); + LocalScripts* scripts = ptr.isEmpty() ? nullptr : ptr.getRefData().getLuaScripts(); if (scripts) scripts->receiveEvent(e.mEventName, e.mEventData); else - Log(Debug::Debug) << "Ignored event " << e.mEventName << " to L" << idToString(e.mDest) + Log(Debug::Debug) << "Ignored event " << e.mEventName << " to L" << e.mDest.toString() << ". Object not found or has no attached scripts"; } @@ -204,14 +205,15 @@ namespace MWLua for (const LocalEngineEvent& e : mLocalEngineEvents) { LObject obj(e.mDest); - if (!obj.isValid()) + const MWWorld::Ptr& ptr = obj.ptrOrNull(); + if (ptr.isEmpty()) { if (luaDebug) - Log(Debug::Verbose) << "Can not call engine handlers: object" << idToString(e.mDest) + Log(Debug::Verbose) << "Can not call engine handlers: object" << e.mDest.toString() << " is not found"; continue; } - LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); + LocalScripts* scripts = ptr.getRefData().getLuaScripts(); if (scripts) scripts->receiveEngineEvent(e.mEvent); } @@ -238,17 +240,18 @@ namespace MWLua for (ObjectId id : mObjectAddedEvents) { GObject obj(id); - if (obj.isValid()) + const MWWorld::Ptr& ptr = obj.ptrOrNull(); + if (!ptr.isEmpty()) { mGlobalScripts.objectActive(obj); - const MWWorld::Class& objClass = obj.ptr().getClass(); + const MWWorld::Class& objClass = ptr.getClass(); if (objClass.isActor()) mGlobalScripts.actorActive(obj); - if (mWorldView.isItem(obj.ptr())) + if (mWorldView.isItem(ptr)) mGlobalScripts.itemActive(obj); } else if (luaDebug) - Log(Debug::Verbose) << "Could not resolve a Lua object added event: object" << idToString(id) + Log(Debug::Verbose) << "Could not resolve a Lua object added event: object" << id.toString() << " is already removed"; } mObjectAddedEvents.clear(); @@ -675,7 +678,7 @@ namespace MWLua selectedScripts = selectedPtr.getRefData().getLuaScripts(); if (selectedScripts) selectedScripts->collectStats(selectedStats); - out << "Profiled object (selected in the in-game console): " << ptrToString(selectedPtr) << "\n"; + out << "Profiled object (selected in the in-game console): " << selectedPtr.toString() << "\n"; } else out << "No selected object. Use the in-game console to select an object for detailed profile.\n"; diff --git a/apps/openmw/mwlua/object.cpp b/apps/openmw/mwlua/object.cpp deleted file mode 100644 index ed1a9dd9ec..0000000000 --- a/apps/openmw/mwlua/object.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "object.hpp" - -#include "types/types.hpp" - -#include - -namespace MWLua -{ - - std::string idToString(const ObjectId& id) - { - return std::to_string(id.mIndex) + "_" + std::to_string(id.mContentFile); - } - - bool isMarker(const MWWorld::Ptr& ptr) - { - return Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId()); - } - - std::string ptrToString(const MWWorld::Ptr& ptr) - { - std::string res = "object"; - if (ptr.getRefData().isDeleted()) - res = "deleted object"; - res.append(idToString(getId(ptr))); - res.append(" ("); - res.append(getLuaObjectTypeName(ptr)); - res.append(", "); - res.append(ptr.getCellRef().getRefId().getRefIdString()); - res.append(")"); - return res; - } - - std::string Object::toString() const - { - if (isValid()) - return ptrToString(ptr()); - else - return "object" + idToString(mId) + " (not found)"; - } - - bool Object::isValid() const - { - MWWorld::WorldModel& w = *MWBase::Environment::get().getWorldModel(); - if (mLastUpdate < w.getPtrIndexUpdateCounter()) - { - mPtr = w.getPtr(mId); - mLastUpdate = w.getPtrIndexUpdateCounter(); - } - return !mPtr.isEmpty(); - } - - const MWWorld::Ptr& Object::ptr() const - { - if (!isValid()) - throw std::runtime_error("Object is not available: " + idToString(mId)); - return mPtr; - } -} diff --git a/apps/openmw/mwlua/object.hpp b/apps/openmw/mwlua/object.hpp index 9c720c67ce..9f4075582a 100644 --- a/apps/openmw/mwlua/object.hpp +++ b/apps/openmw/mwlua/object.hpp @@ -21,39 +21,24 @@ namespace MWLua { return ptr.getCellRef().getRefNum(); } - std::string idToString(const ObjectId& id); - std::string ptrToString(const MWWorld::Ptr& ptr); - bool isMarker(const MWWorld::Ptr& ptr); // Lua scripts can't use MWWorld::Ptr directly, because lifetime of a script can be longer than lifetime of Ptr. // `GObject` and `LObject` are intended to be passed to Lua as a userdata. // It automatically updates the underlying Ptr when needed. - class Object + class Object : public MWWorld::SafePtr { public: - Object(ObjectId id) - : mId(id) - { - } - virtual ~Object() {} - ObjectId id() const { return mId; } - - std::string toString() const; - - // Updates and returns the underlying Ptr. Throws an exception if object is not available. - const MWWorld::Ptr& ptr() const; - - // Returns `true` if calling `ptr()` is safe. - bool isValid() const; - - virtual sol::object getObject(lua_State* lua, ObjectId id) const = 0; // returns LObject or GOBject + using SafePtr::SafePtr; + virtual sol::object getObject(lua_State* lua, ObjectId id) const = 0; // returns LObject or GObject virtual sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const = 0; // returns LCell or GCell - protected: - const ObjectId mId; - - mutable MWWorld::Ptr mPtr; - mutable size_t mLastUpdate = 0; + const MWWorld::Ptr& ptr() const + { + const MWWorld::Ptr& res = ptrOrNull(); + if (res.isEmpty()) + throw std::runtime_error("Object is not available: " + id().toString()); + return res; + } }; // Used only in local scripts @@ -100,7 +85,6 @@ namespace MWLua { Obj mObj; }; - } #endif // MWLUA_OBJECT_H diff --git a/apps/openmw/mwlua/objectbindings.cpp b/apps/openmw/mwlua/objectbindings.cpp index 5b67b9f718..f92906f54e 100644 --- a/apps/openmw/mwlua/objectbindings.cpp +++ b/apps/openmw/mwlua/objectbindings.cpp @@ -116,10 +116,10 @@ namespace MWLua { MWWorld::Ptr object = MWBase::Environment::get().getWorldModel()->getPtr(mObject); if (object.isEmpty()) - throw std::runtime_error(std::string("Object not found: " + idToString(mObject))); + throw std::runtime_error(std::string("Object not found: " + mObject.toString())); MWWorld::Ptr actor = MWBase::Environment::get().getWorldModel()->getPtr(mActor); if (actor.isEmpty()) - throw std::runtime_error(std::string("Actor not found: " + idToString(mActor))); + throw std::runtime_error(std::string("Actor not found: " + mActor.toString())); if (object.getRefData().activate()) { @@ -131,8 +131,8 @@ namespace MWLua std::string toString() const override { - return std::string("ActivateAction object=") + idToString(mObject) + std::string(" actor=") - + idToString(mActor); + return std::string("ActivateAction object=") + mObject.toString() + std::string(" actor=") + + mActor.toString(); } private: @@ -165,7 +165,7 @@ namespace MWLua template void addBasicBindings(sol::usertype& objectT, const Context& context) { - objectT["isValid"] = [](const ObjectT& o) { return o.isValid(); }; + objectT["isValid"] = [](const ObjectT& o) { return !o.ptrOrNull().isEmpty(); }; objectT["recordId"] = sol::readonly_property( [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().getRefIdString(); }); objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional> { @@ -197,7 +197,7 @@ namespace MWLua if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_) throw std::runtime_error( "The argument of `activateBy` must be an actor who activates the object. Got: " - + ptrToString(actor.ptr())); + + actor.toString()); context.mLuaManager->addAction(std::make_unique(context.mLua, o.id(), actor.id())); }; @@ -266,7 +266,7 @@ namespace MWLua 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)); + throw std::runtime_error("There is no script " + std::string(path) + " on " + ptr.toString()); if (localScripts->getAutoStartConf().count(*scriptId) > 0) throw std::runtime_error("Autostarted script can not be removed: " + std::string(path)); localScripts->removeScript(*scriptId); diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 10634d1812..97d797d8d5 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -51,7 +51,7 @@ namespace MWLua || itemPtr.getContainerStore() != static_cast(&store)) { Log(Debug::Warning) - << "Object" << idToString(std::get(item)) << " is not in inventory"; + << "Object" << std::get(item).toString() << " is not in inventory"; return false; } } @@ -77,7 +77,7 @@ namespace MWLua allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); if (firstAllowed == allowedSlots.end()) { - Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); + Log(Debug::Warning) << "No suitable slot for " << itemPtr.toString(); return false; } slot = *firstAllowed; @@ -261,7 +261,7 @@ namespace MWLua if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) { if (!equipment.empty()) - throw std::runtime_error(ptrToString(obj.ptr()) + " has no equipment slots"); + throw std::runtime_error(obj.toString() + " has no equipment slots"); return; } SetEquipmentAction::Equipment eqp; diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index 2ac72e710e..75cc072ce2 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -95,7 +95,7 @@ namespace MWLua std::string msg = "Requires type '"; msg.append(getLuaObjectTypeName(recordType)); msg.append("', but applied to "); - msg.append(ptrToString(ptr)); + msg.append(ptr.toString()); throw std::runtime_error(msg); } return ptr; diff --git a/apps/openmw/mwlua/worldview.cpp b/apps/openmw/mwlua/worldview.cpp index cc1d381531..d06420399b 100644 --- a/apps/openmw/mwlua/worldview.cpp +++ b/apps/openmw/mwlua/worldview.cpp @@ -4,6 +4,8 @@ #include #include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwclass/container.hpp" @@ -39,7 +41,7 @@ namespace MWLua { // It is important to check `isMarker` first. // For example "prisonmarker" has class "Door" despite that it is only an invisible marker. - if (isMarker(ptr)) + if (Misc::ResourceHelpers::isHiddenMarker(ptr.getCellRef().getRefId())) return nullptr; const MWWorld::Class& cls = ptr.getClass(); if (cls.isActivator()) diff --git a/apps/openmw/mwworld/ptr.cpp b/apps/openmw/mwworld/ptr.cpp new file mode 100644 index 0000000000..6ad6f143af --- /dev/null +++ b/apps/openmw/mwworld/ptr.cpp @@ -0,0 +1,42 @@ +#include "ptr.hpp" + +#include "apps/openmw/mwbase/environment.hpp" + +#include "worldmodel.hpp" + +namespace MWWorld +{ + + std::string Ptr::toString() const + { + std::string res = "object"; + if (getRefData().isDeleted()) + res = "deleted object"; + res.append(getCellRef().getRefNum().toString()); + res.append(" ("); + res.append(getTypeDescription()); + res.append(", "); + res.append(getCellRef().getRefId().getRefIdString()); + res.append(")"); + return res; + } + + std::string SafePtr::toString() const + { + update(); + if (mPtr.isEmpty()) + return "object" + mId.toString() + " (not found)"; + else + return mPtr.toString(); + } + + void SafePtr::update() const + { + WorldModel& w = *MWBase::Environment::get().getWorldModel(); + if (mLastUpdate < w.getPtrIndexUpdateCounter()) + { + mPtr = w.getPtr(mId); + mLastUpdate = w.getPtrIndexUpdateCounter(); + } + } +} diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index fdf69b8818..4299130aa8 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -128,6 +128,8 @@ namespace MWWorld : PtrBase(liveCellRef, cell, nullptr) { } + + std::string toString() const; }; /// @note The difference between Ptr and ConstPtr is that the second one adds const to the underlying pointers. @@ -145,6 +147,41 @@ namespace MWWorld } }; + // SafePtr holds Ptr and automatically updates it via WorldModel if the Ptr becomes invalid. + // Uses ESM::RefNum as an unique id. Can not be used for Ptrs without RefNum. + // Note: WorldModel automatically assignes RefNum to all registered Ptrs. + class SafePtr + { + public: + using Id = ESM::RefNum; + + explicit SafePtr(Id id) + : mId(id) + { + } + explicit SafePtr(const Ptr& ptr) + : SafePtr(ptr.getCellRef().getRefNum()) + { + } + virtual ~SafePtr() = default; + Id id() const { return mId; } + + std::string toString() const; + + const Ptr& ptrOrNull() const + { + update(); + return mPtr; + } + + private: + const Id mId; + + mutable Ptr mPtr; + mutable size_t mLastUpdate = 0; + + void update() const; + }; } #endif diff --git a/components/esm3/cellref.cpp b/components/esm3/cellref.cpp index d9cef58a4b..d765497948 100644 --- a/components/esm3/cellref.cpp +++ b/components/esm3/cellref.cpp @@ -175,6 +175,11 @@ namespace ESM } } + std::string RefNum::toString() const + { + return std::to_string(mIndex) + "_" + std::to_string(mContentFile); + } + void CellRef::load(ESMReader& esm, bool& isDeleted, bool wideRefNum) { loadId(esm, wideRefNum); diff --git a/components/esm3/cellref.hpp b/components/esm3/cellref.hpp index 7912c11b69..0ea118178f 100644 --- a/components/esm3/cellref.hpp +++ b/components/esm3/cellref.hpp @@ -27,6 +27,8 @@ namespace ESM inline bool hasContentFile() const { return mContentFile >= 0; } inline bool isSet() const { return mIndex != 0 || mContentFile != -1; } + + std::string toString() const; }; /* Cell reference. This represents ONE object (of many) inside the