Merge pull request #13054 from AndrewMeadows/workload-012

workload: yield simulation ownership of objects outside nearby region
This commit is contained in:
Sam Gateau 2018-05-02 17:25:34 -07:00 committed by GitHub
commit 3c2595869b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 79 deletions

View file

@ -360,7 +360,12 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
// the sender is trying to take or continue ownership
if (entity->getSimulatorID().isNull()) {
// the sender is taking ownership
properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY);
SimulationOwner owner = properties.getSimulationOwner();
if (owner.getPriority() == VOLUNTEER_SIMULATION_PRIORITY) {
// the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash
// when dynamic objects first activate and multiple participants bid simultaneously
properties.promoteSimulationPriority(RECRUIT_SIMULATION_PRIORITY);
}
simulationBlocked = false;
} else if (entity->getSimulatorID() == senderID) {
// the sender is asserting ownership, maybe changing priority
@ -392,6 +397,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
}
if (simulationBlocked) {
// squash ownership and physics-related changes.
// TODO? replace these eight calls with just one?
properties.setSimulationOwnerChanged(false);
properties.setPositionChanged(false);
properties.setRotationChanged(false);
@ -1795,7 +1801,7 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) {
void EntityTree::update(bool simulate) {
PROFILE_RANGE(simulation_physics, "UpdateTree");
PerformanceTimer perfTimer("UpdateTreen");
PerformanceTimer perfTimer("updateTree");
withWriteLock([&] {
fixupNeedsParentFixups();
if (simulate && _simulation) {

View file

@ -19,74 +19,75 @@
#include <UUID.h>
// HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions
// of the same world. When portions overlap only one participant is allowed to be the authority for any
// particular object. For a simulated entity the authoritative participant is called the simulation "owner" and
// their duty is to send transform/velocity updates for the entity to the central entity-server.
// The entity-server relays updates to other participants who apply them as "state synchronization"
// to their own simulation.
// of the same domain. Even when portions overlap only one participant is allowed to be the current
// authority for any particular object's physical simulation. The authoritative participant is called the
// "simulation owner" and its duty is to send "state synchronization" (transform/velocity) updates for an
// entity to the entity-server. The entity-server relays updates to other participants who apply them to
// their own simulation.
//
// Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update:
// {
// "simulationOwner": { "ownerID" : sessionID, "priority" : priority },
// { "simulationOwner": { "ownerID" : sessionID, "priority" : priority },
// transform/velocity properties
// }
//
// The entity-server is the authority as to who owns what and may reject a bid.
// The rules for handling a bid are as follows:
// The entity-server is the authority as to who owns what and may reject a bid. The rules for handling a
// bid are as follows:
//
// (1) A bid may be refused for special ownership restrictions, but otherwise...
//
// (2) A bid at higher priority is accepted
//
// (3) A bid at equal priority is rejected if receieved within a grace-period (200msec)
// of the last ownership transition, otherwise it is accepted
// (3) A bid at equal priority is accepted unless it was received shortly after (within 200msec) of the
// last ownership change. This to avoid rapid ownership transitions should multiple participants
// bid simultaneously.
//
// (4) The current owner is the only participant allowed to clear ownership (entity-server can override).
// (4) The current owner is the only participant allowed to clear their ownership or adjust priority.
//
// (5) The current owner is the only participant allowed to adjust priority (entity-server can override).
//
// (6) If an owner does not update the transform or velocities of an owned entity within some period
// (5) If an owner does not update the transform or velocities of an owned entity within some period
// (5 seconds) then ownership is cleared and the entity's velocities are zeroed. This to handle
// the case when an owner drops off the network.
//
// The priority of a participant's bid depends on how "interested" it is in the entity's motion. The rules
// for bidding are as follows:
//
// (7) A participant (almost) never assumes that a bid is accepted by the entity-server. It packs the
// simulation owner and priority as if they really did change but doesn't actually modify them
// locally. Thus, if the bid packet is lost the participant will re-send after some period.
// The participant only updates its knowledge of who owns what when it recieves an update from the
// entity-server. An exception is when the participant creates a moving entity: it assumes it starts
// off owning any moving entities it creates.
// (6) A participant (almost) never assumes its bid is accepted by the entity-server. It packs the
// simulation owner properties as if they really did change but doesn't actually modify them
// locally. Instead it waits to hear back from the entity-server for bid acceptance. If the entity
// remains unowned the participant will resend the bid (assuming the bid pakcet was lost). The
// exception is when the participant creates a moving entity: it assumes it starts off owning any
// moving entities it creates.
//
// (8) When an unowned entity becomes active in the physics simulation the participant will
// start a timer and if the entity is still unowned after some period (0.5 seconds)
// it will bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER
// (7) When an entity becomes active in the physics simulation but is not owned the participant will
// start a timer and if it is still unowned after expiry (0.5 seconds) the participant will
// bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER
// priority: when a VOLUNTEER bid is accepted the entity-server always promotes the priority to
// RECRUIT (VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership
// RECRUIT (=VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership
// when multiple participants (with variable ping-times to the server) bid simultaneously for a
// recently activated entity.
//
// (9) When a participant changes an entity's transform/velocity it will bid at priority = POKE (=127)
// (8) When a participant's script changes an entity's transform/velocity the participant will bid at
// priority = POKE (=127)
//
// (10) When an entity touches MyAvatar the participant it will bid at priority = POKE.
// (9) When an entity collides against MyAvatar the participant will bid at priority = POKE.
//
// (11) When a participant grabs an entity it will bid at priority = GRAB (=128).
// (10) When a participant grabs an entity it will bid at priority = GRAB (=128). This to allow UserA
// to whack UserB with a "sword" without losing ownership, since UserB will bid at POKE. If UserB
// wants to contest for ownership they must also GRAB it.
//
// (12) When entityA, locally owned at priority = N, collides with an unowned entityB the owner will
// also bid for entityB at priority = N-1 (or VOLUNTEER, whichever is larger).
// (11) When EntityA, locally owned at priority = N, collides with an unowned EntityB the owner will
// also bid for EntityB at priority = N-1 (or VOLUNTEER, whichever is larger).
//
// (13) When an entity comes to rest and is deactivated in the physics simulation the owner will
// send an update to: clear their ownerhsip, set priority to zero, and set the object's
// velocities to be zero. As per a normal bid, the owner does NOT assume that its ownership
// has been cleared until it hears from the entity-server. This, if the packet is lost the
// owner will re-send after some period.
// (12) When an entity comes to rest and is deactivated in the physics simulation the owner will send
// an update to: clear their ownerhsip, set priority to zero, and set the entity's velocities to
// zero. As per a normal bid, the owner does NOT assume its ownership has been cleared until
// it hears back from the entity-server. Thus, if the packet is lost the owner will re-send after
// expiry.
//
// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it
// immediately at priority = VOLUNTEER.
// (13) When an entity is still active but the owner no longer wants to own it, the owner will drop its
// priority to YIELD (=1, below VOLUNTEER) thereby signalling to other participants to bid for it.
//
// (15) When an entity is still active but the owner no longer wants to own it, it will drop its priority
// to YIELD (=1, less than VOLUNTEER) thereby signalling to other participants to bid for it.
// (14) When an entity's ownership priority drops to YIELD (=1, below VOLUNTEER) other participants may
// bid for it immediately at VOLUNTEER.
//
const uint8_t YIELD_SIMULATION_PRIORITY = 1;
const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1;

View file

@ -139,6 +139,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
if (_entity->getSimulatorID().isNull()) {
// simulation ownership has been removed
if (glm::length2(_entity->getWorldVelocity()) == 0.0f) {
// TODO: also check angularVelocity
// this object is coming to rest --> clear the ACTIVATION flag and _bidPriority
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
_body->setActivationState(WANTS_DEACTIVATION);
@ -148,19 +149,21 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
} else {
// disowned object is still moving --> start timer for ownership bid
// TODO? put a delay in here proportional to distance from object?
upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
computeNewBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
_nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
}
_loopsWithoutOwner = 0;
_numInactiveUpdates = 0;
} else if (isLocallyOwned()) {
// we just inherited ownership, make sure our desired priority matches what we have
upgradeBidPriority(_entity->getSimulationPriority());
// we just received ownership, or the priority of our existing ownership has changed
computeNewBidPriority(_entity->getSimulationPriority());
} else {
// the entity is owned by someone else, so we clear _bidPriority here
// but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation
// in which case we may try to bid again
_bidPriority = 0;
// the entity is owned by someone else
// we are always willing to volunteer for nearby objects
// otherwise we zero _bidPriority here
// (it's possible _bidPriority will be promoted in subsequent frames
// when local scripts or owned simulation interact with it)
_bidPriority = (_region == workload::Region::R1) ? VOLUNTEER_SIMULATION_PRIORITY : 0;
_nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
_numInactiveUpdates = 0;
}
@ -170,7 +173,7 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
// (1) we own it but may need to change the priority OR...
// (2) we don't own it but should bid (because a local script has been changing physics properties)
uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority();
upgradeBidPriority(newPriority);
computeNewBidPriority(newPriority);
// reset bid expiry so that we bid ASAP
_nextBidExpiry = 0;
@ -299,7 +302,7 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
if (_entity->getSimulatorID().isNull()) {
_loopsWithoutOwner++;
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) {
upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
computeNewBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
}
}
}
@ -326,21 +329,26 @@ void EntityMotionState::setShape(const btCollisionShape* shape) {
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
// NOTE: we only get here if we think we own the simulation
// NOTE: this method is only ever called when the entity simulation is locally owned
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
// Since we own the simulation: make sure _bidPriority is not less than current owned priority
// because: an _bidPriority of zero indicates that we should drop ownership when we have it.
upgradeBidPriority(_entity->getSimulationPriority());
// because: a _bidPriority of zero indicates that we should drop ownership in the send.
// TODO: need to be able to detect when logic dictates we *decrease* priority
// WIP: print info whenever _bidPriority mismatches what is known to the entity
if (_entity->dynamicDataNeedsTransmit()) {
uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
computeNewBidPriority(priority);
return true;
}
computeNewBidPriority(_entity->getSimulationPriority());
bool parentTransformSuccess;
Transform localToWorld = _entity->getParentTransform(parentTransformSuccess);
Transform worldToLocal;
Transform worldVelocityToLocal;
if (parentTransformSuccess) {
localToWorld.evalInverse(worldToLocal);
worldVelocityToLocal = worldToLocal;
worldVelocityToLocal.setTranslation(glm::vec3(0.0f));
}
int numSteps = simulationStep - _lastStep;
@ -389,12 +397,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
}
}
if (_entity->dynamicDataNeedsTransmit()) {
uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
upgradeBidPriority(priority);
return true;
}
// Else we measure the error between current and extrapolated transform (according to expected behavior
// of remote EntitySimulation) and return true if the error is significant.
@ -439,6 +441,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
}
bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
// NOTE: this method is only ever called when the entity simulation is locally owned
DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend");
// NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called
// after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL.
@ -447,7 +450,7 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
// this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) {
if (_entity->dynamicDataNeedsTransmit() || (!_entity->getDynamic() && _entity->queryAACubeNeedsUpdate())) {
return true;
}
@ -548,10 +551,10 @@ void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t s
_lastStep = step;
_nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS;
// finally: clear _bidPriority
// which will may get promoted before next bid
// or maybe we'll win simulation ownership
_bidPriority = 0;
// after sending the bid we try to clear _bidPriority
// which might get promoted again next frame (after local script or simulation interaction)
// or we might win the bid
computeNewBidPriority(0);
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
@ -686,7 +689,7 @@ uint8_t EntityMotionState::getSimulationPriority() const {
}
void EntityMotionState::slaveBidPriority() {
upgradeBidPriority(_entity->getSimulationPriority());
computeNewBidPriority(_entity->getSimulationPriority());
}
// virtual
@ -697,7 +700,7 @@ QUuid EntityMotionState::getSimulatorID() const {
void EntityMotionState::bump(uint8_t priority) {
assert(priority != 0);
upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
computeNewBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
}
void EntityMotionState::resetMeasuredBodyAcceleration() {
@ -776,11 +779,12 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
}
bool EntityMotionState::shouldSendBid() {
if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) {
// NOTE: this method is only ever called when the entity simulation is NOT locally owned
if (_bidPriority > glm::max(_entity->getSimulationPriority(), YIELD_SIMULATION_PRIORITY)) {
return true;
} else {
// NOTE: this 'else' case has a side-effect: it clears _bidPriority
// which may be updated next simulation step (via collision or script event)
// which might get promoted again next frame (after local script or simulation interaction)
_bidPriority = 0;
return false;
}
@ -791,10 +795,19 @@ bool EntityMotionState::isLocallyOwned() const {
}
bool EntityMotionState::isLocallyOwnedOrShouldBe() const {
return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) ||
// this method could also be called "shouldGenerateCollisionEventForLocalScripts()"
// because that is the only reason it's used
return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority >= _entity->getSimulationPriority()) ||
_entity->getSimulatorID() == Physics::getSessionUUID();
}
void EntityMotionState::setRegion(uint8_t region) {
_region = region;
if (_region == workload::Region::R1 && _bidPriority < VOLUNTEER_SIMULATION_PRIORITY && !isLocallyOwned()) {
_bidPriority = VOLUNTEER_SIMULATION_PRIORITY;
}
}
void EntityMotionState::initForBid() {
assert(_ownershipState != EntityMotionState::OwnershipState::Unownable);
_ownershipState = EntityMotionState::OwnershipState::PendingBid;
@ -805,8 +818,12 @@ void EntityMotionState::initForOwned() {
_ownershipState = EntityMotionState::OwnershipState::LocallyOwned;
}
void EntityMotionState::upgradeBidPriority(uint8_t priority) {
_bidPriority = glm::max<uint8_t>(_bidPriority, priority);
void EntityMotionState::computeNewBidPriority(uint8_t newPriority) {
if (_region == workload::Region::R1) {
_bidPriority = glm::max<uint8_t>(VOLUNTEER_SIMULATION_PRIORITY, glm::max<uint8_t>(_bidPriority, newPriority));
} else {
_bidPriority = glm::min<uint8_t>(YIELD_SIMULATION_PRIORITY, newPriority);
}
}
void EntityMotionState::clearObjectVelocities() const {

View file

@ -14,6 +14,7 @@
#include <EntityTypes.h>
#include <AACube.h>
#include <workload/Region.h>
#include "ObjectMotionState.h"
@ -93,6 +94,8 @@ public:
friend class PhysicalEntitySimulation;
OwnershipState getOwnershipState() const { return _ownershipState; }
void setRegion(uint8_t region);
protected:
void updateSendVelocities();
uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
@ -102,11 +105,10 @@ protected:
void updateServerPhysicsVariables();
bool remoteSimulationOutOfSync(uint32_t simulationStep);
// changes _bidPriority only if priority is larger
void upgradeBidPriority(uint8_t priority);
// computes _bidPriority using newPriority and special case rules
void computeNewBidPriority(uint8_t newPriority);
// upgradeBidPriority to value stored in _entity
void slaveBidPriority();
void slaveBidPriority(); // computeNewBidPriority() with value stored in _entity
void clearObjectVelocities() const;
@ -155,6 +157,7 @@ protected:
mutable uint8_t _accelerationNearlyGravityCount;
uint8_t _numInactiveUpdates { 1 };
uint8_t _bidPriority { 0 };
uint8_t _region { workload::Region::INVALID };
bool _serverVariablesSet { false };
};

View file

@ -53,7 +53,9 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
bool canBeKinematic = region <= workload::Region::R3;
if (shouldBePhysical) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (!motionState) {
if (motionState) {
motionState->setRegion(region);
} else {
_entitiesToAddToPhysics.insert(entity);
}
} else if (canBeKinematic && entity->isMovingRelativeToParent()) {
@ -139,6 +141,7 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
} else {
_incomingChanges.insert(motionState);
}
motionState->setRegion(region);
} else if (shouldBePhysical) {
// The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet.
// Perhaps it's shape has changed and it can now be added?