//// EntityTree.cpp// libraries/entities/src//// Created by Brad Hefta-Gaub on 12/4/13.// Copyright 2013 High Fidelity, Inc.// Copyright 2020 Vircadia contributors.// Copyright 2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// #include "EntityTree.h"#include <QtCore/QDateTime>#include <QtCore/QQueue>#include <openssl/err.h>#include <openssl/pem.h>#include <openssl/x509.h>#include <NetworkingConstants.h>#include <MetaverseAPI.h>#include "AccountManager.h"#include <QJsonObject>#include <QJsonDocument>#include <QJsonArray> #include <Extents.h>#include <PerfStat.h>#include <Profile.h>#include <AddressManager.h> #include "EntitySimulation.h"#include "VariantMapToScriptValue.h" #include "AddEntityOperator.h"#include "UpdateEntityOperator.h"#include "QVariantGLM.h"#include "EntitiesLogging.h"#include "RecurseOctreeToMapOperator.h"#include "RecurseOctreeToJSONOperator.h"#include "LogHandler.h"#include "EntityEditFilters.h"#include "EntityDynamicFactoryInterface.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hourstatic const QString DOMAIN_UNLIMITED = "domainUnlimited"; EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage){ resetClientEditStats();} EntityTree::~EntityTree() { // NOTE: to eraseAllOctreeElements() in this context is useless because // any OctreeElements in the tree still have shared backpointers to this Tree // which means the dtor never would have been called in the first place! // // I'm keeping this useless commented-out line to remind us: // we don't need shared pointer overhead for EntityTrees. // TODO: EntityTreeElement::_tree should be raw back pointer. // AND: EntityItem::_element should be a raw back pointer. //eraseAllOctreeElements(false); // KEEP THIS} void EntityTree::setEntityScriptSourceAllowlist(const QString& entityScriptSourceAllowlist) { _entityScriptSourceAllowlist = entityScriptSourceAllowlist.split(',', Qt::SkipEmptyParts);} void EntityTree::createRootElement() { _rootElement = createNewElement();} OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { auto newElement = EntityTreeElementPointer(new EntityTreeElement(octalCode)); newElement->setTree(std::static_pointer_cast<EntityTree>(shared_from_this())); return std::static_pointer_cast<OctreeElement>(newElement);} void EntityTree::eraseDomainAndNonOwnedEntities() { emit clearingEntities(); if (_simulation) { // local-entities are not in the simulation, so we clear ALL _simulation->clearEntities(); } this->withWriteLock([&] { QHash<EntityItemID, EntityItemPointer> savedEntities; // NOTE: lock the Tree first, then lock the _entityMap. // It should never be done the other way around. QReadLocker locker(&_entityMapLock); foreach(EntityItemPointer entity, _entityMap) { EntityTreeElementPointer element = entity->getElement(); if (element) { element->cleanupDomainAndNonOwnedEntities(); } if (!getIsServer()) { if (entity->isLocalEntity() || entity->isMyAvatarEntity()) { savedEntities[entity->getEntityItemID()] = entity; } else { int32_t spaceIndex = entity->getSpaceIndex(); if (spaceIndex != -1) { // stale spaceIndices will be freed later _staleProxies.push_back(spaceIndex); } } } } _entityMap.swap(savedEntities); }); resetClientEditStats(); clearDeletedEntities(); { QWriteLocker locker(&_needsParentFixupLock); QVector<EntityItemWeakPointer> needParentFixup; foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { auto entity = entityItem.lock(); if (entity && (entity->isLocalEntity() || entity->isMyAvatarEntity())) { needParentFixup.push_back(entityItem); } } _needsParentFixup = needParentFixup; }} void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); if (_simulation) { _simulation->clearEntities(); } QHash<EntityItemID, EntityItemPointer> localMap; localMap.swap(_entityMap); this->withWriteLock([&] { foreach(EntityItemPointer entity, localMap) { EntityTreeElementPointer element = entity->getElement(); if (element) { element->cleanupEntities(); } if (!getIsServer()) { int32_t spaceIndex = entity->getSpaceIndex(); if (spaceIndex != -1) { // assume stale spaceIndices will be freed later _staleProxies.push_back(spaceIndex); } } } }); localMap.clear(); Octree::eraseAllOctreeElements(createNewRoot); resetClientEditStats(); clearDeletedEntities(); { QWriteLocker locker(&_needsParentFixupLock); _needsParentFixup.clear(); }} void EntityTree::readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args) { Octree::readBitstreamToTree(bitstream, bufferSizeBytes, args); // add entities QHash<EntityItemID, EntityItemPointer>::const_iterator itr; for (itr = _entitiesToAdd.constBegin(); itr != _entitiesToAdd.constEnd(); ++itr) { const EntityItemPointer& entityItem = itr.value(); AddEntityOperator theOperator(getThisPointer(), entityItem); recurseTreeWithOperator(&theOperator); postAddEntity(entityItem); } _entitiesToAdd.clear(); // move entities if (_entityMover.hasMovingEntities()) { PerformanceTimer perfTimer("recurseTreeWithOperator"); recurseTreeWithOperator(&_entityMover); _entityMover.reset(); }} int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { const unsigned char* dataAt = data; int bytesRead = 0; uint16_t numberOfEntities = 0; int expectedBytesPerEntity = EntityItem::expectedBytes(); args.elementsPerPacket++; if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) { // read our entities in.... numberOfEntities = *(uint16_t*)dataAt; dataAt += sizeof(numberOfEntities); bytesLeftToRead -= (int)sizeof(numberOfEntities); bytesRead += sizeof(numberOfEntities); if (bytesLeftToRead >= (int)(numberOfEntities * expectedBytesPerEntity)) { for (uint16_t i = 0; i < numberOfEntities; i++) { int bytesForThisEntity = 0; EntityItemID entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead); EntityItemPointer entity = findEntityByEntityItemID(entityItemID); if (entity) { QString entityScriptBefore = entity->getScript(); QUuid parentIDBefore = entity->getParentID(); QString entityServerScriptsBefore = entity->getServerScripts(); quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); if (entity->getDirtyFlags()) { entityChanged(entity); } _entityMover.addEntityToMoveList(entity, entity->getQueryAACube()); QString entityScriptAfter = entity->getScript(); QString entityServerScriptsAfter = entity->getServerScripts(); quint64 entityScriptTimestampAfter = entity->getScriptTimestamp(); bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter; // If the script value has changed on us, or it's timestamp has changed to force // a reload then we want to send out a script changing signal... if (reload || entityScriptBefore != entityScriptAfter) { emitEntityScriptChanging(entityItemID, entityScriptBefore, entityScriptAfter); } if (reload || entityServerScriptsBefore != entityServerScriptsAfter) { emitEntityServerScriptChanging(entityItemID, entityServerScriptsBefore, entityServerScriptsAfter); } QUuid parentIDAfter = entity->getParentID(); if (parentIDBefore != parentIDAfter) { addToNeedsParentFixupList(entity); } } else { entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead); if (entity) { bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); // don't add if we've recently deleted.... if (!isDeletedEntity(entityItemID)) { _entitiesToAdd.insert(entityItemID, entity); if (entity->getCreated() == UNKNOWN_CREATED_TIME) { entity->recordCreationTime(); } #ifdef WANT_DEBUG } else { qCDebug(entities) << "Received packet for previously deleted entity [" << entityItemID << "] ignoring. (inside " << __FUNCTION__ << ")"; #endif } } } // Move the buffer forward to read more entities dataAt += bytesForThisEntity; bytesLeftToRead -= bytesForThisEntity; bytesRead += bytesForThisEntity; } } } return bytesRead;} bool EntityTree::handlesEditPacketType(PacketType packetType) const { // we handle these types of "edit" packets switch (packetType) { case PacketType::EntityAdd: case PacketType::EntityClone: case PacketType::EntityEdit: case PacketType::EntityErase: case PacketType::EntityPhysics: return true; default: return false; }} /// Adds a new entity item to the treevoid EntityTree::postAddEntity(EntityItemPointer entity) { assert(entity); // check to see if we need to simulate this entity.. if (_simulation) { _simulation->addEntity(entity); } if (!entity->getParentID().isNull()) { addToNeedsParentFixupList(entity); } _isDirty = true; // find and hook up any entities with this entity as a (previously) missing parent fixupNeedsParentFixups(); emit addingEntity(entity->getEntityItemID()); emit addingEntityPointer(entity.get());} bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityItemPointer entity; { QReadLocker locker(&_entityMapLock); entity = _entityMap.value(entityID); } if (!entity) { return false; } return updateEntity(entity, properties, senderNode);} bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperties& origProperties, const SharedNodePointer& senderNode) { EntityTreeElementPointer containingElement = entity->getElement(); if (!containingElement) { return false; } EntityItemProperties properties = origProperties; bool allowLockChange; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get<NodeList>(); allowLockChange = nodeList->isAllowedEditor(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->isAllowedEditor(); senderID = senderNode->getUUID(); } if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; } // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { if (properties.lockedChanged()) { bool wantsLocked = properties.getLocked(); if (!wantsLocked) { EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); tempProperties.setLastEdited(properties.getLastEdited()); bool success; AACube queryCube = entity->getQueryAACube(success); if (!success) { qCWarning(entities) << "failed to get query-cube for" << entity->getID(); } UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, queryCube); recurseTreeWithOperator(&theOperator); if (entity->setProperties(tempProperties)) { emit editingEntityPointer(entity); } _isDirty = true; } } } else { if (isEntityServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); if (properties.simulationOwnerChanged()) { QUuid submittedID = properties.getSimulationOwner().getID(); // a legit interface will only submit their own ID or NULL: if (submittedID.isNull()) { if (entity->getSimulatorID() == senderID) { // We only allow the simulation owner to clear their own simulationID's. simulationBlocked = false; properties.clearSimulationOwner(); // clear everything } // else: We assume the sender really did believe it was the simulation owner when it sent } else if (submittedID == senderID) { // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull()) { // the sender is taking ownership if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { // the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash // when dynamic objects first activate and multiple participants bid simultaneously properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); } simulationBlocked = false; } else if (entity->getSimulatorID() == senderID) { // the sender is asserting ownership, maybe changing priority simulationBlocked = false; // the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash // when dynamic objects first activate and multiple participants bid simultaneously if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); } } else { // the sender is trying to steal ownership from another simulator // so we apply the rules for ownership change: // (1) higher priority wins // (2) equal priority wins if ownership filter has expired // (3) VOLUNTEER priority is promoted to RECRUIT uint8_t oldPriority = entity->getSimulationPriority(); uint8_t newPriority = properties.getSimulationOwner().getPriority(); if (newPriority > oldPriority || (newPriority == oldPriority && properties.getSimulationOwner().hasExpired())) { simulationBlocked = false; if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); } } } if (!simulationBlocked) { entity->setSimulationOwnershipExpiry(usecTimestampNow() + MAX_INCOMING_SIMULATION_UPDATE_PERIOD); } } else { // the entire update is suspect --> ignore it return false; } } else if (simulationBlocked) { simulationBlocked = senderID != entity->getSimulatorID(); if (!simulationBlocked) { entity->setSimulationOwnershipExpiry(usecTimestampNow() + MAX_INCOMING_SIMULATION_UPDATE_PERIOD); } } if (simulationBlocked) { // squash ownership and physics-related changes. // TODO? replace these eight calls with just one? properties.setSimulationOwnerChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); properties.setVelocityChanged(false); properties.setAngularVelocityChanged(false); properties.setAccelerationChanged(false); properties.setParentIDChanged(false); properties.setParentJointIndexChanged(false); if (wantTerseEditLogging()) { qCDebug(entities) << (senderNode ? senderNode->getUUID() : "null") << "physical edits suppressed"; } } } // else client accepts what the server says QString entityScriptBefore = entity->getScript(); quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); uint32_t preFlags = entity->getDirtyFlags(); AACube newQueryAACube; if (properties.queryAACubeChanged()) { newQueryAACube = properties.getQueryAACube(); } else { newQueryAACube = entity->getQueryAACube(); } UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube); recurseTreeWithOperator(&theOperator); if (entity->setProperties(properties)) { emit editingEntityPointer(entity); } // if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse QQueue<SpatiallyNestablePointer> toProcess; foreach (SpatiallyNestablePointer child, entity->getChildren()) { if (child && child->getNestableType() == NestableType::Entity) { toProcess.enqueue(child); } } while (!toProcess.empty()) { EntityItemPointer childEntity = std::static_pointer_cast<EntityItem>(toProcess.dequeue()); if (!childEntity) { continue; } EntityTreeElementPointer childContainingElement = childEntity->getElement(); if (!childContainingElement) { continue; } bool success; AACube queryCube = childEntity->getQueryAACube(success); if (!success) { addToNeedsParentFixupList(childEntity); continue; } if (!childEntity->getParentID().isNull()) { addToNeedsParentFixupList(childEntity); } UpdateEntityOperator theChildOperator(getThisPointer(), childContainingElement, childEntity, queryCube); recurseTreeWithOperator(&theChildOperator); foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) { if (childChild && childChild->getNestableType() == NestableType::Entity) { toProcess.enqueue(childChild); } } }Lines 1–500 of 3092. Load more
// // EntityTree.cpp // libraries/entities/src // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "EntityTree.h" #include <QtCore/QDateTime> #include <QtCore/QQueue> #include <openssl/err.h> #include <openssl/pem.h> #include <openssl/x509.h> #include <NetworkingConstants.h> #include <MetaverseAPI.h> #include "AccountManager.h" #include <QJsonObject> #include <QJsonDocument> #include <QJsonArray> #include <Extents.h> #include <PerfStat.h> #include <Profile.h> #include <AddressManager.h> #include "EntitySimulation.h" #include "VariantMapToScriptValue.h" #include "AddEntityOperator.h" #include "UpdateEntityOperator.h" #include "QVariantGLM.h" #include "EntitiesLogging.h" #include "RecurseOctreeToMapOperator.h" #include "RecurseOctreeToJSONOperator.h" #include "LogHandler.h" #include "EntityEditFilters.h" #include "EntityDynamicFactoryInterface.h" static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour static const QString DOMAIN_UNLIMITED = "domainUnlimited"; EntityTree::EntityTree(bool shouldReaverage) : Octree(shouldReaverage) { resetClientEditStats(); } EntityTree::~EntityTree() { // NOTE: to eraseAllOctreeElements() in this context is useless because // any OctreeElements in the tree still have shared backpointers to this Tree // which means the dtor never would have been called in the first place! // // I'm keeping this useless commented-out line to remind us: // we don't need shared pointer overhead for EntityTrees. // TODO: EntityTreeElement::_tree should be raw back pointer. // AND: EntityItem::_element should be a raw back pointer. //eraseAllOctreeElements(false); // KEEP THIS } void EntityTree::setEntityScriptSourceAllowlist(const QString& entityScriptSourceAllowlist) { _entityScriptSourceAllowlist = entityScriptSourceAllowlist.split(',', Qt::SkipEmptyParts); } void EntityTree::createRootElement() { _rootElement = createNewElement(); } OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { auto newElement = EntityTreeElementPointer(new EntityTreeElement(octalCode)); newElement->setTree(std::static_pointer_cast<EntityTree>(shared_from_this())); return std::static_pointer_cast<OctreeElement>(newElement); } void EntityTree::eraseDomainAndNonOwnedEntities() { emit clearingEntities(); if (_simulation) { // local-entities are not in the simulation, so we clear ALL _simulation->clearEntities(); } this->withWriteLock([&] { QHash<EntityItemID, EntityItemPointer> savedEntities; // NOTE: lock the Tree first, then lock the _entityMap. // It should never be done the other way around. QReadLocker locker(&_entityMapLock); foreach(EntityItemPointer entity, _entityMap) { EntityTreeElementPointer element = entity->getElement(); if (element) { element->cleanupDomainAndNonOwnedEntities(); } if (!getIsServer()) { if (entity->isLocalEntity() || entity->isMyAvatarEntity()) { savedEntities[entity->getEntityItemID()] = entity; } else { int32_t spaceIndex = entity->getSpaceIndex(); if (spaceIndex != -1) { // stale spaceIndices will be freed later _staleProxies.push_back(spaceIndex); } } } } _entityMap.swap(savedEntities); }); resetClientEditStats(); clearDeletedEntities(); { QWriteLocker locker(&_needsParentFixupLock); QVector<EntityItemWeakPointer> needParentFixup; foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { auto entity = entityItem.lock(); if (entity && (entity->isLocalEntity() || entity->isMyAvatarEntity())) { needParentFixup.push_back(entityItem); } } _needsParentFixup = needParentFixup; } } void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); if (_simulation) { _simulation->clearEntities(); } QHash<EntityItemID, EntityItemPointer> localMap; localMap.swap(_entityMap); this->withWriteLock([&] { foreach(EntityItemPointer entity, localMap) { EntityTreeElementPointer element = entity->getElement(); if (element) { element->cleanupEntities(); } if (!getIsServer()) { int32_t spaceIndex = entity->getSpaceIndex(); if (spaceIndex != -1) { // assume stale spaceIndices will be freed later _staleProxies.push_back(spaceIndex); } } } }); localMap.clear(); Octree::eraseAllOctreeElements(createNewRoot); resetClientEditStats(); clearDeletedEntities(); { QWriteLocker locker(&_needsParentFixupLock); _needsParentFixup.clear(); } } void EntityTree::readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args) { Octree::readBitstreamToTree(bitstream, bufferSizeBytes, args); // add entities QHash<EntityItemID, EntityItemPointer>::const_iterator itr; for (itr = _entitiesToAdd.constBegin(); itr != _entitiesToAdd.constEnd(); ++itr) { const EntityItemPointer& entityItem = itr.value(); AddEntityOperator theOperator(getThisPointer(), entityItem); recurseTreeWithOperator(&theOperator); postAddEntity(entityItem); } _entitiesToAdd.clear(); // move entities if (_entityMover.hasMovingEntities()) { PerformanceTimer perfTimer("recurseTreeWithOperator"); recurseTreeWithOperator(&_entityMover); _entityMover.reset(); } } int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { const unsigned char* dataAt = data; int bytesRead = 0; uint16_t numberOfEntities = 0; int expectedBytesPerEntity = EntityItem::expectedBytes(); args.elementsPerPacket++; if (bytesLeftToRead >= (int)sizeof(numberOfEntities)) { // read our entities in.... numberOfEntities = *(uint16_t*)dataAt; dataAt += sizeof(numberOfEntities); bytesLeftToRead -= (int)sizeof(numberOfEntities); bytesRead += sizeof(numberOfEntities); if (bytesLeftToRead >= (int)(numberOfEntities * expectedBytesPerEntity)) { for (uint16_t i = 0; i < numberOfEntities; i++) { int bytesForThisEntity = 0; EntityItemID entityItemID = EntityItemID::readEntityItemIDFromBuffer(dataAt, bytesLeftToRead); EntityItemPointer entity = findEntityByEntityItemID(entityItemID); if (entity) { QString entityScriptBefore = entity->getScript(); QUuid parentIDBefore = entity->getParentID(); QString entityServerScriptsBefore = entity->getServerScripts(); quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); if (entity->getDirtyFlags()) { entityChanged(entity); } _entityMover.addEntityToMoveList(entity, entity->getQueryAACube()); QString entityScriptAfter = entity->getScript(); QString entityServerScriptsAfter = entity->getServerScripts(); quint64 entityScriptTimestampAfter = entity->getScriptTimestamp(); bool reload = entityScriptTimestampBefore != entityScriptTimestampAfter; // If the script value has changed on us, or it's timestamp has changed to force // a reload then we want to send out a script changing signal... if (reload || entityScriptBefore != entityScriptAfter) { emitEntityScriptChanging(entityItemID, entityScriptBefore, entityScriptAfter); } if (reload || entityServerScriptsBefore != entityServerScriptsAfter) { emitEntityServerScriptChanging(entityItemID, entityServerScriptsBefore, entityServerScriptsAfter); } QUuid parentIDAfter = entity->getParentID(); if (parentIDBefore != parentIDAfter) { addToNeedsParentFixupList(entity); } } else { entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead); if (entity) { bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); // don't add if we've recently deleted.... if (!isDeletedEntity(entityItemID)) { _entitiesToAdd.insert(entityItemID, entity); if (entity->getCreated() == UNKNOWN_CREATED_TIME) { entity->recordCreationTime(); } #ifdef WANT_DEBUG } else { qCDebug(entities) << "Received packet for previously deleted entity [" << entityItemID << "] ignoring. (inside " << __FUNCTION__ << ")"; #endif } } } // Move the buffer forward to read more entities dataAt += bytesForThisEntity; bytesLeftToRead -= bytesForThisEntity; bytesRead += bytesForThisEntity; } } } return bytesRead; } bool EntityTree::handlesEditPacketType(PacketType packetType) const { // we handle these types of "edit" packets switch (packetType) { case PacketType::EntityAdd: case PacketType::EntityClone: case PacketType::EntityEdit: case PacketType::EntityErase: case PacketType::EntityPhysics: return true; default: return false; } } /// Adds a new entity item to the tree void EntityTree::postAddEntity(EntityItemPointer entity) { assert(entity); // check to see if we need to simulate this entity.. if (_simulation) { _simulation->addEntity(entity); } if (!entity->getParentID().isNull()) { addToNeedsParentFixupList(entity); } _isDirty = true; // find and hook up any entities with this entity as a (previously) missing parent fixupNeedsParentFixups(); emit addingEntity(entity->getEntityItemID()); emit addingEntityPointer(entity.get()); } bool EntityTree::updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode) { EntityItemPointer entity; { QReadLocker locker(&_entityMapLock); entity = _entityMap.value(entityID); } if (!entity) { return false; } return updateEntity(entity, properties, senderNode); } bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperties& origProperties, const SharedNodePointer& senderNode) { EntityTreeElementPointer containingElement = entity->getElement(); if (!containingElement) { return false; } EntityItemProperties properties = origProperties; bool allowLockChange; QUuid senderID; if (senderNode.isNull()) { auto nodeList = DependencyManager::get<NodeList>(); allowLockChange = nodeList->isAllowedEditor(); senderID = nodeList->getSessionUUID(); } else { allowLockChange = senderNode->isAllowedEditor(); senderID = senderNode->getUUID(); } if (!allowLockChange && (entity->getLocked() != properties.getLocked())) { qCDebug(entities) << "Refusing disallowed lock adjustment."; return false; } // enforce support for locked entities. If an entity is currently locked, then the only // property we allow you to change is the locked property. if (entity->getLocked()) { if (properties.lockedChanged()) { bool wantsLocked = properties.getLocked(); if (!wantsLocked) { EntityItemProperties tempProperties; tempProperties.setLocked(wantsLocked); tempProperties.setLastEdited(properties.getLastEdited()); bool success; AACube queryCube = entity->getQueryAACube(success); if (!success) { qCWarning(entities) << "failed to get query-cube for" << entity->getID(); } UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, queryCube); recurseTreeWithOperator(&theOperator); if (entity->setProperties(tempProperties)) { emit editingEntityPointer(entity); } _isDirty = true; } } } else { if (isEntityServer()) { bool simulationBlocked = !entity->getSimulatorID().isNull(); if (properties.simulationOwnerChanged()) { QUuid submittedID = properties.getSimulationOwner().getID(); // a legit interface will only submit their own ID or NULL: if (submittedID.isNull()) { if (entity->getSimulatorID() == senderID) { // We only allow the simulation owner to clear their own simulationID's. simulationBlocked = false; properties.clearSimulationOwner(); // clear everything } // else: We assume the sender really did believe it was the simulation owner when it sent } else if (submittedID == senderID) { // the sender is trying to take or continue ownership if (entity->getSimulatorID().isNull()) { // the sender is taking ownership if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { // the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash // when dynamic objects first activate and multiple participants bid simultaneously properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); } simulationBlocked = false; } else if (entity->getSimulatorID() == senderID) { // the sender is asserting ownership, maybe changing priority simulationBlocked = false; // the entity-server always promotes VOLUNTEER to RECRUIT to avoid ownership thrash // when dynamic objects first activate and multiple participants bid simultaneously if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); } } else { // the sender is trying to steal ownership from another simulator // so we apply the rules for ownership change: // (1) higher priority wins // (2) equal priority wins if ownership filter has expired // (3) VOLUNTEER priority is promoted to RECRUIT uint8_t oldPriority = entity->getSimulationPriority(); uint8_t newPriority = properties.getSimulationOwner().getPriority(); if (newPriority > oldPriority || (newPriority == oldPriority && properties.getSimulationOwner().hasExpired())) { simulationBlocked = false; if (properties.getSimulationOwner().getPriority() == VOLUNTEER_SIMULATION_PRIORITY) { properties.setSimulationPriority(RECRUIT_SIMULATION_PRIORITY); } } } if (!simulationBlocked) { entity->setSimulationOwnershipExpiry(usecTimestampNow() + MAX_INCOMING_SIMULATION_UPDATE_PERIOD); } } else { // the entire update is suspect --> ignore it return false; } } else if (simulationBlocked) { simulationBlocked = senderID != entity->getSimulatorID(); if (!simulationBlocked) { entity->setSimulationOwnershipExpiry(usecTimestampNow() + MAX_INCOMING_SIMULATION_UPDATE_PERIOD); } } if (simulationBlocked) { // squash ownership and physics-related changes. // TODO? replace these eight calls with just one? properties.setSimulationOwnerChanged(false); properties.setPositionChanged(false); properties.setRotationChanged(false); properties.setVelocityChanged(false); properties.setAngularVelocityChanged(false); properties.setAccelerationChanged(false); properties.setParentIDChanged(false); properties.setParentJointIndexChanged(false); if (wantTerseEditLogging()) { qCDebug(entities) << (senderNode ? senderNode->getUUID() : "null") << "physical edits suppressed"; } } } // else client accepts what the server says QString entityScriptBefore = entity->getScript(); quint64 entityScriptTimestampBefore = entity->getScriptTimestamp(); uint32_t preFlags = entity->getDirtyFlags(); AACube newQueryAACube; if (properties.queryAACubeChanged()) { newQueryAACube = properties.getQueryAACube(); } else { newQueryAACube = entity->getQueryAACube(); } UpdateEntityOperator theOperator(getThisPointer(), containingElement, entity, newQueryAACube); recurseTreeWithOperator(&theOperator); if (entity->setProperties(properties)) { emit editingEntityPointer(entity); } // if the entity has children, run UpdateEntityOperator on them. If the children have children, recurse QQueue<SpatiallyNestablePointer> toProcess; foreach (SpatiallyNestablePointer child, entity->getChildren()) { if (child && child->getNestableType() == NestableType::Entity) { toProcess.enqueue(child); } } while (!toProcess.empty()) { EntityItemPointer childEntity = std::static_pointer_cast<EntityItem>(toProcess.dequeue()); if (!childEntity) { continue; } EntityTreeElementPointer childContainingElement = childEntity->getElement(); if (!childContainingElement) { continue; } bool success; AACube queryCube = childEntity->getQueryAACube(success); if (!success) { addToNeedsParentFixupList(childEntity); continue; } if (!childEntity->getParentID().isNull()) { addToNeedsParentFixupList(childEntity); } UpdateEntityOperator theChildOperator(getThisPointer(), childContainingElement, childEntity, queryCube); recurseTreeWithOperator(&theChildOperator); foreach (SpatiallyNestablePointer childChild, childEntity->getChildren()) { if (childChild && childChild->getNestableType() == NestableType::Entity) { toProcess.enqueue(childChild); } } }