mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 11:43:16 +02:00
Merge pull request #4066 from AndrewMeadows/inertia
thread safety for EntitySimulation
This commit is contained in:
commit
0a176b3c9e
10 changed files with 95 additions and 61 deletions
|
@ -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++;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue