From e88b94d0b0b19d491d566f390b97fe07b5e49df6 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 5 Aug 2021 06:26:05 +0200 Subject: [PATCH 1/3] Move btCollisionObject* into PtrHolder Remove unused function --- apps/openmw/mwphysics/actor.cpp | 2 +- apps/openmw/mwphysics/actor.hpp | 7 ------- apps/openmw/mwphysics/object.cpp | 10 ---------- apps/openmw/mwphysics/object.hpp | 3 --- apps/openmw/mwphysics/projectile.hpp | 6 ------ apps/openmw/mwphysics/ptrholder.hpp | 11 +++++++---- 6 files changed, 8 insertions(+), 31 deletions(-) diff --git a/apps/openmw/mwphysics/actor.cpp b/apps/openmw/mwphysics/actor.cpp index c566c3a1ed..e140141e32 100644 --- a/apps/openmw/mwphysics/actor.cpp +++ b/apps/openmw/mwphysics/actor.cpp @@ -21,7 +21,7 @@ namespace MWPhysics Actor::Actor(const MWWorld::Ptr& ptr, const Resource::BulletShape* shape, PhysicsTaskScheduler* scheduler, bool canWaterWalk) : mStandingOnPtr(nullptr), mCanWaterWalk(canWaterWalk), mWalkingOnWater(false) - , mCollisionObject(nullptr), mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents) + , mMeshTranslation(shape->mCollisionBox.center), mOriginalHalfExtents(shape->mCollisionBox.extents) , mVelocity(0,0,0), mStuckFrames(0), mLastStuckPosition{0, 0, 0} , mForce(0.f, 0.f, 0.f), mOnGround(true), mOnSlope(false) , mInternalCollisionMode(true) diff --git a/apps/openmw/mwphysics/actor.hpp b/apps/openmw/mwphysics/actor.hpp index e52a4caca7..0846401c1d 100644 --- a/apps/openmw/mwphysics/actor.hpp +++ b/apps/openmw/mwphysics/actor.hpp @@ -132,11 +132,6 @@ namespace MWPhysics return mInternalCollisionMode && mOnSlope; } - btCollisionObject* getCollisionObject() const - { - return mCollisionObject.get(); - } - /// Sets whether this actor should be able to collide with the water surface void setCanWaterWalk(bool waterWalk); @@ -188,8 +183,6 @@ namespace MWPhysics std::unique_ptr mShape; btConvexShape* mConvexShape; - std::unique_ptr mCollisionObject; - osg::Vec3f mMeshTranslation; osg::Vec3f mOriginalHalfExtents; osg::Vec3f mHalfExtents; diff --git a/apps/openmw/mwphysics/object.cpp b/apps/openmw/mwphysics/object.cpp index 9575a97da2..a95672f8cf 100644 --- a/apps/openmw/mwphysics/object.cpp +++ b/apps/openmw/mwphysics/object.cpp @@ -83,16 +83,6 @@ namespace MWPhysics } } - btCollisionObject* Object::getCollisionObject() - { - return mCollisionObject.get(); - } - - const btCollisionObject* Object::getCollisionObject() const - { - return mCollisionObject.get(); - } - btTransform Object::getTransform() const { std::unique_lock lock(mPositionMutex); diff --git a/apps/openmw/mwphysics/object.hpp b/apps/openmw/mwphysics/object.hpp index fe395dc89b..1484c1472c 100644 --- a/apps/openmw/mwphysics/object.hpp +++ b/apps/openmw/mwphysics/object.hpp @@ -33,8 +33,6 @@ namespace MWPhysics void setRotation(osg::Quat quat); void updatePosition(); void commitPositionChange(); - btCollisionObject* getCollisionObject(); - const btCollisionObject* getCollisionObject() const; btTransform getTransform() const; /// Return solid flag. Not used by the object itself, true by default. bool isSolid() const; @@ -45,7 +43,6 @@ namespace MWPhysics bool animateCollisionShapes(); private: - std::unique_ptr mCollisionObject; osg::ref_ptr mShapeInstance; std::map mRecIndexToNodePath; bool mSolid; diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index f18c455020..b5ccee4f63 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -42,11 +42,6 @@ namespace MWPhysics void setPosition(const osg::Vec3f& position); osg::Vec3f getPosition() const; - btCollisionObject* getCollisionObject() const - { - return mCollisionObject.get(); - } - bool isActive() const { return mActive.load(std::memory_order_acquire); @@ -76,7 +71,6 @@ namespace MWPhysics std::unique_ptr mShape; btConvexShape* mConvexShape; - std::unique_ptr mCollisionObject; bool mTransformUpdatePending; bool mCanCrossWaterSurface; bool mCrossedWaterSurface; diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 152a5d64fc..4d0238ecd9 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -2,6 +2,9 @@ #define OPENMW_MWPHYSICS_PTRHOLDER_H #include +#include + +#include #include "../mwworld/ptr.hpp" @@ -10,7 +13,7 @@ namespace MWPhysics class PtrHolder { public: - virtual ~PtrHolder() {} + virtual ~PtrHolder() = default; void updatePtr(const MWWorld::Ptr& updated) { @@ -24,14 +27,14 @@ namespace MWPhysics return mPtr; } - MWWorld::ConstPtr getPtr() const + btCollisionObject* getCollisionObject() const { - std::scoped_lock lock(mMutex); - return mPtr; + return mCollisionObject.get(); } protected: MWWorld::Ptr mPtr; + std::unique_ptr mCollisionObject; private: mutable std::mutex mMutex; From 07fa1803f77efbdf5861fefd32e6a88573c38b87 Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 5 Aug 2021 10:55:19 +0200 Subject: [PATCH 2/3] 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. --- apps/openmw/mwphysics/actorconvexcallback.cpp | 6 +- .../closestnotmerayresultcallback.cpp | 14 ++--- .../deepestnotmecontacttestresultcallback.cpp | 9 +-- apps/openmw/mwphysics/physicssystem.cpp | 14 +---- apps/openmw/mwphysics/projectile.cpp | 58 ++++++++++++------- apps/openmw/mwphysics/projectile.hpp | 19 +++--- .../mwphysics/projectileconvexcallback.cpp | 35 +++++------ .../mwphysics/projectileconvexcallback.hpp | 3 +- apps/openmw/mwworld/projectilemanager.cpp | 16 ++++- 9 files changed, 90 insertions(+), 84 deletions(-) diff --git a/apps/openmw/mwphysics/actorconvexcallback.cpp b/apps/openmw/mwphysics/actorconvexcallback.cpp index ef52e07c21..672af05058 100644 --- a/apps/openmw/mwphysics/actorconvexcallback.cpp +++ b/apps/openmw/mwphysics/actorconvexcallback.cpp @@ -77,10 +77,8 @@ namespace MWPhysics auto* projectileHolder = static_cast(convexResult.m_hitCollisionObject->getUserPointer()); if (!projectileHolder->isActive()) return btScalar(1); - auto* targetHolder = static_cast(mMe->getUserPointer()); - const MWWorld::Ptr target = targetHolder->getPtr(); - if (projectileHolder->isValidTarget(target)) - projectileHolder->hit(target, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); + if (projectileHolder->isValidTarget(mMe)) + projectileHolder->hit(mMe, convexResult.m_hitPointLocal, convexResult.m_hitNormalLocal); return btScalar(1); } diff --git a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp index 32d97d6c75..3f6cb2b727 100644 --- a/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp +++ b/apps/openmw/mwphysics/closestnotmerayresultcallback.cpp @@ -7,6 +7,7 @@ #include "../mwworld/class.hpp" +#include "collisiontype.hpp" #include "ptrholder.hpp" namespace MWPhysics @@ -19,17 +20,14 @@ namespace MWPhysics 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; - if (!mTargets.empty()) + if (hitObject->getBroadphaseHandle()->m_collisionFilterGroup == CollisionType_Actor && !mTargets.empty()) { - if ((std::find(mTargets.begin(), mTargets.end(), rayResult.m_collisionObject) == mTargets.end())) - { - auto* holder = static_cast(rayResult.m_collisionObject->getUserPointer()); - if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) - return 1.f; - } + if ((std::find(mTargets.begin(), mTargets.end(), hitObject) == mTargets.end())) + return 1.f; } return btCollisionWorld::ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace); diff --git a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp index 7744af14b5..3531cc8eb8 100644 --- a/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp +++ b/apps/openmw/mwphysics/deepestnotmecontacttestresultcallback.cpp @@ -6,6 +6,7 @@ #include "../mwworld/class.hpp" +#include "collisiontype.hpp" #include "ptrholder.hpp" namespace MWPhysics @@ -23,14 +24,10 @@ namespace MWPhysics const btCollisionObject* collisionObject = col1Wrap->m_collisionObject; 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())) - { - PtrHolder* holder = static_cast(collisionObject->getUserPointer()); - if (holder && !holder->getPtr().isEmpty() && holder->getPtr().getClass().isActor()) - return 0.f; - } + return 0.f; } btScalar distsqr = mOrigin.distance2(cp.getPositionWorldOnA()); diff --git a/apps/openmw/mwphysics/physicssystem.cpp b/apps/openmw/mwphysics/physicssystem.cpp index f8862ea400..45120d15b2 100644 --- a/apps/openmw/mwphysics/physicssystem.cpp +++ b/apps/openmw/mwphysics/physicssystem.cpp @@ -635,19 +635,7 @@ namespace MWPhysics if (btFrom == btTo) return; - const auto casterPtr = projectile->getCaster(); - 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); + ProjectileConvexCallback resultCallback(projectile->getCasterCollisionObject(), projectile->getCollisionObject(), btFrom, btTo, projectile); resultCallback.m_collisionFilterMask = 0xff; resultCallback.m_collisionFilterGroup = CollisionType_Projectile; diff --git a/apps/openmw/mwphysics/projectile.cpp b/apps/openmw/mwphysics/projectile.cpp index 1a94de01ee..a8bb444956 100644 --- a/apps/openmw/mwphysics/projectile.cpp +++ b/apps/openmw/mwphysics/projectile.cpp @@ -7,8 +7,10 @@ #include "../mwworld/class.hpp" +#include "actor.hpp" #include "collisiontype.hpp" #include "mtphysics.hpp" +#include "object.hpp" #include "projectile.hpp" namespace MWPhysics @@ -17,7 +19,7 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f : mCanCrossWaterSurface(canCrossWaterSurface) , mCrossedWaterSurface(false) , mActive(true) - , mCaster(caster) + , mHitTarget(nullptr) , mWaterHitPosition(std::nullopt) , mPhysics(physicssystem) , mTaskScheduler(scheduler) @@ -32,6 +34,7 @@ Projectile::Projectile(const MWWorld::Ptr& caster, const osg::Vec3f& position, f mCollisionObject->setUserPointer(this); setPosition(position); + setCaster(caster); const int collisionMask = CollisionType_World | CollisionType_HeightMap | CollisionType_Actor | CollisionType_Door | CollisionType_Water | CollisionType_Projectile; @@ -77,54 +80,67 @@ bool Projectile::canTraverseWater() const 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; - std::scoped_lock lock(mMutex); mHitTarget = target; mHitPosition = pos; mHitNormal = normal; - mActive.store(false, std::memory_order_release); +} + +MWWorld::Ptr Projectile::getTarget() const +{ + assert(!mActive); + auto* target = static_cast(mHitTarget->getUserPointer()); + return target ? target->getPtr() : MWWorld::Ptr(); } MWWorld::Ptr Projectile::getCaster() const { - std::scoped_lock lock(mMutex); return mCaster; } void Projectile::setCaster(const MWWorld::Ptr& caster) { - std::scoped_lock lock(mMutex); 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& targets) { 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); - if (mCaster == target) + if (mCasterColObj == target) return false; - if (target.isEmpty() || mValidTargets.empty()) + if (mValidTargets.empty()) return true; - bool validTarget = false; - for (const auto& targetActor : mValidTargets) - { - if (targetActor == target) - { - validTarget = true; - break; - } - } - return validTarget; + return std::any_of(mValidTargets.begin(), mValidTargets.end(), + [target](const btCollisionObject* actor) { return target == actor; }); } std::optional Projectile::getWaterHitPosition() diff --git a/apps/openmw/mwphysics/projectile.hpp b/apps/openmw/mwphysics/projectile.hpp index b5ccee4f63..dd659b6581 100644 --- a/apps/openmw/mwphysics/projectile.hpp +++ b/apps/openmw/mwphysics/projectile.hpp @@ -47,21 +47,21 @@ namespace MWPhysics return mActive.load(std::memory_order_acquire); } - MWWorld::Ptr getTarget() const - { - assert(!mActive); - return mHitTarget; - } + MWWorld::Ptr getTarget() const; MWWorld::Ptr getCaster() const; void setCaster(const MWWorld::Ptr& caster); + const btCollisionObject* getCasterCollisionObject() const + { + return mCasterColObj; + } 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& targets); - bool isValidTarget(const MWWorld::Ptr& target) const; + bool isValidTarget(const btCollisionObject* target) const; std::optional getWaterHitPosition(); void setWaterHitPosition(btVector3 pos); @@ -76,13 +76,14 @@ namespace MWPhysics bool mCrossedWaterSurface; std::atomic mActive; MWWorld::Ptr mCaster; - MWWorld::Ptr mHitTarget; + const btCollisionObject* mCasterColObj; + const btCollisionObject* mHitTarget; std::optional mWaterHitPosition; osg::Vec3f mPosition; btVector3 mHitPosition; btVector3 mHitNormal; - std::vector mValidTargets; + std::vector mValidTargets; mutable std::mutex mMutex; diff --git a/apps/openmw/mwphysics/projectileconvexcallback.cpp b/apps/openmw/mwphysics/projectileconvexcallback.cpp index b803c4400b..687253e1cc 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.cpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.cpp @@ -1,3 +1,5 @@ +#include + #include "../mwworld/class.hpp" #include "actor.hpp" @@ -8,41 +10,41 @@ 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) - , mMe(me), mProjectile(proj) + , mCaster(caster) + , mMe(me) + , mProjectile(proj) { assert(mProjectile); } btScalar ProjectileConvexCallback::addSingleResult(btCollisionWorld::LocalConvexResult& result, bool normalInWorldSpace) { + const auto* hitObject = result.m_hitCollisionObject; // don't hit the caster - if (result.m_hitCollisionObject == mMe) + if (hitObject == mCaster) return 1.f; // don't hit the projectile - if (result.m_hitCollisionObject == mProjectile->getCollisionObject()) + if (hitObject == mMe) return 1.f; btCollisionWorld::ClosestConvexResultCallback::addSingleResult(result, normalInWorldSpace); - switch (result.m_hitCollisionObject->getBroadphaseHandle()->m_collisionFilterGroup) + switch (hitObject->getBroadphaseHandle()->m_collisionFilterGroup) { case CollisionType_Actor: { - auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getPtr())) + if (!mProjectile->isValidTarget(hitObject)) return 1.f; - mProjectile->hit(target->getPtr(), result.m_hitPointLocal, result.m_hitNormalLocal); break; } case CollisionType_Projectile: { - auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); - if (!mProjectile->isValidTarget(target->getCaster())) + auto* target = static_cast(hitObject->getUserPointer()); + if (!mProjectile->isValidTarget(target->getCasterCollisionObject())) return 1.f; - target->hit(mProjectile->getPtr(), m_hitPointWorld, m_hitNormalWorld); - mProjectile->hit(target->getPtr(), m_hitPointWorld, m_hitNormalWorld); + target->hit(mMe, m_hitPointWorld, m_hitNormalWorld); break; } case CollisionType_Water: @@ -50,17 +52,10 @@ namespace MWPhysics mProjectile->setWaterHitPosition(m_hitPointWorld); if (mProjectile->canTraverseWater()) return 1.f; - mProjectile->hit(MWWorld::Ptr(), m_hitPointWorld, m_hitNormalWorld); - break; - } - default: - { - auto* target = static_cast(result.m_hitCollisionObject->getUserPointer()); - auto ptr = target ? target->getPtr() : MWWorld::Ptr(); - mProjectile->hit(ptr, m_hitPointWorld, m_hitNormalWorld); break; } } + mProjectile->hit(hitObject, m_hitPointWorld, m_hitNormalWorld); return result.m_hitFraction; } diff --git a/apps/openmw/mwphysics/projectileconvexcallback.hpp b/apps/openmw/mwphysics/projectileconvexcallback.hpp index 96c84b1fe2..f35cfbd3c8 100644 --- a/apps/openmw/mwphysics/projectileconvexcallback.hpp +++ b/apps/openmw/mwphysics/projectileconvexcallback.hpp @@ -12,11 +12,12 @@ namespace MWPhysics class ProjectileConvexCallback : public btCollisionWorld::ClosestConvexResultCallback { 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; private: + const btCollisionObject* mCaster; const btCollisionObject* mMe; Projectile* mProjectile; }; diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index 04f4573369..76c6ca7548 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -521,7 +521,7 @@ namespace MWWorld } MWMechanics::projectileHit(caster, target, bow, projectileRef.getPtr(), pos, projectileState.mAttackStrength); - cleanupProjectile(projectileState); + projectileState.mToDelete = true; } for (auto& magicBoltState : mMagicBolts) { @@ -550,7 +550,19 @@ namespace MWWorld 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); - 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.end()); From ee09f3095f84bccb2c6135102dfb34570c653efd Mon Sep 17 00:00:00 2001 From: fredzio Date: Thu, 5 Aug 2021 14:21:56 +0200 Subject: [PATCH 3/3] At last kill PtrHolder mutex --- apps/openmw/mwphysics/ptrholder.hpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/openmw/mwphysics/ptrholder.hpp b/apps/openmw/mwphysics/ptrholder.hpp index 4d0238ecd9..e84f3d1cfe 100644 --- a/apps/openmw/mwphysics/ptrholder.hpp +++ b/apps/openmw/mwphysics/ptrholder.hpp @@ -17,13 +17,11 @@ namespace MWPhysics void updatePtr(const MWWorld::Ptr& updated) { - std::scoped_lock lock(mMutex); mPtr = updated; } MWWorld::Ptr getPtr() { - std::scoped_lock lock(mMutex); return mPtr; } @@ -35,9 +33,6 @@ namespace MWPhysics protected: MWWorld::Ptr mPtr; std::unique_ptr mCollisionObject; - - private: - mutable std::mutex mMutex; }; }