1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-01 00:45:34 +00:00

Use btCollisionObject* instead of MWWorld::Ptr inside of Projectile

collision handling and castRay() to avoid calling getPtr(). It is a step forward
removing the mutex inside of PtrHolder.

Do the same for DeepestNotMeContactTestResultCallback. It is used
only for not-ranged combat for now, but do it anyway for parity with all
other callback. This way, once the PtrHolder mutex is gone one will not
have to worry about wether it is safe to use the callback in a specific
context.

To avoid use-after-free with projectile / projectile collision, defer deletion of projectile.
Since instead of storing a copy of target Ptr we have a pointer to its collision object,
we can't delete projectiles until after we finished iterating over the loops.
This commit is contained in:
fredzio 2021-08-05 10:55:19 +02:00
parent e88b94d0b0
commit 07fa1803f7
9 changed files with 90 additions and 84 deletions

View file

@ -77,10 +77,8 @@ namespace MWPhysics
auto* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer()); auto* projectileHolder = static_cast<Projectile*>(convexResult.m_hitCollisionObject->getUserPointer());
if (!projectileHolder->isActive()) if (!projectileHolder->isActive())
return btScalar(1); return btScalar(1);
auto* targetHolder = static_cast<PtrHolder*>(mMe->getUserPointer()); if (projectileHolder->isValidTarget(mMe))
const MWWorld::Ptr target = targetHolder->getPtr(); projectileHolder->hit(mMe, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
if (projectileHolder->isValidTarget(target))
projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal);
return btScalar(1); return btScalar(1);
} }

View file

@ -7,6 +7,7 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "ptrholder.hpp" #include "ptrholder.hpp"
namespace MWPhysics namespace MWPhysics
@ -19,17 +20,14 @@ namespace MWPhysics
btScalar ClosestNotMeRayResultCallback::addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) btScalar ClosestNotMeRayResultCallback::addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace)
{ {
if (rayResult.m_collisionObject == mMe) const auto* hitObject = rayResult.m_collisionObject;
if (hitObject == mMe)
return 1.f; return 1.f;
if (!mTargets.empty()) if (hitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty())
{ {
if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) if ((std::find(mTargets.begin(), mTargets.end(), hitObject) == mTargets.end()))
{ return 1.f;
auto* holder = static_cast<PtrHolder*>(rayResult.m_collisionObject->getUserPointer());
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
return 1.f;
}
} }
return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);

View file

@ -6,6 +6,7 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "collisiontype.hpp"
#include "ptrholder.hpp" #include "ptrholder.hpp"
namespace MWPhysics namespace MWPhysics
@ -23,14 +24,10 @@ namespace MWPhysics
const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; const btCollisionObject* collisionObject = col1Wrap->m_collisionObject;
if (collisionObject != mMe) if (collisionObject != mMe)
{ {
if (!mTargets.empty()) if (collisionObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty())
{ {
if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end())) if ((std::find(mTargets.begin(), mTargets.end(), collisionObject) == mTargets.end()))
{ return 0.f;
PtrHolder* holder = static_cast<PtrHolder*>(collisionObject->getUserPointer());
if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor())
return 0.f;
}
} }
btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA());

View file

@ -635,19 +635,7 @@ namespace MWPhysics
if (btFrom == btTo) if (btFrom == btTo)
return; return;
const auto casterPtr = projectile->getCaster(); ProjectileConvexCallback resultCallback(projectile->getCasterCollisionObject(), projectile->getCollisionObject(), btFrom, btTo, projectile);
const auto* caster = [this,&casterPtr]() -> const btCollisionObject*
{
const Actor* actor = getActor(casterPtr);
if (actor)
return actor->getCollisionObject();
const Object* object = getObject(casterPtr);
if (object)
return object->getCollisionObject();
return nullptr;
}();
ProjectileConvexCallback resultCallback(caster, btFrom, btTo, projectile);
resultCallback.m_collisionFilterMask = 0xff; resultCallback.m_collisionFilterMask = 0xff;
resultCallback.m_collisionFilterGroup = CollisionType_Projectile; resultCallback.m_collisionFilterGroup = CollisionType_Projectile;

View file

