Merge pull request #4066 from AndrewMeadows/inertia

thread safety for EntitySimulation
This commit is contained in:
Brad Hefta-Gaub 2015-01-08 16:05:07 -08:00
commit 0a176b3c9e
10 changed files with 95 additions and 61 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
// and we can stop searching.
if (entityTreeElement == details.containingElement) {
EntityItemID entityItemID = details.entity->getEntityItemID();
EntityItem* theEntity = entityTreeElement->getEntityWithEntityItemID(entityItemID); // find the actual entity
assert(theEntity);
_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!
EntityItem* theEntity = details.entity;
assert(entityTreeElement->removeEntityItem(theEntity)); // remove it from the element
_tree->setContainingElement(details.entity->getEntityItemID(), NULL); // update or id to element lookup
_foundCount++;
}
}

View file

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

View file

@ -1019,7 +1019,6 @@ void EntityItem::recalculateCollisionShape() {
}
const float MIN_POSITION_DELTA = 0.0001f;
const float MIN_DIMENSION_DELTA = 0.0001f;
const float MIN_ALIGNMENT_DOT = 0.9999f;
const float MIN_MASS_DELTA = 0.001f;
const float MIN_VELOCITY_DELTA = 0.025f;
@ -1045,17 +1044,17 @@ void EntityItem::updatePositionInMeters(const glm::vec3& value) {
}
void EntityItem::updateDimensions(const glm::vec3& value) {
if (glm::distance(_dimensions, value) * (float)TREE_SCALE > MIN_DIMENSION_DELTA) {
_dimensions = value;
if (_dimensions != value) {
_dimensions = glm::abs(value);
recalculateCollisionShape();
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
}
}
void EntityItem::updateDimensionsInMeters(const glm::vec3& value) {
glm::vec3 dimensions = value / (float) TREE_SCALE;
if (glm::distance(_dimensions, dimensions) * (float)TREE_SCALE > MIN_DIMENSION_DELTA) {
_dimensions = dimensions;
glm::vec3 dimensions = glm::abs(value) / (float) TREE_SCALE;
if (_dimensions != dimensions) {
_dimensions = dimensions;
recalculateCollisionShape();
_dirtyFlags |= (EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_MASS);
}

View file

@ -38,6 +38,7 @@ void EntitySimulation::updateEntities(QSet<EntityItem*>& entitiesToDelete) {
_entitiesToDelete.clear();
}
// private
void EntitySimulation::expireMortalEntities(const quint64& now) {
if (now > _nextExpiry) {
// 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) {
PerformanceTimer perfTimer("updatingEntities");
QSet<EntityItem*>::iterator itemItr = _updateableEntities.begin();
@ -79,6 +81,7 @@ void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
}
}
// private
void EntitySimulation::sortEntitiesThatMoved() {
// 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.

View file

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

View file

@ -15,7 +15,6 @@
#include "EntitySimulation.h"
#include "AddEntityOperator.h"
#include "DeleteEntityOperator.h"
#include "MovingEntitiesOperator.h"
#include "UpdateEntityOperator.h"
@ -40,7 +39,9 @@ EntityTreeElement* EntityTree::createNewElement(unsigned char * octalCode) {
void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
// this would be a good place to clean up our entities...
if (_simulation) {
_simulation->lock();
_simulation->clearEntities();
_simulation->unlock();
}
foreach (EntityTreeElement* element, _entityToElementMap) {
element->cleanupEntities();
@ -84,7 +85,9 @@ void EntityTree::postAddEntity(EntityItem* entity) {
assert(entity);
// check to see if we need to simulate this entity..
if (_simulation) {
_simulation->lock();
_simulation->addEntity(entity);
_simulation->unlock();
}
_isDirty = true;
emit addingEntity(entity->getEntityItemID());
@ -141,7 +144,9 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
if (newFlags) {
if (_simulation) {
if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->lock();
_simulation->entityChanged(entity);
_simulation->unlock();
}
} else {
// normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly
@ -204,21 +209,6 @@ EntityItem* EntityTree::addEntity(const EntityItemID& entityID, const EntityItem
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) {
emit entityScriptChanging(entityItemID);
}
@ -231,7 +221,9 @@ void EntityTree::setSimulation(EntitySimulation* simulation) {
if (_simulation && _simulation != simulation) {
// It's important to clearEntities() on the simulation since taht will update each
// EntityItem::_simulationState correctly so as to not confuse the next _simulation.
_simulation->lock();
_simulation->clearEntities();
_simulation->unlock();
}
_simulation = simulation;
}
@ -242,6 +234,7 @@ void EntityTree::deleteEntity(const EntityItemID& entityID) {
// NOTE: callers must lock the tree before using this method
DeleteEntityOperator theOperator(this, entityID);
recurseTreeWithOperator(&theOperator);
processRemovedEntities(theOperator);
_isDirty = true;
}
@ -255,9 +248,36 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs) {
}
recurseTreeWithOperator(&theOperator);
processRemovedEntities(theOperator);
_isDirty = true;
}
void EntityTree::processRemovedEntities(const DeleteEntityOperator& 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();
}
}
/// This method is used to find and fix entity IDs that are shifting from creator token based to known ID based entity IDs.
/// This should only be used on a client side (viewing) tree. The typical usage is that a local editor has been creating
/// entities in the local tree, those entities have creatorToken based entity IDs. But those entity edits are also sent up to
@ -592,7 +612,9 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
void EntityTree::entityChanged(EntityItem* entity) {
if (_simulation) {
_simulation->lock();
_simulation->entityChanged(entity);
_simulation->unlock();
}
}
@ -600,7 +622,9 @@ void EntityTree::update() {
if (_simulation) {
lockForWrite();
QSet<EntityItem*> entitiesToDelete;
_simulation->lock();
_simulation->updateEntities(entitiesToDelete);
_simulation->unlock();
if (entitiesToDelete.size() > 0) {
// translate into list of ID's
QSet<EntityItemID> idsToDelete;

View file

@ -16,6 +16,7 @@
#include <Octree.h>
#include "EntityTreeElement.h"
#include "DeleteEntityOperator.h"
class Model;
@ -146,8 +147,6 @@ public:
void entityChanged(EntityItem* entity);
void trackDeletedEntity(EntityItem* entity);
void emitEntityScriptChanging(const EntityItemID& entityItemID);
void setSimulation(EntitySimulation* simulation);
@ -160,6 +159,7 @@ signals:
private:
void processRemovedEntities(const DeleteEntityOperator& theOperator);
bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties,
EntityTreeElement* containingElement);
static bool findNearPointOperation(OctreeElement* element, void* extraData);

View file

@ -169,7 +169,7 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
_collisionDispatcher = new btCollisionDispatcher(_collisionConfig);
_broadphaseFilter = new btDbvtBroadphase();
_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
// TODO: set up gravity zones
@ -200,13 +200,14 @@ void PhysicsEngine::init(EntityEditPacketSender* packetSender) {
const float FIXED_SUBSTEP = 1.0f / 60.0f;
void PhysicsEngine::stepSimulation() {
lock();
// NOTE: the grand order of operations is:
// (1) relay incoming changes
// (2) step simulation
// (3) synchronize outgoing motion states
// (4) send outgoing packets
// this is step (1)
// This is step (1).
relayIncomingChangesToSimulation();
const int MAX_NUM_SUBSTEPS = 4;
@ -215,9 +216,24 @@ void PhysicsEngine::stepSimulation() {
_clock.reset();
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);
_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:

View file

@ -15,8 +15,6 @@
* Copied and modified from btDiscreteDynamicsWorld.cpp by AndrewMeadows on 2014.11.12.
* */
#include <EntityTree.h>
#include "ThreadSafeDynamicsWorld.h"
#ifdef USE_BULLET_PHYSICS
@ -24,17 +22,8 @@ ThreadSafeDynamicsWorld::ThreadSafeDynamicsWorld(
btDispatcher* dispatcher,
btBroadphaseInterface* pairCache,
btConstraintSolver* constraintSolver,
btCollisionConfiguration* collisionConfiguration,
EntityTree* entities)
btCollisionConfiguration* 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) {
@ -82,10 +71,15 @@ int ThreadSafeDynamicsWorld::stepSimulation( btScalar timeStep, int maxSubSteps,
}
}
// We only sync motion states once at the end of all substeps.
// This is to avoid placing multiple, repeated thread locks on _entities.
synchronizeMotionStates();
// NOTE: We do NOT call synchronizeMotionState() after each substep (to avoid multiple locks on the
// object data outside of the physics engine). A consequence of this is that the transforms of the
// 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();
return subSteps;
return subSteps;
}
#endif // USE_BULLET_PHYSICS

View file

@ -21,8 +21,6 @@
#ifdef USE_BULLET_PHYSICS
#include <BulletDynamics/Dynamics/btDiscreteDynamicsWorld.h>
class EntityTree;
ATTRIBUTE_ALIGNED16(class) ThreadSafeDynamicsWorld : public btDiscreteDynamicsWorld {
public:
BT_DECLARE_ALIGNED_ALLOCATOR();
@ -31,20 +29,15 @@ public:
btDispatcher* dispatcher,
btBroadphaseInterface* pairCache,
btConstraintSolver* constraintSolver,
btCollisionConfiguration* collisionConfiguration,
EntityTree* entities);
btCollisionConfiguration* collisionConfiguration);
// virtual overrides from btDiscreteDynamicsWorld
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
// 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).
float getLocalTimeAccumulation() const { return m_localTime; }
private:
EntityTree* _entities;
};
#else // USE_BULLET_PHYSICS