EntitySimulation is lockable

also DeleteEntityOperator just removes the entities
and EntityTree does the actual delete
(after properly locking its _simulation)
This commit is contained in:
Andrew Meadows 2015-01-08 10:22:25 -08:00
parent 23559743d9
commit c37c1515ba
9 changed files with 85 additions and 54 deletions

View file

@ -91,13 +91,9 @@ bool DeleteEntityOperator::preRecursion(OctreeElement* element) {
// If this is the element we're looking for, then ask it to remove the old entity // If this is the element we're looking for, then ask it to remove the old entity
// and we can stop searching. // and we can stop searching.
if (entityTreeElement == details.containingElement) { if (entityTreeElement == details.containingElement) {
EntityItemID entityItemID = details.entity->getEntityItemID(); EntityItem* theEntity = details.entity;
EntityItem* theEntity = entityTreeElement->getEntityWithEntityItemID(entityItemID); // find the actual entity assert(entityTreeElement->removeEntityItem(theEntity)); // remove it from the element
assert(theEntity); _tree->setContainingElement(details.entity->getEntityItemID(), NULL); // update or id to element lookup
_tree->trackDeletedEntity(theEntity);
entityTreeElement->removeEntityItem(theEntity); // remove it from the element
_tree->setContainingElement(entityItemID, NULL); // update or id to element lookup
delete theEntity; // now actually delete the entity!
_foundCount++; _foundCount++;
} }
} }

View file

@ -14,11 +14,13 @@
class EntityToDeleteDetails { class EntityToDeleteDetails {
public: public:
const EntityItem* entity; EntityItem* entity;
AACube cube; AACube cube;
EntityTreeElement* containingElement; EntityTreeElement* containingElement;
}; };
typedef QSet<EntityToDeleteDetails> RemovedEntities;
inline uint qHash(const EntityToDeleteDetails& a, uint seed) { inline uint qHash(const EntityToDeleteDetails& a, uint seed) {
return qHash(a.entity->getEntityItemID(), seed); return qHash(a.entity->getEntityItemID(), seed);
} }
@ -36,9 +38,11 @@ public:
void addEntityIDToDeleteList(const EntityItemID& searchEntityID); void addEntityIDToDeleteList(const EntityItemID& searchEntityID);
virtual bool preRecursion(OctreeElement* element); virtual bool preRecursion(OctreeElement* element);
virtual bool postRecursion(OctreeElement* element); virtual bool postRecursion(OctreeElement* element);
const RemovedEntities& getEntities() const { return _entitiesToDelete; }
private: private:
EntityTree* _tree; EntityTree* _tree;
QSet<EntityToDeleteDetails> _entitiesToDelete; RemovedEntities _entitiesToDelete;
quint64 _changeTime; quint64 _changeTime;
int _foundCount; int _foundCount;
int _lookingCount; int _lookingCount;

View file

