Merge pull request #14140 from AndrewMeadows/dps-setPos-vs-grab

Make EntityScriptingInterface aware of distributed physics simulation (DPS) rules
This commit is contained in:
John Conklin II 2018-10-19 10:33:14 -07:00 committed by GitHub
commit 81fbb5a332
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 207 additions and 120 deletions

View file

@ -78,8 +78,10 @@ void addAvatarEntities(const QVariantList& avatarEntities) {
}
entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY);
if (entityProperties.getDynamic()) {
// since we're creating a dynamic object we volunteer immediately to own its simulation
entity->upgradeScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY);
}
entityProperties.setLastEdited(entity->getLastEdited());
} else {
qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree";

View file

@ -673,7 +673,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
const QUuid& myNodeID = nodeList->getSessionUUID();
bool weOwnSimulation = _simulationOwner.matchesValidID(myNodeID);
// pack SimulationOwner and terse update properties near each other
// pack SimulationOwner, transform, and velocity 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.
@ -1358,8 +1358,7 @@ EntityItemProperties EntityItem::getProperties(const EntityPropertyFlags& desire
return properties;
}
void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) const {
// a TerseUpdate includes the transform and its derivatives
void EntityItem::getTransformAndVelocityProperties(EntityItemProperties& properties) const {
if (!properties._positionChanged) {
properties._position = getLocalPosition();
}
@ -1383,8 +1382,11 @@ void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) c
properties._accelerationChanged = true;
}
void EntityItem::setScriptSimulationPriority(uint8_t priority) {
uint8_t newPriority = stillHasGrabActions() ? glm::max(priority, SCRIPT_GRAB_SIMULATION_PRIORITY) : priority;
void EntityItem::upgradeScriptSimulationPriority(uint8_t priority) {
uint8_t newPriority = glm::max(priority, _scriptSimulationPriority);
if (newPriority < SCRIPT_GRAB_SIMULATION_PRIORITY && stillHasGrabActions()) {
newPriority = SCRIPT_GRAB_SIMULATION_PRIORITY;
}
if (newPriority != _scriptSimulationPriority) {
// set the dirty flag to trigger a bid or ownership update
markDirtyFlags(Simulation::DIRTY_SIMULATION_OWNERSHIP_PRIORITY);
@ -1419,7 +1421,7 @@ bool EntityItem::stillWaitingToTakeOwnership(uint64_t timestamp) const {
bool EntityItem::setProperties(const EntityItemProperties& properties) {
bool somethingChanged = false;
// these affect TerseUpdate properties
// these affect transform and velocity properties
SET_ENTITY_PROPERTY_FROM_PROPERTIES(simulationOwner, setSimulationOwner);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPosition);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation);
@ -3256,3 +3258,26 @@ void EntityItem::setScriptHasFinishedPreload(bool value) {
bool EntityItem::isScriptPreloadFinished() {
return _scriptPreloadFinished;
}
void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properties, uint64_t now, uint8_t priority) {
if (dynamicDataNeedsTransmit()) {
setDynamicDataNeedsTransmit(false);
properties.setActionData(getDynamicData());
}
if (updateQueryAACube()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
properties.setQueryAACube(getQueryAACube());
}
// set the LastEdited of the properties but NOT the entity itself
properties.setLastEdited(now);
clearScriptSimulationPriority();
properties.setSimulationOwner(Physics::getSessionUUID(), priority);
setPendingOwnershipPriority(priority);
properties.setClientOnly(getClientOnly());
properties.setOwningAvatarID(getOwningAvatarID());
setLastBroadcast(now); // for debug/physics status icons
}

View file

@ -325,7 +325,7 @@ public:
// TODO: move this "ScriptSimulationPriority" and "PendingOwnership" stuff into EntityMotionState
// but first would need to do some other cleanup. In the meantime these live here as "scratch space"
// to allow libs that don't know about each other to communicate.
void setScriptSimulationPriority(uint8_t priority);
void upgradeScriptSimulationPriority(uint8_t priority);
void clearScriptSimulationPriority();
uint8_t getScriptSimulationPriority() const { return _scriptSimulationPriority; }
void setPendingOwnershipPriority(uint8_t priority);
@ -420,7 +420,7 @@ public:
quint64 getLastEditedFromRemote() const { return _lastEditedFromRemote; }
void updateLastEditedFromRemote() { _lastEditedFromRemote = usecTimestampNow(); }
void getAllTerseUpdateProperties(EntityItemProperties& properties) const;
void getTransformAndVelocityProperties(EntityItemProperties& properties) const;
void flagForMotionStateChange() { _flags |= Simulation::DIRTY_MOTION_TYPE; }
@ -535,6 +535,8 @@ public:
const GrabPropertyGroup& getGrabProperties() const { return _grabProperties; }
void prepareForSimulationOwnershipBid(EntityItemProperties& properties, uint64_t now, uint8_t priority);
signals:
void requestRenderUpdate();
void spaceUpdate(std::pair<int32_t, glm::vec4> data);

View file

@ -3244,9 +3244,12 @@ AABox EntityItemProperties::getAABox() const {
return AABox(rotatedExtentsRelativeToRegistrationPoint);
}
bool EntityItemProperties::hasTerseUpdateChanges() const {
// a TerseUpdate includes the transform and its derivatives
return _positionChanged || _velocityChanged || _rotationChanged || _angularVelocityChanged || _accelerationChanged;
bool EntityItemProperties::hasTransformOrVelocityChanges() const {
return _positionChanged ||_localPositionChanged
|| _rotationChanged || _localRotationChanged
|| _velocityChanged || _localVelocityChanged
|| _angularVelocityChanged || _localAngularVelocityChanged
|| _accelerationChanged;
}
bool EntityItemProperties::hasMiscPhysicsChanges() const {
@ -3255,6 +3258,56 @@ bool EntityItemProperties::hasMiscPhysicsChanges() const {
_compoundShapeURLChanged || _dynamicChanged || _collisionlessChanged || _collisionMaskChanged;
}
bool EntityItemProperties::hasSimulationRestrictedChanges() const {
return _positionChanged || _localPositionChanged
|| _rotationChanged || _localRotationChanged
|| _velocityChanged || _localVelocityChanged
|| _angularVelocityChanged || _localAngularVelocityChanged
|| _accelerationChanged
|| _parentIDChanged || _parentJointIndexChanged;
}
void EntityItemProperties::copySimulationRestrictedProperties(const EntityItemPointer& entity) {
if (!_parentIDChanged) {
setParentID(entity->getParentID());
}
if (!_parentJointIndexChanged) {
setParentJointIndex(entity->getParentJointIndex());
}
if (!_localPositionChanged && !_positionChanged) {
setPosition(entity->getWorldPosition());
}
if (!_localRotationChanged && !_rotationChanged) {
setRotation(entity->getWorldOrientation());
}
if (!_localVelocityChanged && !_velocityChanged) {
setVelocity(entity->getWorldVelocity());
}
if (!_localAngularVelocityChanged && !_angularVelocityChanged) {
setAngularVelocity(entity->getWorldAngularVelocity());
}
if (!_accelerationChanged) {
setAcceleration(entity->getAcceleration());
}
if (!_localDimensionsChanged && !_dimensionsChanged) {
setDimensions(entity->getScaledDimensions());
}
}
void EntityItemProperties::clearSimulationRestrictedProperties() {
_positionChanged = false;
_localPositionChanged = false;
_rotationChanged = false;
_localRotationChanged = false;
_velocityChanged = false;
_localVelocityChanged = false;
_angularVelocityChanged = false;
_localAngularVelocityChanged = false;
_accelerationChanged = false;
_parentIDChanged = false;
_parentJointIndexChanged = false;
}
void EntityItemProperties::clearSimulationOwner() {
_simulationOwner.clear();
_simulationOwnerChanged = true;
@ -3273,6 +3326,20 @@ void EntityItemProperties::setSimulationOwner(const QByteArray& data) {
}
}
uint8_t EntityItemProperties::computeSimulationBidPriority() const {
uint8_t priority = 0;
if (_parentIDChanged || _parentJointIndexChanged) {
// we need higher simulation ownership priority to chang parenting info
priority = SCRIPT_GRAB_SIMULATION_PRIORITY;
} else if ( _positionChanged || _localPositionChanged
|| _rotationChanged || _localRotationChanged
|| _velocityChanged || _localVelocityChanged
|| _angularVelocityChanged || _localAngularVelocityChanged) {
priority = SCRIPT_POKE_SIMULATION_PRIORITY;
}
return priority;
}
QList<QString> EntityItemProperties::listChangedProperties() {
QList<QString> out;
if (containsPositionChange()) {

View file

@ -349,13 +349,18 @@ public:
void setCreated(QDateTime& v);
bool hasTerseUpdateChanges() const;
bool hasTransformOrVelocityChanges() const;
bool hasMiscPhysicsChanges() const;
bool hasSimulationRestrictedChanges() const;
void copySimulationRestrictedProperties(const EntityItemPointer& entity);
void clearSimulationRestrictedProperties();
void clearSimulationOwner();
void setSimulationOwner(const QUuid& id, uint8_t priority);
void setSimulationOwner(const QByteArray& data);
void setSimulationPriority(uint8_t priority) { _simulationOwner.setPriority(priority); }
uint8_t computeSimulationBidPriority() const;
void setActionDataDirty() { _actionDataChanged = true; }

View file

@ -174,6 +174,7 @@ EntityItemProperties convertPropertiesToScriptSemantics(const EntityItemProperti
}
// TODO: this method looks expensive and should take properties by reference, update it, and return void
EntityItemProperties convertPropertiesFromScriptSemantics(const EntityItemProperties& scriptSideProperties,
bool scalesWithParent) {
// convert position and rotation properties from world-space to local, unless localPosition and localRotation
@ -242,13 +243,12 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
_activityTracking.addedEntityCount++;
auto nodeList = DependencyManager::get<NodeList>();
auto sessionID = nodeList->getSessionUUID();
const auto sessionID = nodeList->getSessionUUID();
EntityItemProperties propertiesWithSimID = properties;
if (clientOnly) {
const QUuid myNodeID = sessionID;
propertiesWithSimID.setClientOnly(clientOnly);
propertiesWithSimID.setOwningAvatarID(myNodeID);
propertiesWithSimID.setOwningAvatarID(sessionID);
}
propertiesWithSimID.setLastEditedBy(sessionID);
@ -290,7 +290,7 @@ bool EntityScriptingInterface::addLocalEntityCopy(EntityItemProperties& properti
entity->setLastBroadcast(usecTimestampNow());
// since we're creating this object we will immediately volunteer to own its simulation
entity->setScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY);
entity->upgradeScriptSimulationPriority(VOLUNTEER_SIMULATION_PRIORITY);
properties.setLastEdited(entity->getLastEdited());
} else {
qCDebug(entities) << "script failed to add new Entity to local Octree";
@ -530,54 +530,86 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
_activityTracking.editedEntityCount++;
auto nodeList = DependencyManager::get<NodeList>();
auto sessionID = nodeList->getSessionUUID();
const auto sessionID = DependencyManager::get<NodeList>()->getSessionUUID();
EntityItemProperties properties = scriptSideProperties;
properties.setLastEditedBy(sessionID);
EntityItemID entityID(id);
if (!_entityTree) {
properties.setLastEditedBy(sessionID);
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
return id;
}
// If we have a local entity tree set, then also update it.
bool updatedEntity = false;
_entityTree->withWriteLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
EntityItemPointer entity(nullptr);
SimulationOwner simulationOwner;
_entityTree->withReadLock([&] {
// make a copy of entity for local logic outside of tree lock
entity = _entityTree->findEntityByEntityItemID(entityID);
if (!entity) {
return;
}
if (entity->getClientOnly() && entity->getOwningAvatarID() != nodeList->getSessionUUID()) {
if (entity->getClientOnly() && entity->getOwningAvatarID() != sessionID) {
// don't edit other avatar's avatarEntities
properties = EntityItemProperties();
return;
}
// make a copy of simulationOwner for local logic outside of tree lock
simulationOwner = entity->getSimulationOwner();
});
if (scriptSideProperties.parentRelatedPropertyChanged()) {
// All of parentID, parentJointIndex, position, rotation are needed to make sense of any of them.
// If any of these changed, pull any missing properties from the entity.
if (entity) {
if (properties.hasSimulationRestrictedChanges()) {
if (_bidOnSimulationOwnership) {
// flag for simulation ownership, or upgrade existing ownership priority
// (actual bids for simulation ownership are sent by the PhysicalEntitySimulation)
entity->upgradeScriptSimulationPriority(properties.computeSimulationBidPriority());
if (simulationOwner.getID() == sessionID) {
// we own the simulation --> copy ALL restricted properties
properties.copySimulationRestrictedProperties(entity);
} else {
// we don't own the simulation but think we would like to
if (!scriptSideProperties.parentIDChanged()) {
properties.setParentID(entity->getParentID());
}
if (!scriptSideProperties.parentJointIndexChanged()) {
properties.setParentJointIndex(entity->getParentJointIndex());
}
if (!scriptSideProperties.localPositionChanged() && !scriptSideProperties.positionChanged()) {
properties.setPosition(entity->getWorldPosition());
}
if (!scriptSideProperties.localRotationChanged() && !scriptSideProperties.rotationChanged()) {
properties.setRotation(entity->getWorldOrientation());
}
if (!scriptSideProperties.localDimensionsChanged() && !scriptSideProperties.dimensionsChanged()) {
properties.setDimensions(entity->getScaledDimensions());
uint8_t desiredPriority = entity->getScriptSimulationPriority();
if (desiredPriority < simulationOwner.getPriority()) {
// the priority at which we'd like to own it is not high enough
// --> assume failure and clear all restricted property changes
properties.clearSimulationRestrictedProperties();
} else {
// the priority at which we'd like to own it is high enough to win.
// --> assume success and copy ALL restricted properties
properties.copySimulationRestrictedProperties(entity);
}
}
} else if (!simulationOwner.getID().isNull()) {
// someone owns this but not us
// clear restricted properties
properties.clearSimulationRestrictedProperties();
}
// clear the cached simulationPriority level
entity->upgradeScriptSimulationPriority(0);
}
// set these to make EntityItemProperties::getScalesWithParent() work correctly
properties.setClientOnly(entity->getClientOnly());
properties.setOwningAvatarID(entity->getOwningAvatarID());
properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent());
// make sure the properties has a type, so that the encode can know which properties to include
properties.setType(entity->getType());
} else if (_bidOnSimulationOwnership) {
// bail when simulation participants don't know about entity
return QUuid();
}
// TODO: it is possible there is no remaining useful changes in properties and we should bail early.
// How to check for this cheaply?
properties = convertPropertiesFromScriptSemantics(properties, properties.getScalesWithParent());
properties.setLastEditedBy(sessionID);
// done reading and modifying properties --> start write
bool updatedEntity = false;
_entityTree->withWriteLock([&] {
updatedEntity = _entityTree->updateEntity(entityID, properties);
});
@ -590,63 +622,37 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
// return QUuid();
// }
bool entityFound { false };
bool hasQueryAACubeRelatedChanges = properties.queryAACubeRelatedPropertyChanged();
// done writing, send update
_entityTree->withReadLock([&] {
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
// find the entity again: maybe it was removed since we last found it
entity = _entityTree->findEntityByEntityItemID(entityID);
if (entity) {
entityFound = true;
// make sure the properties has a type, so that the encode can know which properties to include
properties.setType(entity->getType());
bool hasTerseUpdateChanges = properties.hasTerseUpdateChanges();
bool hasPhysicsChanges = properties.hasMiscPhysicsChanges() || hasTerseUpdateChanges;
if (_bidOnSimulationOwnership && hasPhysicsChanges) {
auto nodeList = DependencyManager::get<NodeList>();
const QUuid myNodeID = nodeList->getSessionUUID();
uint64_t now = usecTimestampNow();
entity->setLastBroadcast(now);
if (entity->getSimulatorID() == myNodeID) {
// we think we already own the simulation, so make sure to send ALL TerseUpdate properties
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 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_POKE_SIMULATION_PRIORITY) {
// we re-assert our simulation ownership at a higher priority
properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
}
} else {
// we make a bid for simulation ownership
properties.setSimulationOwner(myNodeID, SCRIPT_POKE_SIMULATION_PRIORITY);
entity->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY);
}
}
if (properties.queryAACubeRelatedPropertyChanged()) {
if (hasQueryAACubeRelatedChanges) {
properties.setQueryAACube(entity->getQueryAACube());
}
entity->setLastBroadcast(usecTimestampNow());
properties.setLastEdited(entity->getLastEdited());
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
// if they've changed.
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
if (descendant->getNestableType() == NestableType::Entity) {
if (descendant->updateQueryAACube()) {
EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);
EntityItemProperties newQueryCubeProperties;
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
queueEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties);
entityDescendant->setLastBroadcast(usecTimestampNow());
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
// if they've changed.
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
if (descendant->getNestableType() == NestableType::Entity) {
if (descendant->updateQueryAACube()) {
EntityItemPointer entityDescendant = std::static_pointer_cast<EntityItem>(descendant);
EntityItemProperties newQueryCubeProperties;
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
queueEntityMessage(PacketType::EntityEdit, descendant->getID(), newQueryCubeProperties);
entityDescendant->setLastBroadcast(now);
}
}
}
});
} else {
});
}
}
});
if (!entity) {
if (hasQueryAACubeRelatedChanges) {
// Sometimes ESS don't have the entity they are trying to edit in their local tree. In this case,
// convertPropertiesFromScriptSemantics doesn't get called and local* edits will get dropped.
// This is because, on the script side, "position" is in world frame, but in the network
@ -668,8 +674,6 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
properties.setDimensions(properties.getLocalDimensions());
}
}
});
if (!entityFound) {
// we've made an edit to an entity we don't know about, or to a non-entity. If it's a known non-entity,
// print a warning and don't send an edit packet to the entity-server.
QSharedPointer<SpatialParentFinder> parentFinder = DependencyManager::get<SpatialParentFinder>();
@ -1449,7 +1453,7 @@ QUuid EntityScriptingInterface::addAction(const QString& actionTypeString,
}
action->setIsMine(true);
success = entity->addAction(simulation, action);
entity->setScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY);
entity->upgradeScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY);
return false; // Physics will cause a packet to be sent, so don't send from here.
});
if (success) {
@ -1465,7 +1469,7 @@ bool EntityScriptingInterface::updateAction(const QUuid& entityID, const QUuid&
return actionWorker(entityID, [&](EntitySimulationPointer simulation, EntityItemPointer entity) {
bool success = entity->updateAction(simulation, actionID, arguments);
if (success) {
entity->setScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY);
entity->upgradeScriptSimulationPriority(SCRIPT_GRAB_SIMULATION_PRIORITY);
}
return success;
});
@ -1479,7 +1483,7 @@ bool EntityScriptingInterface::deleteAction(const QUuid& entityID, const QUuid&
success = entity->removeAction(simulation, actionID);
if (success) {
// reduce from grab to poke
entity->setScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY);
entity->upgradeScriptSimulationPriority(SCRIPT_POKE_SIMULATION_PRIORITY);
}
return false; // Physics will cause a packet to be sent, so don't send from here.
});

