untangle 'bidding' from 'owned'

This commit is contained in:
Andrew Meadows 2018-03-28 13:40:16 -07:00
parent 170ec83870
commit 5a5376c3d5
10 changed files with 414 additions and 268 deletions

View file

@ -20,7 +20,7 @@
void EntitySimulation::setEntityTree(EntityTreePointer tree) {
if (_entityTree && _entityTree != tree) {
_mortalEntities.clear();
_nextExpiry = quint64(-1);
_nextExpiry = uint64_t(-1);
_entitiesToUpdate.clear();
_entitiesToSort.clear();
_simpleKinematicEntities.clear();
@ -30,7 +30,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) {
void EntitySimulation::updateEntities() {
QMutexLocker lock(&_mutex);
quint64 now = usecTimestampNow();
uint64_t now = usecTimestampNow();
// these methods may accumulate entries in _entitiesToBeDeleted
expireMortalEntities(now);
@ -70,16 +70,16 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
}
// protected
void EntitySimulation::expireMortalEntities(const quint64& now) {
void EntitySimulation::expireMortalEntities(const uint64_t& now) {
if (now > _nextExpiry) {
PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size());
// only search for expired entities if we expect to find one
_nextExpiry = quint64(-1);
_nextExpiry = uint64_t(-1);
QMutexLocker lock(&_mutex);
SetOfEntities::iterator itemItr = _mortalEntities.begin();
while (itemItr != _mortalEntities.end()) {
EntityItemPointer entity = *itemItr;
quint64 expiry = entity->getExpiry();
uint64_t expiry = entity->getExpiry();
if (expiry < now) {
itemItr = _mortalEntities.erase(itemItr);
entity->die();
@ -99,7 +99,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
}
// protected
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const uint64_t& now) {
PerformanceTimer perfTimer("updatingEntities");
QMutexLocker lock(&_mutex);
SetOfEntities::iterator itemItr = _entitiesToUpdate.begin();
@ -153,7 +153,7 @@ void EntitySimulation::addEntity(EntityItemPointer entity) {
entity->deserializeActions();
if (entity->isMortal()) {
_mortalEntities.insert(entity);
quint64 expiry = entity->getExpiry();
uint64_t expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
}
@ -200,7 +200,7 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
if (entity->isMortal()) {
_mortalEntities.insert(entity);
quint64 expiry = entity->getExpiry();
uint64_t expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
}
@ -220,7 +220,7 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
void EntitySimulation::clearEntities() {
QMutexLocker lock(&_mutex);
_mortalEntities.clear();
_nextExpiry = quint64(-1);
_nextExpiry = uint64_t(-1);
_entitiesToUpdate.clear();
_entitiesToSort.clear();
_simpleKinematicEntities.clear();
@ -231,7 +231,7 @@ void EntitySimulation::clearEntities() {
_deadEntities.clear();
}
void EntitySimulation::moveSimpleKinematics(const quint64& now) {
void EntitySimulation::moveSimpleKinematics(const uint64_t& now) {
PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size());
SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin();
while (itemItr != _simpleKinematicEntities.end()) {

View file

@ -44,7 +44,7 @@ const int DIRTY_SIMULATION_FLAGS =
class EntitySimulation : public QObject, public std::enable_shared_from_this<EntitySimulation> {
public:
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { }
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(uint64_t(-1)) { }
virtual ~EntitySimulation() { setEntityTree(NULL); }
inline EntitySimulationPointer getThisPointer() const {
@ -71,7 +71,7 @@ public:
void clearEntities();
void moveSimpleKinematics(const quint64& now);
void moveSimpleKinematics(const uint64_t& now);
EntityTreePointer getEntityTree() { return _entityTree; }
@ -83,14 +83,14 @@ public:
protected:
// These pure virtual methods are protected because they are not to be called will-nilly. The base class
// calls them in the right places.
virtual void updateEntitiesInternal(const quint64& now) = 0;
virtual void updateEntitiesInternal(const uint64_t& now) = 0;
virtual void addEntityInternal(EntityItemPointer entity) = 0;
virtual void removeEntityInternal(EntityItemPointer entity);
virtual void changeEntityInternal(EntityItemPointer entity) = 0;
virtual void clearEntitiesInternal() = 0;
void expireMortalEntities(const quint64& now);
void callUpdateOnEntitiesThatNeedIt(const quint64& now);
void expireMortalEntities(const uint64_t& now);
void callUpdateOnEntitiesThatNeedIt(const uint64_t& now);
virtual void sortEntitiesThatMoved();
QMutex _mutex{ QMutex::Recursive };
@ -114,7 +114,7 @@ private:
// An entity may be in more than one list.
SetOfEntities _allEntities; // tracks all entities added the simulation
SetOfEntities _mortalEntities; // entities that have an expiry
quint64 _nextExpiry;
uint64_t _nextExpiry;
SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update()

View file

@ -18,7 +18,7 @@
#include "EntityItem.h"
#include "EntitiesLogging.h"
const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND;
const uint64_t MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND;
void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
QMutexLocker lock(&_mutex);
@ -33,7 +33,7 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
if (entity->getDynamic() && entity->hasLocalVelocity()) {
// it is still moving dynamically --> add to orphaned list
_entitiesThatNeedSimulationOwner.insert(entity);
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
if (expiry < _nextOwnerlessExpiry) {
_nextOwnerlessExpiry = expiry;
}
@ -50,7 +50,7 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
}
}
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
void SimpleEntitySimulation::updateEntitiesInternal(const uint64_t& now) {
if (now > _nextOwnerlessExpiry) {
// search for ownerless objects that have expired
QMutexLocker lock(&_mutex);
@ -58,7 +58,7 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin();
while (itemItr != _entitiesThatNeedSimulationOwner.end()) {
EntityItemPointer entity = *itemItr;
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
if (expiry < now) {
// no simulators have volunteered ownership --> remove from list
itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr);
@ -96,7 +96,7 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
} else if (entity->getDynamic() && entity->hasLocalVelocity()) {
QMutexLocker lock(&_mutex);
_entitiesThatNeedSimulationOwner.insert(entity);
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
if (expiry < _nextOwnerlessExpiry) {
_nextOwnerlessExpiry = expiry;
}
@ -127,7 +127,7 @@ void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
_entitiesWithSimulationOwner.remove(entity);
if (entity->getDynamic() && entity->hasLocalVelocity()) {
_entitiesThatNeedSimulationOwner.insert(entity);
quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD;
if (expiry < _nextOwnerlessExpiry) {
_nextOwnerlessExpiry = expiry;
}

View file

@ -28,7 +28,7 @@ public:
void clearOwnership(const QUuid& ownerID);
protected:
virtual void updateEntitiesInternal(const quint64& now) override;
virtual void updateEntitiesInternal(const uint64_t& now) override;
virtual void addEntityInternal(EntityItemPointer entity) override;
virtual void removeEntityInternal(EntityItemPointer entity) override;
virtual void changeEntityInternal(EntityItemPointer entity) override;
@ -38,7 +38,7 @@ protected:
SetOfEntities _entitiesWithSimulationOwner;
SetOfEntities _entitiesThatNeedSimulationOwner;
quint64 _nextOwnerlessExpiry { 0 };
uint64_t _nextOwnerlessExpiry { 0 };
};
#endif // hifi_SimpleEntitySimulation_h

View file

@ -26,12 +26,7 @@
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
#include "EntityTree.h"
#endif
const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool EntityMotionState::entityTreeIsLocked() const {
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
@ -46,6 +41,9 @@ bool entityTreeIsLocked() {
}
#endif
const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) :
ObjectMotionState(nullptr),
@ -59,7 +57,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_serverActionData(QByteArray()),
_lastVelocity(0.0f),
_measuredAcceleration(0.0f),
_nextOwnershipBid(0),
_nextBidExpiry(0),
_measuredDeltaTime(0.0f),
_lastMeasureStep(0),
_lastStep(0),
@ -67,6 +65,12 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
_accelerationNearlyGravityCount(0),
_numInactiveUpdates(1)
{
// Why is _numInactiveUpdates initialied to 1?
// Because: when an entity is first created by a LOCAL operatioin its local simulation ownership is assumed,
// which causes it to be immediately placed on the 'owned' list, but in this case an "update" already just
// went out for the object's creation and there is no need to send another. By initializing _numInactiveUpdates
// to 1 here we trick remoteSimulationOutOfSync() to return "false" the first time through for this case.
_type = MOTIONSTATE_TYPE_ENTITY;
assert(_entity);
assert(entityTreeIsLocked());
@ -75,7 +79,19 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
setShape(shape);
_outgoingPriority = _entity->getPendingOwnershipPriority();
_bidPriority = _entity->getPendingOwnershipPriority();
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
// client-only entities are always thus, so we cache this fact in _ownershipState
_ownershipState = EntityMotionState::OwnershipState::Unownable;
}
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getDynamicData();
}
EntityMotionState::~EntityMotionState() {
@ -87,35 +103,30 @@ EntityMotionState::~EntityMotionState() {
}
void EntityMotionState::updateServerPhysicsVariables() {
assert(entityTreeIsLocked());
if (isLocallyOwned()) {
// don't slam these values if we are the simulation owner
return;
if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) {
// only slam these values if we are NOT the simulation owner
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getDynamicData();
_lastStep = ObjectMotionState::getWorldSimulationStep();
}
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverVariablesSet = true;
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getDynamicData();
}
void EntityMotionState::handleDeactivation() {
if (_serverVariablesSet) {
// copy _server data to entity
Transform localTransform = _entity->getLocalTransform();
localTransform.setTranslation(_serverPosition);
localTransform.setRotation(_serverRotation);
_entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3);
// and also to RigidBody
btTransform worldTrans;
worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition()));
worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation()));
_body->setWorldTransform(worldTrans);
// no need to update velocities... should already be zero
}
// copy _server data to entity
Transform localTransform = _entity->getLocalTransform();
localTransform.setTranslation(_serverPosition);
localTransform.setRotation(_serverRotation);
_entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3);
// and also to RigidBody
btTransform worldTrans;
worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition()));
worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation()));
_body->setWorldTransform(worldTrans);
// no need to update velocities... should already be zero
}
// virtual
@ -128,26 +139,29 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
if (_entity->getSimulatorID().isNull()) {
// simulation ownership has been removed
if (glm::length2(_entity->getWorldVelocity()) == 0.0f) {
// this object is coming to rest --> clear the ACTIVATION flag and _outgoingPriority
// this object is coming to rest --> clear the ACTIVATION flag and _bidPriority
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
_body->setActivationState(WANTS_DEACTIVATION);
_outgoingPriority = 0;
_bidPriority = 0;
const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet
_body->setDeactivationTime(ACTIVATION_EXPIRY);
} else {
// disowned object is still moving --> start timer for ownership bid
// TODO? put a delay in here proportional to distance from object?
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
upgradeBidPriority(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
upgradeOutgoingPriority(_entity->getSimulationPriority());
upgradeBidPriority(_entity->getSimulationPriority());
} else {
_outgoingPriority = 0;
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
// 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;
_nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
_numInactiveUpdates = 0;
}
}
@ -156,10 +170,10 @@ 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();
upgradeOutgoingPriority(newPriority);
upgradeBidPriority(newPriority);
// reset bid expiry so that we bid ASAP
_nextOwnershipBid = 0;
_nextBidExpiry = 0;
}
if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
if (_body->isKinematicObject()) {
@ -282,24 +296,12 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
}
}
// ADEBUG: move this _loopsWithoutOwner stuff out of EntityMotionState
if (_entity->getSimulatorID().isNull()) {
_loopsWithoutOwner++;
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) {
upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) {
upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY);
}
}
#ifdef WANT_DEBUG
quint64 now = usecTimestampNow();
qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID();
qCDebug(physics) << " last edited:" << _entity->getLastEdited()
<< formatUsecTime(now - _entity->getLastEdited()) << "ago";
qCDebug(physics) << " last simulated:" << _entity->getLastSimulated()
<< formatUsecTime(now - _entity->getLastSimulated()) << "ago";
qCDebug(physics) << " last updated:" << _entity->getLastUpdated()
<< formatUsecTime(now - _entity->getLastUpdated()) << "ago";
#endif
}
@ -323,26 +325,13 @@ void EntityMotionState::setShape(const btCollisionShape* shape) {
}
}
bool EntityMotionState::isCandidateForOwnership() const {
assert(_body);
assert(entityTreeIsLocked());
if (isLocallyOwned()) {
return true;
} else if (_entity->getClientOnly()) {
// don't send updates for someone else's avatarEntities
return false;
}
return _outgoingPriority != 0 || _entity->dynamicDataNeedsTransmit();
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
// NOTE: we only get here if we think we own the simulation
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
assert(_body);
// Since we own the simulation: make sure _outgoingPriority is not less than current owned priority
// because: an _outgoingPriority of zero indicates that we should drop ownership when we have it.
upgradeOutgoingPriority(_entity->getSimulationPriority());
// 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());
bool parentTransformSuccess;
Transform localToWorld = _entity->getParentTransform(parentTransformSuccess);
@ -354,27 +343,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
worldVelocityToLocal.setTranslation(glm::vec3(0.0f));
}
// if we've never checked before, our _lastStep will be 0, and we need to initialize our state
if (_lastStep == 0) {
btTransform xform = _body->getWorldTransform();
_serverVariablesSet = true;
_serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin()));
_serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation());
_serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma());
_serverAcceleration = Vectors::ZERO;
_serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity()));
_lastStep = simulationStep;
_serverActionData = _entity->getDynamicData();
_numInactiveUpdates = 1;
return false;
}
#ifdef WANT_DEBUG
glm::vec3 wasPosition = _serverPosition;
glm::quat wasRotation = _serverRotation;
glm::vec3 wasAngularVelocity = _serverAngularVelocity;
#endif
int numSteps = simulationStep - _lastStep;
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
@ -385,9 +353,9 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
_entity->clearSimulationOwnership();
return false;
}
// 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)
// we resend the inactive update with a growing delay: every INACTIVE_UPDATE_PERIOD * _numInactiveUpdates
// until it is removed from the owned list
// (which happens when we no longer own the simulation)
const float INACTIVE_UPDATE_PERIOD = 0.5f;
return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates);
}
@ -418,7 +386,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
if (_entity->dynamicDataNeedsTransmit()) {
uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY;
upgradeOutgoingPriority(priority);
upgradeBidPriority(priority);
return true;
}
@ -443,13 +411,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
const float MIN_ERROR_RATIO_SQUARED = 0.0025f; // corresponds to 5% error in 1 second
const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec
if (speed2 < MIN_SPEED_SQUARED || dx2 / speed2 > MIN_ERROR_RATIO_SQUARED) {
#ifdef WANT_DEBUG
qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ....";
qCDebug(physics) << "wasPosition:" << wasPosition;
qCDebug(physics) << "bullet position:" << position;
qCDebug(physics) << "_serverPosition:" << _serverPosition;
qCDebug(physics) << "dx2:" << dx2;
#endif
return true;
}
}
@ -469,22 +430,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation
glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation());
#ifdef WANT_DEBUG
if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) {
qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) ....";
qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity;
qCDebug(physics) << "_serverAngularVelocity:" << _serverAngularVelocity;
qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity);
qCDebug(physics) << "length _serverAngularVelocity:" << glm::length(_serverAngularVelocity);
qCDebug(physics) << "wasRotation:" << wasRotation;
qCDebug(physics) << "bullet actualRotation:" << actualRotation;
qCDebug(physics) << "_serverRotation:" << _serverRotation;
}
#endif
return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT);
}
@ -492,10 +437,9 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
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.
assert(_body);
assert(entityTreeIsLocked());
// this case prevented by isCandidateForOwnership()
// this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) {
@ -506,33 +450,111 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
return false;
}
if (!isLocallyOwned()) {
// we don't own the simulation
return remoteSimulationOutOfSync(simulationStep);
}
// NOTE: we do not volunteer to own kinematic or static objects
uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0;
void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Bid");
assert(entityTreeIsLocked());
bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND
usecTimestampNow() > _nextOwnershipBid; // it is time to bid again
if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) {
// we are insufficiently interested so clear _outgoingPriority
// and reset the bid expiry
_outgoingPriority = 0;
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
if (!_body->isActive()) {
// make sure all derivatives are zero
clearObjectVelocities();
_numInactiveUpdates = 1;
} else {
glm::vec3 gravity = _entity->getGravity();
// if this entity has been accelerated at close to gravity for a certain number of simulation-steps
// let the entity server's estimates include gravity.
const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4;
if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) {
_entity->setAcceleration(gravity);
} else {
_entity->setAcceleration(Vectors::ZERO);
}
return shouldBid;
if (!_body->isStaticOrKinematicObject()) {
const float DYNAMIC_LINEAR_VELOCITY_THRESHOLD = 0.05f; // 5 cm/sec
const float DYNAMIC_ANGULAR_VELOCITY_THRESHOLD = 0.087266f; // ~5 deg/sec
bool movingSlowlyLinear =
glm::length2(_entity->getWorldVelocity()) < (DYNAMIC_LINEAR_VELOCITY_THRESHOLD * DYNAMIC_LINEAR_VELOCITY_THRESHOLD);
bool movingSlowlyAngular = glm::length2(_entity->getWorldAngularVelocity()) <
(DYNAMIC_ANGULAR_VELOCITY_THRESHOLD * DYNAMIC_ANGULAR_VELOCITY_THRESHOLD);
bool movingSlowly = movingSlowlyLinear && movingSlowlyAngular && _entity->getAcceleration() == Vectors::ZERO;
if (movingSlowly) {
// velocities might not be zero, but we'll fake them as such, which will hopefully help convince
// other simulating observers to deactivate their own copies
clearObjectVelocities();
}
}
_numInactiveUpdates = 0;
}
return remoteSimulationOutOfSync(simulationStep);
EntityItemProperties properties;
Transform localTransform;
glm::vec3 linearVelocity;
glm::vec3 angularVelocity;
_entity->getLocalTransformAndVelocities(localTransform, linearVelocity, angularVelocity);
properties.setPosition(localTransform.getTranslation());
properties.setRotation(localTransform.getRotation());
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
uint8_t bidPriority = glm::max<uint8_t>(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY);
properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
// copy _bidPriority into pendingPriority...
_entity->setPendingOwnershipPriority(_bidPriority, now);
// don't forget to remember that we have made a bid
_entity->rememberHasSimulationOwnershipBid();
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); // ffor 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.
_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;
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
assert(entityTreeIsLocked());
assert(_entity->isLocallyOwned());
if (!_body->isActive()) {
// make sure all derivatives are zero
zeroCleanObjectVelocities();
clearObjectVelocities();
_numInactiveUpdates++;
} else {
glm::vec3 gravity = _entity->getGravity();
@ -559,13 +581,13 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
if (movingSlowly) {
// velocities might not be zero, but we'll fake them as such, which will hopefully help convince
// other simulating observers to deactivate their own copies
zeroCleanObjectVelocities();
clearObjectVelocities();
}
}
_numInactiveUpdates = 0;
}
// remember properties for local server prediction
// remember _serverFoo data for local prediction of server state
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverPosition = localTransform.getTranslation();
@ -574,11 +596,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
_serverActionData = _entity->getDynamicData();
EntityItemProperties properties;
// explicitly set the properties that changed so that they will be packed
properties.setPosition(_entity->getLocalPosition());
properties.setRotation(_entity->getLocalOrientation());
properties.setVelocity(_serverVelocity);
properties.setAcceleration(_serverAcceleration);
properties.setAngularVelocity(_serverAngularVelocity);
@ -587,61 +606,35 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.setActionData(_serverActionData);
}
if (properties.transformChanged()) {
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());
}
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);
#ifdef WANT_DEBUG
quint64 lastSimulated = _entity->getLastSimulated();
qCDebug(physics) << "EntityMotionState::sendUpdate()";
qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID()
<< "---------------------------------------------";
qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now);
#endif //def WANT_DEBUG
if (_numInactiveUpdates > 0) {
// we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID
// the entity is stopped and inactive so we tell the server we're clearing simulatorID
// but we remember we do still own it... and rely on the server to tell us we don't
properties.clearSimulationOwner();
_outgoingPriority = 0;
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
} else if (!isLocallyOwned()) {
// we don't own the simulation for this entity yet, but we're sending a bid for it
uint8_t bidPriority = glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY);
properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority);
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;
// copy _outgoingPriority into pendingPriority...
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
// don't forget to remember that we have made a bid
_entity->rememberHasSimulationOwnershipBid();
// ...then reset _outgoingPriority
_outgoingPriority = 0;
// _outgoingPriority will be re-computed before next bid,
// or will be set to agree with ownership priority should we win the bid
} else if (_outgoingPriority != _entity->getSimulationPriority()) {
// we own the simulation but our desired priority has changed
if (_outgoingPriority == 0) {
_bidPriority = 0;
_entity->setPendingOwnershipPriority(_bidPriority, now);
} else if (_bidPriority != _entity->getSimulationPriority()) {
// our desired priority has changed
if (_bidPriority == 0) {
// we should release ownership
properties.clearSimulationOwner();
} else {
// we just need to change the priority
properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority);
properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority);
}
_entity->setPendingOwnershipPriority(_outgoingPriority, now);
_entity->setPendingOwnershipPriority(_bidPriority, now);
}
EntityItemID id(_entity->getID());
EntityEditPacketSender* entityPacketSender = static_cast<EntityEditPacketSender*>(packetSender);
#ifdef WANT_DEBUG
qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()...";
#endif
EntityTreeElementPointer element = _entity->getElement();
EntityTreePointer tree = element ? element->getTree() : nullptr;
@ -650,7 +643,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
properties.setOwningAvatarID(_entity->getOwningAvatarID());
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties);
_entity->setLastBroadcast(now);
_entity->setLastBroadcast(now); // for debug/physics status icons
// if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server
// if they've changed.
@ -661,13 +654,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_
EntityItemProperties newQueryCubeProperties;
newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube());
newQueryCubeProperties.setLastEdited(properties.getLastEdited());
newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly());
newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID());
entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree,
descendant->getID(), newQueryCubeProperties);
entityDescendant->setLastBroadcast(now);
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
}
}
});
@ -726,7 +718,7 @@ QUuid EntityMotionState::getSimulatorID() const {
void EntityMotionState::bump(uint8_t priority) {
assert(priority != 0);
upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
}
void EntityMotionState::resetMeasuredBodyAcceleration() {
@ -750,13 +742,14 @@ void EntityMotionState::measureBodyAcceleration() {
_lastMeasureStep = thisStep;
_measuredDeltaTime = dt;
// Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt
// Note: the integration equation for velocity uses damping (D): v1 = (v0 + a * dt) * (1 - D)^dt
// hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt
glm::vec3 velocity = getBodyLinearVelocityGTSigma();
_measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt;
_lastVelocity = velocity;
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
// we fall in here when _lastMeasureStep is old: the body has just become active
_loopsWithoutOwner = 0;
_lastStep = ObjectMotionState::getWorldSimulationStep();
_numInactiveUpdates = 0;
@ -803,20 +796,41 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
_entity->computeCollisionGroupAndFinalMask(group, mask);
}
bool EntityMotionState::shouldSendBid() {
if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_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)
_bidPriority = 0;
return false;
}
}
bool EntityMotionState::isLocallyOwned() const {
return _entity->getSimulatorID() == Physics::getSessionUUID();
}
bool EntityMotionState::shouldBeLocallyOwned() const {
return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) ||
bool EntityMotionState::isLocallyOwnedOrShouldBe() const {
return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) ||
_entity->getSimulatorID() == Physics::getSessionUUID();
}
void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) {
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
void EntityMotionState::initForBid() {
assert(_ownershipState != EntityMotionState::OwnershipState::Unownable);
_ownershipState = EntityMotionState::OwnershipState::PendingBid;
}
void EntityMotionState::zeroCleanObjectVelocities() const {
void EntityMotionState::initForOwned() {
assert(_ownershipState != EntityMotionState::OwnershipState::Unownable);
_ownershipState = EntityMotionState::OwnershipState::LocallyOwned;
}
void EntityMotionState::upgradeBidPriority(uint8_t priority) {
_bidPriority = glm::max<uint8_t>(_bidPriority, priority);
}
void EntityMotionState::clearObjectVelocities() const {
// If transform or velocities are flagged as dirty it means a network or scripted change
// occured between the beginning and end of the stepSimulation() and we DON'T want to apply
// these physics simulation results.

View file

@ -24,12 +24,17 @@
class EntityMotionState : public ObjectMotionState {
public:
enum class OwnershipState {
NotLocallyOwned = 0,
PendingBid,
LocallyOwned,
Unownable
};
EntityMotionState() = delete;
EntityMotionState(btCollisionShape* shape, EntityItemPointer item);
virtual ~EntityMotionState();
void updateServerPhysicsVariables();
void handleDeactivation();
virtual void handleEasyChanges(uint32_t& flags) override;
virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override;
@ -45,9 +50,8 @@ public:
// this relays outgoing position/rotation to the EntityItem
virtual void setWorldTransform(const btTransform& worldTrans) override;
bool isCandidateForOwnership() const;
bool remoteSimulationOutOfSync(uint32_t simulationStep);
bool shouldSendUpdate(uint32_t simulationStep);
void sendBid(OctreeEditPacketSender* packetSender, uint32_t step);
void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step);
virtual uint32_t getIncomingDirtyFlags() override;
@ -80,15 +84,24 @@ public:
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
bool shouldSendBid();
bool isLocallyOwned() const override;
bool shouldBeLocallyOwned() const override;
bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents()
friend class PhysicalEntitySimulation;
OwnershipState getOwnershipState() const { return _ownershipState; }
protected:
// changes _outgoingPriority only if priority is larger
void upgradeOutgoingPriority(uint8_t priority);
void zeroCleanObjectVelocities() const;
uint64_t getNextBidExpiry() const { return _nextBidExpiry; }
void initForBid();
void initForOwned();
void clearOwnershipState() { _ownershipState = OwnershipState::NotLocallyOwned; }
void updateServerPhysicsVariables();
bool remoteSimulationOutOfSync(uint32_t simulationStep);
// changes _bidPriority only if priority is larger
void upgradeBidPriority(uint8_t priority);
void clearObjectVelocities() const;
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool entityTreeIsLocked() const;
@ -103,7 +116,17 @@ protected:
// and is only cleared in the DTOR
EntityItemPointer _entity;
bool _serverVariablesSet { false };
// These "_serverFoo" variables represent what we think the server knows.
// They are used in two different modes:
//
// (1) For remotely owned simulation: we store the last values recieved from the server.
// When the body comes to rest and goes inactive we slam its final transforms to agree with the last server
// update. This to reduce state synchronization errors when the local simulation deviated from remote.
//
// (2) For locally owned simulation: we store the last values sent to the server, integrated forward over time
// according to how we think the server doing it. We calculate the error between the true local transform
// and the remote to decide when to send another update.
//
glm::vec3 _serverPosition; // in simulation-frame (not world-frame)
glm::quat _serverRotation;
glm::vec3 _serverVelocity;
@ -114,16 +137,18 @@ protected:
glm::vec3 _lastVelocity;
glm::vec3 _measuredAcceleration;
quint64 _nextOwnershipBid { 0 };
quint64 _nextBidExpiry { 0 };
float _measuredDeltaTime;
uint32_t _lastMeasureStep;
uint32_t _lastStep; // last step of server extrapolation
OwnershipState _ownershipState { OwnershipState::NotLocallyOwned };
uint8_t _loopsWithoutOwner;
mutable uint8_t _accelerationNearlyGravityCount;
uint8_t _numInactiveUpdates { 1 };
uint8_t _outgoingPriority { 0 };
uint8_t _bidPriority { 0 };
bool _serverVariablesSet { false };
};
#endif // hifi_EntityMotionState_h

View file

@ -164,7 +164,7 @@ public:
void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; }
virtual bool isLocallyOwned() const { return false; }
virtual bool shouldBeLocallyOwned() const { return false; }
virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents()
friend class PhysicsEngine;

View file

@ -40,7 +40,7 @@ void PhysicalEntitySimulation::init(
}
// begin EntitySimulation overrides
void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) {
void PhysicalEntitySimulation::updateEntitiesInternal(const uint64_t& now) {
// Do nothing here because the "internal" update the PhysicsEngine::stepSimualtion() which is done elsewhere.
}
@ -65,7 +65,7 @@ void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_outgoingChanges.remove(motionState);
removeOwnershipData(motionState);
_entitiesToRemoveFromPhysics.insert(entity);
} else if (entity->isDead() && entity->getElement()) {
_deadEntities.insert(entity);
@ -73,6 +73,42 @@ void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
}
}
void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) {
assert(motionState);
if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) {
for (uint32_t i = 0; i < _owned.size(); ++i) {
if (_owned[i] == motionState) {
_owned[i]->clearOwnershipState();
if (i < _owned.size() - 1) {
_owned[i] = _owned.back();
}
_owned.pop_back();
}
}
} else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) {
for (uint32_t i = 0; i < _bids.size(); ++i) {
if (_bids[i] == motionState) {
_bids[i]->clearOwnershipState();
if (i < _bids.size() - 1) {
_bids[i] = _bids.back();
}
_bids.pop_back();
}
}
}
}
void PhysicalEntitySimulation::clearOwnershipData() {
for (uint32_t i = 0; i < _owned.size(); ++i) {
_owned[i]->clearOwnershipState();
}
_owned.clear();
for (uint32_t i = 0; i < _bids.size(); ++i) {
_bids[i]->clearOwnershipState();
}
_bids.clear();
}
void PhysicalEntitySimulation::takeDeadEntities(SetOfEntities& deadEntities) {
QMutexLocker lock(&_mutex);
for (auto entity : _deadEntities) {
@ -93,15 +129,15 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
if (motionState) {
if (!entity->shouldBePhysical()) {
// the entity should be removed from the physical simulation
_pendingChanges.remove(motionState);
_incomingChanges.remove(motionState);
_physicalObjects.remove(motionState);
_outgoingChanges.remove(motionState);
removeOwnershipData(motionState);
_entitiesToRemoveFromPhysics.insert(entity);
if (entity->isMovingRelativeToParent()) {
_simpleKinematicEntities.insert(entity);
}
} else {
_pendingChanges.insert(motionState);
_incomingChanges.insert(motionState);
}
} else if (entity->shouldBePhysical()) {
// The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet.
@ -136,10 +172,10 @@ void PhysicalEntitySimulation::clearEntitiesInternal() {
_physicalObjects.clear();
// clear all other lists specific to this derived class
clearOwnershipData();
_entitiesToRemoveFromPhysics.clear();
_entitiesToAddToPhysics.clear();
_pendingChanges.clear();
_outgoingChanges.clear();
_incomingChanges.clear();
}
// virtual
@ -163,8 +199,8 @@ const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhys
_deadEntities.insert(entity);
}
_pendingChanges.remove(motionState);
_outgoingChanges.remove(motionState);
_incomingChanges.remove(motionState);
removeOwnershipData(motionState);
_physicalObjects.remove(motionState);
// remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine)
@ -232,18 +268,18 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) {
QMutexLocker lock(&_mutex);
for (auto object : objectsToChange) {
_pendingChanges.insert(static_cast<EntityMotionState*>(object));
_incomingChanges.insert(static_cast<EntityMotionState*>(object));
}
}
void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) {
result.clear();
QMutexLocker lock(&_mutex);
for (auto stateItr : _pendingChanges) {
for (auto stateItr : _incomingChanges) {
EntityMotionState* motionState = &(*stateItr);
result.push_back(motionState);
}
_pendingChanges.clear();
_incomingChanges.clear();
}
void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) {
@ -263,18 +299,22 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta
PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size());
QMutexLocker lock(&_mutex);
// walk the motionStates looking for those that correspond to entities
{
PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size());
for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr);
assert(state);
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
if (entityState->isCandidateForOwnership()) {
_outgoingChanges.insert(entityState);
for (auto stateItr : motionStates) {
ObjectMotionState* state = &(*stateItr);
assert(state);
if (state->getType() == MOTIONSTATE_TYPE_ENTITY) {
EntityMotionState* entityState = static_cast<EntityMotionState*>(state);
_entitiesToSort.insert(entityState->getEntity());
if (entityState->getOwnershipState() == EntityMotionState::OwnershipState::NotLocallyOwned) {
// NOTE: entityState->getOwnershipState() reflects what ownership list (_bids or _owned) it is in
// and is distinct from entityState->isLocallyOwned() which checks the simulation ownership
// properties of the corresponding EntityItem. It is possible for the two states to be out
// of sync. In fact, we're trying to put them back into sync here.
if (entityState->isLocallyOwned()) {
addOwnership(entityState);
} else if (entityState->shouldSendBid()) {
addOwnershipBid(entityState);
}
_entitiesToSort.insert(entityState->getEntity());
}
}
}
@ -284,26 +324,83 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta
_lastStepSendPackets = numSubsteps;
if (Physics::getSessionUUID().isNull()) {
// usually don't get here, but if so --> nothing to do
_outgoingChanges.clear();
return;
// usually don't get here, but if so clear all ownership
clearOwnershipData();
}
// send updates before bids, because this simplifies the logic thasuccessful bids will immediately send an update when added to the 'owned' list
sendOwnedUpdates(numSubsteps);
sendOwnershipBids(numSubsteps);
}
}
// look for entities to prune or update
PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size());
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
while (stateItr != _outgoingChanges.end()) {
EntityMotionState* state = *stateItr;
if (!state->isCandidateForOwnership()) {
// prune
stateItr = _outgoingChanges.erase(stateItr);
} else if (state->shouldSendUpdate(numSubsteps)) {
// update
state->sendUpdate(_entityPacketSender, numSubsteps);
++stateItr;
} else {
++stateItr;
void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) {
motionState->initForBid();
motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps());
_bids.push_back(motionState);
_nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry());
}
void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) {
motionState->initForOwned();
_owned.push_back(motionState);
}
void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) {
uint64_t now = usecTimestampNow();
if (now > _nextBidExpiry) {
PROFILE_RANGE_EX(simulation_physics, "Bid", 0x00000000, (uint64_t)_bids.size());
_nextBidExpiry = (uint64_t)(-1);
uint32_t i = 0;
while (i < _bids.size()) {
bool removeBid = false;
if (_bids[i]->isLocallyOwned()) {
// when an object transitions from 'bid' to 'owned' we are changing the "mode" of data stored
// in the EntityMotionState::_serverFoo variables (please see comments in EntityMotionState.h)
// therefore we need to immediately send an update so that the values stored are what we're
// "telling" the server rather than what we've been "hearing" from the server.
_bids[i]->sendUpdate(_entityPacketSender, numSubsteps);
addOwnership(_bids[i]);
removeBid = true;
} else if (!_bids[i]->shouldSendBid()) {
removeBid = true;
_bids[i]->clearOwnershipState();
}
if (removeBid) {
if (i < _bids.size() - 1) {
_bids[i] = _bids.back();
}
_bids.pop_back();
} else {
if (now > _bids[i]->getNextBidExpiry()) {
_bids[i]->sendBid(_entityPacketSender, numSubsteps);
_nextBidExpiry = glm::min(_nextBidExpiry, _bids[i]->getNextBidExpiry());
}
++i;
}
}
}
}
void PhysicalEntitySimulation::sendOwnedUpdates(uint32_t numSubsteps) {
PROFILE_RANGE_EX(simulation_physics, "Update", 0x00000000, (uint64_t)_owned.size());
uint32_t i = 0;
while (i < _owned.size()) {
if (!_owned[i]->isLocallyOwned()) {
if (_owned[i]->shouldSendBid()) {
addOwnershipBid(_owned[i]);
} else {
_owned[i]->clearOwnershipState();
}
if (i < _owned.size() - 1) {
_owned[i] = _owned.back();
}
_owned.pop_back();
} else {
if (_owned[i]->shouldSendUpdate(numSubsteps)) {
_owned[i]->sendUpdate(_entityPacketSender, numSubsteps);
}
++i;
}
}
}
@ -318,7 +415,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll
}
}
void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) {
if (_physicsEngine) {
// FIXME put fine grain locking into _physicsEngine

View file

@ -45,12 +45,15 @@ signals:
protected: // only called by EntitySimulation
// overrides for EntitySimulation
virtual void updateEntitiesInternal(const quint64& now) override;
virtual void updateEntitiesInternal(const uint64_t& now) override;
virtual void addEntityInternal(EntityItemPointer entity) override;
virtual void removeEntityInternal(EntityItemPointer entity) override;
virtual void changeEntityInternal(EntityItemPointer entity) override;
virtual void clearEntitiesInternal() override;
void removeOwnershipData(EntityMotionState* motionState);
void clearOwnershipData();
public:
virtual void prepareEntityForDelete(EntityItemPointer entity) override;
@ -67,20 +70,28 @@ public:
EntityEditPacketSender* getPacketSender() { return _entityPacketSender; }
void addOwnershipBid(EntityMotionState* motionState);
void addOwnership(EntityMotionState* motionState);
void sendOwnershipBids(uint32_t numSubsteps);
void sendOwnedUpdates(uint32_t numSubsteps);
private:
SetOfEntities _entitiesToAddToPhysics;
SetOfEntities _entitiesToRemoveFromPhysics;
VectorOfMotionStates _objectsToDelete;
SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed
SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server
SetOfEntityMotionStates _incomingChanges; // EntityMotionStates that have changed from external sources
// and need their RigidBodies updated
SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine
PhysicsEnginePointer _physicsEngine = nullptr;
EntityEditPacketSender* _entityPacketSender = nullptr;
std::vector<EntityMotionState*> _owned;
std::vector<EntityMotionState*> _bids;
uint64_t _nextBidExpiry;
uint32_t _lastStepSendPackets { 0 };
};

View file

@ -571,7 +571,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
// modify the logic below.
//
// We only create events when at least one of the objects is (or should be) owned in the local simulation.
if (motionStateA && (motionStateA->shouldBeLocallyOwned())) {
if (motionStateA && (motionStateA->isLocallyOwnedOrShouldBe())) {
QUuid idA = motionStateA->getObjectID();
QUuid idB;
if (motionStateB) {
@ -582,7 +582,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() {
(motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f));
glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB);
_collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange));
} else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) {
} else if (motionStateB && (motionStateB->isLocallyOwnedOrShouldBe())) {
QUuid idB = motionStateB->getObjectID();
QUuid idA;
if (motionStateA) {