Merge pull request #4852 from AndrewMeadows/nova

improved distributed simulation ownership negotiation
This commit is contained in:
Seth Alves 2015-05-14 11:40:49 -07:00
commit 41d77e6146
17 changed files with 250 additions and 184 deletions

View file

@ -2456,7 +2456,7 @@ void Application::update(float deltaTime) {
if (_physicsEngine.hasOutgoingChanges()) { if (_physicsEngine.hasOutgoingChanges()) {
_entitySimulation.lock(); _entitySimulation.lock();
_entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges()); _entitySimulation.handleOutgoingChanges(_physicsEngine.getOutgoingChanges(), _physicsEngine.getSessionID());
_entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents()); _entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents());
_entitySimulation.unlock(); _entitySimulation.unlock();
_physicsEngine.dumpStatsIfNecessary(); _physicsEngine.dumpStatsIfNecessary();

View file

@ -569,7 +569,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData);
if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { 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) { 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(collisionsWillMove, updateCollisionsWillMove);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked); SET_ENTITY_PROPERTY_FROM_PROPERTIES(locked, setLocked);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(userData, setUserData); 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(marketplaceID, setMarketplaceID);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); 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) { void EntityItem::updateVelocity(const glm::vec3& value) {
auto delta = glm::distance(_velocity, value); auto delta = glm::distance(_velocity, value);
if (delta > IGNORE_LINEAR_VELOCITY_DELTA) { if (delta > IGNORE_LINEAR_VELOCITY_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY;
const float MIN_LINEAR_SPEED = 0.001f; const float MIN_LINEAR_SPEED = 0.001f;
if (glm::length(value) < MIN_LINEAR_SPEED) { if (glm::length(value) < MIN_LINEAR_SPEED) {
_velocity = ENTITY_ITEM_ZERO_VEC3; _velocity = ENTITY_ITEM_ZERO_VEC3;
} else { } else {
_velocity = value; _velocity = value;
} // only activate when setting non-zero velocity
_dirtyFlags |= EntityItem::DIRTY_LINEAR_VELOCITY; if (delta > ACTIVATION_LINEAR_VELOCITY_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
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; _angularVelocity = ENTITY_ITEM_ZERO_VEC3;
} else { } else {
_angularVelocity = value; _angularVelocity = value;
} // only activate when setting non-zero velocity
if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) { if (delta > ACTIVATION_ANGULAR_VELOCITY_DELTA) {
_dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION; _dirtyFlags |= EntityItem::DIRTY_PHYSICS_ACTIVATION;
}
} }
} }
} }
@ -1275,8 +1276,14 @@ void EntityItem::updateLifetime(float value) {
} }
void EntityItem::setSimulatorID(const QUuid& value) { void EntityItem::setSimulatorID(const QUuid& value) {
_simulatorID = value;
_simulatorIDChangedTime = usecTimestampNow();
}
void EntityItem::updateSimulatorID(const QUuid& value) {
if (_simulatorID != value) { if (_simulatorID != value) {
_simulatorID = value; _simulatorID = value;
_simulatorIDChangedTime = usecTimestampNow(); _simulatorIDChangedTime = usecTimestampNow();
_dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID;
} }
} }

View file

@ -82,6 +82,7 @@ public:
DIRTY_UPDATEABLE = 0x0200, DIRTY_UPDATEABLE = 0x0200,
DIRTY_MATERIAL = 0x00400, DIRTY_MATERIAL = 0x00400,
DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object
DIRTY_SIMULATOR_ID = 0x1000,
DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION, DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION,
DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY
}; };
@ -286,6 +287,7 @@ public:
QUuid getSimulatorID() const { return _simulatorID; } QUuid getSimulatorID() const { return _simulatorID; }
void setSimulatorID(const QUuid& value); void setSimulatorID(const QUuid& value);
void updateSimulatorID(const QUuid& value);
quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; } quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; }
const QString& getMarketplaceID() const { return _marketplaceID; } const QString& getMarketplaceID() const { return _marketplaceID; }

View file