@ -38,6 +38,7 @@ void EntitySimulation::updateEntities(QSet<EntityItem*>& entitiesToDelete) {
_entitiesToDelete.clear(); _entitiesToDelete.clear();
} }
// private
void EntitySimulation::expireMortalEntities(const quint64& now) { void EntitySimulation::expireMortalEntities(const quint64& now) {
if (now > _nextExpiry) { if (now > _nextExpiry) {
// only search for expired entities if we expect to find one // only search for expired entities if we expect to find one
@ -63,6 +64,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
} }
} }
// private
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
PerformanceTimer perfTimer("updatingEntities"); PerformanceTimer perfTimer("updatingEntities");
QSet<EntityItem*>::iterator itemItr = _updateableEntities.begin(); QSet<EntityItem*>::iterator itemItr = _updateableEntities.begin();
@ -79,6 +81,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
} }
} }
// private
void EntitySimulation::sortEntitiesThatMoved() { void EntitySimulation::sortEntitiesThatMoved() {
// NOTE: this is only for entities that have been moved by THIS EntitySimulation. // NOTE: this is only for entities that have been moved by THIS EntitySimulation.
// External changes to entity position/shape are expected to be sorted outside of the EntitySimulation. // External changes to entity position/shape are expected to be sorted outside of the EntitySimulation.

View file

@ -33,9 +33,12 @@ const int DIRTY_SIMULATION_FLAGS =
class EntitySimulation { class EntitySimulation {
public: public:
EntitySimulation() : _entityTree(NULL) { } EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL) { }
virtual ~EntitySimulation() { setEntityTree(NULL); } virtual ~EntitySimulation() { setEntityTree(NULL); }
void lock() { _mutex.lock(); }
void unlock() { _mutex.unlock(); }
/// \param tree pointer to EntityTree which is stored internally /// \param tree pointer to EntityTree which is stored internally
void setEntityTree(EntityTree* tree); void setEntityTree(EntityTree* tree);
@ -80,6 +83,8 @@ protected:
void callUpdateOnEntitiesThatNeedIt(const quint64& now); void callUpdateOnEntitiesThatNeedIt(const quint64& now);
void sortEntitiesThatMoved(); void sortEntitiesThatMoved();
QMutex _mutex;
// back pointer to EntityTree structure // back pointer to EntityTree structure
EntityTree* _entityTree; EntityTree* _entityTree;

View file

@ -40,7 +40,9 @@ EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) {
void EntityTree::eraseAllOctreeElements(bool createNewRoot) { void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
// this would be a good place to clean up our entities... // this would be a good place to clean up our entities...
if (_simulation) { if (_simulation) {
_simulation->lock();
_simulation->clearEntities(); _simulation->clearEntities();
_simulation->unlock();
} }
foreach (EntityTreeElement* element, _entityToElementMap) { foreach (EntityTreeElement* element, _entityToElementMap) {
element->cleanupEntities(); element->cleanupEntities();
@ -84,7 +86,9 @@ void EntityTree::postAddEntity(EntityItem* entity) {
assert(entity); assert(entity);
// check to see if we need to simulate this entity.. // check to see if we need to simulate this entity..
if (_simulation) { if (_simulation) {
_simulation->lock();
_simulation->addEntity(entity); _simulation->addEntity(entity);
_simulation->unlock();
} }
_isDirty = true; _isDirty = true;
emit addingEntity(entity->getEntityItemID()); emit addingEntity(entity->getEntityItemID());
@ -141,7 +145,9 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
if (newFlags) { if (newFlags) {
if (_simulation) { if (_simulation) {
if (newFlags & DIRTY_SIMULATION_FLAGS) { if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->lock();
_simulation->entityChanged(entity); _simulation->entityChanged(entity);
_simulation->unlock();
} }
} else { } else {
// normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly
@ -204,21 +210,6 @@ EntityItem* EntityTree::addEntity(const EntityItemID& entityID, const EntityItem
return result; return result;
} }
void EntityTree::trackDeletedEntity(EntityItem* entity) {
if (_simulation) {
_simulation->removeEntity(entity);
}
// this is only needed on the server to send delete messages for recently deleted entities to the viewers
if (getIsServer()) {
// set up the deleted entities ID
quint64 deletedAt = usecTimestampNow();
_recentlyDeletedEntitiesLock.lockForWrite();
_recentlyDeletedEntityItemIDs.insert(deletedAt, entity->getEntityItemID().id);
_recentlyDeletedEntitiesLock.unlock();
}
}
void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID) { void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID) {
emit entityScriptChanging(entityItemID); emit entityScriptChanging(entityItemID);
} }
@ -231,7 +222,9 @@ void EntityTree::setSimulation(EntitySimulation* simulation) {
if (_simulation && _simulation != simulation) { if (_simulation && _simulation != simulation) {
// It's important to clearEntities() on the simulation since taht will update each // It's important to clearEntities() on the simulation since taht will update each
// EntityItem::_simulationState correctly so as to not confuse the next _simulation. // EntityItem::_simulationState correctly so as to not confuse the next _simulation.
_simulation->lock();
_simulation->clearEntities(); _simulation->clearEntities();
_simulation->unlock();
} }
_simulation = simulation; _simulation = simulation;
} }
@ -255,6 +248,31 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs) {
} }
recurseTreeWithOperator(&theOperator); recurseTreeWithOperator(&theOperator);
const RemovedEntities& entities = theOperator.getEntities();
if (_simulation) {
_simulation->lock();
}
foreach(const EntityToDeleteDetails& details, entities) {
EntityItem* theEntity = details.entity;
if (getIsServer()) {
// set up the deleted entities ID
quint64 deletedAt = usecTimestampNow();
_recentlyDeletedEntitiesLock.lockForWrite();
_recentlyDeletedEntityItemIDs.insert(deletedAt, theEntity->getEntityItemID().id);
_recentlyDeletedEntitiesLock.unlock();
}
if (_simulation) {
_simulation->removeEntity(theEntity);
}
delete theEntity; // now actually delete the entity!
}
if (_simulation) {
_simulation->unlock();
}
_isDirty = true; _isDirty = true;
} }
@ -592,7 +610,9 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
void EntityTree::entityChanged(EntityItem* entity) { void EntityTree::entityChanged(EntityItem* entity) {
if (_simulation) { if (_simulation) {
_simulation->lock();
_simulation->entityChanged(entity); _simulation->entityChanged(entity);
_simulation->unlock();
} }
} }
@ -600,7 +620,9 @@ void EntityTree::update() {
if (_simulation) { if (_simulation) {
lockForWrite(); lockForWrite();
QSet<EntityItem*> entitiesToDelete; QSet<EntityItem*> entitiesToDelete;
_simulation->lock();
_simulation->updateEntities(entitiesToDelete); _simulation->updateEntities(entitiesToDelete);
_simulation->unlock();
if (entitiesToDelete.size() > 0) { if (entitiesToDelete.size() > 0) {
// translate into list of ID's // translate into list of ID's
QSet<EntityItemID> idsToDelete; QSet<EntityItemID> idsToDelete;

View file

@ -146,8 +146,6 @@ public:
void entityChanged(EntityItem* entity); void entityChanged(EntityItem* entity);
void trackDeletedEntity(EntityItem* entity);
void emitEntityScriptChanging(const EntityItemID& entityItemID); void emitEntityScriptChanging(const EntityItemID& entityItemID);
void setSimulation(EntitySimulation* simulation); void setSimulation(EntitySimulation* simulation);

View file

@ -169,7 +169,7 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
_collisionDispatcher = new btCollisionDispatcher(_collisionConfig); _collisionDispatcher = new btCollisionDispatcher(_collisionConfig);
_broadphaseFilter = new btDbvtBroadphase(); _broadphaseFilter = new btDbvtBroadphase();
_constraintSolver = new btSequentialImpulseConstraintSolver; _constraintSolver = new btSequentialImpulseConstraintSolver;
_dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig, _entityTree); _dynamicsWorld = new ThreadSafeDynamicsWorld(_collisionDispatcher, _broadphaseFilter, _constraintSolver, _collisionConfig);
// default gravity of the world is zero, so each object must specify its own gravity // default gravity of the world is zero, so each object must specify its own gravity
// TODO: set up gravity zones // TODO: set up gravity zones
@ -200,13 +200,14 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
const float FIXED_SUBSTEP = 1.0f / 60.0f; const float FIXED_SUBSTEP = 1.0f / 60.0f;
void PhysicsEngine::stepSimulation() { void PhysicsEngine::stepSimulation() {
lock();
// NOTE: the grand order of operations is: // NOTE: the grand order of operations is:
// (1) relay incoming changes // (1) relay incoming changes
// (2) step simulation // (2) step simulation
// (3) synchronize outgoing motion states // (3) synchronize outgoing motion states
// (4) send outgoing packets // (4) send outgoing packets
// this is step (1) // This is step (1).
relayIncomingChangesToSimulation(); relayIncomingChangesToSimulation();
const int MAX_NUM_SUBSTEPS = 4; const int MAX_NUM_SUBSTEPS = 4;
@ -215,9 +216,24 @@ void PhysicsEngine::stepSimulation() {
_clock.reset(); _clock.reset();
float timeStep = btMin(dt, MAX_TIMESTEP); float timeStep = btMin(dt, MAX_TIMESTEP);
// steps (2) and (3) are performed by ThreadSafeDynamicsWorld::stepSimulation()) // This is step (2).
int numSubSteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, FIXED_SUBSTEP); int numSubSteps = _dynamicsWorld->stepSimulation(timeStep, MAX_NUM_SUBSTEPS, FIXED_SUBSTEP);
_frameCount += (uint32_t)numSubSteps; _frameCount += (uint32_t)numSubSteps;
unlock();
// This is step (3) which is done outside of stepSimulation() so we can lock _entityTree.
//
// Unfortunately we have to unlock the simulation (above) before we try to lock the _entityTree
// to avoid deadlock -- the _entityTree may try to lock its EntitySimulation (from which this
// PhysicsEngine derives) when updating/adding/deleting entities so we need to wait for our own
// lock on the tree before we re-lock ourselves.
//
// TODO: untangle these lock sequences.
_entityTree->lockForWrite();
lock();
_dynamicsWorld->synchronizeMotionStates();
unlock();
_entityTree->unlock();
} }
// Bullet collision flags are as follows: // Bullet collision flags are as follows:

View file

@ -15,8 +15,6 @@
* Copied and modified from btDiscreteDynamicsWorld.cpp by AndrewMeadows on 2014.11.12. * Copied and modified from btDiscreteDynamicsWorld.cpp by AndrewMeadows on 2014.11.12.
* */ * */
#include <EntityTree.h>
#include "ThreadSafeDynamicsWorld.h" #include "ThreadSafeDynamicsWorld.h"
#ifdef USE_BULLET_PHYSICS #ifdef USE_BULLET_PHYSICS
@ -24,17 +22,8 @@ ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
btDispatcher* dispatcher, btDispatcher* dispatcher,
btBroadphaseInterface* pairCache, btBroadphaseInterface* pairCache,
btConstraintSolver* constraintSolver, btConstraintSolver* constraintSolver,
btCollisionConfiguration* collisionConfiguration, btCollisionConfiguration* collisionConfiguration)
EntityTree* entities)
: btDiscreteDynamicsWorld(dispatcher, pairCache, constraintSolver, collisionConfiguration) { : btDiscreteDynamicsWorld(dispatcher, pairCache, constraintSolver, collisionConfiguration) {
assert(entities);
_entities = entities;
}
void ThreadSafeDynamicsWorld::synchronizeMotionStates() {
_entities->lockForWrite();
btDiscreteDynamicsWorld::synchronizeMotionStates();
_entities->unlock();
} }
int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep) { int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps, btScalar fixedTimeStep) {
@ -82,10 +71,15 @@ int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps,
} }
} }
// We only sync motion states once at the end of all substeps. // NOTE: We do NOT call synchronizeMotionState() after each substep (to avoid multiple locks on the
// This is to avoid placing multiple, repeated thread locks on _entities. // object data outside of the physics engine). A consequence of this is that the transforms of the
synchronizeMotionStates(); // external objects only ever update at the end of the full step.
// NOTE: We do NOT call synchronizeMotionStates() here. Instead it is called by an external class
// that knows how to lock threads correctly.
clearForces(); clearForces();
return subSteps;
return subSteps;
} }
#endif // USE_BULLET_PHYSICS #endif // USE_BULLET_PHYSICS

