1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-02-19 12:40:49 +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)
: 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));

View File

@ -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";

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

View File

@ -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 <class ObjectT>
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(
[](const ObjectT& o) -> std::string { return o.ptr().getCellRef().getRefId().getRefIdString(); });
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_)
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<ActivateAction>(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);

View File

@ -51,7 +51,7 @@ namespace MWLua
|| itemPtr.getContainerStore() != static_cast<const MWWorld::ContainerStore*>(&store))
{
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;
}
}
@ -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;

View File

@ -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;

View File

@ -4,6 +4,8 @@
#include <components/esm3/esmwriter.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/misc/resourcehelpers.hpp>
#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())

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)
{
}
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

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)
{
loadId(esm, wideRefNum);

View File

@ -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