@ -7,8 +7,10 @@
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "actor.hpp"
#include "collisiontype.hpp" #include "collisiontype.hpp"
#include "mtphysics.hpp" #include "mtphysics.hpp"
#include "object.hpp"
#include "projectile.hpp" #include "projectile.hpp"
namespace MWPhysics namespace MWPhysics
@ -17,7 +19,7 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f
: mCanCrossWaterSurface(canCrossWaterSurface) : mCanCrossWaterSurface(canCrossWaterSurface)
, mCrossedWaterSurface(false) , mCrossedWaterSurface(false)
, mActive(true) , mActive(true)
, mCaster(caster) , mHitTarget(nullptr)
, mWaterHitPosition(std::nullopt) , mWaterHitPosition(std::nullopt)
, mPhysics(physicssystem) , mPhysics(physicssystem)
, mTaskScheduler(scheduler) , mTaskScheduler(scheduler)
@ -32,6 +34,7 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f
mCollisionObject->setUserPointer(this); mCollisionObject->setUserPointer(this);
setPosition(position); setPosition(position);
setCaster(caster);
const int collisionMask = CollisionType_World | CollisionType_HeightMap | const int collisionMask = CollisionType_World | CollisionType_HeightMap |
CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile;
@ -77,54 +80,67 @@ bool Projectile::canTraverseWater() const
return mCanCrossWaterSurface; return mCanCrossWaterSurface;
} }
void Projectile::hit(const MWWorld::Ptr& target, btVector3 pos, btVector3 normal) void Projectile::hit(const btCollisionObject* target, btVector3 pos, btVector3 normal)
{ {
if (!mActive.load(std::memory_order_acquire)) bool active = true;
if (!mActive.compare_exchange_strong(active, false, std::memory_order_relaxed) || !active)
return; return;
std::scoped_lock lock(mMutex);
mHitTarget = target; mHitTarget = target;
mHitPosition = pos; mHitPosition = pos;
mHitNormal = normal; mHitNormal = normal;
mActive.store(false, std::memory_order_release); }
MWWorld::Ptr Projectile::getTarget() const
{
assert(!mActive);
auto* target = static_cast<PtrHolder*>(mHitTarget->getUserPointer());
return target ? target->getPtr() : MWWorld::Ptr();
} }
MWWorld::Ptr Projectile::getCaster() const MWWorld::Ptr Projectile::getCaster() const
{ {
std::scoped_lock lock(mMutex);
return mCaster; return mCaster;
} }
void Projectile::setCaster(const MWWorld::Ptr& caster) void Projectile::setCaster(const MWWorld::Ptr& caster)
{ {
std::scoped_lock lock(mMutex);
mCaster = caster; mCaster = caster;
mCasterColObj = [this,&caster]() -> const btCollisionObject*
{
const Actor* actor = mPhysics->getActor(caster);
if (actor)
return actor->getCollisionObject();
const Object* object = mPhysics->getObject(caster);
if (object)
return object->getCollisionObject();
return nullptr;
}();
} }
void Projectile::setValidTargets(const std::vector<MWWorld::Ptr>& targets) void Projectile::setValidTargets(const std::vector<MWWorld::Ptr>& targets)
{ {
std::scoped_lock lock(mMutex); std::scoped_lock lock(mMutex);
mValidTargets = targets; mValidTargets.clear();
for (const auto& ptr : targets)
{
const auto* physicActor = mPhysics->getActor(ptr);
if (physicActor)
mValidTargets.push_back(physicActor->getCollisionObject());
}
} }
bool Projectile::isValidTarget(const MWWorld::Ptr& target) const bool Projectile::isValidTarget(const btCollisionObject* target) const
{ {
assert(target);
std::scoped_lock lock(mMutex); std::scoped_lock lock(mMutex);
if (mCaster == target) if (mCasterColObj == target)
return false; return false;
if (target.isEmpty() || mValidTargets.empty()) if (mValidTargets.empty())
return true; return true;
bool validTarget = false; return std::any_of(mValidTargets.begin(), mValidTargets.end(),
for (const auto& targetActor : mValidTargets) [target](const btCollisionObject* actor) { return target == actor; });
{
if (targetActor == target)
{
validTarget = true;
break;
}
}
return validTarget;
} }
std::optional<btVector3> Projectile::getWaterHitPosition() std::optional<btVector3> Projectile::getWaterHitPosition()

View file

@ -47,21 +47,21 @@ namespace MWPhysics
return mActive.load(std::memory_order_acquire); return mActive.load(std::memory_order_acquire);
} }
MWWorld::Ptr getTarget() const MWWorld::Ptr getTarget() const;
{
assert(!mActive);
return mHitTarget;
}
MWWorld::Ptr getCaster() const; MWWorld::Ptr getCaster() const;
void setCaster(const MWWorld::Ptr& caster); void setCaster(const MWWorld::Ptr& caster);
const btCollisionObject* getCasterCollisionObject() const
{
return mCasterColObj;
}
bool canTraverseWater() const; bool canTraverseWater() const;
void hit(const MWWorld::Ptr& target, btVector3 pos, btVector3 normal); void hit(const btCollisionObject* target, btVector3 pos, btVector3 normal);
void setValidTargets(const std::vector<MWWorld::Ptr>& targets); void setValidTargets(const std::vector<MWWorld::Ptr>& targets);
bool isValidTarget(const MWWorld::Ptr& target) const; bool isValidTarget(const btCollisionObject* target) const;
std::optional<btVector3> getWaterHitPosition(); std::optional<btVector3> getWaterHitPosition();
void setWaterHitPosition(btVector3 pos); void setWaterHitPosition(btVector3 pos);
@ -76,13 +76,14 @@ namespace MWPhysics
bool mCrossedWaterSurface; bool mCrossedWaterSurface;
std::atomic<bool> mActive; std::atomic<bool> mActive;
MWWorld::Ptr mCaster; MWWorld::Ptr mCaster;
MWWorld::Ptr mHitTarget; const btCollisionObject* mCasterColObj;
const btCollisionObject* mHitTarget;
std::optional<btVector3> mWaterHitPosition; std::optional<btVector3> mWaterHitPosition;
osg::Vec3f mPosition; osg::Vec3f mPosition;
btVector3 mHitPosition; btVector3 mHitPosition;
btVector3 mHitNormal; btVector3 mHitNormal;
std::vector<MWWorld::Ptr> mValidTargets; std::vector<const btCollisionObject*> mValidTargets;
mutable std::mutex mMutex; mutable std::mutex mMutex;