@ -495,7 +495,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked); COPY_PROPERTY_FROM_QSCRIPTVALUE(locked, bool, setLocked);
COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures); COPY_PROPERTY_FROM_QSCRIPTVALUE(textures, QString, setTextures);
COPY_PROPERTY_FROM_QSCRIPTVALUE(userData, QString, setUserData); 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(text, QString, setText);
COPY_PROPERTY_FROM_QSCRIPTVALUE(lineHeight, float, setLineHeight); COPY_PROPERTY_FROM_QSCRIPTVALUE(lineHeight, float, setLineHeight);
COPY_PROPERTY_FROM_QSCRIPTVALUE(textColor, xColor, setTextColor); COPY_PROPERTY_FROM_QSCRIPTVALUE(textColor, xColor, setTextColor);

View file

@ -61,13 +61,12 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) {
} }
} }
void bidForSimulationOwnership(EntityItemProperties& properties) {
// We make a bid for simulation ownership by declaring our sessionID as simulation owner
void setSimId(EntityItemProperties& propertiesWithSimID, EntityItem* entity) { // in the outgoing properties. The EntityServer may accept the bid or might not.
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID(); const QUuid myNodeID = nodeList->getSessionUUID();
propertiesWithSimID.setSimulatorID(myNodeID); properties.setSimulatorID(myNodeID);
entity->setSimulatorID(myNodeID);
} }
@ -89,7 +88,7 @@ EntityItemID EntityScriptingInterface::addEntity(const EntityItemProperties& pro
entity->setLastBroadcast(usecTimestampNow()); entity->setLastBroadcast(usecTimestampNow());
if (entity) { if (entity) {
// This Node is creating a new object. If it's in motion, set this Node as the simulator. // This Node is creating a new object. If it's in motion, set this Node as the simulator.
setSimId(propertiesWithSimID, entity); bidForSimulationOwnership(propertiesWithSimID);
} else { } else {
qCDebug(entities) << "script failed to add new Entity to local Octree"; qCDebug(entities) << "script failed to add new Entity to local Octree";
success = false; 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 // 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 // the actual id, because we can edit out local entities just with creatorTokenID
if (_entityTree) { if (_entityTree) {
_entityTree->lockForWrite(); _entityTree->lockForWrite();
_entityTree->updateEntity(entityID, propertiesWithSimID, canAdjustLocks()); _entityTree->updateEntity(entityID, properties);
_entityTree->unlock(); _entityTree->unlock();
} }
// if at this point, we know the id, send the update to the entity server // if at this point, we know the id, send the update to the entity server
if (entityID.isKnownID) { if (entityID.isKnownID) {
// make sure the properties has a type, so that the encode can know which properties to include // 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); EntityItem* entity = _entityTree->findEntityByEntityItemID(entityID);
if (entity) { if (entity) {
// we need to change the outgoing properties, so we make a copy, modify, and send.
EntityItemProperties modifiedProperties = properties;
entity->setLastBroadcast(usecTimestampNow()); entity->setLastBroadcast(usecTimestampNow());
propertiesWithSimID.setType(entity->getType()); modifiedProperties.setType(entity->getType());
setSimId(propertiesWithSimID, entity); bidForSimulationOwnership(modifiedProperties);
queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, modifiedProperties);
return entityID;
} }
} }
queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, propertiesWithSimID); queueEntityMessage(PacketTypeEntityAddOrEdit, entityID, properties);
} }
return entityID; return entityID;

View file

@ -37,7 +37,8 @@ const int DIRTY_SIMULATION_FLAGS =
EntityItem::DIRTY_SHAPE | EntityItem::DIRTY_SHAPE |
EntityItem::DIRTY_LIFETIME | EntityItem::DIRTY_LIFETIME |
EntityItem::DIRTY_UPDATEABLE | EntityItem::DIRTY_UPDATEABLE |
EntityItem::DIRTY_MATERIAL; EntityItem::DIRTY_MATERIAL |
EntityItem::DIRTY_SIMULATOR_ID;
class EntitySimulation : public QObject { class EntitySimulation : public QObject {
Q_OBJECT Q_OBJECT

View file

@ -86,7 +86,7 @@ void EntityTree::postAddEntity(EntityItem* entity) {
emit addingEntity(entity->getEntityItemID()); 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); EntityTreeElement* containingElement = getContainingElement(entityID);
if (!containingElement) { if (!containingElement) {
qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entityID doesn't exist!!! entityID=" << entityID; 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 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()); EntityTreeElement* containingElement = getContainingElement(entity->getEntityItemID());
if (!containingElement) { if (!containingElement) {
qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID=" qCDebug(entities) << "UNEXPECTED!!!! EntityTree::updateEntity() entity-->element lookup failed!!! entityID="
<< entity->getEntityItemID(); << entity->getEntityItemID();
return false; return false;
} }
return updateEntityWithElement(entity, properties, containingElement, allowLockChange); return updateEntityWithElement(entity, properties, containingElement, senderNode);
} }
bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& origProperties, bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemProperties& origProperties,
EntityTreeElement* containingElement, bool allowLockChange) { EntityTreeElement* containingElement, const SharedNodePointer& senderNode) {
EntityItemProperties properties = origProperties; EntityItemProperties properties = origProperties;
bool allowLockChange;
QUuid senderID;
if (senderNode.isNull()) {
auto nodeList = DependencyManager::get<NodeList>();
allowLockChange = nodeList->getThisNodeCanAdjustLocks();
senderID = nodeList->getSessionUUID();
} else {
allowLockChange = senderNode->getCanAdjustLocks();
senderID = senderNode->getUUID();
}
if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { if (!allowLockChange && (entity->getLocked() != properties.getLocked())) {
qCDebug(entities) << "Refusing disallowed lock adjustment."; qCDebug(entities) << "Refusing disallowed lock adjustment.";
return false; return false;
@ -134,22 +146,41 @@ bool EntityTree::updateEntityWithElement(EntityItem* entity, const EntityItemPro
} }
} }
} else { } else {
if (properties.simulatorIDChanged() && if (getIsServer()) {
!entity->getSimulatorID().isNull() && bool simulationBlocked = !entity->getSimulatorID().isNull();
properties.getSimulatorID() != entity->getSimulatorID()) { if (properties.simulatorIDChanged()) {
// A Node is trying to take ownership of the simulation of this entity from another Node. Only allow this QUuid submittedID = properties.getSimulatorID();
// if ownership hasn't recently changed. // a legit interface will only submit their own ID or NULL:
if (usecTimestampNow() - entity->getSimulatorIDChangedTime() < SIMULATOR_CHANGE_LOCKOUT_PERIOD) { if (submittedID.isNull()) {
qCDebug(entities) << "simulator_change_lockout_period:" if (entity->getSimulatorID() == senderID) {
<< entity->getSimulatorID() << "to" << properties.getSimulatorID(); // 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. // squash the physics-related changes.
properties.setSimulatorIDChanged(false); properties.setSimulatorIDChanged(false);
properties.setPositionChanged(false); properties.setPositionChanged(false);
properties.setRotationChanged(false); properties.setRotationChanged(false);
} else {
qCDebug(entities) << "allowing simulatorID change";
} }
} }
// else client accepts what the server says
QString entityScriptBefore = entity->getScript(); QString entityScriptBefore = entity->getScript();
uint32_t preFlags = entity->getDirtyFlags(); 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) << "User [" << senderNode->getUUID() << "] editing entity. ID:" << entityItemID;
qCDebug(entities) << " properties:" << properties; qCDebug(entities) << " properties:" << properties;
} }
updateEntity(entityItemID, properties, senderNode->getCanAdjustLocks()); updateEntity(entityItemID, properties, senderNode);
existingEntity->markAsChangedOnServer(); existingEntity->markAsChangedOnServer();
} else { } else {
qCDebug(entities) << "User attempted to edit an unknown entity. ID:" << entityItemID; qCDebug(entities) << "User attempted to edit an unknown entity. ID:" << entityItemID;

View file

@ -88,10 +88,10 @@ public:
EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties); EntityItem* addEntity(const EntityItemID& entityID, const EntityItemProperties& properties);
// use this method if you only know the entityID // 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) // 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 deleteEntity(const EntityItemID& entityID, bool force = false, bool ignoreWarnings = false);
void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = false); void deleteEntities(QSet<EntityItemID> entityIDs, bool force = false, bool ignoreWarnings = false);
@ -180,7 +180,8 @@ private:
void processRemovedEntities(const DeleteEntityOperator& theOperator); void processRemovedEntities(const DeleteEntityOperator& theOperator);
bool updateEntityWithElement(EntityItem* entity, const EntityItemProperties& properties, 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 findNearPointOperation(OctreeElement* element, void* extraData);
static bool findInSphereOperation(OctreeElement* element, void* extraData); static bool findInSphereOperation(OctreeElement* element, void* extraData);
static bool findInCubeOperation(OctreeElement* element, void* extraData); static bool findInCubeOperation(OctreeElement* element, void* extraData);

View file

@ -32,6 +32,7 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID()); SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID());
if (ownerNode.isNull() || !ownerNode->isAlive()) { if (ownerNode.isNull() || !ownerNode->isAlive()) {
qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID();
// TODO: zero velocities when we clear simulatorID?
entity->setSimulatorID(QUuid()); entity->setSimulatorID(QUuid());
itemItr = _hasSimulationOwnerEntities.erase(itemItr); itemItr = _hasSimulationOwnerEntities.erase(itemItr);
} else { } else {

View file

@ -25,7 +25,7 @@ static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4;
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity) : EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity) :
ObjectMotionState(shape), ObjectMotionState(shape),
_entity(entity), _entity(entity),
_sentMoving(false), _sentActive(false),
_numNonMovingUpdates(0), _numNonMovingUpdates(0),
_lastStep(0), _lastStep(0),
_serverPosition(0.0f), _serverPosition(0.0f),
@ -35,8 +35,9 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItem* entity
_serverGravity(0.0f), _serverGravity(0.0f),
_serverAcceleration(0.0f), _serverAcceleration(0.0f),
_accelerationNearlyGravityCount(0), _accelerationNearlyGravityCount(0),
_shouldClaimSimulationOwnership(false), _candidateForOwnership(false),
_movingStepsWithoutSimulationOwner(0) _loopsSinceOwnershipBid(0),
_loopsWithoutOwner(0)
{ {
_type = MOTION_STATE_TYPE_ENTITY; _type = MOTION_STATE_TYPE_ENTITY;
assert(entity != nullptr); assert(entity != nullptr);
@ -66,6 +67,28 @@ void EntityMotionState::updateServerPhysicsVariables(uint32_t flags) {
void EntityMotionState::handleEasyChanges(uint32_t flags) { void EntityMotionState::handleEasyChanges(uint32_t flags) {
updateServerPhysicsVariables(flags); updateServerPhysicsVariables(flags);
ObjectMotionState::handleEasyChanges(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<NodeList>();
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(); 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: // This callback is invoked by the physics simulation in two cases:
// (1) when the RigidBody is first added to the world // (1) when the RigidBody is first added to the world
// (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC) // (irregardless of MotionType: STATIC, DYNAMIC, or KINEMATIC)
@ -143,19 +157,16 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
_entity->setLastSimulated(usecTimestampNow()); _entity->setLastSimulated(usecTimestampNow());
// if (_entity->getSimulatorID().isNull() && isMoving()) { if (_entity->getSimulatorID().isNull()) {
if (_entity->getSimulatorID().isNull() && isMovingVsServer()) { _loopsWithoutOwner++;
// if object is moving and has no owner, attempt to claim simulation ownership.
_movingStepsWithoutSimulationOwner++; const uint32_t OWNERSHIP_BID_DELAY = 50;
if (_loopsWithoutOwner > OWNERSHIP_BID_DELAY) {
//qDebug() << "Warning -- claiming something I saw moving." << getName();
_candidateForOwnership = true;
}
} else { } else {
_movingStepsWithoutSimulationOwner = 0; _loopsWithoutOwner = 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);
} }
#ifdef WANT_DEBUG #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. // we alwasy resend packets for objects that have stopped moving up to some max limit.
const int MAX_NUM_NON_MOVING_UPDATES = 5; const int MAX_NUM_NON_MOVING_UPDATES = 5;
bool EntityMotionState::doesNotNeedToSendUpdate() const { bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const {
return !_body || (_body->isActive() && _numNonMovingUpdates > MAX_NUM_NON_MOVING_UPDATES); if (!_body || !_entity) {
return false;
}
return _candidateForOwnership || sessionID == _entity->getSimulatorID();
} }
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
@ -191,6 +205,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
_serverVelocity = bulletToGLM(_body->getLinearVelocity()); _serverVelocity = bulletToGLM(_body->getLinearVelocity());
_serverAngularVelocity = bulletToGLM(_body->getAngularVelocity()); _serverAngularVelocity = bulletToGLM(_body->getAngularVelocity());
_lastStep = simulationStep; _lastStep = simulationStep;
_sentActive = false;
return false; return false;
} }
@ -202,21 +217,26 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
int numSteps = simulationStep - _lastStep; int numSteps = simulationStep - _lastStep;
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; 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 (!isActive) {
if (_sentMoving) { // object has gone inactive but our last send was moving --> send non-moving update immediately
// this object just went inactive so send an update immediately return true;
return true; }
} else {
const float NON_MOVING_UPDATE_PERIOD = 1.0f; _lastStep = simulationStep;
if (dt > NON_MOVING_UPDATE_PERIOD && _numNonMovingUpdates < MAX_NUM_NON_MOVING_UPDATES) { if (glm::length2(_serverVelocity) > 0.0f) {
// RELIABLE_SEND_HACK: since we're not yet using a reliable method for non-moving update packets we repeat these _serverVelocity += _serverAcceleration * dt;
// at a faster rate than the MAX period above, and only send a limited number of them. _serverVelocity *= powf(1.0f - _body->getLinearDamping(), dt);
return true; _serverPosition += dt * _serverVelocity;
}
}
} }
// Else we measure the error between current and extrapolated transform (according to expected behavior // 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 // NOTE: math is done in the simulation-frame, which is NOT necessarily the same as the world-frame
// due to _worldOffset. // due to _worldOffset.
// TODO: compensate for _worldOffset offset here
// compute position error // 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(); btTransform worldTrans = _body->getWorldTransform();
glm::vec3 position = bulletToGLM(worldTrans.getOrigin()); 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); return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT);
} }
bool EntityMotionState::shouldSendUpdate(uint32_t simulationFrame) { bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID) {
if (!_entity || !remoteSimulationOutOfSync(simulationFrame)) { // 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; return false;
} }
if (getShouldClaimSimulationOwnership()) { if (_entity->getSimulatorID() == sessionID) {
// we own the simulation
_candidateForOwnership = false;
return true; return true;
} }
auto nodeList = DependencyManager::get<NodeList>(); const uint32_t FRAMES_BETWEEN_OWNERSHIP_CLAIMS = 30;
const QUuid& myNodeID = nodeList->getSessionUUID(); if (_candidateForOwnership) {
const QUuid& simulatorID = _entity->getSimulatorID(); _candidateForOwnership = false;
++_loopsSinceOwnershipBid;
if (simulatorID != myNodeID) { if (_loopsSinceOwnershipBid > FRAMES_BETWEEN_OWNERSHIP_CLAIMS) {
// some other Node owns the simulating of this, so don't broadcast the results of local simulation. // we don't own the simulation, but it's time to bid for it
return false; _loopsSinceOwnershipBid = 0;
return true;
}
} }
return true; _candidateForOwnership = false;
return false;
} }
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) {
if (!_entity || !_entity->isKnownID()) { assert(_entity);
return; // never update entities that are unknown assert(_entity->isKnownID());
}
bool active = _body->isActive(); bool active = _body->isActive();
if (!active) { if (!active) {
if (_sentMoving) { // make sure all derivatives are zero
// make sure all derivatives are zero glm::vec3 zero(0.0f);
glm::vec3 zero(0.0f); _entity->setVelocity(zero);
_entity->setVelocity(zero); _entity->setAngularVelocity(zero);
_entity->setAngularVelocity(zero); _entity->setAcceleration(zero);
_entity->setAcceleration(zero); _sentActive = false;
}
} else { } else {
float gravityLength = glm::length(_entity->getGravity()); float gravityLength = glm::length(_entity->getGravity());
float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength);
@ -343,6 +366,21 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
} else { } else {
_entity->setAcceleration(glm::vec3(0.0f)); _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 // remember properties for local server prediction
@ -352,59 +390,41 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
_serverAcceleration = _entity->getAcceleration(); _serverAcceleration = _entity->getAcceleration();
_serverAngularVelocity = _entity->getAngularVelocity(); _serverAngularVelocity = _entity->getAngularVelocity();
_sentMoving = _serverVelocity != glm::vec3(0.0f) || _serverAngularVelocity != glm::vec3(0.0f);
EntityItemProperties properties = _entity->getProperties(); 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.setPosition(_serverPosition);
properties.setRotation(_serverRotation); properties.setRotation(_serverRotation);
properties.setVelocity(_serverVelocity); properties.setVelocity(_serverVelocity);
properties.setAcceleration(_serverAcceleration); properties.setAcceleration(_serverAcceleration);
properties.setAngularVelocity(_serverAngularVelocity); properties.setAngularVelocity(_serverAngularVelocity);
// RELIABLE_SEND_HACK: count number of updates for entities at rest // we only update lastEdited when we're sending new physics data
// so we can stop sending them after some limit. quint64 lastSimulated = _entity->getLastSimulated();
if (_sentMoving) { _entity->setLastEdited(lastSimulated);
_numNonMovingUpdates = 0; 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 { } else {
_numNonMovingUpdates++; // we don't own the simulation for this entity yet, but we're sending a bid for it
} properties.setSimulatorID(sessionID);
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<NodeList>();
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());
} }
if (EntityItem::getSendPhysicsUpdates()) { if (EntityItem::getSendPhysicsUpdates()) {
@ -453,7 +473,7 @@ QUuid EntityMotionState::getSimulatorID() const {
// virtual // virtual
void EntityMotionState::bump() { void EntityMotionState::bump() {
setShouldClaimSimulationOwnership(true); _candidateForOwnership = true;
} }
void EntityMotionState::resetMeasuredBodyAcceleration() { void EntityMotionState::resetMeasuredBodyAcceleration() {

View file

@ -36,7 +36,6 @@ public:
virtual MotionType computeObjectMotionType() const; virtual MotionType computeObjectMotionType() const;
virtual bool isMoving() const; virtual bool isMoving() const;
virtual bool isMovingVsServer() const;
// this relays incoming position/rotation to the RigidBody // this relays incoming position/rotation to the RigidBody
virtual void getWorldTransform(btTransform& worldTrans) const; virtual void getWorldTransform(btTransform& worldTrans) const;
@ -46,13 +45,10 @@ public:
virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo); virtual void computeObjectShapeInfo(ShapeInfo& shapeInfo);
bool doesNotNeedToSendUpdate() const; bool isCandidateForOwnership(const QUuid& sessionID) const;
bool remoteSimulationOutOfSync(uint32_t simulationStep); bool remoteSimulationOutOfSync(uint32_t simulationStep);
bool shouldSendUpdate(uint32_t simulationFrame); bool shouldSendUpdate(uint32_t simulationStep, const QUuid& sessionID);
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); void sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step);
void setShouldClaimSimulationOwnership(bool value) { _shouldClaimSimulationOwnership = value; }
bool getShouldClaimSimulationOwnership() { return _shouldClaimSimulationOwnership; }
virtual uint32_t getAndClearIncomingDirtyFlags() const; virtual uint32_t getAndClearIncomingDirtyFlags() const;
@ -92,7 +88,7 @@ protected:
EntityItem* _entity; 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 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 // these are for the prediction of the remote server's simple extrapolation
@ -109,8 +105,9 @@ protected:
glm::vec3 _measuredAcceleration; glm::vec3 _measuredAcceleration;
quint8 _accelerationNearlyGravityCount; quint8 _accelerationNearlyGravityCount;
bool _shouldClaimSimulationOwnership; bool _candidateForOwnership;
quint32 _movingStepsWithoutSimulationOwner; uint32_t _loopsSinceOwnershipBid;
uint32_t _loopsWithoutOwner;
}; };
#endif // hifi_EntityMotionState_h #endif // hifi_EntityMotionState_h

