1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-06-23 22:41:33 +00:00

Create MWWorld::SafePtr

This commit is contained in:
Petr Mikheev 2023-03-05 21:47:49 +01:00
parent cf0ba158f6
commit 0b385d5db9
12 changed files with 124 additions and 108 deletions

View file

@ -198,7 +198,7 @@ namespace MWLua
} }
LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj) LocalScripts::LocalScripts(LuaUtil::LuaState* lua, const LObject& obj)
: LuaUtil::ScriptsContainer(lua, "L" + idToString(obj.id())) : LuaUtil::ScriptsContainer(lua, "L" + obj.id().toString())
, mData(obj) , mData(obj)
{ {
this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData)); this->addPackage("openmw.self", sol::make_object(lua->sol(), &mData));

View file

@ -187,11 +187,12 @@ namespace MWLua
for (LocalEvent& e : localEvents) for (LocalEvent& e : localEvents)
{ {
LObject obj(e.mDest); 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) if (scripts)
scripts->receiveEvent(e.mEventName, e.mEventData); scripts->receiveEvent(e.mEventName, e.mEventData);
else 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"; << ". Object not found or has no attached scripts";
} }
@ -204,14 +205,15 @@ namespace MWLua
for (const LocalEngineEvent& e : mLocalEngineEvents) for (const LocalEngineEvent& e : mLocalEngineEvents)
{ {
LObject obj(e.mDest); LObject obj(e.mDest);
if (!obj.isValid()) const MWWorld::Ptr& ptr = obj.ptrOrNull();
if (ptr.isEmpty())
{ {
if (luaDebug) 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"; << " is not found";
continue; continue;
} }
LocalScripts* scripts = obj.ptr().getRefData().getLuaScripts(); LocalScripts* scripts = ptr.getRefData().getLuaScripts();
if (scripts) if (scripts)
scripts->receiveEngineEvent(e.mEvent); scripts->receiveEngineEvent(e.mEvent);
} }
@ -238,17 +240,18 @@ namespace MWLua
for (ObjectId id : mObjectAddedEvents) for (ObjectId id : mObjectAddedEvents)
{ {
GObject obj(id); GObject obj(id);
if (obj.isValid()) const MWWorld::Ptr& ptr = obj.ptrOrNull();
if (!ptr.isEmpty())
{ {
mGlobalScripts.objectActive(obj); mGlobalScripts.objectActive(obj);
const MWWorld::Class& objClass = obj.ptr().getClass(); const MWWorld::Class& objClass = ptr.getClass();
if (objClass.isActor()) if (objClass.isActor())
mGlobalScripts.actorActive(obj); mGlobalScripts.actorActive(obj);
if (mWorldView.isItem(obj.ptr())) if (mWorldView.isItem(ptr))
mGlobalScripts.itemActive(obj); mGlobalScripts.itemActive(obj);
} }
else if (luaDebug) 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"; << " is already removed";
} }
mObjectAddedEvents.clear(); mObjectAddedEvents.clear();
@ -675,7 +678,7 @@ namespace MWLua
selectedScripts = selectedPtr.getRefData().getLuaScripts(); selectedScripts = selectedPtr.getRefData().getLuaScripts();
if (selectedScripts) if (selectedScripts)
selectedScripts->collectStats(selectedStats); 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 else
out << "No selected object. Use the in-game console to select an object for detailed profile.\n"; out << "No selected object. Use the in-game console to select an object for detailed profile.\n";

View file

@ -1,59 +0,0 @@
#include "object.hpp"
#include "types/types.hpp"
#include <components/misc/resourcehelpers.hpp>
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;
}
}

View file

@ -21,39 +21,24 @@ namespace MWLua
{ {
return ptr.getCellRef().getRefNum(); 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. // 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. // `GObject` and `LObject` are intended to be passed to Lua as a userdata.
// It automatically updates the underlying Ptr when needed. // It automatically updates the underlying Ptr when needed.
class Object class Object : public MWWorld::SafePtr
{ {
public: public:
Object(ObjectId id) using SafePtr::SafePtr;
: mId(id) virtual sol::object getObject(lua_State* lua, ObjectId id) const = 0; // returns LObject or GObject
{
}
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
virtual sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const = 0; // returns LCell or GCell virtual sol::object getCell(lua_State* lua, MWWorld::CellStore* store) const = 0; // returns LCell or GCell
protected: const MWWorld::Ptr& ptr() const
const ObjectId mId; {
const MWWorld::Ptr& res = ptrOrNull();
mutable MWWorld::Ptr mPtr; if (res.isEmpty())
mutable size_t mLastUpdate = 0; throw std::runtime_error("Object is not available: " + id().toString());
return res;
}
}; };
// Used only in local scripts // Used only in local scripts
@ -100,7 +85,6 @@ namespace MWLua
{ {
Obj mObj; Obj mObj;
}; };
} }
#endif // MWLUA_OBJECT_H #endif // MWLUA_OBJECT_H

View file

