From 5a5376c3d5ddd2b3ff44f758015f0bd2fd6641d1 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 28 Mar 2018 13:40:16 -0700 Subject: [PATCH] untangle 'bidding' from 'owned' --- libraries/entities/src/EntitySimulation.cpp | 20 +- libraries/entities/src/EntitySimulation.h | 12 +- .../entities/src/SimpleEntitySimulation.cpp | 12 +- .../entities/src/SimpleEntitySimulation.h | 4 +- libraries/physics/src/EntityMotionState.cpp | 388 +++++++++--------- libraries/physics/src/EntityMotionState.h | 45 +- libraries/physics/src/ObjectMotionState.h | 2 +- .../physics/src/PhysicalEntitySimulation.cpp | 178 ++++++-- .../physics/src/PhysicalEntitySimulation.h | 17 +- libraries/physics/src/PhysicsEngine.cpp | 4 +- 10 files changed, 414 insertions(+), 268 deletions(-) diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 47e3e51fb9..82418e83e9 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -20,7 +20,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { if (_entityTree && _entityTree != tree) { _mortalEntities.clear(); - _nextExpiry = quint64(-1); + _nextExpiry = uint64_t(-1); _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); @@ -30,7 +30,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { void EntitySimulation::updateEntities() { QMutexLocker lock(&_mutex); - quint64 now = usecTimestampNow(); + uint64_t now = usecTimestampNow(); // these methods may accumulate entries in _entitiesToBeDeleted expireMortalEntities(now); @@ -70,16 +70,16 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { } // protected -void EntitySimulation::expireMortalEntities(const quint64& now) { +void EntitySimulation::expireMortalEntities(const uint64_t& now) { if (now > _nextExpiry) { PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size()); // only search for expired entities if we expect to find one - _nextExpiry = quint64(-1); + _nextExpiry = uint64_t(-1); QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _mortalEntities.begin(); while (itemItr != _mortalEntities.end()) { EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < now) { itemItr = _mortalEntities.erase(itemItr); entity->die(); @@ -99,7 +99,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) { } // protected -void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { +void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const uint64_t& now) { PerformanceTimer perfTimer("updatingEntities"); QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _entitiesToUpdate.begin(); @@ -153,7 +153,7 @@ void EntitySimulation::addEntity(EntityItemPointer entity) { entity->deserializeActions(); if (entity->isMortal()) { _mortalEntities.insert(entity); - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < _nextExpiry) { _nextExpiry = expiry; } @@ -200,7 +200,7 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { if (dirtyFlags & Simulation::DIRTY_LIFETIME) { if (entity->isMortal()) { _mortalEntities.insert(entity); - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < _nextExpiry) { _nextExpiry = expiry; } @@ -220,7 +220,7 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { void EntitySimulation::clearEntities() { QMutexLocker lock(&_mutex); _mortalEntities.clear(); - _nextExpiry = quint64(-1); + _nextExpiry = uint64_t(-1); _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); @@ -231,7 +231,7 @@ void EntitySimulation::clearEntities() { _deadEntities.clear(); } -void EntitySimulation::moveSimpleKinematics(const quint64& now) { +void EntitySimulation::moveSimpleKinematics(const uint64_t& now) { PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); while (itemItr != _simpleKinematicEntities.end()) { diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index b5edb31907..19ab170ce5 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -44,7 +44,7 @@ const int DIRTY_SIMULATION_FLAGS = class EntitySimulation : public QObject, public std::enable_shared_from_this { public: - EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { } + EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(uint64_t(-1)) { } virtual ~EntitySimulation() { setEntityTree(NULL); } inline EntitySimulationPointer getThisPointer() const { @@ -71,7 +71,7 @@ public: void clearEntities(); - void moveSimpleKinematics(const quint64& now); + void moveSimpleKinematics(const uint64_t& now); EntityTreePointer getEntityTree() { return _entityTree; } @@ -83,14 +83,14 @@ public: protected: // These pure virtual methods are protected because they are not to be called will-nilly. The base class // calls them in the right places. - virtual void updateEntitiesInternal(const quint64& now) = 0; + virtual void updateEntitiesInternal(const uint64_t& now) = 0; virtual void addEntityInternal(EntityItemPointer entity) = 0; virtual void removeEntityInternal(EntityItemPointer entity); virtual void changeEntityInternal(EntityItemPointer entity) = 0; virtual void clearEntitiesInternal() = 0; - void expireMortalEntities(const quint64& now); - void callUpdateOnEntitiesThatNeedIt(const quint64& now); + void expireMortalEntities(const uint64_t& now); + void callUpdateOnEntitiesThatNeedIt(const uint64_t& now); virtual void sortEntitiesThatMoved(); QMutex _mutex{ QMutex::Recursive }; @@ -114,7 +114,7 @@ private: // An entity may be in more than one list. SetOfEntities _allEntities; // tracks all entities added the simulation SetOfEntities _mortalEntities; // entities that have an expiry - quint64 _nextExpiry; + uint64_t _nextExpiry; SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 583a310b41..266f10cf91 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -18,7 +18,7 @@ #include "EntityItem.h" #include "EntitiesLogging.h" -const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; +const uint64_t MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { QMutexLocker lock(&_mutex); @@ -33,7 +33,7 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { if (entity->getDynamic() && entity->hasLocalVelocity()) { // it is still moving dynamically --> add to orphaned list _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; if (expiry < _nextOwnerlessExpiry) { _nextOwnerlessExpiry = expiry; } @@ -50,7 +50,7 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { } } -void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { +void SimpleEntitySimulation::updateEntitiesInternal(const uint64_t& now) { if (now > _nextOwnerlessExpiry) { // search for ownerless objects that have expired QMutexLocker lock(&_mutex); @@ -58,7 +58,7 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin(); while (itemItr != _entitiesThatNeedSimulationOwner.end()) { EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; if (expiry < now) { // no simulators have volunteered ownership --> remove from list itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr); @@ -96,7 +96,7 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { } else if (entity->getDynamic() && entity->hasLocalVelocity()) { QMutexLocker lock(&_mutex); _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; if (expiry < _nextOwnerlessExpiry) { _nextOwnerlessExpiry = expiry; } @@ -127,7 +127,7 @@ void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { _entitiesWithSimulationOwner.remove(entity); if (entity->getDynamic() && entity->hasLocalVelocity()) { _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; if (expiry < _nextOwnerlessExpiry) { _nextOwnerlessExpiry = expiry; } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 9c7c9a374e..10714be299 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -28,7 +28,7 @@ public: void clearOwnership(const QUuid& ownerID); protected: - virtual void updateEntitiesInternal(const quint64& now) override; + virtual void updateEntitiesInternal(const uint64_t& now) override; virtual void addEntityInternal(EntityItemPointer entity) override; virtual void removeEntityInternal(EntityItemPointer entity) override; virtual void changeEntityInternal(EntityItemPointer entity) override; @@ -38,7 +38,7 @@ protected: SetOfEntities _entitiesWithSimulationOwner; SetOfEntities _entitiesThatNeedSimulationOwner; - quint64 _nextOwnerlessExpiry { 0 }; + uint64_t _nextOwnerlessExpiry { 0 }; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 093010c9e4..9e891806f2 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -26,12 +26,7 @@ #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS #include "EntityTree.h" -#endif -const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; -const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; - -#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool EntityMotionState::entityTreeIsLocked() const { EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -46,6 +41,9 @@ bool entityTreeIsLocked() { } #endif +const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; +const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; + EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : ObjectMotionState(nullptr), @@ -59,7 +57,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _serverActionData(QByteArray()), _lastVelocity(0.0f), _measuredAcceleration(0.0f), - _nextOwnershipBid(0), + _nextBidExpiry(0), _measuredDeltaTime(0.0f), _lastMeasureStep(0), _lastStep(0), @@ -67,6 +65,12 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _accelerationNearlyGravityCount(0), _numInactiveUpdates(1) { + // Why is _numInactiveUpdates initialied to 1? + // Because: when an entity is first created by a LOCAL operatioin its local simulation ownership is assumed, + // which causes it to be immediately placed on the 'owned' list, but in this case an "update" already just + // went out for the object's creation and there is no need to send another. By initializing _numInactiveUpdates + // to 1 here we trick remoteSimulationOutOfSync() to return "false" the first time through for this case. + _type = MOTIONSTATE_TYPE_ENTITY; assert(_entity); assert(entityTreeIsLocked()); @@ -75,7 +79,19 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer // rather than pass the legit shape pointer to the ObjectMotionState ctor above. setShape(shape); - _outgoingPriority = _entity->getPendingOwnershipPriority(); + _bidPriority = _entity->getPendingOwnershipPriority(); + if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { + // client-only entities are always thus, so we cache this fact in _ownershipState + _ownershipState = EntityMotionState::OwnershipState::Unownable; + } + + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); + _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getDynamicData(); + } EntityMotionState::~EntityMotionState() { @@ -87,35 +103,30 @@ EntityMotionState::~EntityMotionState() { } void EntityMotionState::updateServerPhysicsVariables() { - assert(entityTreeIsLocked()); - if (isLocallyOwned()) { - // don't slam these values if we are the simulation owner - return; + if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) { + // only slam these values if we are NOT the simulation owner + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); + _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getDynamicData(); + _lastStep = ObjectMotionState::getWorldSimulationStep(); } - - Transform localTransform; - _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); - _serverVariablesSet = true; - _serverPosition = localTransform.getTranslation(); - _serverRotation = localTransform.getRotation(); - _serverAcceleration = _entity->getAcceleration(); - _serverActionData = _entity->getDynamicData(); } void EntityMotionState::handleDeactivation() { - if (_serverVariablesSet) { - // copy _server data to entity - Transform localTransform = _entity->getLocalTransform(); - localTransform.setTranslation(_serverPosition); - localTransform.setRotation(_serverRotation); - _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); - // and also to RigidBody - btTransform worldTrans; - worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); - worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); - _body->setWorldTransform(worldTrans); - // no need to update velocities... should already be zero - } + // copy _server data to entity + Transform localTransform = _entity->getLocalTransform(); + localTransform.setTranslation(_serverPosition); + localTransform.setRotation(_serverRotation); + _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); + // and also to RigidBody + btTransform worldTrans; + worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); + worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); + _body->setWorldTransform(worldTrans); + // no need to update velocities... should already be zero } // virtual @@ -128,26 +139,29 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { if (_entity->getSimulatorID().isNull()) { // simulation ownership has been removed if (glm::length2(_entity->getWorldVelocity()) == 0.0f) { - // this object is coming to rest --> clear the ACTIVATION flag and _outgoingPriority + // this object is coming to rest --> clear the ACTIVATION flag and _bidPriority flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; _body->setActivationState(WANTS_DEACTIVATION); - _outgoingPriority = 0; + _bidPriority = 0; const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet _body->setDeactivationTime(ACTIVATION_EXPIRY); } else { // disowned object is still moving --> start timer for ownership bid // TODO? put a delay in here proportional to distance from object? - upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); + _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; } _loopsWithoutOwner = 0; _numInactiveUpdates = 0; } else if (isLocallyOwned()) { // we just inherited ownership, make sure our desired priority matches what we have - upgradeOutgoingPriority(_entity->getSimulationPriority()); + upgradeBidPriority(_entity->getSimulationPriority()); } else { - _outgoingPriority = 0; - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + // the entity is owned by someone else, so we clear _bidPriority here + // but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation + // in which case we may try to bid again + _bidPriority = 0; + _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _numInactiveUpdates = 0; } } @@ -156,10 +170,10 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // (1) we own it but may need to change the priority OR... // (2) we don't own it but should bid (because a local script has been changing physics properties) uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); - upgradeOutgoingPriority(newPriority); + upgradeBidPriority(newPriority); // reset bid expiry so that we bid ASAP - _nextOwnershipBid = 0; + _nextBidExpiry = 0; } if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { if (_body->isKinematicObject()) { @@ -282,24 +296,12 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { } } - // ADEBUG: move this _loopsWithoutOwner stuff out of EntityMotionState if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; - if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { - upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); + if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) { + upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); } } - - #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); - qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID(); - qCDebug(physics) << " last edited:" << _entity->getLastEdited() - << formatUsecTime(now - _entity->getLastEdited()) << "ago"; - qCDebug(physics) << " last simulated:" << _entity->getLastSimulated() - << formatUsecTime(now - _entity->getLastSimulated()) << "ago"; - qCDebug(physics) << " last updated:" << _entity->getLastUpdated() - << formatUsecTime(now - _entity->getLastUpdated()) << "ago"; - #endif } @@ -323,26 +325,13 @@ void EntityMotionState::setShape(const btCollisionShape* shape) { } } -bool EntityMotionState::isCandidateForOwnership() const { - assert(_body); - assert(entityTreeIsLocked()); - if (isLocallyOwned()) { - return true; - } else if (_entity->getClientOnly()) { - // don't send updates for someone else's avatarEntities - return false; - } - return _outgoingPriority != 0 || _entity->dynamicDataNeedsTransmit(); -} - bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // NOTE: we only get here if we think we own the simulation DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); - assert(_body); - // Since we own the simulation: make sure _outgoingPriority is not less than current owned priority - // because: an _outgoingPriority of zero indicates that we should drop ownership when we have it. - upgradeOutgoingPriority(_entity->getSimulationPriority()); + // Since we own the simulation: make sure _bidPriority is not less than current owned priority + // because: an _bidPriority of zero indicates that we should drop ownership when we have it. + upgradeBidPriority(_entity->getSimulationPriority()); bool parentTransformSuccess; Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); @@ -354,27 +343,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { worldVelocityToLocal.setTranslation(glm::vec3(0.0f)); } - // if we've never checked before, our _lastStep will be 0, and we need to initialize our state - if (_lastStep == 0) { - btTransform xform = _body->getWorldTransform(); - _serverVariablesSet = true; - _serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin())); - _serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation()); - _serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma()); - _serverAcceleration = Vectors::ZERO; - _serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity())); - _lastStep = simulationStep; - _serverActionData = _entity->getDynamicData(); - _numInactiveUpdates = 1; - return false; - } - - #ifdef WANT_DEBUG - glm::vec3 wasPosition = _serverPosition; - glm::quat wasRotation = _serverRotation; - glm::vec3 wasAngularVelocity = _serverAngularVelocity; - #endif - int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; @@ -385,9 +353,9 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _entity->clearSimulationOwnership(); return false; } - // we resend the inactive update every INACTIVE_UPDATE_PERIOD - // until it is removed from the outgoing updates - // (which happens when we don't own the simulation and it isn't touching our simulation) + // we resend the inactive update with a growing delay: every INACTIVE_UPDATE_PERIOD * _numInactiveUpdates + // until it is removed from the owned list + // (which happens when we no longer own the simulation) const float INACTIVE_UPDATE_PERIOD = 0.5f; return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates); } @@ -418,7 +386,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { if (_entity->dynamicDataNeedsTransmit()) { uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; - upgradeOutgoingPriority(priority); + upgradeBidPriority(priority); return true; } @@ -443,13 +411,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { const float MIN_ERROR_RATIO_SQUARED = 0.0025f; // corresponds to 5% error in 1 second const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec if (speed2 < MIN_SPEED_SQUARED || dx2 / speed2 > MIN_ERROR_RATIO_SQUARED) { - #ifdef WANT_DEBUG - qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ...."; - qCDebug(physics) << "wasPosition:" << wasPosition; - qCDebug(physics) << "bullet position:" << position; - qCDebug(physics) << "_serverPosition:" << _serverPosition; - qCDebug(physics) << "dx2:" << dx2; - #endif return true; } } @@ -469,22 +430,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation()); - #ifdef WANT_DEBUG - if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) { - qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) ...."; - - qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity; - qCDebug(physics) << "_serverAngularVelocity:" << _serverAngularVelocity; - - qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity); - qCDebug(physics) << "length _serverAngularVelocity:" << glm::length(_serverAngularVelocity); - - qCDebug(physics) << "wasRotation:" << wasRotation; - qCDebug(physics) << "bullet actualRotation:" << actualRotation; - qCDebug(physics) << "_serverRotation:" << _serverRotation; - } - #endif - return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } @@ -492,10 +437,9 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. - assert(_body); assert(entityTreeIsLocked()); - // this case prevented by isCandidateForOwnership() + // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { @@ -506,33 +450,111 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { return false; } - if (!isLocallyOwned()) { - // we don't own the simulation + return remoteSimulationOutOfSync(simulationStep); +} - // NOTE: we do not volunteer to own kinematic or static objects - uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0; +void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Bid"); + assert(entityTreeIsLocked()); - bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND - usecTimestampNow() > _nextOwnershipBid; // it is time to bid again - if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) { - // we are insufficiently interested so clear _outgoingPriority - // and reset the bid expiry - _outgoingPriority = 0; - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + if (!_body->isActive()) { + // make sure all derivatives are zero + clearObjectVelocities(); + _numInactiveUpdates = 1; + } else { + glm::vec3 gravity = _entity->getGravity(); + + // if this entity has been accelerated at close to gravity for a certain number of simulation-steps + // let the entity server's estimates include gravity. + const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4; + if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) { + _entity->setAcceleration(gravity); + } else { + _entity->setAcceleration(Vectors::ZERO); } - return shouldBid; + + if (!_body->isStaticOrKinematicObject()) { + const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec + const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec + + bool movingSlowlyLinear = + glm::length2(_entity->getWorldVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD); + bool movingSlowlyAngular = glm::length2(_entity->getWorldAngularVelocity()) < + (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD); + bool movingSlowly = movingSlowlyLinear && movingSlowlyAngular && _entity->getAcceleration() == Vectors::ZERO; + + if (movingSlowly) { + // velocities might not be zero, but we'll fake them as such, which will hopefully help convince + // other simulating observers to deactivate their own copies + clearObjectVelocities(); + } + } + _numInactiveUpdates = 0; } - return remoteSimulationOutOfSync(simulationStep); + EntityItemProperties properties; + Transform localTransform; + glm::vec3 linearVelocity; + glm::vec3 angularVelocity; + _entity->getLocalTransformAndVelocities(localTransform, linearVelocity, angularVelocity); + properties.setPosition(localTransform.getTranslation()); + properties.setRotation(localTransform.getRotation()); + properties.setVelocity(linearVelocity); + properties.setAcceleration(_entity->getAcceleration()); + properties.setAngularVelocity(angularVelocity); + if (_entity->dynamicDataNeedsTransmit()) { + _entity->setDynamicDataNeedsTransmit(false); + properties.setActionData(_entity->getDynamicData()); + } + + if (_entity->updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); + } + + // set the LastEdited of the properties but NOT the entity itself + quint64 now = usecTimestampNow(); + properties.setLastEdited(now); + + // we don't own the simulation for this entity yet, but we're sending a bid for it + uint8_t bidPriority = glm::max(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY); + properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); + // copy _bidPriority into pendingPriority... + _entity->setPendingOwnershipPriority(_bidPriority, now); + // don't forget to remember that we have made a bid + _entity->rememberHasSimulationOwnershipBid(); + + EntityTreeElementPointer element = _entity->getElement(); + EntityTreePointer tree = element ? element->getTree() : nullptr; + + properties.setClientOnly(_entity->getClientOnly()); + properties.setOwningAvatarID(_entity->getOwningAvatarID()); + + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); + _entity->setLastBroadcast(now); // ffor debug/physics status icons + + // NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent + // then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary. + + _lastStep = step; + _nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS; + + // finally: clear _bidPriority + // which will may get promoted before next bid + // or maybe we'll win simulation ownership + _bidPriority = 0; } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { DETAILED_PROFILE_RANGE(simulation_physics, "Send"); assert(entityTreeIsLocked()); + assert(_entity->isLocallyOwned()); if (!_body->isActive()) { // make sure all derivatives are zero - zeroCleanObjectVelocities(); + clearObjectVelocities(); _numInactiveUpdates++; } else { glm::vec3 gravity = _entity->getGravity(); @@ -559,13 +581,13 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies - zeroCleanObjectVelocities(); + clearObjectVelocities(); } } _numInactiveUpdates = 0; } - // remember properties for local server prediction + // remember _serverFoo data for local prediction of server state Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _serverPosition = localTransform.getTranslation(); @@ -574,11 +596,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _serverActionData = _entity->getDynamicData(); EntityItemProperties properties; - - // explicitly set the properties that changed so that they will be packed properties.setPosition(_entity->getLocalPosition()); properties.setRotation(_entity->getLocalOrientation()); - properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); @@ -587,61 +606,35 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setActionData(_serverActionData); } - if (properties.transformChanged()) { - if (_entity->updateQueryAACube()) { - // due to parenting, the server may not know where something is in world-space, so include the bounding cube. - properties.setQueryAACube(_entity->getQueryAACube()); - } + if (_entity->updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); - #ifdef WANT_DEBUG - quint64 lastSimulated = _entity->getLastSimulated(); - qCDebug(physics) << "EntityMotionState::sendUpdate()"; - qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() - << "---------------------------------------------"; - qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); - #endif //def WANT_DEBUG - if (_numInactiveUpdates > 0) { - // we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID + // the entity is stopped and inactive so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); - _outgoingPriority = 0; - _entity->setPendingOwnershipPriority(_outgoingPriority, now); - } else if (!isLocallyOwned()) { - // we don't own the simulation for this entity yet, but we're sending a bid for it - uint8_t bidPriority = glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); - properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); - _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; - // copy _outgoingPriority into pendingPriority... - _entity->setPendingOwnershipPriority(_outgoingPriority, now); - // don't forget to remember that we have made a bid - _entity->rememberHasSimulationOwnershipBid(); - // ...then reset _outgoingPriority - _outgoingPriority = 0; - // _outgoingPriority will be re-computed before next bid, - // or will be set to agree with ownership priority should we win the bid - } else if (_outgoingPriority != _entity->getSimulationPriority()) { - // we own the simulation but our desired priority has changed - if (_outgoingPriority == 0) { + _bidPriority = 0; + _entity->setPendingOwnershipPriority(_bidPriority, now); + } else if (_bidPriority != _entity->getSimulationPriority()) { + // our desired priority has changed + if (_bidPriority == 0) { // we should release ownership properties.clearSimulationOwner(); } else { // we just need to change the priority - properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority); + properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority); } - _entity->setPendingOwnershipPriority(_outgoingPriority, now); + _entity->setPendingOwnershipPriority(_bidPriority, now); } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast(packetSender); - #ifdef WANT_DEBUG - qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; - #endif EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -650,7 +643,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setOwningAvatarID(_entity->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); - _entity->setLastBroadcast(now); + _entity->setLastBroadcast(now); // for debug/physics status icons // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. @@ -661,13 +654,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); - newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, descendant->getID(), newQueryCubeProperties); - entityDescendant->setLastBroadcast(now); + entityDescendant->setLastBroadcast(now); // for debug/physics status icons } } }); @@ -726,7 +718,7 @@ QUuid EntityMotionState::getSimulatorID() const { void EntityMotionState::bump(uint8_t priority) { assert(priority != 0); - upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); + upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -750,13 +742,14 @@ void EntityMotionState::measureBodyAcceleration() { _lastMeasureStep = thisStep; _measuredDeltaTime = dt; - // Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt + // Note: the integration equation for velocity uses damping (D): v1 = (v0 + a * dt) * (1 - D)^dt // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt glm::vec3 velocity = getBodyLinearVelocityGTSigma(); _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; _lastVelocity = velocity; if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { + // we fall in here when _lastMeasureStep is old: the body has just become active _loopsWithoutOwner = 0; _lastStep = ObjectMotionState::getWorldSimulationStep(); _numInactiveUpdates = 0; @@ -803,20 +796,41 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma _entity->computeCollisionGroupAndFinalMask(group, mask); } +bool EntityMotionState::shouldSendBid() { + if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) { + return true; + } else { + // NOTE: this 'else' case has a side-effect: it clears _bidPriority + // which may be updated next simulation step (via collision or script event) + _bidPriority = 0; + return false; + } +} + bool EntityMotionState::isLocallyOwned() const { return _entity->getSimulatorID() == Physics::getSessionUUID(); } -bool EntityMotionState::shouldBeLocallyOwned() const { - return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || +bool EntityMotionState::isLocallyOwnedOrShouldBe() const { + return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) || _entity->getSimulatorID() == Physics::getSessionUUID(); } -void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) { - _outgoingPriority = glm::max(_outgoingPriority, priority); +void EntityMotionState::initForBid() { + assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); + _ownershipState = EntityMotionState::OwnershipState::PendingBid; } -void EntityMotionState::zeroCleanObjectVelocities() const { +void EntityMotionState::initForOwned() { + assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); + _ownershipState = EntityMotionState::OwnershipState::LocallyOwned; +} + +void EntityMotionState::upgradeBidPriority(uint8_t priority) { + _bidPriority = glm::max(_bidPriority, priority); +} + +void EntityMotionState::clearObjectVelocities() const { // If transform or velocities are flagged as dirty it means a network or scripted change // occured between the beginning and end of the stepSimulation() and we DON'T want to apply // these physics simulation results. diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 9722fe0a7a..b37fa9df68 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -24,12 +24,17 @@ class EntityMotionState : public ObjectMotionState { public: + enum class OwnershipState { + NotLocallyOwned = 0, + PendingBid, + LocallyOwned, + Unownable + }; EntityMotionState() = delete; EntityMotionState(btCollisionShape* shape, EntityItemPointer item); virtual ~EntityMotionState(); - void updateServerPhysicsVariables(); void handleDeactivation(); virtual void handleEasyChanges(uint32_t& flags) override; virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; @@ -45,9 +50,8 @@ public: // this relays outgoing position/rotation to the EntityItem virtual void setWorldTransform(const btTransform& worldTrans) override; - bool isCandidateForOwnership() const; - bool remoteSimulationOutOfSync(uint32_t simulationStep); bool shouldSendUpdate(uint32_t simulationStep); + void sendBid(OctreeEditPacketSender* packetSender, uint32_t step); void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); virtual uint32_t getIncomingDirtyFlags() override; @@ -80,15 +84,24 @@ public: virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; + bool shouldSendBid(); bool isLocallyOwned() const override; - bool shouldBeLocallyOwned() const override; + bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents() friend class PhysicalEntitySimulation; + OwnershipState getOwnershipState() const { return _ownershipState; } protected: - // changes _outgoingPriority only if priority is larger - void upgradeOutgoingPriority(uint8_t priority); - void zeroCleanObjectVelocities() const; + uint64_t getNextBidExpiry() const { return _nextBidExpiry; } + void initForBid(); + void initForOwned(); + void clearOwnershipState() { _ownershipState = OwnershipState::NotLocallyOwned; } + void updateServerPhysicsVariables(); + bool remoteSimulationOutOfSync(uint32_t simulationStep); + + // changes _bidPriority only if priority is larger + void upgradeBidPriority(uint8_t priority); + void clearObjectVelocities() const; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool entityTreeIsLocked() const; @@ -103,7 +116,17 @@ protected: // and is only cleared in the DTOR EntityItemPointer _entity; - bool _serverVariablesSet { false }; + // These "_serverFoo" variables represent what we think the server knows. + // They are used in two different modes: + // + // (1) For remotely owned simulation: we store the last values recieved from the server. + // When the body comes to rest and goes inactive we slam its final transforms to agree with the last server + // update. This to reduce state synchronization errors when the local simulation deviated from remote. + // + // (2) For locally owned simulation: we store the last values sent to the server, integrated forward over time + // according to how we think the server doing it. We calculate the error between the true local transform + // and the remote to decide when to send another update. + // glm::vec3 _serverPosition; // in simulation-frame (not world-frame) glm::quat _serverRotation; glm::vec3 _serverVelocity; @@ -114,16 +137,18 @@ protected: glm::vec3 _lastVelocity; glm::vec3 _measuredAcceleration; - quint64 _nextOwnershipBid { 0 }; + quint64 _nextBidExpiry { 0 }; float _measuredDeltaTime; uint32_t _lastMeasureStep; uint32_t _lastStep; // last step of server extrapolation + OwnershipState _ownershipState { OwnershipState::NotLocallyOwned }; uint8_t _loopsWithoutOwner; mutable uint8_t _accelerationNearlyGravityCount; uint8_t _numInactiveUpdates { 1 }; - uint8_t _outgoingPriority { 0 }; + uint8_t _bidPriority { 0 }; + bool _serverVariablesSet { false }; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 8383c539f4..fbda9366fc 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -164,7 +164,7 @@ public: void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } virtual bool isLocallyOwned() const { return false; } - virtual bool shouldBeLocallyOwned() const { return false; } + virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents() friend class PhysicsEngine; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 103640e138..9129e29671 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -40,7 +40,7 @@ void PhysicalEntitySimulation::init( } // begin EntitySimulation overrides -void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) { +void PhysicalEntitySimulation::updateEntitiesInternal(const uint64_t& now) { // Do nothing here because the "internal" update the PhysicsEngine::stepSimualtion() which is done elsewhere. } @@ -65,7 +65,7 @@ void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { - _outgoingChanges.remove(motionState); + removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); } else if (entity->isDead() && entity->getElement()) { _deadEntities.insert(entity); @@ -73,6 +73,42 @@ void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { } } +void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) { + assert(motionState); + if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) { + for (uint32_t i = 0; i < _owned.size(); ++i) { + if (_owned[i] == motionState) { + _owned[i]->clearOwnershipState(); + if (i < _owned.size() - 1) { + _owned[i] = _owned.back(); + } + _owned.pop_back(); + } + } + } else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) { + for (uint32_t i = 0; i < _bids.size(); ++i) { + if (_bids[i] == motionState) { + _bids[i]->clearOwnershipState(); + if (i < _bids.size() - 1) { + _bids[i] = _bids.back(); + } + _bids.pop_back(); + } + } + } +} + +void PhysicalEntitySimulation::clearOwnershipData() { + for (uint32_t i = 0; i < _owned.size(); ++i) { + _owned[i]->clearOwnershipState(); + } + _owned.clear(); + for (uint32_t i = 0; i < _bids.size(); ++i) { + _bids[i]->clearOwnershipState(); + } + _bids.clear(); +} + void PhysicalEntitySimulation::takeDeadEntities(SetOfEntities& deadEntities) { QMutexLocker lock(&_mutex); for (auto entity : _deadEntities) { @@ -93,15 +129,15 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { if (motionState) { if (!entity->shouldBePhysical()) { // the entity should be removed from the physical simulation - _pendingChanges.remove(motionState); + _incomingChanges.remove(motionState); _physicalObjects.remove(motionState); - _outgoingChanges.remove(motionState); + removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); if (entity->isMovingRelativeToParent()) { _simpleKinematicEntities.insert(entity); } } else { - _pendingChanges.insert(motionState); + _incomingChanges.insert(motionState); } } else if (entity->shouldBePhysical()) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. @@ -136,10 +172,10 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { _physicalObjects.clear(); // clear all other lists specific to this derived class + clearOwnershipData(); _entitiesToRemoveFromPhysics.clear(); _entitiesToAddToPhysics.clear(); - _pendingChanges.clear(); - _outgoingChanges.clear(); + _incomingChanges.clear(); } // virtual @@ -163,8 +199,8 @@ const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhys _deadEntities.insert(entity); } - _pendingChanges.remove(motionState); - _outgoingChanges.remove(motionState); + _incomingChanges.remove(motionState); + removeOwnershipData(motionState); _physicalObjects.remove(motionState); // remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine) @@ -232,18 +268,18 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) { QMutexLocker lock(&_mutex); for (auto object : objectsToChange) { - _pendingChanges.insert(static_cast(object)); + _incomingChanges.insert(static_cast(object)); } } void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) { result.clear(); QMutexLocker lock(&_mutex); - for (auto stateItr : _pendingChanges) { + for (auto stateItr : _incomingChanges) { EntityMotionState* motionState = &(*stateItr); result.push_back(motionState); } - _pendingChanges.clear(); + _incomingChanges.clear(); } void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) { @@ -263,18 +299,22 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size()); QMutexLocker lock(&_mutex); - // walk the motionStates looking for those that correspond to entities - { - PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size()); - for (auto stateItr : motionStates) { - ObjectMotionState* state = &(*stateItr); - assert(state); - if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { - EntityMotionState* entityState = static_cast(state); - if (entityState->isCandidateForOwnership()) { - _outgoingChanges.insert(entityState); + for (auto stateItr : motionStates) { + ObjectMotionState* state = &(*stateItr); + assert(state); + if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { + EntityMotionState* entityState = static_cast(state); + _entitiesToSort.insert(entityState->getEntity()); + if (entityState->getOwnershipState() == EntityMotionState::OwnershipState::NotLocallyOwned) { + // NOTE: entityState->getOwnershipState() reflects what ownership list (_bids or _owned) it is in + // and is distinct from entityState->isLocallyOwned() which checks the simulation ownership + // properties of the corresponding EntityItem. It is possible for the two states to be out + // of sync. In fact, we're trying to put them back into sync here. + if (entityState->isLocallyOwned()) { + addOwnership(entityState); + } else if (entityState->shouldSendBid()) { + addOwnershipBid(entityState); } - _entitiesToSort.insert(entityState->getEntity()); } } } @@ -284,26 +324,83 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta _lastStepSendPackets = numSubsteps; if (Physics::getSessionUUID().isNull()) { - // usually don't get here, but if so --> nothing to do - _outgoingChanges.clear(); - return; + // usually don't get here, but if so clear all ownership + clearOwnershipData(); } + // send updates before bids, because this simplifies the logic thasuccessful bids will immediately send an update when added to the 'owned' list + sendOwnedUpdates(numSubsteps); + sendOwnershipBids(numSubsteps); + } +} - // look for entities to prune or update - PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size()); - QSet::iterator stateItr = _outgoingChanges.begin(); - while (stateItr != _outgoingChanges.end()) { - EntityMotionState* state = *stateItr; - if (!state->isCandidateForOwnership()) { - // prune - stateItr = _outgoingChanges.erase(stateItr); - } else if (state->shouldSendUpdate(numSubsteps)) { - // update - state->sendUpdate(_entityPacketSender, numSubsteps); - ++stateItr; - } else { - ++stateItr; +void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) { + motionState->initForBid(); + motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps()); + _bids.push_back(motionState); + _nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry()); +} + +void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) { + motionState->initForOwned(); + _owned.push_back(motionState); +} + +void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) { + uint64_t now = usecTimestampNow(); + if (now > _nextBidExpiry) { + PROFILE_RANGE_EX(simulation_physics, "Bid", 0x00000000, (uint64_t)_bids.size()); + _nextBidExpiry = (uint64_t)(-1); + uint32_t i = 0; + while (i < _bids.size()) { + bool removeBid = false; + if (_bids[i]->isLocallyOwned()) { + // when an object transitions from 'bid' to 'owned' we are changing the "mode" of data stored + // in the EntityMotionState::_serverFoo variables (please see comments in EntityMotionState.h) + // therefore we need to immediately send an update so that the values stored are what we're + // "telling" the server rather than what we've been "hearing" from the server. + _bids[i]->sendUpdate(_entityPacketSender, numSubsteps); + + addOwnership(_bids[i]); + removeBid = true; + } else if (!_bids[i]->shouldSendBid()) { + removeBid = true; + _bids[i]->clearOwnershipState(); } + if (removeBid) { + if (i < _bids.size() - 1) { + _bids[i] = _bids.back(); + } + _bids.pop_back(); + } else { + if (now > _bids[i]->getNextBidExpiry()) { + _bids[i]->sendBid(_entityPacketSender, numSubsteps); + _nextBidExpiry = glm::min(_nextBidExpiry, _bids[i]->getNextBidExpiry()); + } + ++i; + } + } + } +} + +void PhysicalEntitySimulation::sendOwnedUpdates(uint32_t numSubsteps) { + PROFILE_RANGE_EX(simulation_physics, "Update", 0x00000000, (uint64_t)_owned.size()); + uint32_t i = 0; + while (i < _owned.size()) { + if (!_owned[i]->isLocallyOwned()) { + if (_owned[i]->shouldSendBid()) { + addOwnershipBid(_owned[i]); + } else { + _owned[i]->clearOwnershipState(); + } + if (i < _owned.size() - 1) { + _owned[i] = _owned.back(); + } + _owned.pop_back(); + } else { + if (_owned[i]->shouldSendUpdate(numSubsteps)) { + _owned[i]->sendUpdate(_entityPacketSender, numSubsteps); + } + ++i; } } } @@ -318,7 +415,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll } } - void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) { if (_physicsEngine) { // FIXME put fine grain locking into _physicsEngine diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 90e4a79a18..72107fa278 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -45,12 +45,15 @@ signals: protected: // only called by EntitySimulation // overrides for EntitySimulation - virtual void updateEntitiesInternal(const quint64& now) override; + virtual void updateEntitiesInternal(const uint64_t& now) override; virtual void addEntityInternal(EntityItemPointer entity) override; virtual void removeEntityInternal(EntityItemPointer entity) override; virtual void changeEntityInternal(EntityItemPointer entity) override; virtual void clearEntitiesInternal() override; + void removeOwnershipData(EntityMotionState* motionState); + void clearOwnershipData(); + public: virtual void prepareEntityForDelete(EntityItemPointer entity) override; @@ -67,20 +70,28 @@ public: EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } + void addOwnershipBid(EntityMotionState* motionState); + void addOwnership(EntityMotionState* motionState); + void sendOwnershipBids(uint32_t numSubsteps); + void sendOwnedUpdates(uint32_t numSubsteps); + private: SetOfEntities _entitiesToAddToPhysics; SetOfEntities _entitiesToRemoveFromPhysics; VectorOfMotionStates _objectsToDelete; - SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed - SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server + SetOfEntityMotionStates _incomingChanges; // EntityMotionStates that have changed from external sources + // and need their RigidBodies updated SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine PhysicsEnginePointer _physicsEngine = nullptr; EntityEditPacketSender* _entityPacketSender = nullptr; + std::vector _owned; + std::vector _bids; + uint64_t _nextBidExpiry; uint32_t _lastStepSendPackets { 0 }; }; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 2d84f0cef1..1d4c385f07 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -571,7 +571,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { // modify the logic below. // // We only create events when at least one of the objects is (or should be) owned in the local simulation. - if (motionStateA && (motionStateA->shouldBeLocallyOwned())) { + if (motionStateA && (motionStateA->isLocallyOwnedOrShouldBe())) { QUuid idA = motionStateA->getObjectID(); QUuid idB; if (motionStateB) { @@ -582,7 +582,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { (motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f)); glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB); _collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange)); - } else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) { + } else if (motionStateB && (motionStateB->isLocallyOwnedOrShouldBe())) { QUuid idB = motionStateB->getObjectID(); QUuid idA; if (motionStateA) {