View file

@ -146,10 +146,6 @@ void ObjectMotionState::handleEasyChanges(uint32_t flags) {
_body->setMassProps(mass, inertia); _body->setMassProps(mass, inertia);
_body->updateInertiaTensor(); _body->updateInertiaTensor();
} }
if (flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) {
_body->activate();
}
} }
void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) { void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine) {

View file

@ -36,11 +36,11 @@ enum MotionStateType {
// and re-added to the physics engine and "easy" which just updates the body properties. // 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 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 | const uint32_t EASY_DIRTY_PHYSICS_FLAGS = (uint32_t)(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES |
EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP | EntityItem::DIRTY_MASS | EntityItem::DIRTY_COLLISION_GROUP);
EntityItem::DIRTY_MATERIAL | EntityItem::DIRTY_PHYSICS_ACTIVATION);
// These are the set of incoming flags that the PhysicsEngine needs to hear about: // 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: // These are the outgoing flags that the PhysicsEngine can affect:
const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES; const uint32_t OUTGOING_DIRTY_PHYSICS_FLAGS = EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES;

View file

@ -188,7 +188,7 @@ VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToChange() {
return _tempVector; 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 // walk the motionStates looking for those that correspond to entities
for (auto stateItr : motionStates) { for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr); ObjectMotionState* state = &(*stateItr);
@ -196,24 +196,32 @@ void PhysicalEntitySimulation::handleOutgoingChanges(VectorOfMotionStates& motio
EntityMotionState* entityState = static_cast<EntityMotionState*>(state); EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
EntityItem* entity = entityState->getEntity(); EntityItem* entity = entityState->getEntity();
if (entity) { if (entity) {
_outgoingChanges.insert(entityState); if (entity->isKnownID() && entityState->isCandidateForOwnership(sessionID)) {
_outgoingChanges.insert(entityState);
}
_entitiesToSort.insert(entityState->getEntity()); _entitiesToSort.insert(entityState->getEntity());
} }
} }
} }
// send outgoing packets
uint32_t numSubsteps = _physicsEngine->getNumSubsteps(); uint32_t numSubsteps = _physicsEngine->getNumSubsteps();
if (_lastStepSendPackets != numSubsteps) { if (_lastStepSendPackets != numSubsteps) {
_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<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin(); QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
while (stateItr != _outgoingChanges.end()) { while (stateItr != _outgoingChanges.end()) {
EntityMotionState* state = *stateItr; EntityMotionState* state = *stateItr;
if (state->doesNotNeedToSendUpdate()) { if (!state->isCandidateForOwnership(sessionID)) {
stateItr = _outgoingChanges.erase(stateItr); stateItr = _outgoingChanges.erase(stateItr);
} else if (state->shouldSendUpdate(numSubsteps)) { } else if (state->shouldSendUpdate(numSubsteps, sessionID)) {
state->sendUpdate(_entityPacketSender, numSubsteps); state->sendUpdate(_entityPacketSender, sessionID, numSubsteps);
++stateItr; ++stateItr;
} else { } else {
++stateItr; ++stateItr;

View file

@ -48,7 +48,7 @@ public:
VectorOfMotionStates& getObjectsToAdd(); VectorOfMotionStates& getObjectsToAdd();
VectorOfMotionStates& getObjectsToChange(); VectorOfMotionStates& getObjectsToChange();
void handleOutgoingChanges(VectorOfMotionStates& motionStates); void handleOutgoingChanges(VectorOfMotionStates& motionStates, const QUuid& sessionID);
void handleCollisionEvents(CollisionEvents& collisionEvents); void handleCollisionEvents(CollisionEvents& collisionEvents);
private: private:

View file

@ -53,6 +53,7 @@ public:
void init(); void init();
void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; } void setSessionUUID(const QUuid& sessionID) { _sessionID = sessionID; }
const QUuid& getSessionID() const { return _sessionID; }
void addObject(ObjectMotionState* motionState); void addObject(ObjectMotionState* motionState);
void removeObject(ObjectMotionState* motionState); void removeObject(ObjectMotionState* motionState);

View file

@ -103,7 +103,7 @@ void EntityTests::entityTreeTests(bool verbose) {
properties.setPosition(newPosition); properties.setPosition(newPosition);
tree.updateEntity(entityID, properties, true); tree.updateEntity(entityID, properties);
float targetRadius = oneMeter * 2.0f; float targetRadius = oneMeter * 2.0f;
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOrigin, targetRadius); const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionNearOrigin, targetRadius);
@ -143,7 +143,7 @@ void EntityTests::entityTreeTests(bool verbose) {
properties.setPosition(newPosition); properties.setPosition(newPosition);
tree.updateEntity(entityID, properties, true); tree.updateEntity(entityID, properties);
float targetRadius = oneMeter * 2.0f; float targetRadius = oneMeter * 2.0f;
const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenter, targetRadius); const EntityItem* foundEntityByRadius = tree.findClosestEntity(positionAtCenter, targetRadius);