474 lines · 24.4 KB · C++
RawDownload
////  EntityTreeSendThread.cpp//  assignment-client/src/entities////  Created by Stephen Birarda on 2/15/17.//  Copyright 2017 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "EntityTreeSendThread.h" #include <EntityNodeData.h>#include <EntityTypes.h>#include <OctreeUtils.h> #include "EntityServer.h" #define SEND_SORTED_ENTITIES EntityTreeSendThread::EntityTreeSendThread(OctreeServer* myServer, const SharedNodePointer& node) :    OctreeSendThread(myServer, node){    connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::editingEntityPointer, this, &EntityTreeSendThread::editingEntityPointer, Qt::QueuedConnection);    connect(std::static_pointer_cast<EntityTree>(myServer->getOctree()).get(), &EntityTree::deletingEntityPointer, this, &EntityTreeSendThread::deletingEntityPointer, Qt::QueuedConnection);} void EntityTreeSendThread::preDistributionProcessing() {    auto node = _node.toStrongRef();    auto nodeData = static_cast<EntityNodeData*>(node->getLinkedData());     if (nodeData) {        auto jsonQuery = nodeData->getJSONParameters();         // check if we have a JSON query with flags        auto flags = jsonQuery[EntityJSONQueryProperties::FLAGS_PROPERTY].toObject();        if (!flags.isEmpty()) {            // check the flags object for specific flags that require special pre-processing             bool includeAncestors = flags[EntityJSONQueryProperties::INCLUDE_ANCESTORS_PROPERTY].toBool();            bool includeDescendants = flags[EntityJSONQueryProperties::INCLUDE_DESCENDANTS_PROPERTY].toBool();             if (includeAncestors || includeDescendants) {                // we need to either include the ancestors, descendants, or both for entities matching the filter                // included in the JSON query                 // first reset our flagged extra entities so we start with an empty set                nodeData->resetFlaggedExtraEntities();                 auto entityTree = std::static_pointer_cast<EntityTree>(_myServer->getOctree());                 bool requiresFullScene = false;                 // enumerate the set of entity IDs we know currently match the filter                foreach(const QUuid& entityID, nodeData->getSentFilteredEntities()) {                    if (includeAncestors) {                        // we need to include ancestors - recurse up to reach them all and add their IDs                        // to the set of extra entities to include for this node                        entityTree->withReadLock([&]{                            auto filteredEntity = entityTree->findEntityByID(entityID);                            if (filteredEntity) {                                requiresFullScene |= addAncestorsToExtraFlaggedEntities(entityID, *filteredEntity, *nodeData);                            }                        });                    }                     if (includeDescendants) {                        // we need to include descendants - recurse down to reach them all and add their IDs                        // to the set of extra entities to include for this node                        entityTree->withReadLock([&]{                            auto filteredEntity = entityTree->findEntityByID(entityID);                            if (filteredEntity) {                                requiresFullScene |= addDescendantsToExtraFlaggedEntities(entityID, *filteredEntity, *nodeData);                            }                        });                    }                }                 if (requiresFullScene) {                    // for one or more of the entities matching our filter we found new extra entities to include                     // because it is possible that one of these entities hasn't changed since our last send                    // and therefore would not be recursed to, we need to force a full traversal for this pass                    // of the tree to allow it to grab all of the extra entities we're asking it to include                    nodeData->setShouldForceFullScene(requiresFullScene);                }            }        }    }} void EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, OctreeQueryNode* nodeData,            bool viewFrustumChanged, bool isFullScene) {    if (nodeData->getUsesFrustum()) {        if (viewFrustumChanged || _traversal.finished()) {            ViewFrustum viewFrustum;            nodeData->copyCurrentViewFrustum(viewFrustum);            EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());            int32_t lodLevelOffset = nodeData->getBoundaryLevelAdjust() + (viewFrustumChanged ? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);            startNewTraversal(viewFrustum, root, lodLevelOffset);             // When the viewFrustum changed the sort order may be incorrect, so we re-sort            // and also use the opportunity to cull anything no longer in view            if (viewFrustumChanged && !_sendQueue.empty()) {                EntityPriorityQueue prevSendQueue;                _sendQueue.swap(prevSendQueue);                _entitiesInQueue.clear();                // Re-add elements from previous traversal if they still need to be sent                while (!prevSendQueue.empty()) {                    EntityItemPointer entity = prevSendQueue.top().getEntity();                    bool forceSend = prevSendQueue.top().shouldForceSend();                    prevSendQueue.pop();                    if (entity) {                        bool success = false;                        AACube cube = entity->getQueryAACube(success);                        if (success) {                            if (forceSend || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {                                float priority = _conicalView.computePriority(cube);                                if (forceSend || priority != PrioritizedEntity::DO_NOT_SEND) {                                    float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(),                                                                                   cube,                                                                                   _traversal.getCurrentRootSizeScale(),                                                                                   lodLevelOffset);                                     // Only send entities if they are large enough to see, or we need to update them to be out of view                                    if (forceSend || renderAccuracy > 0.0f) {                                        _sendQueue.push(PrioritizedEntity(entity, priority));                                        _entitiesInQueue.insert(entity.get());                                    }                                }                            }                        } else {                            _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));                            _entitiesInQueue.insert(entity.get());                        }                    }                }            }        }    }     if (!_traversal.finished()) {        uint64_t startTime = usecTimestampNow(); #ifdef DEBUG        const uint64_t TIME_BUDGET = 400; // usec#else        const uint64_t TIME_BUDGET = 200; // usec#endif        _traversal.traverse(TIME_BUDGET);         if (_sendQueue.size() > 0) {            uint64_t dt = usecTimestampNow() - startTime;            std::cout << "adebug  traversal complete " << "  Q.size = " << _sendQueue.size() << "  dt = " << dt << std::endl;  // adebug        }    } #ifndef SEND_SORTED_ENTITIES    if (!_sendQueue.empty()) {        uint64_t sendTime = usecTimestampNow();        // print what needs to be sent        while (!_sendQueue.empty()) {            PrioritizedEntity entry = _sendQueue.top();            EntityItemPointer entity = entry.getEntity();            if (entity) {                std::cout << "adebug    send '" << entity->getName().toStdString() << "'" << "  :  " << entry.getPriority() << std::endl;  // adebug                _knownState[entity] = sendTime;            }            _sendQueue.pop();            _entitiesInQueue.erase(entry.getRawEntityPointer());        }    }#endif // SEND_SORTED_ENTITIES     OctreeSendThread::traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene);} bool EntityTreeSendThread::addAncestorsToExtraFlaggedEntities(const QUuid& filteredEntityID,                                                              EntityItem& entityItem, EntityNodeData& nodeData) {    // check if this entity has a parent that is also an entity    bool success = false;    auto entityParent = entityItem.getParentPointer(success);     if (success && entityParent && entityParent->getNestableType() == NestableType::Entity) {        // we found a parent that is an entity item         // first add it to the extra list of things we need to send        bool parentWasNew = nodeData.insertFlaggedExtraEntity(filteredEntityID, entityParent->getID());         // now recursively call ourselves to get its ancestors added too        auto parentEntityItem = std::static_pointer_cast<EntityItem>(entityParent);        bool ancestorsWereNew = addAncestorsToExtraFlaggedEntities(filteredEntityID, *parentEntityItem, nodeData);         // return boolean if our parent or any of our ancestors were new additions (via insertFlaggedExtraEntity)        return parentWasNew || ancestorsWereNew;    }     // since we didn't have a parent niether of our parents or ancestors could be new additions    return false;} bool EntityTreeSendThread::addDescendantsToExtraFlaggedEntities(const QUuid& filteredEntityID,                                                                EntityItem& entityItem, EntityNodeData& nodeData) {    bool hasNewChild = false;    bool hasNewDescendants = false;     // enumerate the immediate children of this entity    foreach (SpatiallyNestablePointer child, entityItem.getChildren()) {         if (child && child->getNestableType() == NestableType::Entity) {            // this is a child that is an entity             // first add it to the extra list of things we need to send            hasNewChild |= nodeData.insertFlaggedExtraEntity(filteredEntityID, child->getID());             // now recursively call ourselves to get its descendants added too            auto childEntityItem = std::static_pointer_cast<EntityItem>(child);            hasNewDescendants |= addDescendantsToExtraFlaggedEntities(filteredEntityID, *childEntityItem, nodeData);        }    }     // return our boolean indicating if we added new children or descendants as extra entities to send    // (via insertFlaggedExtraEntity)    return hasNewChild || hasNewDescendants;} void EntityTreeSendThread::startNewTraversal(const ViewFrustum& view, EntityTreeElementPointer root, int32_t lodLevelOffset) {    DiffTraversal::Type type = _traversal.prepareNewTraversal(view, root, lodLevelOffset);    // there are three types of traversal:    //    //      (1) FirstTime = at login --> find everything in view    //      (2) Repeat = view hasn't changed --> find what has changed since last complete traversal    //      (3) Differential = view has changed --> find what has changed or in new view but not old    //    // The "scanCallback" we provide to the traversal depends on the type:    //    // The _conicalView is updated here as a cached view approximation used by the lambdas for efficient    // computation of entity sorting priorities.    //    _conicalView.set(_traversal.getCurrentView());     switch (type) {    case DiffTraversal::First:        _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) {            // When we get to a First traversal, clear the _knownState            _knownState.clear();            next.element->forEachEntity([&](EntityItemPointer entity) {                // Bail early if we've already checked this entity this frame                if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {                    return;                }                bool success = false;                AACube cube = entity->getQueryAACube(success);                if (success) {                    if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {                        // Check the size of the entity, it's possible that a "too small to see" entity is included in a                        // larger octree cell because of its position (for example if it crosses the boundary of a cell it                        // pops to the next higher cell. So we want to check to see that the entity is large enough to be seen                        // before we consider including it.                        //                        // TODO: compare priority against a threshold rather than bother with                        // calculateRenderAccuracy().  Would need to replace all calculateRenderAccuracy()                        // stuff everywhere with threshold in one sweep.                        float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(),                                                                       cube,                                                                       _traversal.getCurrentRootSizeScale(),                                                                       _traversal.getCurrentLODOffset());                         // Only send entities if they are large enough to see                        if (renderAccuracy > 0.0f) {                            float priority = _conicalView.computePriority(cube);                            _sendQueue.push(PrioritizedEntity(entity, priority));                            _entitiesInQueue.insert(entity.get());                        }                    }                } else {                    _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));                    _entitiesInQueue.insert(entity.get());                }            });        });        break;    case DiffTraversal::Repeat:        _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) {            uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();            if (next.element->getLastChangedContent() > startOfCompletedTraversal) {                next.element->forEachEntity([&](EntityItemPointer entity) {                    // Bail early if we've already checked this entity this frame                    if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {                        return;                    }                    if (_knownState.find(entity.get()) == _knownState.end() ||                            (_knownState.find(entity.get()) != _knownState.end() && entity->getLastEdited() > _knownState[entity.get()])) {                        bool success = false;                        AACube cube = entity->getQueryAACube(success);                        if (success) {                            if (next.intersection == ViewFrustum::INSIDE || _traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {                                // See the DiffTraversal::First case for an explanation of the "entity is too small" check                                float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(),                                                                               cube,                                                                               _traversal.getCurrentRootSizeScale(),                                                                               _traversal.getCurrentLODOffset());                                 // Only send entities if they are large enough to see                                if (renderAccuracy > 0.0f) {                                    float priority = _conicalView.computePriority(cube);                                    _sendQueue.push(PrioritizedEntity(entity, priority));                                    _entitiesInQueue.insert(entity.get());                                }                            }                        } else {                            _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));                            _entitiesInQueue.insert(entity.get());                        }                    }                });            }        });        break;    case DiffTraversal::Differential:        _traversal.setScanCallback([&] (DiffTraversal::VisibleElement& next) {            // NOTE: for Differential case: next.intersection is against completedView not currentView            uint64_t startOfCompletedTraversal = _traversal.getStartOfCompletedTraversal();            if (next.element->getLastChangedContent() > startOfCompletedTraversal ||                    next.intersection != ViewFrustum::INSIDE) {                next.element->forEachEntity([&](EntityItemPointer entity) {                    // Bail early if we've already checked this entity this frame                    if (_entitiesInQueue.find(entity.get()) != _entitiesInQueue.end()) {                        return;                    }                    if (_knownState.find(entity.get()) == _knownState.end() ||                            (_knownState.find(entity.get()) != _knownState.end() && entity->getLastEdited() > _knownState[entity.get()])) {                        bool success = false;                        AACube cube = entity->getQueryAACube(success);                        if (success) {                            if (_traversal.getCurrentView().cubeIntersectsKeyhole(cube)) {                                // See the DiffTraversal::First case for an explanation of the "entity is too small" check                                float renderAccuracy = calculateRenderAccuracy(_traversal.getCurrentView().getPosition(),                                                                               cube,                                                                               _traversal.getCurrentRootSizeScale(),                                                                               _traversal.getCurrentLODOffset());                                 // Only send entities if they are large enough to see                                if (renderAccuracy > 0.0f) {                                    if (!_traversal.getCompletedView().cubeIntersectsKeyhole(cube)) {                                        float priority = _conicalView.computePriority(cube);                                        _sendQueue.push(PrioritizedEntity(entity, priority));                                        _entitiesInQueue.insert(entity.get());                                    } else {                                        // If this entity was skipped last time because it was too small, we still need to send it                                        float lastRenderAccuracy = calculateRenderAccuracy(_traversal.getCompletedView().getPosition(),                                                                                           cube,                                                                                           _traversal.getCompletedRootSizeScale(),                                                                                           _traversal.getCompletedLODOffset());                                         if (lastRenderAccuracy <= 0.0f) {                                            float priority = _conicalView.computePriority(cube);                                            _sendQueue.push(PrioritizedEntity(entity, priority));                                            _entitiesInQueue.insert(entity.get());                                        }                                    }                                }                            }                        } else {                            _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY));                            _entitiesInQueue.insert(entity.get());                        }                    }                });            }        });        break;    }} bool EntityTreeSendThread::traverseTreeAndBuildNextPacketPayload(EncodeBitstreamParams& params) {#ifdef SEND_SORTED_ENTITIES    //auto entityTree = std::static_pointer_cast<EntityTree>(_myServer->getOctree());    if (_sendQueue.empty()) {        return false;    }    if (!_packetData.hasContent()) {        // This is the beginning of a new packet.        // We pack minimal data for this to be accepted as an OctreeElement payload for the root element.        // The Octree header bytes look like this:        //        // 0x00  octalcode for root        // 0x00  colors (1 bit where recipient should call: child->readElementDataFromBuffer())        // 0xXX  childrenInTreeMask (when params.includeExistsBits is true: 1 bit where child is existant)        // 0x00  childrenInBufferMask (1 bit where recipient should call: child->readElementData() recursively)        const uint8_t zeroByte = 0;        _packetData.appendValue(zeroByte); // octalcode        _packetData.appendValue(zeroByte); // colors        if (params.includeExistsBits) {            uint8_t childrenExistBits = 0;            EntityTreeElementPointer root = std::dynamic_pointer_cast<EntityTreeElement>(_myServer->getOctree()->getRoot());            for (int32_t i = 0; i < NUMBER_OF_CHILDREN; ++i) {                if (root->getChildAtIndex(i)) {                    childrenExistBits += (1 << i);                }            }            _packetData.appendValue(childrenExistBits); // childrenInTreeMask        }        _packetData.appendValue(zeroByte); // childrenInBufferMask         // Pack zero for numEntities.        // But before we do: grab current byteOffset so we can come back later        // and update this with the real number.        _numEntities = 0;        _numEntitiesOffset = _packetData.getUncompressedByteOffset();        _packetData.appendValue(_numEntities);    }     LevelDetails entitiesLevel = _packetData.startLevel();    uint64_t sendTime = usecTimestampNow();    while(!_sendQueue.empty()) {        PrioritizedEntity queuedItem = _sendQueue.top();        EntityItemPointer entity = queuedItem.getEntity();        if (entity) {            OctreeElement::AppendState appendEntityState = entity->appendEntityData(&_packetData, params, _extraEncodeData);            _knownState[entity.get()] = sendTime;             if (appendEntityState != OctreeElement::COMPLETED) {                if (appendEntityState == OctreeElement::PARTIAL) {                    ++_numEntities;                }                params.stopReason = EncodeBitstreamParams::DIDNT_FIT;                break;            }            ++_numEntities;        }        _sendQueue.pop();        _entitiesInQueue.erase(entity.get());    }    if (_sendQueue.empty()) {        assert(_entitiesInQueue.empty());        params.stopReason = EncodeBitstreamParams::FINISHED;        _extraEncodeData->entities.clear();    }     if (_numEntities == 0) {        _packetData.discardLevel(entitiesLevel);        return false;    }    _packetData.endLevel(entitiesLevel);    _packetData.updatePriorBytes(_numEntitiesOffset, (const unsigned char*)&_numEntities, sizeof(_numEntities));    return true; #else // SEND_SORTED_ENTITIES    return OctreeSendThread::traverseTreeAndBuildNextPacketPayload(params);#endif // SEND_SORTED_ENTITIES} void EntityTreeSendThread::editingEntityPointer(const EntityItemPointer entity) {    if (entity) {        if (_entitiesInQueue.find(entity.get()) == _entitiesInQueue.end() && _knownState.find(entity.get()) != _knownState.end()) {            bool success = false;            AACube cube = entity->getQueryAACube(success);            if (success) {                float priority = _conicalView.computePriority(cube);                _sendQueue.push(PrioritizedEntity(entity, priority, true));                _entitiesInQueue.insert(entity.get());            } else {                _sendQueue.push(PrioritizedEntity(entity, PrioritizedEntity::WHEN_IN_DOUBT_PRIORITY, true));                _entitiesInQueue.insert(entity.get());            }        }    }} void EntityTreeSendThread::deletingEntityPointer(EntityItem* entity) {    _knownState.erase(entity);}