@ -116,10 +116,10 @@ namespace MWLua
{ {
MWWorld::Ptr object = MWBase::Environment::get().getWorldModel()->getPtr(mObject); MWWorld::Ptr object = MWBase::Environment::get().getWorldModel()->getPtr(mObject);
if (object.isEmpty()) 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); MWWorld::Ptr actor = MWBase::Environment::get().getWorldModel()->getPtr(mActor);
if (actor.isEmpty()) 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()) if (object.getRefData().activate())
{ {
@ -131,8 +131,8 @@ namespace MWLua
std::string toString() const override std::string toString() const override
{ {
return std::string("ActivateAction object=") + idToString(mObject) + std::string(" actor=") return std::string("ActivateAction object=") + mObject.toString() + std::string(" actor=")
+ idToString(mActor); + mActor.toString();
} }
private: private:
@ -165,7 +165,7 @@ namespace MWLua
template <class ObjectT> template <class ObjectT>
void addBasicBindings(sol::usertype<ObjectT>& objectT, const Context& context) void addBasicBindings(sol::usertype<ObjectT>& 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( objectT["recordId"] = sol::readonly_property(
[](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().getRefIdString(); }); [](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().getRefIdString(); });
objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<Cell<ObjectT>> { objectT["cell"] = sol::readonly_property([](const ObjectT& o) -> sol::optional<Cell<ObjectT>> {
@ -197,7 +197,7 @@ namespace MWLua
if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_) if (esmRecordType != ESM::REC_CREA && esmRecordType != ESM::REC_NPC_)
throw std::runtime_error( throw std::runtime_error(
"The argument of `activateBy` must be an actor who activates the object. Got: " "The argument of `activateBy` must be an actor who activates the object. Got: "
+ ptrToString(actor.ptr())); + actor.toString());
context.mLuaManager->addAction(std::make_unique<ActivateAction>(context.mLua, o.id(), actor.id())); context.mLuaManager->addAction(std::make_unique<ActivateAction>(context.mLua, o.id(), actor.id()));
}; };
@ -266,7 +266,7 @@ namespace MWLua
MWWorld::Ptr ptr = object.ptr(); MWWorld::Ptr ptr = object.ptr();
LocalScripts* localScripts = ptr.getRefData().getLuaScripts(); LocalScripts* localScripts = ptr.getRefData().getLuaScripts();
if (!localScripts || !localScripts->hasScript(*scriptId)) 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) if (localScripts->getAutoStartConf().count(*scriptId) > 0)
throw std::runtime_error("Autostarted script can not be removed: " + std::string(path)); throw std::runtime_error("Autostarted script can not be removed: " + std::string(path));
localScripts->removeScript(*scriptId); localScripts->removeScript(*scriptId);

View file

@ -51,7 +51,7 @@ namespace MWLua
|| itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store)) || itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
{ {
Log(Debug::Warning) Log(Debug::Warning)
<< "Object" << idToString(std::get<ObjectId>(item)) << " is not in inventory"; << "Object" << std::get<ObjectId>(item).toString() << " is not in inventory";
return false; return false;
} }
} }
@ -77,7 +77,7 @@ namespace MWLua
allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; }); allowedSlots.begin(), allowedSlots.end(), [&](int s) { return !usedSlots[s]; });
if (firstAllowed == allowedSlots.end()) if (firstAllowed == allowedSlots.end())
{ {
Log(Debug::Warning) << "No suitable slot for " << ptrToString(itemPtr); Log(Debug::Warning) << "No suitable slot for " << itemPtr.toString();
return false; return false;
} }
slot = *firstAllowed; slot = *firstAllowed;
@ -261,7 +261,7 @@ namespace MWLua
if (!obj.ptr().getClass().hasInventoryStore(obj.ptr())) if (!obj.ptr().getClass().hasInventoryStore(obj.ptr()))
{ {
if (!equipment.empty()) 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; return;
} }
SetEquipmentAction::Equipment eqp; SetEquipmentAction::Equipment eqp;

View file

@ -95,7 +95,7 @@ namespace MWLua
std::string msg = "Requires type '"; std::string msg = "Requires type '";
msg.append(getLuaObjectTypeName(recordType)); msg.append(getLuaObjectTypeName(recordType));
msg.append("', but applied to "); msg.append("', but applied to ");
msg.append(ptrToString(ptr)); msg.append(ptr.toString());
throw std::runtime_error(msg); throw std::runtime_error(msg);
} }
return ptr; return ptr;

View file

@ -4,6 +4,8 @@
#include <components/esm3/esmwriter.hpp> #include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadcell.hpp> #include <components/esm3/loadcell.hpp>
#include <components/misc/resourcehelpers.hpp>
#include "../mwbase/windowmanager.hpp" #include "../mwbase/windowmanager.hpp"
#include "../mwclass/container.hpp" #include "../mwclass/container.hpp"
@ -39,7 +41,7 @@ namespace MWLua
{ {
// It is important to check `isMarker` first. // It is important to check `isMarker` first.
// For example "prisonmarker" has class "Door" despite that it is only an invisible marker. // 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; return nullptr;
const MWWorld::Class& cls = ptr.getClass(); const MWWorld::Class& cls = ptr.getClass();
if (cls.isActivator()) if (cls.isActivator())

View file

@ -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();
}
}
}

View file

@ -128,6 +128,8 @@ namespace MWWorld
: PtrBase(liveCellRef, cell, nullptr) : 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. /// @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 #endif

View file

@ -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) void CellRef::load(ESMReader& esm, bool& isDeleted, bool wideRefNum)
{ {
loadId(esm, wideRefNum); loadId(esm, wideRefNum);

View file

@ -27,6 +27,8 @@ namespace ESM
inline bool hasContentFile() const { return mContentFile >= 0; } inline bool hasContentFile() const { return mContentFile >= 0; }
inline bool isSet() const { return mIndex != 0 || mContentFile != -1; } inline bool isSet() const { return mIndex != 0 || mContentFile != -1; }
std::string toString() const;
}; };
/* Cell reference. This represents ONE object (of many) inside the /* Cell reference. This represents ONE object (of many) inside the