View file

@ -1,3 +1,5 @@
#include <BulletCollision/CollisionDispatch/btCollisionObject.h>
#include "../mwworld/class.hpp" #include "../mwworld/class.hpp"
#include "actor.hpp" #include "actor.hpp"
@ -8,41 +10,41 @@
namespace MWPhysics namespace MWPhysics
{ {
ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj) ProjectileConvexCallback::ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj)
: btCollisionWorld::ClosestConvexResultCallback(from, to) : btCollisionWorld::ClosestConvexResultCallback(from, to)
, mMe(me), mProjectile(proj) , mCaster(caster)
, mMe(me)
, mProjectile(proj)
{ {
assert(mProjectile); assert(mProjectile);
} }
btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace)
{ {
const auto* hitObject = result.m_hitCollisionObject;
// don't hit the caster // don't hit the caster
if (result.m_hitCollisionObject == mMe) if (hitObject == mCaster)
return 1.f; return 1.f;
// don't hit the projectile // don't hit the projectile
if (result.m_hitCollisionObject == mProjectile->getCollisionObject()) if (hitObject == mMe)
return 1.f; return 1.f;
btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace);
switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup) switch (hitObject->getBroadphaseHandle()->m_collisionFilterGroup)
{ {
case CollisionType_Actor: case CollisionType_Actor:
{ {
auto* target = static_cast<Actor*>(result.m_hitCollisionObject->getUserPointer()); if (!mProjectile->isValidTarget(hitObject))
if (!mProjectile->isValidTarget(target->getPtr()))
return 1.f; return 1.f;
mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal);
break; break;
} }
case CollisionType_Projectile: case CollisionType_Projectile:
{ {
auto* target = static_cast<Projectile*>(result.m_hitCollisionObject->getUserPointer()); auto* target = static_cast<Projectile*>(hitObject->getUserPointer());
if (!mProjectile->isValidTarget(target->getCaster())) if (!mProjectile->isValidTarget(target->getCasterCollisionObject()))
return 1.f; return 1.f;
target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); target->hit(mMe, m_hitPointWorld, m_hitNormalWorld);
mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld);
break; break;
} }
case CollisionType_Water: case CollisionType_Water:
@ -50,17 +52,10 @@ namespace MWPhysics
mProjectile->setWaterHitPosition(m_hitPointWorld); mProjectile->setWaterHitPosition(m_hitPointWorld);
if (mProjectile->canTraverseWater()) if (mProjectile->canTraverseWater())
return 1.f; return 1.f;
mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld);
break;
}
default:
{
auto* target = static_cast<PtrHolder*>(result.m_hitCollisionObject->getUserPointer());
auto ptr = target ? target->getPtr() : MWWorld::Ptr();
mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld);
break; break;
} }
} }
mProjectile->hit(hitObject, m_hitPointWorld, m_hitNormalWorld);
return result.m_hitFraction; return result.m_hitFraction;
} }

View file

@ -12,11 +12,12 @@ namespace MWPhysics
class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback
{ {
public: public:
ProjectileConvexCallback(const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj); ProjectileConvexCallback(const btCollisionObject* caster, const btCollisionObject* me, const btVector3& from, const btVector3& to, Projectile* proj);
btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override; btScalar addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) override;
private: private:
const btCollisionObject* mCaster;
const btCollisionObject* mMe; const btCollisionObject* mMe;
Projectile* mProjectile; Projectile* mProjectile;
}; };

View file

@ -521,7 +521,7 @@ namespace MWWorld
} }
MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength);
cleanupProjectile(projectileState); projectileState.mToDelete = true;
} }
for (auto& magicBoltState : mMagicBolts) for (auto& magicBoltState : mMagicBolts)
{ {
@ -550,7 +550,19 @@ namespace MWWorld
cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true); cast.inflict(target, caster, magicBoltState.mEffects, ESM::RT_Target, false, true);
MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName); MWBase::Environment::get().getWorld()->explodeSpell(pos, magicBoltState.mEffects, caster, target, ESM::RT_Target, magicBoltState.mSpellId, magicBoltState.mSourceName);
cleanupMagicBolt(magicBoltState); magicBoltState.mToDelete = true;
}
for (auto& projectileState : mProjectiles)
{
if (projectileState.mToDelete)
cleanupProjectile(projectileState);
}
for (auto& magicBoltState : mMagicBolts)
{
if (magicBoltState.mToDelete)
cleanupMagicBolt(magicBoltState);
} }
mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }), mProjectiles.erase(std::remove_if(mProjectiles.begin(), mProjectiles.end(), [](const State& state) { return state.mToDelete; }),
mProjectiles.end()); mProjectiles.end());