View file

@ -502,36 +502,18 @@ void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t s
properties.setVelocity(linearVelocity);
properties.setAcceleration(_entity->getAcceleration());
properties.setAngularVelocity(angularVelocity);
if (_entity->dynamicDataNeedsTransmit()) {
_entity->setDynamicDataNeedsTransmit(false);
properties.setActionData(_entity->getDynamicData());
}
if (_entity->updateQueryAACube()) {
// due to parenting, the server may not know where something is in world-space, so include the bounding cube.
properties.setQueryAACube(_entity->getQueryAACube());
}
// set the LastEdited of the properties but NOT the entity itself
quint64 now = usecTimestampNow();
properties.setLastEdited(now);
// we don't own the simulation for this entity yet, but we're sending a bid for it
quint64 now = usecTimestampNow();
uint8_t finalBidPriority = computeFinalBidPriority();
_entity->clearScriptSimulationPriority();
properties.setSimulationOwner(Physics::getSessionUUID(), finalBidPriority);
_entity->setPendingOwnershipPriority(finalBidPriority);
_entity->prepareForSimulationOwnershipBid(properties, now, finalBidPriority);
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
properties.setClientOnly(_entity->getClientOnly());
properties.setOwningAvatarID(_entity->getOwningAvatarID());
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties);
_entity->setLastBroadcast(now); // for debug/physics status icons
// NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent
// then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary.