diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 06da384a94..b106ee6398 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -148,10 +148,6 @@ QUuid AvatarMotionState::getSimulatorID() const { return _avatar->getSessionUUID(); } -// virtual -void AvatarMotionState::bump() { -} - // virtual int16_t AvatarMotionState::computeCollisionGroup() { return COLLISION_GROUP_OTHER_AVATAR; diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 79a4d23179..ac813e764c 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -55,7 +55,6 @@ public: virtual const QUuid& getObjectID() const; virtual QUuid getSimulatorID() const; - virtual void bump(); void setBoundingBox(const glm::vec3& corner, const glm::vec3& diagonal); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 61d045769e..508adbb1f2 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "EntityItem.h" + #include #include @@ -22,12 +24,15 @@ #include #include "EntityScriptingInterface.h" -#include "EntityItem.h" #include "EntitiesLogging.h" #include "EntityTree.h" #include "EntitySimulation.h" #include "EntityActionFactoryInterface.h" + +const quint64 DEFAULT_SIMULATOR_CHANGE_LOCKOUT_PERIOD = (quint64)(0.2f * USECS_PER_SECOND); +const quint64 MAX_SIMULATOR_CHANGE_LOCKOUT_PERIOD = 2 * USECS_PER_SECOND; + bool EntityItem::_sendPhysicsUpdates = true; int EntityItem::_maxActionDataSize = 800; @@ -66,8 +71,7 @@ EntityItem::EntityItem(const EntityItemID& entityItemID) : _collisionsWillMove(ENTITY_ITEM_DEFAULT_COLLISIONS_WILL_MOVE), _locked(ENTITY_ITEM_DEFAULT_LOCKED), _userData(ENTITY_ITEM_DEFAULT_USER_DATA), - _simulatorID(ENTITY_ITEM_DEFAULT_SIMULATOR_ID), - _simulatorIDChangedTime(0), + _simulationOwner(), _marketplaceID(ENTITY_ITEM_DEFAULT_MARKETPLACE_ID), _name(ENTITY_ITEM_DEFAULT_NAME), _href(""), @@ -105,13 +109,16 @@ EntityItem::~EntityItem() { EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties; + requestedProperties += PROP_SIMULATION_OWNER; requestedProperties += PROP_POSITION; - requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete requestedProperties += PROP_ROTATION; - requestedProperties += PROP_DENSITY; requestedProperties += PROP_VELOCITY; - requestedProperties += PROP_GRAVITY; + requestedProperties += PROP_ANGULAR_VELOCITY; requestedProperties += PROP_ACCELERATION; + + requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete + requestedProperties += PROP_DENSITY; + requestedProperties += PROP_GRAVITY; requestedProperties += PROP_DAMPING; requestedProperties += PROP_RESTITUTION; requestedProperties += PROP_FRICTION; @@ -120,7 +127,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_SCRIPT_TIMESTAMP; requestedProperties += PROP_COLLISION_SOUND_URL; requestedProperties += PROP_REGISTRATION_POINT; - requestedProperties += PROP_ANGULAR_VELOCITY; requestedProperties += PROP_ANGULAR_DAMPING; requestedProperties += PROP_VISIBLE; requestedProperties += PROP_IGNORE_FOR_COLLISIONS; @@ -129,7 +135,6 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_USER_DATA; requestedProperties += PROP_MARKETPLACE_ID; requestedProperties += PROP_NAME; - requestedProperties += PROP_SIMULATOR_ID; requestedProperties += PROP_HREF; requestedProperties += PROP_DESCRIPTION; requestedProperties += PROP_ACTION_DATA; @@ -238,13 +243,16 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, + APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, _simulationOwner.toByteArray()); APPEND_ENTITY_PROPERTY(PROP_POSITION, getPosition()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete APPEND_ENTITY_PROPERTY(PROP_ROTATION, getRotation()); - APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, getVelocity()); - APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); + APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); + + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete + APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); + APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); APPEND_ENTITY_PROPERTY(PROP_RESTITUTION, getRestitution()); APPEND_ENTITY_PROPERTY(PROP_FRICTION, getFriction()); @@ -252,14 +260,12 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_SCRIPT, getScript()); APPEND_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, getScriptTimestamp()); APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, getRegistrationPoint()); - APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, getAngularDamping()); APPEND_ENTITY_PROPERTY(PROP_VISIBLE, getVisible()); APPEND_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, getIgnoreForCollisions()); APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, getCollisionsWillMove()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked()); APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData()); - APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, getSimulatorID()); APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID()); APPEND_ENTITY_PROPERTY(PROP_NAME, getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL()); @@ -329,8 +335,8 @@ int EntityItem::expectedBytes() { } +// clients use this method to unpack FULL updates from entity-server int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { - if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_SPLIT_MTU) { // NOTE: This shouldn't happen. The only versions of the bit stream that didn't support split mtu buffers should @@ -354,14 +360,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef return 0; } - // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. - glm::vec3 savePosition = getPosition(); - glm::quat saveRotation = getRotation(); - glm::vec3 saveVelocity = _velocity; - glm::vec3 saveAngularVelocity = _angularVelocity; - int originalLength = bytesLeftToRead; - QByteArray originalDataBuffer((const char*)data, originalLength); + // TODO: figure out a way to avoid the big deep copy below. + QByteArray originalDataBuffer((const char*)data, originalLength); // big deep copy! int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; @@ -539,40 +540,70 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef EntityPropertyFlags propertyFlags = encodedPropertyFlags; dataAt += propertyFlags.getEncodedLength(); bytesRead += propertyFlags.getEncodedLength(); - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); - // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS - if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { - if (propertyFlags.getHasProperty(PROP_RADIUS)) { - float fromBuffer; - memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); - dataAt += sizeof(fromBuffer); - bytesRead += sizeof(fromBuffer); - if (overwriteLocalData) { - setRadius(fromBuffer); + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_SIMULATION_OWNER) { + // pack SimulationOwner and terse update properties near each other + + // NOTE: the server is authoritative for changes to simOwnerID so we always unpack ownership data + // even when we would otherwise ignore the rest of the packet. + + if (propertyFlags.getHasProperty(PROP_SIMULATION_OWNER)) { + QByteArray simOwnerData; + int bytes = OctreePacketData::unpackDataFromBytes(dataAt, simOwnerData); + SimulationOwner newSimOwner; + newSimOwner.fromByteArray(simOwnerData); + dataAt += bytes; + bytesRead += bytes; + + if (_simulationOwner.set(newSimOwner)) { + _dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; } } - } else { + { // When we own the simulation we don't accept updates to the entity's transform/velocities + // but since we're using macros below we have to temporarily modify overwriteLocalData. + auto nodeList = DependencyManager::get(); + bool weOwnIt = _simulationOwner.matchesValidID(nodeList->getSessionUUID()); + bool oldOverwrite = overwriteLocalData; + overwriteLocalData = overwriteLocalData && !weOwnIt; + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); + READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); + overwriteLocalData = oldOverwrite; + } + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); - } - - READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); - READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); + + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + } else { + // legacy order of packing here + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); + READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); + + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); } - READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); - READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); - READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); - READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); - READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); - READ_ENTITY_PROPERTY(PROP_SCRIPT_TIMESTAMP, quint64, setScriptTimestamp); - READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); - //READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); @@ -580,12 +611,14 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + if (args.bitstreamVersion < VERSION_ENTITIES_HAVE_SIMULATION_OWNER) { + // this code for when there is only simulatorID and no simulation priority + // we always accept the server's notion of simulatorID, so we fake overwriteLocalData as true // before we try to READ_ENTITY_PROPERTY it bool temp = overwriteLocalData; overwriteLocalData = true; - READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); + READ_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, QUuid, updateSimulatorID); overwriteLocalData = temp; } @@ -628,7 +661,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef #ifdef WANT_DEBUG qCDebug(entities) << "skipTimeForward:" << skipTimeForward; #endif - // we want to extrapolate the motion forward to compensate for packet travel time, but // we don't want the side effect of flag setting. simulateKinematicMotion(skipTimeForward, false); @@ -638,15 +670,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); if (overwriteLocalData) { - if (_simulatorID == myNodeID && !_simulatorID.isNull()) { - // we own the simulation, so we keep our transform+velocities and remove any related dirty flags - // rather than accept the values in the packet - setPosition(savePosition); - setRotation(saveRotation); - _velocity = saveVelocity; - _angularVelocity = saveAngularVelocity; - _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); - } else { + if (!_simulationOwner.matchesValidID(myNodeID)) { + _lastSimulated = now; } } @@ -910,6 +935,7 @@ EntityItemProperties EntityItem::getProperties() const { properties._type = getType(); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulationOwner, getSimulationOwner); COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPosition); COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensions); // NOTE: radius is obsolete COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation); @@ -935,7 +961,6 @@ EntityItemProperties EntityItem::getProperties() const { COPY_ENTITY_PROPERTY_TO_PROPERTIES(collisionsWillMove, getCollisionsWillMove); COPY_ENTITY_PROPERTY_TO_PROPERTIES(locked, getLocked); COPY_ENTITY_PROPERTY_TO_PROPERTIES(userData, getUserData); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(simulatorID, getSimulatorID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(marketplaceID, getMarketplaceID); COPY_ENTITY_PROPERTY_TO_PROPERTIES(name, getName); COPY_ENTITY_PROPERTY_TO_PROPERTIES(href, getHref); @@ -966,6 +991,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; // these affect TerseUpdate properties + SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner); SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, updatePosition); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, updateRotation); SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, updateVelocity); @@ -987,7 +1013,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(collisionsWillMove, updateCollisionsWillMove); SET_ENTITY_PROPERTY_FROM_PROPERTIES(created, updateCreated); SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, updateLifetime); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulatorID, updateSimulatorID); // non-simulation properties below SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript); @@ -1363,19 +1388,28 @@ void EntityItem::updateCreated(uint64_t value) { } } -void EntityItem::setSimulatorID(const QUuid& value) { - _simulatorID = value; - _simulatorIDChangedTime = usecTimestampNow(); +void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { + _simulationOwner.set(id, priority); +} + +void EntityItem::setSimulationOwner(const SimulationOwner& owner) { + _simulationOwner.set(owner); } void EntityItem::updateSimulatorID(const QUuid& value) { - if (_simulatorID != value) { - _simulatorID = value; - _simulatorIDChangedTime = usecTimestampNow(); + if (_simulationOwner.setID(value)) { _dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; } } +void EntityItem::clearSimulationOwnership() { + _simulationOwner.clear(); + // don't bother setting the DIRTY_SIMULATOR_ID flag because clearSimulationOwnership() + // is only ever called entity-server-side and the flags are only used client-side + //_dirtyFlags |= EntityItem::DIRTY_SIMULATOR_ID; + +} + bool EntityItem::addAction(EntitySimulation* simulation, EntityActionPointer action) { assert(action); auto actionOwnerEntity = action->getOwnerEntity().lock(); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 6859d4252d..8656a5d4cf 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -29,6 +29,7 @@ #include "EntityItemProperties.h" #include "EntityItemPropertiesDefaults.h" #include "EntityTypes.h" +#include "SimulationOwner.h" class EntitySimulation; class EntityTreeElement; @@ -60,7 +61,6 @@ const float ACTIVATION_LINEAR_VELOCITY_DELTA = 0.01f; const float ACTIVATION_GRAVITY_DELTA = 0.1f; const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; - #define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0; #define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() { }; @@ -68,7 +68,6 @@ const float ACTIVATION_ANGULAR_VELOCITY_DELTA = 0.03f; #define debugTimeOnly(T) qPrintable(QString("%1").arg(T, 16, 10)) #define debugTreeVector(V) V << "[" << V << " in meters ]" - /// EntityItem class this is the base class for all entity types. It handles the basic properties and functionality available /// to all other entity types. In particular: postion, size, rotation, age, lifetime, velocity, gravity. You can not instantiate /// one directly, instead you must only construct one of it's derived classes with additional features. @@ -92,8 +91,9 @@ public: DIRTY_LIFETIME = 0x0100, DIRTY_UPDATEABLE = 0x0200, DIRTY_MATERIAL = 0x00400, - DIRTY_PHYSICS_ACTIVATION = 0x0800, // we want to activate the object - DIRTY_SIMULATOR_ID = 0x1000, + DIRTY_PHYSICS_ACTIVATION = 0x0800, // should activate object in physics engine + DIRTY_SIMULATOR_OWNERSHIP = 0x1000, // should claim simulator ownership + DIRTY_SIMULATOR_ID = 0x2000, // the simulatorID has changed DIRTY_TRANSFORM = DIRTY_POSITION | DIRTY_ROTATION, DIRTY_VELOCITIES = DIRTY_LINEAR_VELOCITY | DIRTY_ANGULAR_VELOCITY }; @@ -317,11 +317,16 @@ public: const QString& getUserData() const { return _userData; } void setUserData(const QString& value) { _userData = value; } - - QUuid getSimulatorID() const { return _simulatorID; } - void setSimulatorID(const QUuid& value); + + const SimulationOwner& getSimulationOwner() const { return _simulationOwner; } + void setSimulationOwner(const QUuid& id, quint8 priority); + void setSimulationOwner(const SimulationOwner& owner); + void promoteSimulationPriority(quint8 priority); + + quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); } + QUuid getSimulatorID() const { return _simulationOwner.getID(); } void updateSimulatorID(const QUuid& value); - quint64 getSimulatorIDChangedTime() const { return _simulatorIDChangedTime; } + void clearSimulationOwnership(); const QString& getMarketplaceID() const { return _marketplaceID; } void setMarketplaceID(const QString& value) { _marketplaceID = value; } @@ -358,7 +363,7 @@ public: virtual void updateShapeType(ShapeType type) { /* do nothing */ } uint32_t getDirtyFlags() const { return _dirtyFlags; } - void clearDirtyFlags(uint32_t mask = 0xffff) { _dirtyFlags &= ~mask; } + void clearDirtyFlags(uint32_t mask = 0xffffffff) { _dirtyFlags &= ~mask; } bool isMoving() const; @@ -379,6 +384,8 @@ public: void getAllTerseUpdateProperties(EntityItemProperties& properties) const; + void flagForOwnership() { _dirtyFlags |= DIRTY_SIMULATOR_OWNERSHIP; } + bool addAction(EntitySimulation* simulation, EntityActionPointer action); bool updateAction(EntitySimulation* simulation, const QUuid& actionID, const QVariantMap& arguments); bool removeAction(EntitySimulation* simulation, const QUuid& actionID); @@ -431,8 +438,7 @@ protected: bool _collisionsWillMove; bool _locked; QString _userData; - QUuid _simulatorID; // id of Node which is currently responsible for simulating this Entity - quint64 _simulatorIDChangedTime; // when was _simulatorID last updated? + SimulationOwner _simulationOwner; QString _marketplaceID; QString _name; QString _href; //Hyperlink href diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 9956937a3f..9a1a5494b7 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -38,7 +38,7 @@ EntityPropertyList PROP_LAST_ITEM = (EntityPropertyList)(PROP_AFTER_LAST_ITEM - EntityItemProperties::EntityItemProperties() : CONSTRUCT_PROPERTY(visible, ENTITY_ITEM_DEFAULT_VISIBLE), -CONSTRUCT_PROPERTY(position, 0), +CONSTRUCT_PROPERTY(position, 0.0f), CONSTRUCT_PROPERTY(dimensions, ENTITY_ITEM_DEFAULT_DIMENSIONS), CONSTRUCT_PROPERTY(rotation, ENTITY_ITEM_DEFAULT_ROTATION), CONSTRUCT_PROPERTY(density, ENTITY_ITEM_DEFAULT_DENSITY), @@ -73,7 +73,7 @@ CONSTRUCT_PROPERTY(locked, ENTITY_ITEM_DEFAULT_LOCKED), CONSTRUCT_PROPERTY(textures, ""), CONSTRUCT_PROPERTY(animationSettings, ""), CONSTRUCT_PROPERTY(userData, ENTITY_ITEM_DEFAULT_USER_DATA), -CONSTRUCT_PROPERTY(simulatorID, ENTITY_ITEM_DEFAULT_SIMULATOR_ID), +CONSTRUCT_PROPERTY(simulationOwner, SimulationOwner()), CONSTRUCT_PROPERTY(text, TextEntityItem::DEFAULT_TEXT), CONSTRUCT_PROPERTY(lineHeight, TextEntityItem::DEFAULT_LINE_HEIGHT), CONSTRUCT_PROPERTY(textColor, TextEntityItem::DEFAULT_TEXT_COLOR), @@ -288,8 +288,8 @@ void EntityItemProperties::setBackgroundModeFromString(const QString& background EntityPropertyFlags EntityItemProperties::getChangedProperties() const { EntityPropertyFlags changedProperties; - CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); CHECK_PROPERTY_CHANGE(PROP_POSITION, position); + CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions); CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation); CHECK_PROPERTY_CHANGE(PROP_DENSITY, density); CHECK_PROPERTY_CHANGE(PROP_VELOCITY, velocity); @@ -323,7 +323,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_LOCKED, locked); CHECK_PROPERTY_CHANGE(PROP_TEXTURES, textures); CHECK_PROPERTY_CHANGE(PROP_USER_DATA, userData); - CHECK_PROPERTY_CHANGE(PROP_SIMULATOR_ID, simulatorID); + CHECK_PROPERTY_CHANGE(PROP_SIMULATION_OWNER, simulationOwner); CHECK_PROPERTY_CHANGE(PROP_TEXT, text); CHECK_PROPERTY_CHANGE(PROP_LINE_HEIGHT, lineHeight); CHECK_PROPERTY_CHANGE(PROP_TEXT_COLOR, textColor); @@ -419,7 +419,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(locked); COPY_PROPERTY_TO_QSCRIPTVALUE(textures); COPY_PROPERTY_TO_QSCRIPTVALUE(userData); - COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(simulatorID, getSimulatorIDAsString()); + //COPY_PROPERTY_TO_QSCRIPTVALUE(simulationOwner); // TODO: expose this for JSON saves? COPY_PROPERTY_TO_QSCRIPTVALUE(text); COPY_PROPERTY_TO_QSCRIPTVALUE(lineHeight); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(textColor, getTextColor()); @@ -572,7 +572,8 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool auto result = QDateTime::fromMSecsSinceEpoch(_created / 1000, Qt::UTC); // usec per msec return result; }); - COPY_PROPERTY_FROM_QSCRIPTVALUE(simulatorID, QUuid, setSimulatorID); + // TODO: expose this to QScriptValue for JSON saves? + //COPY_PROPERTY_FROM_QSCRIPTVALUE(simulationOwner, ???, setSimulatorPriority); } _stage.copyFromScriptValue(object, _defaultSettings); @@ -707,6 +708,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, + APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray()); APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition()); APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation()); @@ -729,7 +731,6 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, properties.getCollisionsWillMove()); APPEND_ENTITY_PROPERTY(PROP_LOCKED, properties.getLocked()); APPEND_ENTITY_PROPERTY(PROP_USER_DATA, properties.getUserData()); - APPEND_ENTITY_PROPERTY(PROP_SIMULATOR_ID, properties.getSimulatorID()); APPEND_ENTITY_PROPERTY(PROP_HREF, properties.getHref()); APPEND_ENTITY_PROPERTY(PROP_DESCRIPTION, properties.getDescription()); @@ -962,7 +963,8 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int EntityPropertyFlags propertyFlags = encodedPropertyFlags; dataAt += propertyFlags.getEncodedLength(); processedBytes += propertyFlags.getEncodedLength(); - + + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation); @@ -985,7 +987,6 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISIONS_WILL_MOVE, bool, setCollisionsWillMove); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LOCKED, bool, setLocked); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USER_DATA, QString, setUserData); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATOR_ID, QUuid, setSimulatorID); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_HREF, QString, setHref); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DESCRIPTION, QString, setDescription); @@ -1102,6 +1103,7 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt } void EntityItemProperties::markAllChanged() { + _simulationOwnerChanged = true; _positionChanged = true; _dimensionsChanged = true; _rotationChanged = true; @@ -1114,7 +1116,6 @@ void EntityItemProperties::markAllChanged() { _frictionChanged = true; _lifetimeChanged = true; _userDataChanged = true; - _simulatorIDChanged = true; _scriptChanged = true; _scriptTimestampChanged = true; _collisionSoundURLChanged = true; @@ -1230,3 +1231,27 @@ bool EntityItemProperties::hasTerseUpdateChanges() const { // a TerseUpdate includes the transform and its derivatives return _positionChanged || _velocityChanged || _rotationChanged || _angularVelocityChanged || _accelerationChanged; } + +bool EntityItemProperties::hasMiscPhysicsChanges() const { + return _gravityChanged || _dimensionsChanged || _densityChanged || _frictionChanged + || _restitutionChanged || _dampingChanged || _angularDampingChanged || _registrationPointChanged || + _compoundShapeURLChanged || _collisionsWillMoveChanged || _ignoreForCollisionsChanged; +} + +void EntityItemProperties::clearSimulationOwner() { + _simulationOwner.clear(); + _simulationOwnerChanged = true; +} + +void EntityItemProperties::setSimulationOwner(const QUuid& id, uint8_t priority) { + if (!_simulationOwner.matchesValidID(id) || _simulationOwner.getPriority() != priority) { + _simulationOwner.set(id, priority); + _simulationOwnerChanged = true; + } +} + +void EntityItemProperties::setSimulationOwner(const QByteArray& data) { + if (_simulationOwner.fromByteArray(data)) { + _simulationOwnerChanged = true; + } +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 570d14d709..b8200d025c 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -34,6 +34,7 @@ #include "EntityItemPropertiesMacros.h" #include "EntityTypes.h" #include "EntityPropertyFlags.h" +#include "SimulationOwner.h" #include "SkyboxPropertyGroup.h" #include "StagePropertyGroup.h" @@ -120,7 +121,7 @@ public: DEFINE_PROPERTY_REF(PROP_TEXTURES, Textures, textures, QString); DEFINE_PROPERTY_REF_WITH_SETTER_AND_GETTER(PROP_ANIMATION_SETTINGS, AnimationSettings, animationSettings, QString); DEFINE_PROPERTY_REF(PROP_USER_DATA, UserData, userData, QString); - DEFINE_PROPERTY_REF(PROP_SIMULATOR_ID, SimulatorID, simulatorID, QUuid); + DEFINE_PROPERTY_REF(PROP_SIMULATION_OWNER, SimulationOwner, simulationOwner, SimulationOwner); DEFINE_PROPERTY_REF(PROP_TEXT, Text, text, QString); DEFINE_PROPERTY(PROP_LINE_HEIGHT, LineHeight, lineHeight, float); DEFINE_PROPERTY_REF(PROP_TEXT_COLOR, TextColor, textColor, xColor); @@ -197,7 +198,7 @@ public: const QStringList& getTextureNames() const { return _textureNames; } void setTextureNames(const QStringList& value) { _textureNames = value; } - QString getSimulatorIDAsString() const { return _simulatorID.toString().mid(1,36).toUpper(); } + QString getSimulatorIDAsString() const { return _simulationOwner.getID().toString().mid(1,36).toUpper(); } void setVoxelDataDirty() { _voxelDataChanged = true; } @@ -206,6 +207,12 @@ public: void setCreated(QDateTime& v); bool hasTerseUpdateChanges() const; + bool hasMiscPhysicsChanges() const; + + void clearSimulationOwner(); + void setSimulationOwner(const QUuid& id, uint8_t priority); + void setSimulationOwner(const QByteArray& data); + void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); } void setActionDataDirty() { _actionDataChanged = true; } @@ -287,7 +294,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, Locked, locked, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Textures, textures, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, UserData, userData, ""); - DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulatorID, simulatorID, QUuid()); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, SimulationOwner, simulationOwner, SimulationOwner()); DEBUG_PROPERTY_IF_CHANGED(debug, properties, Text, text, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, LineHeight, lineHeight, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, TextColor, textColor, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 363b36d34f..be16683f39 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -104,7 +104,7 @@ enum EntityPropertyList { PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities PROP_MARKETPLACE_ID, // all entities PROP_ACCELERATION, // all entities - PROP_SIMULATOR_ID, // all entities + PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID PROP_NAME, // all entities PROP_COLLISION_SOUND_URL, PROP_RESTITUTION, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index bc89e74863..51c698e7e8 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -9,18 +9,20 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include "EntityScriptingInterface.h" + #include +#include "EntitiesLogging.h" +#include "EntityActionFactoryInterface.h" +#include "EntityActionInterface.h" +#include "EntitySimulation.h" #include "EntityTree.h" #include "LightEntityItem.h" #include "ModelEntityItem.h" +#include "SimulationOwner.h" #include "ZoneEntityItem.h" -#include "EntitiesLogging.h" -#include "EntitySimulation.h" -#include "EntityActionInterface.h" -#include "EntityActionFactoryInterface.h" -#include "EntityScriptingInterface.h" EntityScriptingInterface::EntityScriptingInterface() : _entityTree(NULL) @@ -61,16 +63,6 @@ void EntityScriptingInterface::setEntityTree(EntityTree* modelTree) { } } -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(); - properties.setSimulatorID(myNodeID); -} - - - QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties) { EntityItemProperties propertiesWithSimID = properties; @@ -83,11 +75,15 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties _entityTree->lockForWrite(); EntityItemPointer entity = _entityTree->addEntity(id, propertiesWithSimID); if (entity) { - entity->setLastBroadcast(usecTimestampNow()); // This Node is creating a new object. If it's in motion, set this Node as the simulator. - bidForSimulationOwnership(propertiesWithSimID); + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + propertiesWithSimID.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); + // and make note of it now, so we can act on it right away. - entity->setSimulatorID(propertiesWithSimID.getSimulatorID()); + entity->setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); + + entity->setLastBroadcast(usecTimestampNow()); } else { qCDebug(entities) << "script failed to add new Entity to local Octree"; success = false; @@ -146,23 +142,35 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, EntityItemProperties proper if (entity) { // make sure the properties has a type, so that the encode can know which properties to include properties.setType(entity->getType()); - if (properties.hasTerseUpdateChanges()) { + bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges(); + bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges; + if (hasPhysicsChanges) { auto nodeList = DependencyManager::get(); const QUuid myNodeID = nodeList->getSessionUUID(); if (entity->getSimulatorID() == myNodeID) { // we think we already own the simulation, so make sure to send ALL TerseUpdate properties - entity->getAllTerseUpdateProperties(properties); - // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object - // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update + if (hasTerseUpdateChanges) { + entity->getAllTerseUpdateProperties(properties); + } + // TODO: if we knew that ONLY TerseUpdate properties have changed in properties AND the object + // is dynamic AND it is active in the physics simulation then we could chose to NOT queue an update // and instead let the physics simulation decide when to send a terse update. This would remove // the "slide-no-rotate" glitch (and typical a double-update) that we see during the "poke rolling // balls" test. However, even if we solve this problem we still need to provide a "slerp the visible // proxy toward the true physical position" feature to hide the final glitches in the remote watcher's // simulation. + + if (entity->getSimulationPriority() < SCRIPT_EDIT_SIMULATION_PRIORITY) { + // we re-assert our simulation ownership at a higher priority + properties.setSimulationOwner(myNodeID, + glm::max(entity->getSimulationPriority(), SCRIPT_EDIT_SIMULATION_PRIORITY)); + } + } else { + // we make a bid for simulation ownership + properties.setSimulationOwner(myNodeID, SCRIPT_EDIT_SIMULATION_PRIORITY); + entity->flagForOwnership(); } - // we make a bid for (or assert existing) simulation ownership - properties.setSimulatorID(myNodeID); } entity->setLastBroadcast(usecTimestampNow()); } @@ -567,6 +575,11 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, return false; } if (actionFactory->factory(simulation, actionType, actionID, entity, arguments)) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + if (entity->getSimulatorID() != myNodeID) { + entity->flagForOwnership(); + } return true; } return false; @@ -580,7 +593,15 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString, bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid& actionID, const QVariantMap& arguments) { return actionWorker(entityID, [&](EntitySimulation* simulation, EntityItemPointer entity) { - return entity->updateAction(simulation, actionID, arguments); + bool success = entity->updateAction(simulation, actionID, arguments); + if (success) { + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + if (entity->getSimulatorID() != myNodeID) { + entity->flagForOwnership(); + } + } + return success; }); } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 752082932b..3d5878784a 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -150,23 +150,34 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI } else { if (getIsServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); - if (properties.simulatorIDChanged()) { - QUuid submittedID = properties.getSimulatorID(); + if (properties.simulationOwnerChanged()) { + QUuid submittedID = properties.getSimulationOwner().getID(); // 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; + properties.clearSimulationOwner(); // clear everything } // 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) { + if (entity->getSimulatorID().isNull()) { + // the sender it taking ownership + properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY); + simulationBlocked = false; + } else if (entity->getSimulatorID() == senderID) { + // the sender is asserting ownership 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) { + // so we apply the rules for ownership change: + // (1) higher priority wins + // (2) equal priority wins if ownership filter has expired except... + uint8_t oldPriority = entity->getSimulationPriority(); + uint8_t newPriority = properties.getSimulationOwner().getPriority(); + if (newPriority > oldPriority || + (newPriority == oldPriority && properties.getSimulationOwner().hasExpired())) { simulationBlocked = false; } } @@ -174,12 +185,17 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI // the entire update is suspect --> ignore it return false; } + } else { + simulationBlocked = senderID != entity->getSimulatorID(); } if (simulationBlocked) { - // squash the physics-related changes. - properties.setSimulatorIDChanged(false); + // squash ownership and physics-related changes. + properties.setSimulationOwnerChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); + properties.setVelocityChanged(false); + properties.setAngularVelocityChanged(false); + properties.setAccelerationChanged(false); } } // else client accepts what the server says diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 0d1eda1ac8..c9d4fdb2f6 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -23,19 +23,20 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { // has finished simulating it. auto nodeList = DependencyManager::get(); - SetOfEntities::iterator itemItr = _hasSimulationOwnerEntities.begin(); - while (itemItr != _hasSimulationOwnerEntities.end()) { + SetOfEntities::iterator itemItr = _entitiesWithSimulator.begin(); + while (itemItr != _entitiesWithSimulator.end()) { EntityItemPointer entity = *itemItr; if (entity->getSimulatorID().isNull()) { - itemItr = _hasSimulationOwnerEntities.erase(itemItr); + itemItr = _entitiesWithSimulator.erase(itemItr); } else if (now - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) { SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID()); if (ownerNode.isNull() || !ownerNode->isAlive()) { qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); - entity->setSimulatorID(QUuid()); itemItr = _hasSimulationOwnerEntities.erase(itemItr); - + // TODO: zero velocities when we clear simulatorID? + entity->clearSimulationOwnership(); + itemItr = _entitiesWithSimulator.erase(itemItr); // zero the velocity on this entity so that it doesn't drift far away entity->setVelocity(glm::vec3(0.0f)); } else { @@ -50,23 +51,23 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { EntitySimulation::addEntityInternal(entity); if (!entity->getSimulatorID().isNull()) { - _hasSimulationOwnerEntities.insert(entity); + _entitiesWithSimulator.insert(entity); } } void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { - _hasSimulationOwnerEntities.remove(entity); + _entitiesWithSimulator.remove(entity); } void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { EntitySimulation::changeEntityInternal(entity); if (!entity->getSimulatorID().isNull()) { - _hasSimulationOwnerEntities.insert(entity); + _entitiesWithSimulator.insert(entity); } entity->clearDirtyFlags(); } void SimpleEntitySimulation::clearEntitiesInternal() { - _hasSimulationOwnerEntities.clear(); + _entitiesWithSimulator.clear(); } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 6eb3980dd3..fff0659067 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -28,7 +28,7 @@ protected: virtual void changeEntityInternal(EntityItemPointer entity); virtual void clearEntitiesInternal(); - SetOfEntities _hasSimulationOwnerEntities; + SetOfEntities _entitiesWithSimulator; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp new file mode 100644 index 0000000000..d6957873e2 --- /dev/null +++ b/libraries/entities/src/SimulationOwner.cpp @@ -0,0 +1,181 @@ +// +// SimulationOwner.cpp +// libraries/entities/src +// +// Created by Andrew Meadows on 2015.06.19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "SimulationOwner.h" + +#include // included for tests +#include + +#include + +// static +const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; + + +SimulationOwner::SimulationOwner(const SimulationOwner& other) + : _id(other._id), _priority(other._priority), _expiry(other._expiry) { +} + +QByteArray SimulationOwner::toByteArray() const { + QByteArray data = _id.toRfc4122(); + data.append(_priority); + return data; +} + +bool SimulationOwner::fromByteArray(const QByteArray& data) { + if (data.size() == NUM_BYTES_ENCODED) { + QByteArray idBytes = data.left(NUM_BYTES_RFC4122_UUID); + _id = QUuid::fromRfc4122(idBytes); + _priority = data[NUM_BYTES_RFC4122_UUID]; + return true; + } + return false; +} + +void SimulationOwner::clear() { + _id = QUuid(); + _priority = 0; + _expiry = 0; +} + +void SimulationOwner::setPriority(quint8 priority) { + _priority = priority; + if (_priority == 0) { + // when priority is zero we clear everything + _expiry = 0; + _id = QUuid(); + } +} + +void SimulationOwner::promotePriority(quint8 priority) { + if (priority > _priority) { + _priority = priority; + updateExpiry(); + } +} + +bool SimulationOwner::setID(const QUuid& id) { + if (_id != id) { + _id = id; + updateExpiry(); + if (_id.isNull()) { + _priority = 0; + } + return true; + } + return false; +} + +bool SimulationOwner::set(const QUuid& id, quint8 priority) { + setPriority(priority); + return setID(id); +} + +bool SimulationOwner::set(const SimulationOwner& owner) { + setPriority(owner._priority); + return setID(owner._id); +} + +void SimulationOwner::updateExpiry() { + const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5; + _expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY; +} + +// NOTE: eventually this code will be moved into unit tests +// static debug +void SimulationOwner::test() { + { // test default constructor + SimulationOwner simOwner; + if (!simOwner.isNull()) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should be NULL" << std::endl; + } + + if (simOwner.getPriority() != 0) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl; + } + } + + { // test set constructor + QUuid id = QUuid::createUuid(); + quint8 priority = 128; + SimulationOwner simOwner(id, priority); + if (simOwner.isNull()) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl; + } + + if (simOwner.getID() != id) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl; + } + + if (simOwner.getPriority() != priority) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl; + } + + QUuid otherID = QUuid::createUuid(); + if (simOwner.getID() == otherID) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl; + } + } + + { // test set() + QUuid id = QUuid::createUuid(); + quint8 priority = 1; + SimulationOwner simOwner; + simOwner.set(id, priority); + if (simOwner.isNull()) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl; + } + + if (simOwner.getID() != id) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner with unexpected id" << std::endl; + } + + if (simOwner.getPriority() != priority) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : unexpeced SimulationOwner priority" << std::endl; + } + } + + { // test encode/decode + SimulationOwner ownerA(QUuid::createUuid(), 1); + SimulationOwner ownerB(QUuid::createUuid(), 2); + + QByteArray data = ownerA.toByteArray(); + ownerB.fromByteArray(data); + + if (ownerA.getID() != ownerB.getID()) { + std::cout << __FILE__ << ":" << __LINE__ << " ERROR : ownerA._id should be equal to ownerB._id" << std::endl; + } + } +} + +bool SimulationOwner::operator!=(const SimulationOwner& other) { + return (_id != other._id && _priority != other._priority); +} + +SimulationOwner& SimulationOwner::operator=(const SimulationOwner& other) { + _priority = other._priority; + if (_priority == 0) { + _id = QUuid(); + _expiry = 0; + } else { + if (_id != other._id) { + updateExpiry(); + } + _id = other._id; + } + return *this; +} + +QDebug& operator<<(QDebug& d, const SimulationOwner& simOwner) { + d << "{ id : " << simOwner._id << ", priority : " << (int)simOwner._priority << " }"; + return d; +} + diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h new file mode 100644 index 0000000000..325be54e62 --- /dev/null +++ b/libraries/entities/src/SimulationOwner.h @@ -0,0 +1,86 @@ +// +// SimulationOwner.h +// libraries/entities/src +// +// Created by Andrew Meadows on 2015.06.19 +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_SimulationOwner_h +#define hifi_SimulationOwner_h + +#include +#include + +#include +#include + +// Simulation observers will bid to simulate unowned active objects at the lowest possible priority +// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it +// to RECRUIT priority so that other volunteers don't accidentally take over. +const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01; +const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; + +// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. +const quint8 SCRIPT_EDIT_SIMULATION_PRIORITY = 0x80; + +// PERSONAL priority (needs a better name) is the level at which a simulation observer will bid for +// objects that collide its MyAvatar. +const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_EDIT_SIMULATION_PRIORITY - 1; + + +class SimulationOwner { +public: + static const int NUM_BYTES_ENCODED; + + SimulationOwner() : _id(), _priority(0), _expiry(0) {} + SimulationOwner(const QUuid& id, quint8 priority) : _id(id), _priority(priority), _expiry(0) {} + SimulationOwner(const SimulationOwner& other); + + const QUuid& getID() const { return _id; } + quint8 getPriority() const { return _priority; } + const quint64& getExpiry() const { return _expiry; } + + QByteArray toByteArray() const; + bool fromByteArray(const QByteArray& data); + + void clear(); + + void setPriority(quint8 priority); + void promotePriority(quint8 priority); + + // return true if id is changed + bool setID(const QUuid& id); + bool set(const QUuid& id, quint8 priority); + bool set(const SimulationOwner& owner); + + bool isNull() const { return _id.isNull(); } + bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); } + + void updateExpiry(); + + bool hasExpired() const { return usecTimestampNow() > _expiry; } + + bool operator>=(quint8 priority) const { return _priority >= priority; } + bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); } + + bool operator!=(const SimulationOwner& other); + SimulationOwner& operator=(const SimulationOwner& other); + + friend QDebug& operator<<(QDebug& d, const SimulationOwner& simOwner); + + // debug + static void test(); + +private: + QUuid _id; + quint8 _priority; + quint64 _expiry; +}; + + + +#endif // hifi_SimulationOwner_h diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index fab9ca7070..1a579cb6c9 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -73,7 +73,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketTypeEntityAdd: case PacketTypeEntityEdit: case PacketTypeEntityData: - return VERSION_ACTIONS_OVER_WIRE; + return VERSION_ENTITIES_HAVE_SIMULATION_OWNER; case PacketTypeEntityErase: return 2; case PacketTypeAudioStreamStats: diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 5d6e9d7c83..e30daac328 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -187,5 +187,6 @@ const PacketVersion VERSION_ENTITIES_FACE_CAMERA = 30; const PacketVersion VERSION_ENTITIES_SCRIPT_TIMESTAMP = 31; const PacketVersion VERSION_ENTITIES_SCRIPT_TIMESTAMP_FIX = 32; const PacketVersion VERSION_ACTIONS_OVER_WIRE = 33; +const PacketVersion VERSION_ENTITIES_HAVE_SIMULATION_OWNER = 34; #endif // hifi_PacketHeaders_h diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 9476fe024e..e6f86bb861 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -247,7 +247,6 @@ public: static int unpackDataFromBytes(const unsigned char* dataBytes, QVector& result); static int unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result); - private: /// appends raw bytes, might fail if byte would cause packet to be too large bool append(const unsigned char* data, int length); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index b6b8a2787c..d04d190145 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -26,6 +26,9 @@ static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f; static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4; +const uint32_t LOOPS_FOR_SIMULATION_ORPHAN = 50; +//const uint32_t LOOPS_BETWEEN_OWNERSHIP_BIDS = 30; +const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool EntityMotionState::entityTreeIsLocked() const { @@ -58,8 +61,7 @@ bool entityTreeIsLocked() { EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : ObjectMotionState(shape), _entity(entity), - _sentActive(false), - _numNonMovingUpdates(0), + _sentInactive(true), _lastStep(0), _serverPosition(0.0f), _serverRotation(), @@ -72,8 +74,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _measuredAcceleration(glm::vec3(0.0f)), _measuredDeltaTime(0.0f), _accelerationNearlyGravityCount(0), - _candidateForOwnership(false), - _loopsSinceOwnershipBid(0), + _nextOwnershipBid(0), _loopsWithoutOwner(0) { _type = MOTIONSTATE_TYPE_ENTITY; @@ -97,28 +98,35 @@ void EntityMotionState::updateServerPhysicsVariables() { } // virtual -void EntityMotionState::handleEasyChanges(uint32_t flags) { +void EntityMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { assert(entityTreeIsLocked()); updateServerPhysicsVariables(); - ObjectMotionState::handleEasyChanges(flags); + ObjectMotionState::handleEasyChanges(flags, engine); + if (flags & EntityItem::DIRTY_SIMULATOR_ID) { _loopsWithoutOwner = 0; - _candidateForOwnership = 0; - if (_entity->getSimulatorID().isNull() - && !_entity->isMoving() - && _body->isActive()) { + if (_entity->getSimulatorID().isNull()) { + // simulation ownership is being removed // 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; + // hint to Bullet that the object is deactivating _body->setActivationState(WANTS_DEACTIVATION); - } else { - auto nodeList = DependencyManager::get(); - const QUuid& sessionID = nodeList->getSessionUUID(); - if (_entity->getSimulatorID() != sessionID) { - _loopsSinceOwnershipBid = 0; + _outgoingPriority = 0; + } else { + _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + if (engine->getSessionID() == _entity->getSimulatorID() || _entity->getSimulationPriority() > _outgoingPriority) { + // we own the simulation or our priority looses to remote + _outgoingPriority = 0; } } } + if (flags & EntityItem::DIRTY_SIMULATOR_OWNERSHIP) { + // (DIRTY_SIMULATOR_OWNERSHIP really means "we should bid for ownership with SCRIPT priority") + // we're manipulating this object directly via script, so we artificially + // manipulate the logic to trigger an immediate bid for ownership + setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY); + } if ((flags & EntityItem::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { _body->activate(); } @@ -195,13 +203,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; - const uint32_t OWNERSHIP_BID_DELAY = 50; - if (_loopsWithoutOwner > OWNERSHIP_BID_DELAY) { + if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { //qDebug() << "Warning -- claiming something I saw moving." << getName(); - _candidateForOwnership = true; + setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); } - } else { - _loopsWithoutOwner = 0; } #ifdef WANT_DEBUG @@ -232,7 +237,7 @@ bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { return false; } assert(entityTreeIsLocked()); - return _candidateForOwnership || sessionID == _entity->getSimulatorID(); + return _outgoingPriority > 0 || sessionID == _entity->getSimulatorID(); } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { @@ -245,7 +250,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _serverVelocity = bulletToGLM(_body->getLinearVelocity()); _serverAngularVelocity = bulletToGLM(_body->getAngularVelocity()); _lastStep = simulationStep; - _sentActive = false; + _sentInactive = true; return false; } @@ -259,7 +264,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; const float INACTIVE_UPDATE_PERIOD = 0.5f; - if (!_sentActive) { + if (_sentInactive) { // 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) @@ -347,30 +352,20 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s assert(_body); assert(entityTreeIsLocked()); - if (!remoteSimulationOutOfSync(simulationStep)) { - _candidateForOwnership = false; - return false; - } - - if (_entity->getSimulatorID() == sessionID) { - // we own the simulation - _candidateForOwnership = false; - return true; - } - - 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; + if (_entity->getSimulatorID() != sessionID) { + // we don't own the simulation, but maybe we should... + if (_outgoingPriority > 0) { + if (_outgoingPriority < _entity->getSimulationPriority()) { + // our priority looses to remote, so we don't bother to bid + _outgoingPriority = 0; + return false; + } + return usecTimestampNow() > _nextOwnershipBid; } - } - - _candidateForOwnership = false; - return false; + return false; + } + + return remoteSimulationOutOfSync(simulationStep); } void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const QUuid& sessionID, uint32_t step) { @@ -384,7 +379,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _entity->setVelocity(zero); _entity->setAngularVelocity(zero); _entity->setAcceleration(zero); - _sentActive = false; + _sentInactive = true; } else { float gravityLength = glm::length(_entity->getGravity()); float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); @@ -421,7 +416,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _entity->setVelocity(zero); _entity->setAngularVelocity(zero); } - _sentActive = true; + _sentInactive = false; } // remember properties for local server prediction @@ -431,7 +426,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _serverAcceleration = _entity->getAcceleration(); _serverAngularVelocity = _entity->getAngularVelocity(); - EntityItemProperties properties = _entity->getProperties(); + EntityItemProperties properties; // explicitly set the properties that changed so that they will be packed properties.setPosition(_serverPosition); @@ -457,14 +452,14 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q 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); + properties.clearSimulationOwner(); + _outgoingPriority = 0; } + // else the ownership is not changing so we don't bother to pack it } else { // we don't own the simulation for this entity yet, but we're sending a bid for it - properties.setSimulatorID(sessionID); + properties.setSimulationOwner(sessionID, glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); + _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; } if (EntityItem::getSendPhysicsUpdates()) { @@ -502,6 +497,13 @@ uint32_t EntityMotionState::getAndClearIncomingDirtyFlags() { return dirtyFlags; } +// virtual +quint8 EntityMotionState::getSimulationPriority() const { + if (_entity) { + return _entity->getSimulationPriority(); + } + return 0; +} // virtual QUuid EntityMotionState::getSimulatorID() const { @@ -512,10 +514,11 @@ QUuid EntityMotionState::getSimulatorID() const { return QUuid(); } - // virtual -void EntityMotionState::bump() { - _candidateForOwnership = true; +void EntityMotionState::bump(quint8 priority) { + if (_entity) { + setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); + } } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -543,9 +546,10 @@ void EntityMotionState::measureBodyAcceleration() { glm::vec3 velocity = bulletToGLM(_body->getLinearVelocity()); _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; _lastVelocity = velocity; - if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS && !_candidateForOwnership) { - _loopsSinceOwnershipBid = 0; + if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { _loopsWithoutOwner = 0; + _lastStep = ObjectMotionState::getWorldSimulationStep(); + _sentInactive = false; } } } @@ -584,3 +588,7 @@ int16_t EntityMotionState::computeCollisionGroup() { } return COLLISION_GROUP_DEFAULT; } + +void EntityMotionState::setOutgoingPriority(quint8 priority) { + _outgoingPriority = glm::max(_outgoingPriority, priority); +} diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 4f777a4575..9f7bafd416 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -29,7 +29,7 @@ public: virtual ~EntityMotionState(); void updateServerPhysicsVariables(); - virtual void handleEasyChanges(uint32_t flags); + virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine); virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); /// \return MOTION_TYPE_DYNAMIC or MOTION_TYPE_STATIC based on params set in EntityItem @@ -68,8 +68,9 @@ public: virtual const QUuid& getObjectID() const { return _entity->getID(); } + virtual quint8 getSimulationPriority() const; virtual QUuid getSimulatorID() const; - virtual void bump(); + virtual void bump(quint8 priority); EntityItemPointer getEntity() const { return _entity; } @@ -80,6 +81,9 @@ public: virtual int16_t computeCollisionGroup(); + // eternal logic can suggest a simuator priority bid for the next outgoing update + void setOutgoingPriority(quint8 priority); + friend class PhysicalEntitySimulation; protected: @@ -93,8 +97,7 @@ protected: EntityItemPointer _entity; - 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 + bool _sentInactive; // true if body was inactive when we sent last update // these are for the prediction of the remote server's simple extrapolation uint32_t _lastStep; // last step of server extrapolation @@ -111,9 +114,9 @@ protected: float _measuredDeltaTime; quint8 _accelerationNearlyGravityCount; - bool _candidateForOwnership; - uint32_t _loopsSinceOwnershipBid; + quint64 _nextOwnershipBid = 0; uint32_t _loopsWithoutOwner; + quint8 _outgoingPriority = 0; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 85e67d72f7..7ff119fb79 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -114,7 +114,7 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) { } } -void ObjectMotionState::handleEasyChanges(uint32_t flags) { +void ObjectMotionState::handleEasyChanges(uint32_t flags, PhysicsEngine* engine) { if (flags & EntityItem::DIRTY_POSITION) { btTransform worldTrans; if (flags & EntityItem::DIRTY_ROTATION) { @@ -159,7 +159,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* if ((flags & HARD_DIRTY_PHYSICS_FLAGS) == 0) { // no HARD flags remain, so do any EASY changes if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - handleEasyChanges(flags); + handleEasyChanges(flags, engine); } return; } @@ -174,7 +174,7 @@ void ObjectMotionState::handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* } } if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - handleEasyChanges(flags); + handleEasyChanges(flags, engine); } // it is possible that there are no HARD flags at this point (if DIRTY_SHAPE was removed) // so we check again befoe we reinsert: diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 561ce02d62..30394ef5fc 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -40,7 +40,8 @@ enum MotionStateType { 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_MATERIAL | EntityItem::DIRTY_SIMULATOR_ID | + EntityItem::DIRTY_SIMULATOR_OWNERSHIP); // These are the set of incoming flags that the PhysicsEngine needs to hear about: const uint32_t DIRTY_PHYSICS_FLAGS = (uint32_t)(HARD_DIRTY_PHYSICS_FLAGS | EASY_DIRTY_PHYSICS_FLAGS | @@ -70,7 +71,7 @@ public: ObjectMotionState(btCollisionShape* shape); ~ObjectMotionState(); - virtual void handleEasyChanges(uint32_t flags); + virtual void handleEasyChanges(uint32_t flags, PhysicsEngine* engine); virtual void handleHardAndEasyChanges(uint32_t flags, PhysicsEngine* engine); void updateBodyMaterialProperties(); @@ -118,8 +119,9 @@ public: virtual const QUuid& getObjectID() const = 0; + virtual quint8 getSimulationPriority() const { return 0; } virtual QUuid getSimulatorID() const = 0; - virtual void bump() = 0; + virtual void bump(quint8 priority) {} virtual QString getName() { return ""; } diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 24baa365ef..f533bba7c5 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -194,7 +194,7 @@ void PhysicsEngine::changeObjects(VectorOfMotionStates& objects) { if (flags & HARD_DIRTY_PHYSICS_FLAGS) { object->handleHardAndEasyChanges(flags, this); } else if (flags & EASY_DIRTY_PHYSICS_FLAGS) { - object->handleEasyChanges(flags); + object->handleEasyChanges(flags, this); } } } @@ -260,9 +260,6 @@ void PhysicsEngine::stepSimulation() { void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const btCollisionObject* objectB) { BT_PROFILE("ownershipInfection"); - if (_sessionID.isNull()) { - return; - } const btCollisionObject* characterObject = _characterController ? _characterController->getCollisionObject() : nullptr; @@ -272,14 +269,16 @@ void PhysicsEngine::doOwnershipInfection(const btCollisionObject* objectA, const if (b && ((a && a->getSimulatorID() == _sessionID && !objectA->isStaticObject()) || (objectA == characterObject))) { // NOTE: we might own the simulation of a kinematic object (A) // but we don't claim ownership of kinematic objects (B) based on collisions here. - if (!objectB->isStaticOrKinematicObject()) { - b->bump(); + if (!objectB->isStaticOrKinematicObject() && b->getSimulatorID() != _sessionID) { + quint8 priority = a ? a->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; + b->bump(priority); } } else if (a && ((b && b->getSimulatorID() == _sessionID && !objectB->isStaticObject()) || (objectB == characterObject))) { // SIMILARLY: we might own the simulation of a kinematic object (B) // but we don't claim ownership of kinematic objects (A) based on collisions here. - if (!objectA->isStaticOrKinematicObject()) { - a->bump(); + if (!objectA->isStaticOrKinematicObject() && a->getSimulatorID() != _sessionID) { + quint8 priority = b ? b->getSimulationPriority() : PERSONAL_SIMULATION_PRIORITY; + a->bump(priority); } } } @@ -311,7 +310,9 @@ void PhysicsEngine::updateContactMap() { _contactMap[ContactKey(a, b)].update(_numContactFrames, contactManifold->getContactPoint(0)); } - doOwnershipInfection(objectA, objectB); + if (!_sessionID.isNull()) { + doOwnershipInfection(objectA, objectB); + } } } } @@ -402,7 +403,7 @@ void PhysicsEngine::bump(ObjectMotionState* motionState) { if (!objectA->isStaticOrKinematicObject()) { ObjectMotionState* motionStateA = static_cast(objectA->getUserPointer()); if (motionStateA) { - motionStateA->bump(); + motionStateA->bump(VOLUNTEER_SIMULATION_PRIORITY); objectA->setActivationState(ACTIVE_TAG); } } @@ -410,7 +411,7 @@ void PhysicsEngine::bump(ObjectMotionState* motionState) { if (!objectB->isStaticOrKinematicObject()) { ObjectMotionState* motionStateB = static_cast(objectB->getUserPointer()); if (motionStateB) { - motionStateB->bump(); + motionStateB->bump(VOLUNTEER_SIMULATION_PRIORITY); objectB->setActivationState(ACTIVE_TAG); } } diff --git a/libraries/shared/src/PhysicsHelpers.h b/libraries/shared/src/PhysicsHelpers.h index 0e58ae99f0..7ceafea915 100644 --- a/libraries/shared/src/PhysicsHelpers.h +++ b/libraries/shared/src/PhysicsHelpers.h @@ -15,7 +15,7 @@ #include #include -const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 4; +const int PHYSICS_ENGINE_MAX_NUM_SUBSTEPS = 6; // Bullet will start to "lose time" at 10 FPS. const float PHYSICS_ENGINE_FIXED_SUBSTEP = 1.0f / 60.0f; // return incremental rotation (Bullet-style) caused by angularVelocity over timeStep diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp index b2ea5d95a4..5726728f30 100644 --- a/libraries/shared/src/StreamUtils.cpp +++ b/libraries/shared/src/StreamUtils.cpp @@ -15,9 +15,9 @@ #include "StreamUtils.h" -const char* hex_digits = "0123456789abcdef"; void StreamUtil::dump(std::ostream& s, const QByteArray& buffer) { + const char* hex_digits = "0123456789abcdef"; int row_size = 32; int i = 0; while (i < buffer.size()) {