diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b8e17738f3..20c93a7ae5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2456,7 +2456,7 @@ void Application::update(float deltaTime) { if (_physicsEngine.hasOutgoingChanges()) { _entitySimulation.lock(); - _entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges()); + _entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges(), _physicsEngine.getSessionID()); _entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents()); _entitySimulation.unlock(); _physicsEngine.dumpStatsIfNecessary(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 470426d55e..e618504a5c 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -569,7 +569,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, setSimulatorID); + READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); } if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { @@ -946,7 +946,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove); SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, setSimulatorID); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, updateSimulatorID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(marketplaceID, setMarketplaceID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); @@ -1191,16 +1191,16 @@ void EntityItem::updateVelocityInDomainUnits(const glm::vec3& value) { void EntityItem::updateVelocity(const glm::vec3& value) { auto delta = glm::distance(_velocity, value); if (delta > IGNORE_LINEAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY; const float MIN_LINEAR_SPEED = 0.001f; if (glm::length(value) < MIN_LINEAR_SPEED) { _velocity = ENTITY_ITEM_ZERO_VEC3; } else { _velocity = value; - } - _dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY; - - if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { - _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + // only activate when setting non-zero velocity + if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } } @@ -1238,9 +1238,10 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) { _angularVelocity = ENTITY_ITEM_ZERO_VEC3; } else { _angularVelocity = value; - } - if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { - _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + // only activate when setting non-zero velocity + if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { + _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; + } } } } @@ -1275,8 +1276,14 @@ void EntityItem::updateLifetime(float value) { } void EntityItem::setSimulatorID(const QUuid& value) { + _simulatorID = value; + _simulatorIDChangedTime = usecTimestampNow(); +} + +void EntityItem::updateSimulatorID(const QUuid& value) { if (_simulatorID != value) { _simulatorID = value; _simulatorIDChangedTime = usecTimestampNow(); + _dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; } } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index b9dc14251c..cc3ef54f53 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -82,6 +82,7 @@ public: DIRTY_UPDATEABLE = 0x0200, DIRTY_MATERIAL = 0x00400, DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object + DIRTY_SIMULATOR_ID = 0x1000, DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION, DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY }; @@ -286,6 +287,7 @@ public: QUuid getSimulatorID() const { return _simulatorID; } void setSimulatorID(const QUuid& value); + void updateSimulatorID(const QUuid& value); quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; } const QString& getMarketplaceID() const { return _marketplaceID; } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 2d68a20022..c86d9b5efd 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -495,7 +495,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked); COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures); COPY_PROPERTY_FROM_QSCRIPTVALUE(userData, QString, setUserData); - COPY_PROPERTY_FROM_QSCRIPTVALUE(simulatorID, QUuid, setSimulatorID); + //COPY_PROPERTY_FROM_QSCRIPTVALUE(simulatorID, QUuid, setSimulatorID); DO NOT accept this info from QScriptValue COPY_PROPERTY_FROM_QSCRIPTVALUE(text, QString, setText); COPY_PROPERTY_FROM_QSCRIPTVALUE(lineHeight, float, setLineHeight); COPY_PROPERTY_FROM_QSCRIPTVALUE(textColor, xColor, setTextColor); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 46ca70aa43..62e61dc039 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -61,13 +61,12 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { } } - - -void setSimId(EntityItemProperties& propertiesWithSimID, EntityItem* entity) { +void bidForSimulationOwnership(EntityItemProperties& properties) { + // We make a bid for simulation ownership by declaring our sessionID as simulation owner + // in the outgoing properties. The EntityServer may accept the bid or might not. auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); - propertiesWithSimID.setSimulatorID(myNodeID); - entity->setSimulatorID(myNodeID); + properties.setSimulatorID(myNodeID); } @@ -89,7 +88,7 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro entity->setLastBroadcast(usecTimestampNow()); if (entity) { // This Node is creating a new object. If it's in motion, set this Node as the simulator. - setSimId(propertiesWithSimID, entity); + bidForSimulationOwnership(propertiesWithSimID); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; @@ -163,29 +162,31 @@ EntityItemID EntityScriptingInterface::editEntity(EntityItemID entityID, const E } } - EntityItemProperties propertiesWithSimID = properties; - // If we have a local entity tree set, then also update it. We can do this even if we don't know // the actual id, because we can edit out local entities just with creatorTokenID if (_entityTree) { _entityTree->lockForWrite(); - _entityTree->updateEntity(entityID, propertiesWithSimID, canAdjustLocks()); + _entityTree->updateEntity(entityID, properties); _entityTree->unlock(); } // if at this point, we know the id, send the update to the entity server if (entityID.isKnownID) { // make sure the properties has a type, so that the encode can know which properties to include - if (propertiesWithSimID.getType() == EntityTypes::Unknown) { + if (properties.getType() == EntityTypes::Unknown) { EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { + // we need to change the outgoing properties, so we make a copy, modify, and send. + EntityItemProperties modifiedProperties = properties; entity->setLastBroadcast(usecTimestampNow()); - propertiesWithSimID.setType(entity->getType()); - setSimId(propertiesWithSimID, entity); + modifiedProperties.setType(entity->getType()); + bidForSimulationOwnership(modifiedProperties); + queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, modifiedProperties); + return entityID; } } - queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, propertiesWithSimID); + queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties); } return entityID; diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index a73afe9fd7..f5a100eba0 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -37,7 +37,8 @@ const int DIRTY_SIMULATION_FLAGS = EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_LIFETIME | EntityItem::DIRTY_UPDATEABLE | - EntityItem::DIRTY_MATERIAL; + EntityItem::DIRTY_MATERIAL | + EntityItem::DIRTY_SIMULATOR_ID; class EntitySimulation : public QObject { Q_OBJECT diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ef49aca87a..e90a01383b 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -86,7 +86,7 @@ void EntityTree::postAddEntity(EntityItem* entity) { emit addingEntity(entity->getEntityItemID()); } -bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool allowLockChange) { +bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entityID); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID; @@ -99,22 +99,34 @@ bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProp return false; } - return updateEntityWithElement(existingEntity, properties, containingElement, allowLockChange); + return updateEntityWithElement(existingEntity, properties, containingElement, senderNode); } -bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange) { +bool EntityTree::updateEntity(EntityItem* entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID()); if (!containingElement) { qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID=" << entity->getEntityItemID(); return false; } - return updateEntityWithElement(entity, properties, containingElement, allowLockChange); + return updateEntityWithElement(entity, properties, containingElement, senderNode); } bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& origProperties, - EntityTreeElement* containingElement, bool allowLockChange) { + EntityTreeElement* containingElement, const SharedNodePointer& senderNode) { EntityItemProperties properties = origProperties; + + bool allowLockChange; + QUuid senderID; + if (senderNode.isNull()) { + auto nodeList = DependencyManager::get(); + allowLockChange = nodeList->getThisNodeCanAdjustLocks(); + senderID = nodeList->getSessionUUID(); + } else { + allowLockChange = senderNode->getCanAdjustLocks(); + senderID = senderNode->getUUID(); + } + if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; @@ -134,22 +146,41 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro } } } else { - if (properties.simulatorIDChanged() && - !entity->getSimulatorID().isNull() && - properties.getSimulatorID() != entity->getSimulatorID()) { - // A Node is trying to take ownership of the simulation of this entity from another Node. Only allow this - // if ownership hasn't recently changed. - if (usecTimestampNow() - entity->getSimulatorIDChangedTime() < SIMULATOR_CHANGE_LOCKOUT_PERIOD) { - qCDebug(entities) << "simulator_change_lockout_period:" - << entity->getSimulatorID() << "to" << properties.getSimulatorID(); + if (getIsServer()) { + bool simulationBlocked = !entity->getSimulatorID().isNull(); + if (properties.simulatorIDChanged()) { + QUuid submittedID = properties.getSimulatorID(); + // a legit interface will only submit their own ID or NULL: + if (submittedID.isNull()) { + if (entity->getSimulatorID() == senderID) { + // We only allow the simulation owner to clear their own simulationID's. + simulationBlocked = false; + } + // else: We assume the sender really did believe it was the simulation owner when it sent + } else if (submittedID == senderID) { + // the sender is trying to take or continue ownership + if (entity->getSimulatorID().isNull() || entity->getSimulatorID() == senderID) { + simulationBlocked = false; + } else { + // the sender is trying to steal ownership from another simulator + // so we apply the ownership change filter + if (usecTimestampNow() - entity->getSimulatorIDChangedTime() > SIMULATOR_CHANGE_LOCKOUT_PERIOD) { + simulationBlocked = false; + } + } + } else { + // the entire update is suspect --> ignore it + return false; + } + } + if (simulationBlocked) { // squash the physics-related changes. properties.setSimulatorIDChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); - } else { - qCDebug(entities) << "allowing simulatorID change"; } } + // else client accepts what the server says QString entityScriptBefore = entity->getScript(); uint32_t preFlags = entity->getDirtyFlags(); @@ -664,7 +695,7 @@ int EntityTree::processEditPacketData(PacketType packetType, const unsigned char qCDebug(entities) << "User [" << senderNode->getUUID() << "] editing entity. ID:" << entityItemID; qCDebug(entities) << " properties:" << properties; } - updateEntity(entityItemID, properties, senderNode->getCanAdjustLocks()); + updateEntity(entityItemID, properties, senderNode); existingEntity->markAsChangedOnServer(); } else { qCDebug(entities) << "User attempted to edit an unknown entity. ID:" << entityItemID; diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 76d648cf46..fb6e0f26eb 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -88,10 +88,10 @@ public: EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties); // use this method if you only know the entityID - bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool allowLockChange); + bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); // use this method if you have a pointer to the entity (avoid an extra entity lookup) - bool updateEntity(EntityItem* entity, const EntityItemProperties& properties, bool allowLockChange); + bool updateEntity(EntityItem* entity, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); void deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false); void deleteEntities(QSet entityIDs, bool force = false, bool ignoreWarnings = false); @@ -180,7 +180,8 @@ private: void processRemovedEntities(const DeleteEntityOperator& theOperator); bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties, - EntityTreeElement* containingElement, bool allowLockChange); + EntityTreeElement* containingElement, + const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); static bool findNearPointOperation(OctreeElement* element, void* extraData); static bool findInSphereOperation(OctreeElement* element, void* extraData); static bool findInCubeOperation(OctreeElement* element, void* extraData); diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 18d5c4ecb0..07c56e7121 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -32,6 +32,7 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID()); if (ownerNode.isNull() || !ownerNode->isAlive()) { qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); + // TODO: zero velocities when we clear simulatorID? entity->setSimulatorID(QUuid()); itemItr = _hasSimulationOwnerEntities.erase(itemItr); } else { diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index b5d14e4814..3986762f6f 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -25,7 +25,7 @@ static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4; EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity) : ObjectMotionState(shape), _entity(entity), - _sentMoving(false), + _sentActive(false), _numNonMovingUpdates(0), _lastStep(0), _serverPosition(0.0f), @@ -35,8 +35,9 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity _serverGravity(0.0f), _serverAcceleration(0.0f), _accelerationNearlyGravityCount(0), - _shouldClaimSimulationOwnership(false), - _movingStepsWithoutSimulationOwner(0) + _candidateForOwnership(false), + _loopsSinceOwnershipBid(0), + _loopsWithoutOwner(0) { _type = MOTION_STATE_TYPE_ENTITY; assert(entity != nullptr); @@ -66,6 +67,28 @@ void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) { void EntityMotionState::handleEasyChanges(uint32_t flags) { updateServerPhysicsVariables(flags); ObjectMotionState::handleEasyChanges(flags); + if (flags & EntityItem::DIRTY_SIMULATOR_ID) { + _loopsWithoutOwner = 0; + _candidateForOwnership = 0; + if (_entity->getSimulatorID().isNull() + && !_entity->isMoving() + && _body->isActive()) { + // remove the ACTIVATION flag because this object is coming to rest + // according to a remote simulation and we don't want to wake it up again + flags &= ~EntityItem::DIRTY_PHYSICS_ACTIVATION; + _body->setActivationState(WANTS_DEACTIVATION); + } else { + auto nodeList = DependencyManager::get(); + const QUuid& sessionID = nodeList->getSessionUUID(); + if (_entity->getSimulatorID() != sessionID) { + _loopsSinceOwnershipBid = 0; + } + } + } + if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { + _body->activate(); + } + } @@ -95,15 +118,6 @@ bool EntityMotionState::isMoving() const { return _entity && _entity->isMoving(); } -bool EntityMotionState::isMovingVsServer() const { - auto alignmentDot = glm::abs(glm::dot(_serverRotation, _entity->getRotation())); - if (glm::distance(_serverPosition, _entity->getPosition()) > IGNORE_POSITION_DELTA || - alignmentDot < IGNORE_ALIGNMENT_DOT) { - return true; - } - return false; -} - // This callback is invoked by the physics simulation in two cases: // (1) when the RigidBody is first added to the world // (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC) @@ -143,19 +157,16 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { _entity->setLastSimulated(usecTimestampNow()); - // if (_entity->getSimulatorID().isNull() && isMoving()) { - if (_entity->getSimulatorID().isNull() && isMovingVsServer()) { - // if object is moving and has no owner, attempt to claim simulation ownership. - _movingStepsWithoutSimulationOwner++; + if (_entity->getSimulatorID().isNull()) { + _loopsWithoutOwner++; + + const uint32_t OWNERSHIP_BID_DELAY = 50; + if (_loopsWithoutOwner > OWNERSHIP_BID_DELAY) { + //qDebug() << "Warning -- claiming something I saw moving." << getName(); + _candidateForOwnership = true; + } } else { - _movingStepsWithoutSimulationOwner = 0; - } - - uint32_t ownershipClaimDelay = 50; // TODO -- how to pick this? based on meters from our characterController? - - if (_movingStepsWithoutSimulationOwner > ownershipClaimDelay) { - //qDebug() << "Warning -- claiming something I saw moving." << getName(); - setShouldClaimSimulationOwnership(true); + _loopsWithoutOwner = 0; } #ifdef WANT_DEBUG @@ -177,8 +188,11 @@ void EntityMotionState::computeObjectShapeInfo(ShapeInfo& shapeInfo) { // we alwasy resend packets for objects that have stopped moving up to some max limit. const int MAX_NUM_NON_MOVING_UPDATES = 5; -bool EntityMotionState::doesNotNeedToSendUpdate() const { - return !_body || (_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES); +bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { + if (!_body || !_entity) { + return false; + } + return _candidateForOwnership || sessionID == _entity->getSimulatorID(); } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { @@ -191,6 +205,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _serverVelocity = bulletToGLM(_body->getLinearVelocity()); _serverAngularVelocity = bulletToGLM(_body->getAngularVelocity()); _lastStep = simulationStep; + _sentActive = false; return false; } @@ -202,21 +217,26 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; - _lastStep = simulationStep; - bool isActive = _body->isActive(); + const float INACTIVE_UPDATE_PERIOD = 0.5f; + if (!_sentActive) { + // 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) + return (dt > INACTIVE_UPDATE_PERIOD); + } + + bool isActive = _body->isActive(); if (!isActive) { - if (_sentMoving) { - // this object just went inactive so send an update immediately - return true; - } else { - const float NON_MOVING_UPDATE_PERIOD = 1.0f; - if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) { - // RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these - // at a faster rate than the MAX period above, and only send a limited number of them. - return true; - } - } + // object has gone inactive but our last send was moving --> send non-moving update immediately + return true; + } + + _lastStep = simulationStep; + if (glm::length2(_serverVelocity) > 0.0f) { + _serverVelocity += _serverAcceleration * dt; + _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt); + _serverPosition += dt * _serverVelocity; } // Else we measure the error between current and extrapolated transform (according to expected behavior @@ -224,15 +244,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { // NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame // due to _worldOffset. + // TODO: compensate for _worldOffset offset here // compute position error - if (glm::length2(_serverVelocity) > 0.0f) { - _serverVelocity += _serverAcceleration * dt; - _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt); - _serverPosition += dt * _serverVelocity; - } - // TODO: compensate for simulation offset here btTransform worldTrans = _body->getWorldTransform(); glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); @@ -285,42 +300,50 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } -bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) { - if (!_entity || !remoteSimulationOutOfSync(simulationFrame)) { +bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID) { + // 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(_entity); + assert(_body); + + if (!remoteSimulationOutOfSync(simulationStep)) { + _candidateForOwnership = false; return false; } - if (getShouldClaimSimulationOwnership()) { + if (_entity->getSimulatorID() == sessionID) { + // we own the simulation + _candidateForOwnership = false; return true; } - auto nodeList = DependencyManager::get(); - const QUuid& myNodeID = nodeList->getSessionUUID(); - const QUuid& simulatorID = _entity->getSimulatorID(); - - if (simulatorID != myNodeID) { - // some other Node owns the simulating of this, so don't broadcast the results of local simulation. - return false; + const uint32_t FRAMES_BETWEEN_OWNERSHIP_CLAIMS = 30; + if (_candidateForOwnership) { + _candidateForOwnership = false; + ++_loopsSinceOwnershipBid; + if (_loopsSinceOwnershipBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) { + // we don't own the simulation, but it's time to bid for it + _loopsSinceOwnershipBid = 0; + return true; + } } - return true; + _candidateForOwnership = false; + return false; } -void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { - if (!_entity || !_entity->isKnownID()) { - return; // never update entities that are unknown - } +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) { + assert(_entity); + assert(_entity->isKnownID()); bool active = _body->isActive(); if (!active) { - if (_sentMoving) { - // make sure all derivatives are zero - glm::vec3 zero(0.0f); - _entity->setVelocity(zero); - _entity->setAngularVelocity(zero); - _entity->setAcceleration(zero); - } - + // make sure all derivatives are zero + glm::vec3 zero(0.0f); + _entity->setVelocity(zero); + _entity->setAngularVelocity(zero); + _entity->setAcceleration(zero); + _sentActive = false; } else { float gravityLength = glm::length(_entity->getGravity()); float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); @@ -343,6 +366,21 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ } else { _entity->setAcceleration(glm::vec3(0.0f)); } + + const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec + const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec + bool movingSlowly = glm::length2(_entity->getVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD) + && glm::length2(_entity->getAngularVelocity()) < (DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD) + && _entity->getAcceleration() == glm::vec3(0.0f); + + 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 + glm::vec3 zero(0.0f); + _entity->setVelocity(zero); + _entity->setAngularVelocity(zero); + } + _sentActive = true; } // remember properties for local server prediction @@ -352,59 +390,41 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _serverAcceleration = _entity->getAcceleration(); _serverAngularVelocity = _entity->getAngularVelocity(); - _sentMoving = _serverVelocity != glm::vec3(0.0f) || _serverAngularVelocity != glm::vec3(0.0f); - EntityItemProperties properties = _entity->getProperties(); - // explicitly set the properties that changed + // explicitly set the properties that changed so that they will be packed properties.setPosition(_serverPosition); properties.setRotation(_serverRotation); properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); - // RELIABLE_SEND_HACK: count number of updates for entities at rest - // so we can stop sending them after some limit. - if (_sentMoving) { - _numNonMovingUpdates = 0; + // we only update lastEdited when we're sending new physics data + quint64 lastSimulated = _entity->getLastSimulated(); + _entity->setLastEdited(lastSimulated); + properties.setLastEdited(lastSimulated); + + #ifdef WANT_DEBUG + quint64 now = usecTimestampNow(); + qCDebug(physics) << "EntityMotionState::sendUpdate()"; + qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() + << "---------------------------------------------"; + qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); + #endif //def WANT_DEBUG + + if (sessionID == _entity->getSimulatorID()) { + // we think we own the simulation + if (!active) { + // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID + // but we remember that we do still own it... and rely on the server to tell us that we don't + properties.setSimulatorID(QUuid()); + } else { + // explicitly set the property's simulatorID so that it is flagged as changed and will be packed + properties.setSimulatorID(sessionID); + } } else { - _numNonMovingUpdates++; - } - if (_numNonMovingUpdates <= 1) { - // we only update lastEdited when we're sending new physics data - quint64 lastSimulated = _entity->getLastSimulated(); - _entity->setLastEdited(lastSimulated); - properties.setLastEdited(lastSimulated); - - #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); - qCDebug(physics) << "EntityMotionState::sendUpdate()"; - qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() - << "---------------------------------------------"; - qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); - #endif //def WANT_DEBUG - - } else { - properties.setLastEdited(_entity->getLastEdited()); - } - - auto nodeList = DependencyManager::get(); - QUuid myNodeID = nodeList->getSessionUUID(); - QUuid simulatorID = _entity->getSimulatorID(); - - if (getShouldClaimSimulationOwnership()) { - // we think we should own it, so we tell the server that we do, - // but we don't remember that we own it... - // instead we expect the sever to tell us later whose ownership it has accepted - properties.setSimulatorID(myNodeID); - setShouldClaimSimulationOwnership(false); - } else if (simulatorID == myNodeID - && !_sentMoving - && _numNonMovingUpdates == MAX_NUM_NON_MOVING_UPDATES) { - // we own it, the entity has stopped, and we're sending the last non-moving update - // --> give up ownership - _entity->setSimulatorID(QUuid()); - properties.setSimulatorID(QUuid()); + // we don't own the simulation for this entity yet, but we're sending a bid for it + properties.setSimulatorID(sessionID); } if (EntityItem::getSendPhysicsUpdates()) { @@ -453,7 +473,7 @@ QUuid EntityMotionState::getSimulatorID() const { // virtual void EntityMotionState::bump() { - setShouldClaimSimulationOwnership(true); + _candidateForOwnership = true; } void EntityMotionState::resetMeasuredBodyAcceleration() { diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 6028662aa0..83b89a5a29 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -36,7 +36,6 @@ public: virtual MotionType computeObjectMotionType() const; virtual bool isMoving() const; - virtual bool isMovingVsServer() const; // this relays incoming position/rotation to the RigidBody virtual void getWorldTransform(btTransform& worldTrans) const; @@ -46,13 +45,10 @@ public: virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo); - bool doesNotNeedToSendUpdate() const; + bool isCandidateForOwnership(const QUuid& sessionID) const; bool remoteSimulationOutOfSync(uint32_t simulationStep); - bool shouldSendUpdate(uint32_t simulationFrame); - void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); - - void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; } - bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; } + bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID); + void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step); virtual uint32_t getAndClearIncomingDirtyFlags() const; @@ -92,7 +88,7 @@ protected: EntityItem* _entity; - bool _sentMoving; // true if last update was moving + bool _sentActive; // true if body was active when we sent last update int _numNonMovingUpdates; // RELIABLE_SEND_HACK for "not so reliable" resends of packets for non-moving objects // these are for the prediction of the remote server's simple extrapolation @@ -109,8 +105,9 @@ protected: glm::vec3 _measuredAcceleration; quint8 _accelerationNearlyGravityCount; - bool _shouldClaimSimulationOwnership; - quint32 _movingStepsWithoutSimulationOwner; + bool _candidateForOwnership; + uint32_t _loopsSinceOwnershipBid; + uint32_t _loopsWithoutOwner; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index c1258ad6bc..c5288cfa76 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -146,10 +146,6 @@ void ObjectMotionState::handleEasyChanges(uint32_t flags) { _body->setMassProps(mass, inertia); _body->updateInertiaTensor(); } - - if (flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) { - _body->activate(); - } } void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 1407be0d20..bfc9310ec6 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -36,11 +36,11 @@ enum MotionStateType { // and re-added to the physics engine and "easy" which just updates the body properties. const uint32_t HARD_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_MOTION_TYPE | EntityItem::DIRTY_SHAPE); const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES | - EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | - EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_PHYSICS_ACTIVATION); + EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP); // These are the set of incoming flags that the PhysicsEngine needs to hear about: -const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS; +const uint32_t DIRTY_PHYSICS_FLAGS = HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS | + EntityItem::DIRTY_MATERIAL | (uint32_t)EntityItem::DIRTY_PHYSICS_ACTIVATION; // These are the outgoing flags that the PhysicsEngine can affect: const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index 50e81b4788..3e43ab7454 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -188,7 +188,7 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() { return _tempVector; } -void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates) { +void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID) { // walk the motionStates looking for those that correspond to entities for (auto stateItr : motionStates) { ObjectMotionState* state = &(*stateItr); @@ -196,24 +196,32 @@ void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motio EntityMotionState* entityState = static_cast(state); EntityItem* entity = entityState->getEntity(); if (entity) { - _outgoingChanges.insert(entityState); + if (entity->isKnownID() && entityState->isCandidateForOwnership(sessionID)) { + _outgoingChanges.insert(entityState); + } _entitiesToSort.insert(entityState->getEntity()); } } } - // send outgoing packets uint32_t numSubsteps = _physicsEngine->getNumSubsteps(); if (_lastStepSendPackets != numSubsteps) { _lastStepSendPackets = numSubsteps; + if (sessionID.isNull()) { + // usually don't get here, but if so --> nothing to do + _outgoingChanges.clear(); + return; + } + + // send outgoing packets QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; - if (state->doesNotNeedToSendUpdate()) { + if (!state->isCandidateForOwnership(sessionID)) { stateItr = _outgoingChanges.erase(stateItr); - } else if (state->shouldSendUpdate(numSubsteps)) { - state->sendUpdate(_entityPacketSender, numSubsteps); + } else if (state->shouldSendUpdate(numSubsteps, sessionID)) { + state->sendUpdate(_entityPacketSender, sessionID, numSubsteps); ++stateItr; } else { ++stateItr; diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index 4fd54c60fb..b3ee7af1e1 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -48,7 +48,7 @@ public: VectorOfMotionStates& getObjectsToAdd(); VectorOfMotionStates& getObjectsToChange(); - void handleOutgoingChanges(VectorOfMotionStates& motionStates); + void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID); void handleCollisionEvents(CollisionEvents& collisionEvents); private: diff --git a/libraries/physics/src/PhysicsEngine.h b/libraries/physics/src/PhysicsEngine.h index 8b947d2510..d1dc5bcd79 100644 --- a/libraries/physics/src/PhysicsEngine.h +++ b/libraries/physics/src/PhysicsEngine.h @@ -53,6 +53,7 @@ public: void init(); void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; } + const QUuid& getSessionID() const { return _sessionID; } void addObject(ObjectMotionState* motionState); void removeObject(ObjectMotionState* motionState); diff --git a/tests/octree/src/ModelTests.cpp b/tests/octree/src/ModelTests.cpp index e4309100af..2e2b873115 100644 --- a/tests/octree/src/ModelTests.cpp +++ b/tests/octree/src/ModelTests.cpp @@ -103,7 +103,7 @@ void EntityTests::entityTreeTests(bool verbose) { properties.setPosition(newPosition); - tree.updateEntity(entityID, properties, true); + tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0f; const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOrigin, targetRadius); @@ -143,7 +143,7 @@ void EntityTests::entityTreeTests(bool verbose) { properties.setPosition(newPosition); - tree.updateEntity(entityID, properties, true); + tree.updateEntity(entityID, properties); float targetRadius = oneMeter * 2.0f; const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenter, targetRadius);