3092 lines · 129.7 KB · C++
RawDownload
////  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.