Merge pull request #12748 from AndrewMeadows/untangle-003

cleanup around simulation-ownership
This commit is contained in:
John Conklin II 2018-04-05 10:58:45 -07:00 committed by GitHub
commit 9e21bab67e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 655 additions and 506 deletions

View file

@ -33,7 +33,7 @@ uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar)
return 0;
}
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) {
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) {
std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
if (itr != _lastOtherAvatarEncodeTime.end()) {
itr->second = time;

View file

@ -113,7 +113,7 @@ public:
ViewFrustum getViewFrustum() const { return _currentViewFrustum; }
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time);
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time);
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar];

View file

@ -4679,7 +4679,7 @@ void Application::init() {
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
// connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts
connect(_entitySimulation.get(), &EntitySimulation::entityCollisionWithEntity,
connect(_entitySimulation.get(), &PhysicalEntitySimulation::entityCollisionWithEntity,
getEntities().data(), &EntityTreeRenderer::entityCollisionWithEntity);
// connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing
@ -5274,11 +5274,13 @@ void Application::update(float deltaTime) {
{
PROFILE_RANGE(simulation_physics, "PreStep");
PerformanceTimer perfTimer("preStep)");
static VectorOfMotionStates motionStates;
_entitySimulation->getObjectsToRemoveFromPhysics(motionStates);
_physicsEngine->removeObjects(motionStates);
_entitySimulation->deleteObjectsRemovedFromPhysics();
{
const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics();
_physicsEngine->removeObjects(motionStates);
_entitySimulation->deleteObjectsRemovedFromPhysics();
}
VectorOfMotionStates motionStates;
getEntities()->getTree()->withReadLock([&] {
_entitySimulation->getObjectsToAddToPhysics(motionStates);
_physicsEngine->addObjects(motionStates);
@ -5292,7 +5294,7 @@ void Application::update(float deltaTime) {
_entitySimulation->applyDynamicChanges();
avatarManager->getObjectsToRemoveFromPhysics(motionStates);
avatarManager->getObjectsToRemoveFromPhysics(motionStates);
_physicsEngine->removeObjects(motionStates);
avatarManager->getObjectsToAddToPhysics(motionStates);
_physicsEngine->addObjects(motionStates);

View file

@ -1911,7 +1911,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask
}
}
void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) {
void EntityItem::setSimulationOwner(const QUuid& id, uint8_t priority) {
if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) {
qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority;
}
@ -1942,7 +1942,7 @@ void EntityItem::clearSimulationOwnership() {
}
void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& timestamp) {
void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) {
_simulationOwner.setPendingPriority(priority, timestamp);
}
@ -2962,13 +2962,6 @@ void EntityItem::retrieveMarketplacePublicKey() {
}
void EntityItem::preDelete() {
// clear out any left-over actions
EntityTreeElementPointer element = _element; // use local copy of _element for logic below
EntityTreePointer entityTree = element ? element->getTree() : nullptr;
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
if (simulation) {
clearActions(simulation);
}
}
void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) {

View file

@ -304,14 +304,14 @@ public:
// FIXME not thread safe?
const SimulationOwner& getSimulationOwner() const { return _simulationOwner; }
void setSimulationOwner(const QUuid& id, quint8 priority);
void setSimulationOwner(const QUuid& id, uint8_t priority);
void setSimulationOwner(const SimulationOwner& owner);
void promoteSimulationPriority(quint8 priority);
void promoteSimulationPriority(uint8_t priority);
quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); }
uint8_t getSimulationPriority() const { return _simulationOwner.getPriority(); }
QUuid getSimulatorID() const { return _simulationOwner.getID(); }
void clearSimulationOwnership();
void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp);
void setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp);
uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); }
void rememberHasSimulationOwnershipBid() const;

View file

@ -326,7 +326,7 @@ public:
void clearSimulationOwner();
void setSimulationOwner(const QUuid& id, uint8_t priority);
void setSimulationOwner(const QByteArray& data);
void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); }
void promoteSimulationPriority(uint8_t priority) { _simulationOwner.promotePriority(priority); }
void setActionDataDirty() { _actionDataChanged = true; }

View file