View file

@ -21,8 +21,6 @@
#ifdef USE_BULLET_PHYSICS #ifdef USE_BULLET_PHYSICS
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h> #include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
class EntityTree;
ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld { ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld {
public: public:
BT_DECLARE_ALIGNED_ALLOCATOR(); BT_DECLARE_ALIGNED_ALLOCATOR();
@ -31,20 +29,15 @@ public:
btDispatcher* dispatcher, btDispatcher* dispatcher,
btBroadphaseInterface* pairCache, btBroadphaseInterface* pairCache,
btConstraintSolver* constraintSolver, btConstraintSolver* constraintSolver,
btCollisionConfiguration* collisionConfiguration, btCollisionConfiguration* collisionConfiguration);
EntityTree* entities);
// virtual overrides from btDiscreteDynamicsWorld // virtual overrides from btDiscreteDynamicsWorld
int stepSimulation( btScalar timeStep, int maxSubSteps=1, btScalar fixedTimeStep=btScalar(1.)/btScalar(60.)); int stepSimulation( btScalar timeStep, int maxSubSteps=1, btScalar fixedTimeStep=btScalar(1.)/btScalar(60.));
void synchronizeMotionStates();
// btDiscreteDynamicsWorld::m_localTime is the portion of real-time that has not yet been simulated // btDiscreteDynamicsWorld::m_localTime is the portion of real-time that has not yet been simulated
// but is used for MotionState::setWorldTransform() extrapolation (a feature that Bullet uses to provide // but is used for MotionState::setWorldTransform() extrapolation (a feature that Bullet uses to provide
// smoother rendering of objects when the physics simulation loop is ansynchronous to the render loop). // smoother rendering of objects when the physics simulation loop is ansynchronous to the render loop).
float getLocalTimeAccumulation() const { return m_localTime; } float getLocalTimeAccumulation() const { return m_localTime; }
private:
EntityTree* _entities;
}; };
#else // USE_BULLET_PHYSICS #else // USE_BULLET_PHYSICS