@ -20,7 +20,7 @@
void EntitySimulation::setEntityTree(EntityTreePointer tree) {
if (_entityTree && _entityTree != tree) {
_mortalEntities.clear();
_nextExpiry = quint64(-1);
_nextExpiry = std::numeric_limits<uint64_t>::max();
_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);
@ -40,18 +40,14 @@ void EntitySimulation::updateEntities() {
sortEntitiesThatMoved();
}
void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
void EntitySimulation::takeDeadEntities(SetOfEntities& entitiesToDelete) {
QMutexLocker lock(&_mutex);
for (auto entity : _entitiesToDelete) {
// push this entity onto the external list
entitiesToDelete.push_back(entity);
}
_entitiesToDelete.clear();
entitiesToDelete.swap(_deadEntities);
_deadEntities.clear();
}
void EntitySimulation::removeEntityInternal(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
// remove from all internal lists except _entitiesToDelete
// remove from all internal lists except _deadEntities
_mortalEntities.remove(entity);
_entitiesToUpdate.remove(entity);
_entitiesToSort.remove(entity);
@ -67,42 +63,23 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
entity->clearActions(getThisPointer());
removeEntityInternal(entity);
_entitiesToDelete.insert(entity);
}
}
void EntitySimulation::addEntityInternal(EntityItemPointer entity) {
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
QMutexLocker lock(&_mutex);
_simpleKinematicEntities.insert(entity);
entity->setLastSimulated(usecTimestampNow());
}
}
void EntitySimulation::changeEntityInternal(EntityItemPointer entity) {
QMutexLocker lock(&_mutex);
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
int numKinematicEntities = _simpleKinematicEntities.size();
_simpleKinematicEntities.insert(entity);
if (numKinematicEntities != _simpleKinematicEntities.size()) {
entity->setLastSimulated(usecTimestampNow());
if (entity->getElement()) {
_deadEntities.insert(entity);
}
} else {
_simpleKinematicEntities.remove(entity);
}
}
// protected
void EntitySimulation::expireMortalEntities(const quint64& now) {
void EntitySimulation::expireMortalEntities(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 = std::numeric_limits<uint64_t>::max();
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();
@ -122,7 +99,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) {
}
// protected
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) {
void EntitySimulation::callUpdateOnEntitiesThatNeedIt(uint64_t now) {
PerformanceTimer perfTimer("updatingEntities");
QMutexLocker lock(&_mutex);
SetOfEntities::iterator itemItr = _entitiesToUpdate.begin();
@ -176,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;
}
@ -207,7 +184,6 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
// Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes
// it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence
// we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag.
bool wasRemoved = false;
uint32_t dirtyFlags = entity->getDirtyFlags();
if (dirtyFlags & Simulation::DIRTY_POSITION) {
AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE);
@ -217,50 +193,45 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) {
qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds.";
entity->die();
prepareEntityForDelete(entity);
wasRemoved = true;
return;
}
}
if (!wasRemoved) {
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
if (entity->isMortal()) {
_mortalEntities.insert(entity);
quint64 expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
}
} else {
_mortalEntities.remove(entity);
if (dirtyFlags & Simulation::DIRTY_LIFETIME) {
if (entity->isMortal()) {
_mortalEntities.insert(entity);
uint64_t expiry = entity->getExpiry();
if (expiry < _nextExpiry) {
_nextExpiry = expiry;
}
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME);
}
if (entity->needsToCallUpdate()) {
_entitiesToUpdate.insert(entity);
} else {
_entitiesToUpdate.remove(entity);
_mortalEntities.remove(entity);
}
changeEntityInternal(entity);
entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME);
}
if (entity->needsToCallUpdate()) {
_entitiesToUpdate.insert(entity);
} else {
_entitiesToUpdate.remove(entity);
}
changeEntityInternal(entity);
}
void EntitySimulation::clearEntities() {
QMutexLocker lock(&_mutex);
_mortalEntities.clear();
_nextExpiry = quint64(-1);
_nextExpiry = std::numeric_limits<uint64_t>::max();
_entitiesToUpdate.clear();
_entitiesToSort.clear();
_simpleKinematicEntities.clear();
clearEntitiesInternal();
for (auto entity : _allEntities) {
entity->setSimulated(false);
entity->die();
}
_allEntities.clear();
_entitiesToDelete.clear();
_deadEntities.clear();
}
void EntitySimulation::moveSimpleKinematics(const quint64& now) {
void EntitySimulation::moveSimpleKinematics(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

@ -12,6 +12,8 @@
#ifndef hifi_EntitySimulation_h
#define hifi_EntitySimulation_h
#include <limits>
#include <QtCore/QObject>
#include <QSet>
#include <QVector>
@ -43,9 +45,8 @@ const int DIRTY_SIMULATION_FLAGS =
Simulation::DIRTY_SIMULATOR_ID;
class EntitySimulation : public QObject, public std::enable_shared_from_this<EntitySimulation> {
Q_OBJECT
public:
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { }
EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(std::numeric_limits<uint64_t>::max()) { }
virtual ~EntitySimulation() { setEntityTree(NULL); }
inline EntitySimulationPointer getThisPointer() const {
@ -57,8 +58,6 @@ public:
void updateEntities();
// friend class EntityTree;
virtual void addDynamic(EntityDynamicPointer dynamic);
virtual void removeDynamic(const QUuid dynamicID);
virtual void removeDynamics(QList<QUuid> dynamicIDsToRemove);
@ -74,29 +73,26 @@ public:
void clearEntities();
void moveSimpleKinematics(const quint64& now);
void moveSimpleKinematics(uint64_t now);
EntityTreePointer getEntityTree() { return _entityTree; }
virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete);
virtual void takeDeadEntities(SetOfEntities& entitiesToDelete);
/// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others.
virtual void prepareEntityForDelete(EntityItemPointer entity);
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
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 addEntityInternal(EntityItemPointer entity);
virtual void removeEntityInternal(EntityItemPointer entity) = 0;
virtual void changeEntityInternal(EntityItemPointer entity);
virtual void updateEntitiesInternal(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(uint64_t now);
void callUpdateOnEntitiesThatNeedIt(uint64_t now);
virtual void sortEntitiesThatMoved();
QMutex _mutex{ QMutex::Recursive };
@ -108,7 +104,7 @@ protected:
QMutex _dynamicsMutex { QMutex::Recursive };
protected:
SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete)
SetOfEntities _deadEntities;
private:
void moveSimpleKinematics();
@ -120,11 +116,10 @@ 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()
};
#endif // hifi_EntitySimulation_h

View file

@ -94,7 +94,6 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) {
void EntityTree::eraseAllOctreeElements(bool createNewRoot) {
emit clearingEntities();
// this would be a good place to clean up our entities...
if (_simulation) {
_simulation->clearEntities();
}
@ -260,7 +259,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) {
return;
}
}
// check to see if we need to simulate this entity..
if (_simulation) {
_simulation->addEntity(entity);
@ -453,12 +452,13 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
uint32_t newFlags = entity->getDirtyFlags() & ~preFlags;
if (newFlags) {
if (_simulation) {
if (entity->isSimulated()) {
assert((bool)_simulation);
if (newFlags & DIRTY_SIMULATION_FLAGS) {
_simulation->changeEntity(entity);
}
} else {
// normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly
// normally the _simulation clears ALL dirtyFlags, but when not possible we do it explicitly
entity->clearDirtyFlags();
}
}
@ -469,7 +469,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
if (entityScriptBefore != entityScriptAfter || reload) {
emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed
}
}
}
// TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG).
if (!entity->getElement()) {
@ -551,8 +551,6 @@ void EntityTree::setSimulation(EntitySimulationPointer simulation) {
assert(simulation->getEntityTree().get() == this);
}
if (_simulation && _simulation != simulation) {
// It's important to clearEntities() on the simulation since taht will update each
// EntityItem::_simulationState correctly so as to not confuse the next _simulation.
_simulation->clearEntities();
}
_simulation = simulation;
@ -650,7 +648,7 @@ void EntityTree::deleteEntities(QSet<EntityItemID> entityIDs, bool force, bool i
emit deletingEntityPointer(existingEntity.get());
}
if (theOperator.getEntities().size() > 0) {
if (!theOperator.getEntities().empty()) {
recurseTreeWithOperator(&theOperator);
processRemovedEntities(theOperator);
_isDirty = true;
@ -692,7 +690,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator)
trackDeletedEntity(theEntity->getEntityItemID());
}
if (_simulation) {
if (theEntity->isSimulated()) {
_simulation->prepareEntityForDelete(theEntity);
}
}
@ -1688,7 +1686,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod
}
void EntityTree::entityChanged(EntityItemPointer entity) {
if (_simulation) {
if (entity->isSimulated()) {
_simulation->changeEntity(entity);
}
}
@ -1802,13 +1800,13 @@ void EntityTree::update(bool simulate) {
_simulation->updateEntities();
{
PROFILE_RANGE(simulation_physics, "Deletes");
VectorOfEntities pendingDeletes;
_simulation->takeEntitiesToDelete(pendingDeletes);
if (pendingDeletes.size() > 0) {
SetOfEntities deadEntities;
_simulation->takeDeadEntities(deadEntities);
if (!deadEntities.empty()) {
// translate into list of ID's
QSet<EntityItemID> idsToDelete;
for (auto entity : pendingDeletes) {
for (auto entity : deadEntities) {
idsToDelete.insert(entity->getEntityItemID());
}

View file

@ -267,8 +267,11 @@ void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) {
void MaterialEntityItem::removeMaterial() {
graphics::MaterialPointer material = getMaterial();
if (!material) {
return;
}
QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID();
if (!material || parentID.isNull()) {
if (parentID.isNull()) {
return;
}
@ -336,4 +339,4 @@ void MaterialEntityItem::update(const quint64& now) {
}
EntityItem::update(now);
}
}

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,15 +50,15 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) {
}
}
void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) {
if (now > _nextOwnerlessExpiry) {
// search for ownerless objects that have expired
QMutexLocker lock(&_mutex);
_nextOwnerlessExpiry = -1;
_nextOwnerlessExpiry = std::numeric_limits<uint64_t>::max();
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);
@ -85,14 +85,18 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) {
}
void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
EntitySimulation::addEntityInternal(entity);
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
QMutexLocker lock(&_mutex);
_simpleKinematicEntities.insert(entity);
entity->setLastSimulated(usecTimestampNow());
}
if (!entity->getSimulatorID().isNull()) {
QMutexLocker lock(&_mutex);
_entitiesWithSimulationOwner.insert(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;
}
@ -101,19 +105,29 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) {
void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
EntitySimulation::removeEntityInternal(entity);
QMutexLocker lock(&_mutex);
_entitiesWithSimulationOwner.remove(entity);
_entitiesThatNeedSimulationOwner.remove(entity);
}
void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
EntitySimulation::changeEntityInternal(entity);
{
QMutexLocker lock(&_mutex);
if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) {
int numKinematicEntities = _simpleKinematicEntities.size();
_simpleKinematicEntities.insert(entity);
if (numKinematicEntities != _simpleKinematicEntities.size()) {
entity->setLastSimulated(usecTimestampNow());
}
} else {
_simpleKinematicEntities.remove(entity);
}
}
if (entity->getSimulatorID().isNull()) {
QMutexLocker lock(&_mutex);
_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(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

@ -16,9 +16,9 @@
#include <NumericalConstants.h>
const quint8 PENDING_STATE_NOTHING = 0;
const quint8 PENDING_STATE_TAKE = 1;
const quint8 PENDING_STATE_RELEASE = 2;
const uint8_t PENDING_STATE_NOTHING = 0;
const uint8_t PENDING_STATE_TAKE = 1;
const uint8_t PENDING_STATE_RELEASE = 2;
// static
const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1;
@ -33,7 +33,7 @@ SimulationOwner::SimulationOwner() :
{
}
SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) :
SimulationOwner::SimulationOwner(const QUuid& id, uint8_t priority) :
_id(id),
_expiry(0),
_pendingBidTimestamp(0),
@ -67,11 +67,11 @@ void SimulationOwner::clear() {
_pendingState = PENDING_STATE_NOTHING;
}
void SimulationOwner::setPriority(quint8 priority) {
void SimulationOwner::setPriority(uint8_t priority) {
_priority = priority;
}
void SimulationOwner::promotePriority(quint8 priority) {
void SimulationOwner::promotePriority(uint8_t priority) {
if (priority > _priority) {
_priority = priority;
}
@ -89,7 +89,7 @@ bool SimulationOwner::setID(const QUuid& id) {
return false;
}
bool SimulationOwner::set(const QUuid& id, quint8 priority) {
bool SimulationOwner::set(const QUuid& id, uint8_t priority) {
uint8_t oldPriority = _priority;
setPriority(priority);
return setID(id) || oldPriority != _priority;
@ -101,22 +101,22 @@ bool SimulationOwner::set(const SimulationOwner& owner) {
return setID(owner._id) || oldPriority != _priority;
}
void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) {
void SimulationOwner::setPendingPriority(uint8_t priority, uint64_t timestamp) {
_pendingBidPriority = priority;
_pendingBidTimestamp = timestamp;
_pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE;
}
void SimulationOwner::updateExpiry() {
const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5;
const uint64_t OWNERSHIP_LOCKOUT_EXPIRY = 200 * USECS_PER_MSEC;
_expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY;
}
bool SimulationOwner::pendingRelease(const quint64& timestamp) {
bool SimulationOwner::pendingRelease(uint64_t timestamp) {
return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp;
}
bool SimulationOwner::pendingTake(const quint64& timestamp) {
bool SimulationOwner::pendingTake(uint64_t timestamp) {
return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp;
}
@ -142,7 +142,7 @@ void SimulationOwner::test() {
{ // test set constructor
QUuid id = QUuid::createUuid();
quint8 priority = 128;
uint8_t priority = 128;
SimulationOwner simOwner(id, priority);
if (simOwner.isNull()) {
std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl;
@ -164,7 +164,7 @@ void SimulationOwner::test() {
{ // test set()
QUuid id = QUuid::createUuid();
quint8 priority = 1;
uint8_t priority = 1;
SimulationOwner simOwner;
simOwner.set(id, priority);
if (simOwner.isNull()) {

View file

@ -18,20 +18,88 @@
#include <SharedUtil.h>
#include <UUID.h>
// Simulation observers will bid to simulate unowned active objects at the lowest possible priority
// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it
// to RECRUIT priority so that other volunteers don't accidentally take over.
const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01;
const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
// 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.
//
// Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update:
// {
// "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:
//
// (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
//
// (4) The current owner is the only participant allowed to clear ownership (entity-server can override).
//
// (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 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.
//
// (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
// 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
// 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)
//
// (10) When an entity touches MyAvatar the participant it will bid at priority = POKE.
//
// (11) When a participant grabs an entity it will bid at priority = GRAB (=128).
//
// (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).
//
// (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.
//
// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it
// immediately at priority = VOLUNTEER.
//
// (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.
//
const uint8_t YIELD_SIMULATION_PRIORITY = 1;
const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1;
const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
// When poking objects with scripts an observer will bid at SCRIPT_EDIT priority.
const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80;
const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1;
const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128;
const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1;
const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1;
// PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar
// which really just means: things that collide with it will be bid at a priority level one lower
const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY;
class SimulationOwner {
@ -39,25 +107,25 @@ public:
static const int NUM_BYTES_ENCODED;
SimulationOwner();
SimulationOwner(const QUuid& id, quint8 priority);
SimulationOwner(const QUuid& id, uint8_t priority);
const QUuid& getID() const { return _id; }
const quint64& getExpiry() const { return _expiry; }
quint8 getPriority() const { return _priority; }
const uint64_t& getExpiry() const { return _expiry; }
uint8_t getPriority() const { return _priority; }
QByteArray toByteArray() const;
bool fromByteArray(const QByteArray& data);
void clear();
void setPriority(quint8 priority);
void promotePriority(quint8 priority);
void setPriority(uint8_t priority);
void promotePriority(uint8_t priority);
// return true if id is changed
bool setID(const QUuid& id);
bool set(const QUuid& id, quint8 priority);
bool set(const QUuid& id, uint8_t priority);
bool set(const SimulationOwner& owner);
void setPendingPriority(quint8 priority, const quint64& timestamp);
void setPendingPriority(uint8_t priority, uint64_t timestamp);
bool isNull() const { return _id.isNull(); }
bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); }
@ -67,11 +135,11 @@ public:
bool hasExpired() const { return usecTimestampNow() > _expiry; }
uint8_t getPendingPriority() const { return _pendingBidPriority; }
bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE
bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE
bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE
bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE
void clearCurrentOwner();
bool operator>=(quint8 priority) const { return _priority >= priority; }
bool operator>=(uint8_t priority) const { return _priority >= priority; }
bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); }
bool operator!=(const SimulationOwner& other);
@ -84,11 +152,11 @@ public:
private:
QUuid _id; // owner
quint64 _expiry; // time when ownership can transition at equal priority
quint64 _pendingBidTimestamp; // time when pending bid was set
quint8 _priority; // priority of current owner
quint8 _pendingBidPriority; // priority at which we'd like to own it
quint8 _pendingState; // NOTHING, TAKE, or RELEASE
uint64_t _expiry; // time when ownership can transition at equal priority
uint64_t _pendingBidTimestamp; // time when pending bid was set
uint8_t _priority; // priority of current owner
uint8_t _pendingBidPriority; // priority at which we'd like to own it
uint8_t _pendingState; // NOTHING, TAKE, or RELEASE
};

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,11 +41,13 @@ 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),
_entityPtr(entity),
_entity(entity.get()),
_entity(entity),
_serverPosition(0.0f),
_serverRotation(),
_serverVelocity(0.0f),
@ -60,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),
@ -68,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());
@ -76,77 +79,89 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
setShape(shape);
_outgoingPriority = _entity->getPendingOwnershipPriority();
}
EntityMotionState::~EntityMotionState() {
assert(_entity);
_entity = nullptr;
}
void EntityMotionState::updateServerPhysicsVariables() {
assert(entityTreeIsLocked());
if (isLocallyOwned()) {
// don't slam these values if we are the simulation owner
return;
_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);
_serverVariablesSet = true;
_serverPosition = localTransform.getTranslation();
_serverRotation = localTransform.getRotation();
_serverAcceleration = _entity->getAcceleration();
_serverActionData = _entity->getDynamicData();
}
EntityMotionState::~EntityMotionState() {
if (_entity) {
assert(_entity->getPhysicsInfo() == this);
_entity->setPhysicsInfo(nullptr);
_entity.reset();
}
}
void EntityMotionState::updateServerPhysicsVariables() {
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();
}
}
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
void EntityMotionState::handleEasyChanges(uint32_t& flags) {
assert(_entity);
assert(entityTreeIsLocked());
updateServerPhysicsVariables();
ObjectMotionState::handleEasyChanges(flags);
if (flags & Simulation::DIRTY_SIMULATOR_ID) {
if (_entity->getSimulatorID().isNull()) {
// simulation ownership has been removed by an external simulator
// 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;
}
}
@ -155,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()) {
@ -175,7 +190,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) {
// virtual
bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) {
assert(_entity);
updateServerPhysicsVariables();
return ObjectMotionState::handleHardAndEasyChanges(flags, engine);
}
@ -253,7 +267,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const {
// This callback is invoked by the physics simulation at the end of each simulation step...
// iff the corresponding RigidBody is DYNAMIC and ACTIVE.
void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
assert(_entity);
assert(entityTreeIsLocked());
measureBodyAcceleration();
@ -285,21 +298,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
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,19 +325,13 @@ void EntityMotionState::setShape(const btCollisionShape* shape) {
}
}
bool EntityMotionState::isCandidateForOwnership() const {
assert(_body);
assert(_entity);
assert(entityTreeIsLocked());
return _outgoingPriority != 0
|| isLocallyOwned()
|| _entity->dynamicDataNeedsTransmit();
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");
// NOTE: we only get here if we think we own the simulation
assert(_body);
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());
bool parentTransformSuccess;
Transform localToWorld = _entity->getParentTransform(parentTransformSuccess);
@ -347,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;
@ -378,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);
}
@ -411,14 +386,10 @@ 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;
}
if (_entity->shouldSuppressLocationEdits()) {
return false;
}
// 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.
@ -440,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;
}
}
@ -466,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);
}
@ -489,14 +437,10 @@ 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(_entity);
assert(_body);
assert(entityTreeIsLocked());
if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) {
// don't send updates for someone else's avatarEntities
return false;
}
// this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor
assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()));
if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) {
return true;
@ -506,44 +450,19 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) {
return false;
}
if (!isLocallyOwned()) {
// we don't own the simulation
// NOTE: we do not volunteer to own kinematic or static objects
uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0;
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;
}
return shouldBid;
} else {
// When 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());
}
return remoteSimulationOutOfSync(simulationStep);
}
void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Send");
assert(_entity);
assert(entityTreeIsLocked());
void EntityMotionState::updateSendVelocities() {
if (!_body->isActive()) {
// make sure all derivatives are zero
zeroCleanObjectVelocities();
_numInactiveUpdates++;
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.
// 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);
@ -564,13 +483,82 @@ 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
void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) {
DETAILED_PROFILE_RANGE(simulation_physics, "Bid");
assert(entityTreeIsLocked());
updateSendVelocities();
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); // 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.
_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(isLocallyOwned());
updateSendVelocities();
// remember _serverFoo data for local prediction of server state
Transform localTransform;
_entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity);
_serverPosition = localTransform.getTranslation();
@ -579,11 +567,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);
@ -592,61 +577,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
quint8 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;
// _outgoingPrioriuty 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;
@ -655,7 +614,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.
@ -666,13 +625,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
}
}
});
@ -723,6 +681,10 @@ uint8_t EntityMotionState::getSimulationPriority() const {
return _entity->getSimulationPriority();
}
void EntityMotionState::slaveBidPriority() {
upgradeBidPriority(_entity->getSimulationPriority());
}
// virtual
QUuid EntityMotionState::getSimulatorID() const {
assert(entityTreeIsLocked());
@ -731,7 +693,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() {
@ -755,13 +717,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;
@ -805,24 +768,44 @@ QString EntityMotionState::getName() const {
// virtual
void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
assert(_entity);
_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,11 +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;
@ -44,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;
@ -70,7 +75,9 @@ public:
virtual QUuid getSimulatorID() const override;
virtual void bump(uint8_t priority) override;
EntityItemPointer getEntity() const { return _entityPtr.lock(); }
// getEntity() returns a smart-pointer by reference because it is only ever used
// to insert into lists of smart pointers, and the lists will make their own copies
const EntityItemPointer& getEntity() const { return _entity; }
void resetMeasuredBodyAcceleration();
void measureBodyAcceleration();
@ -79,15 +86,29 @@ 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;
void updateSendVelocities();
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);
// upgradeBidPriority to value stored in _entity
void slaveBidPriority();
void clearObjectVelocities() const;
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
bool entityTreeIsLocked() const;
@ -98,17 +119,21 @@ protected:
void setShape(const btCollisionShape* shape) override;
void setMotionType(PhysicsMotionType motionType) override;
// In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be
// properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that
// state of affairs we can't keep a real EntityItemPointer as data member (it would produce a
// recursive dependency). Instead we keep a EntityItemWeakPointer to break that dependency while
// still granting us the capability to generate EntityItemPointers as necessary (for external data
// structures that use the MotionState to get to the EntityItem).
EntityItemWeakPointer _entityPtr;
// Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid.
EntityItem* _entity;
// EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR
// 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;
@ -119,16 +144,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

@ -148,9 +148,9 @@ public:
virtual const QUuid getObjectID() const = 0;
virtual quint8 getSimulationPriority() const { return 0; }
virtual uint8_t getSimulationPriority() const { return 0; }
virtual QUuid getSimulatorID() const = 0;
virtual void bump(quint8 priority) {}
virtual void bump(uint8_t priority) {}
virtual QString getName() const { return ""; }
@ -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(uint64_t now) {
// Do nothing here because the "internal" update the PhysicsEngine::stepSimualtion() which is done elsewhere.
}
@ -61,33 +61,58 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) {
void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) {
if (entity->isSimulated()) {
EntitySimulation::removeEntityInternal(entity);
QMutexLocker lock(&_mutex);
_entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_outgoingChanges.remove(motionState);
removeOwnershipData(motionState);
_entitiesToRemoveFromPhysics.insert(entity);
} else {
_entitiesToDelete.insert(entity);
} else if (entity->isDead() && entity->getElement()) {
_deadEntities.insert(entity);
}
}
}
void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) {
QMutexLocker lock(&_mutex);
for (auto entity : _entitiesToDelete) {
// this entity is still in its tree, so we insert into the external list
entitiesToDelete.push_back(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();
_owned.remove(i);
}
}
} else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) {
for (uint32_t i = 0; i < _bids.size(); ++i) {
if (_bids[i] == motionState) {
_bids[i]->clearOwnershipState();
_bids.remove(i);
}
}
}
}
// Someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
// rather than do it here
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) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_entitiesToRemoveFromPhysics.insert(entity);
}
}
_entitiesToDelete.clear();
_deadEntities.swap(deadEntities);
_deadEntities.clear();
}
void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) {
@ -98,15 +123,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.
@ -125,80 +150,69 @@ void PhysicalEntitySimulation::clearEntitiesInternal() {
// while it is in the middle of a simulation step. As it is, we're probably in shutdown mode
// anyway, so maybe the simulation was already properly shutdown? Cross our fingers...
// copy everything into _entitiesToDelete
for (auto stateItr : _physicalObjects) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
_entitiesToDelete.insert(motionState->getEntity());
}
// then remove the objects (aka MotionStates) from physics
// remove the objects (aka MotionStates) from physics
_physicsEngine->removeSetOfObjects(_physicalObjects);
// delete the MotionStates
// TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete
// its own PhysicsInfo rather than do it here
for (auto entity : _entitiesToDelete) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
entity->setPhysicsInfo(nullptr);
delete motionState;
}
for (auto stateItr : _physicalObjects) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(&(*stateItr));
assert(motionState);
EntityItemPointer entity = motionState->getEntity();
entity->setPhysicsInfo(nullptr);
// TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
// until then we must do it here
delete motionState;
}
// finally clear all lists maintained by this class
_physicalObjects.clear();
// clear all other lists specific to this derived class
clearOwnershipData();
_entitiesToRemoveFromPhysics.clear();
_entitiesToRelease.clear();
_entitiesToAddToPhysics.clear();
_pendingChanges.clear();
_outgoingChanges.clear();
_incomingChanges.clear();
}
// virtual
void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) {
assert(entity);
assert(entity->isDead());
QMutexLocker lock(&_mutex);
entity->clearActions(getThisPointer());
removeEntityInternal(entity);
}
// end EntitySimulation overrides
void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) {
result.clear();
const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhysics() {
QMutexLocker lock(&_mutex);
for (auto entity: _entitiesToRemoveFromPhysics) {
// make sure it isn't on any side lists
_entitiesToAddToPhysics.remove(entity);
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
if (motionState) {
_pendingChanges.remove(motionState);
_outgoingChanges.remove(motionState);
_physicalObjects.remove(motionState);
result.push_back(motionState);
_entitiesToRelease.insert(entity);
assert(motionState);
_entitiesToAddToPhysics.remove(entity);
if (entity->isDead() && entity->getElement()) {
_deadEntities.insert(entity);
}
if (entity->isDead()) {
_entitiesToDelete.insert(entity);
}
_incomingChanges.remove(motionState);
removeOwnershipData(motionState);
_physicalObjects.remove(motionState);
// remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine)
_objectsToDelete.push_back(motionState);
}
_entitiesToRemoveFromPhysics.clear();
return _objectsToDelete;
}
void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() {
QMutexLocker lock(&_mutex);
for (auto entity: _entitiesToRelease) {
EntityMotionState* motionState = static_cast<EntityMotionState*>(entity->getPhysicsInfo());
assert(motionState);
entity->setPhysicsInfo(nullptr);
for (auto motionState : _objectsToDelete) {
// someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo
// until then we must do it here
// NOTE: a reference to the EntityItemPointer is released in the EntityMotionState::dtor
delete motionState;
if (entity->isDead()) {
_entitiesToDelete.insert(entity);
}
}
_entitiesToRelease.clear();
_objectsToDelete.clear();
}
void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) {
@ -248,18 +262,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) {
@ -279,20 +293,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);
EntityItemPointer entity = entityState->getEntity();
assert(entity.get());
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(entity);
}
}
}
@ -302,26 +318,78 @@ 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 = std::numeric_limits<uint64_t>::max();
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]->slaveBidPriority();
_bids[i]->sendUpdate(_entityPacketSender, numSubsteps);
addOwnership(_bids[i]);
removeBid = true;
} else if (!_bids[i]->shouldSendBid()) {
removeBid = true;
_bids[i]->clearOwnershipState();
}
if (removeBid) {
_bids.remove(i);
} 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();
}
_owned.remove(i);
} else {
if (_owned[i]->shouldSendUpdate(numSubsteps)) {
_owned[i]->sendUpdate(_entityPacketSender, numSubsteps);
}
++i;
}
}
}
@ -336,7 +404,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll
}
}
void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) {
if (_physicsEngine) {
// FIXME put fine grain locking into _physicsEngine

View file

@ -27,7 +27,19 @@ class PhysicalEntitySimulation;
using PhysicalEntitySimulationPointer = std::shared_ptr<PhysicalEntitySimulation>;
using SetOfEntityMotionStates = QSet<EntityMotionState*>;
class VectorOfEntityMotionStates: public std::vector<EntityMotionState*> {
public:
void remove(uint32_t index) {
assert(index < size());
if (index < size() - 1) {
(*this)[index] = back();
}
pop_back();
}
};
class PhysicalEntitySimulation : public EntitySimulation {
Q_OBJECT
public:
PhysicalEntitySimulation();
~PhysicalEntitySimulation();
@ -37,21 +49,28 @@ public:
virtual void addDynamic(EntityDynamicPointer dynamic) override;
virtual void applyDynamicChanges() override;
virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override;
virtual void takeDeadEntities(SetOfEntities& deadEntities) override;
signals:
void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
protected: // only called by EntitySimulation
// overrides for EntitySimulation
virtual void updateEntitiesInternal(const quint64& now) override;
virtual void updateEntitiesInternal(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;
void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result);
const VectorOfMotionStates& getObjectsToRemoveFromPhysics();
void deleteObjectsRemovedFromPhysics();
void getObjectsToAddToPhysics(VectorOfMotionStates& result);
void setObjectsToChange(const VectorOfMotionStates& objectsToChange);
void getObjectsToChange(VectorOfMotionStates& result);
@ -62,19 +81,28 @@ public:
EntityEditPacketSender* getPacketSender() { return _entityPacketSender; }
private:
SetOfEntities _entitiesToRemoveFromPhysics;
SetOfEntities _entitiesToRelease;
SetOfEntities _entitiesToAddToPhysics;
void addOwnershipBid(EntityMotionState* motionState);
void addOwnership(EntityMotionState* motionState);
void sendOwnershipBids(uint32_t numSubsteps);
void sendOwnedUpdates(uint32_t numSubsteps);
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
private:
SetOfEntities _entitiesToAddToPhysics;
SetOfEntities _entitiesToRemoveFromPhysics;
VectorOfMotionStates _objectsToDelete;
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;
VectorOfEntityMotionStates _owned;
VectorOfEntityMotionStates _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) {