int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { int totalBytes = 0; EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData()); if (nodeData) { quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); quint64 deletePacketSentAt = usecTimestampNow(); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); bool hasMoreToSend = true; packetsSent = 0; while (hasMoreToSend) { auto specialPacket = tree->encodeEntitiesDeletedSince(queryNode->getSequenceNumber(), deletedEntitiesSentAt, hasMoreToSend); queryNode->packetSent(*specialPacket); totalBytes += specialPacket->getDataSize(); packetsSent++; DependencyManager::get<NodeList>()->sendPacket(std::move(specialPacket), *node); } nodeData->setLastDeletedEntitiesSentAt(deletePacketSentAt); } // TODO: caller is expecting a packetLength, what if we send more than one packet?? return totalBytes; }
void OctreeTests::elementAddChildTests() { EntityTreePointer tree = std::make_shared<EntityTree>(); auto elem = tree->createNewElement(); QCOMPARE((bool)elem->getChildAtIndex(0), false); elem->addChildAtIndex(0); QCOMPARE((bool)elem->getChildAtIndex(0), true); const int MAX_CHILD_INDEX = 8; for (int i = 0; i < MAX_CHILD_INDEX; i++) { for (int j = 0; j < MAX_CHILD_INDEX; j++) { auto e = tree->createNewElement(); // add a single child. auto firstChild = e->addChildAtIndex(i); QCOMPARE(e->getChildAtIndex(i), firstChild); if (i != j) { // add a second child. auto secondChild = e->addChildAtIndex(j); QCOMPARE(e->getChildAtIndex(i), firstChild); QCOMPARE(e->getChildAtIndex(j), secondChild); // remove scecond child. e->removeChildAtIndex(j); QCOMPARE((bool)e->getChildAtIndex(j), false); } QCOMPARE(e->getChildAtIndex(i), firstChild); } } }
void AvatarManager::removeDeadAvatarEntities(const SetOfEntities& deadEntities) { auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; for (auto entity : deadEntities) { QUuid entityOwnerID = entity->getOwningAvatarID(); AvatarSharedPointer avatar = getAvatarBySessionID(entityOwnerID); const bool REQUIRES_REMOVAL_FROM_TREE = false; if (avatar) { avatar->clearAvatarEntity(entity->getID(), REQUIRES_REMOVAL_FROM_TREE); } if (entityTree && entity->isMyAvatarEntity()) { entityTree->withWriteLock([&] { // We only need to delete the direct children (rather than the descendants) because // when the child is deleted, it will take care of its own children. If the child // is also an avatar-entity, we'll end up back here. If it's not, the entity-server // will take care of it in the usual way. entity->forEachChild([&](SpatiallyNestablePointer child) { EntityItemPointer childEntity = std::dynamic_pointer_cast<EntityItem>(child); if (childEntity) { entityTree->deleteEntity(childEntity->getID(), true, true); if (avatar) { avatar->clearAvatarEntity(childEntity->getID(), REQUIRES_REMOVAL_FROM_TREE); } } }); }); } } }
void EntityTreeHeadlessViewer::update() { if (_tree) { EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); tree->withTryWriteLock([&] { tree->update(); }); } }
EntityServer::~EntityServer() { if (_pruneDeletedEntitiesTimer) { _pruneDeletedEntitiesTimer->stop(); _pruneDeletedEntitiesTimer->deleteLater(); } EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); tree->removeNewlyCreatedHook(this); }
void EntityTreeHeadlessViewer::init() { OctreeHeadlessViewer::init(); if (!_simulation) { SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() }; EntityTreePointer entityTree = std::static_pointer_cast<EntityTree>(_tree); simpleSimulation->setEntityTree(entityTree); entityTree->setSimulation(simpleSimulation); _simulation = simpleSimulation; } }
bool EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { bool wantEditLogging = false; readOptionBool(QString("wantEditLogging"), settingsSectionObject, wantEditLogging); qDebug("wantEditLogging=%s", debug::valueOf(wantEditLogging)); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); tree->setWantEditLogging(wantEditLogging); return true; }
void addAvatarEntities(const QVariantList& avatarEntities) { auto nodeList = DependencyManager::get<NodeList>(); const QUuid myNodeID = nodeList->getSessionUUID(); EntityTreePointer entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree(); if (!entityTree) { return; } EntitySimulationPointer entitySimulation = entityTree->getSimulation(); PhysicalEntitySimulationPointer physicalEntitySimulation = std::static_pointer_cast<PhysicalEntitySimulation>(entitySimulation); EntityEditPacketSender* entityPacketSender = physicalEntitySimulation->getPacketSender(); QScriptEngine scriptEngine; for (int index = 0; index < avatarEntities.count(); index++) { const QVariantMap& avatarEntityProperties = avatarEntities.at(index).toMap(); QVariant variantProperties = avatarEntityProperties["properties"]; QVariantMap asMap = variantProperties.toMap(); QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine); EntityItemProperties entityProperties; EntityItemPropertiesFromScriptValueHonorReadOnly(scriptProperties, entityProperties); entityProperties.setParentID(myNodeID); entityProperties.setClientOnly(true); entityProperties.setOwningAvatarID(myNodeID); entityProperties.setSimulationOwner(myNodeID, AVATAR_ENTITY_SIMULATION_PRIORITY); entityProperties.markAllChanged(); EntityItemID id = EntityItemID(QUuid::createUuid()); bool success = true; entityTree->withWriteLock([&] { EntityItemPointer entity = entityTree->addEntity(id, entityProperties); if (entity) { if (entityProperties.queryAACubeRelatedPropertyChanged()) { // due to parenting, the server may not know where something is in world-space, so include the bounding cube. bool success; AACube queryAACube = entity->getQueryAACube(success); if (success) { entityProperties.setQueryAACube(queryAACube); } } entity->setLastBroadcast(usecTimestampNow()); // since we're creating this object we will immediately volunteer to own its simulation entity->flagForOwnershipBid(VOLUNTEER_SIMULATION_PRIORITY); entityProperties.setLastEdited(entity->getLastEdited()); } else { qCDebug(entities) << "AvatarEntitiesBookmark failed to add new Entity to local Octree"; success = false; } }); if (success) { entityPacketSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, entityProperties); } } }
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) { auto currentCompoundShapeURL = getCompoundShapeURL(); ModelEntityItem::setCompoundShapeURL(url); if (getCompoundShapeURL() != currentCompoundShapeURL || !_model) { EntityTreePointer tree = getTree(); if (tree) { QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); } } }
void RenderableModelEntityItem::setModelURL(const QString& url) { auto& currentURL = getParsedModelURL(); ModelEntityItem::setModelURL(url); if (currentURL != getParsedModelURL() || !_model) { EntityTreePointer tree = getTree(); if (tree) { QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); } } }
OctreePointer EntityServer::createTree() { EntityTreePointer tree = EntityTreePointer(new EntityTree(true)); tree->createRootElement(); tree->addNewlyCreatedHook(this); if (!_entitySimulation) { SimpleEntitySimulation* simpleSimulation = new SimpleEntitySimulation(); simpleSimulation->setEntityTree(tree); tree->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; } return tree; }
EntityItemPointer ObjectDynamic::getEntityByID(EntityItemID entityID) const { EntityItemPointer ownerEntity; withReadLock([&]{ ownerEntity = _ownerEntity.lock(); }); EntityTreeElementPointer element = ownerEntity ? ownerEntity->getElement() : nullptr; EntityTreePointer tree = element ? element->getTree() : nullptr; if (!tree) { return nullptr; } return tree->findEntityByID(entityID); }
// EntityServer will use the "special packets" to send list of recently deleted entities bool EntityServer::hasSpecialPacketsToSend(const SharedNodePointer& node) { bool shouldSendDeletedEntities = false; // check to see if any new entities have been added since we last sent to this node... EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData()); if (nodeData) { quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); shouldSendDeletedEntities = tree->hasEntitiesDeletedSince(deletedEntitiesSentAt); } return shouldSendDeletedEntities; }
OctreePointer EntityServer::createTree() { EntityTreePointer tree = EntityTreePointer(new EntityTree(true)); tree->createRootElement(); tree->addNewlyCreatedHook(this); if (!_entitySimulation) { SimpleEntitySimulationPointer simpleSimulation { new SimpleEntitySimulation() }; simpleSimulation->setEntityTree(tree); tree->setSimulation(simpleSimulation); _entitySimulation = simpleSimulation; } DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>(); DependencyManager::set<AssignmentParentFinder>(tree); return tree; }
void EntityServer::pruneDeletedEntities() { EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); if (tree->hasAnyDeletedEntities()) { quint64 earliestLastDeletedEntitiesSent = usecTimestampNow() + 1; // in the future DependencyManager::get<NodeList>()->eachNode([&earliestLastDeletedEntitiesSent](const SharedNodePointer& node) { if (node->getLinkedData()) { EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData()); quint64 nodeLastDeletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); if (nodeLastDeletedEntitiesSentAt < earliestLastDeletedEntitiesSent) { earliestLastDeletedEntitiesSent = nodeLastDeletedEntitiesSentAt; } } }); tree->forgetEntitiesDeletedBefore(earliestLastDeletedEntitiesSent); } }
SpatiallyNestableWeakPointer InterfaceParentFinder::find(QUuid parentID, bool& success, SpatialParentTree* entityTree) const { SpatiallyNestableWeakPointer parent; if (parentID.isNull()) { success = true; return parent; } // search entities if (entityTree) { parent = entityTree->findByID(parentID); } else { auto treeRenderer = qApp->getEntities(); EntityTreePointer tree = treeRenderer ? treeRenderer->getTree() : nullptr; parent = tree ? tree->findEntityByEntityItemID(parentID) : nullptr; } if (!parent.expired()) { success = true; return parent; } // search avatars QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>(); parent = avatarManager->getAvatarBySessionID(parentID); if (!parent.expired()) { success = true; return parent; } if (parentID == AVATAR_SELF_ID) { success = true; return avatarManager->getMyAvatar(); } // search overlays auto& overlays = qApp->getOverlays(); auto overlay = overlays.getOverlay(parentID); parent = std::dynamic_pointer_cast<SpatiallyNestable>(overlay); // this will return nullptr for non-3d overlays if (!parent.expired()) { success = true; return parent; } success = false; return parent; }
// EntityServer will use the "special packets" to send list of recently deleted entities bool EntityServer::hasSpecialPacketsToSend(const SharedNodePointer& node) { bool shouldSendDeletedEntities = false; // check to see if any new entities have been added since we last sent to this node... EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData()); if (nodeData) { quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); shouldSendDeletedEntities = tree->hasEntitiesDeletedSince(deletedEntitiesSentAt); #ifdef EXTRA_ERASE_DEBUGGING if (shouldSendDeletedEntities) { int elapsed = usecTimestampNow() - deletedEntitiesSentAt; qDebug() << "shouldSendDeletedEntities to node:" << node->getUUID() << "deletedEntitiesSentAt:" << deletedEntitiesSentAt << "elapsed:" << elapsed; } #endif } return shouldSendDeletedEntities; }
bool RenderableModelEntityItem::isReadyToComputeShape() { ShapeType type = getShapeType(); if (type == SHAPE_TYPE_COMPOUND) { if (!_model || _model->getCollisionURL().isEmpty()) { EntityTreePointer tree = getTree(); if (tree) { QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); } return false; } if (_model->getURL().isEmpty()) { // we need a render geometry with a scale to proceed, so give up. return false; } const QSharedPointer<NetworkGeometry> collisionNetworkGeometry = _model->getCollisionGeometry(); const QSharedPointer<NetworkGeometry> renderNetworkGeometry = _model->getGeometry(); if ((collisionNetworkGeometry && collisionNetworkGeometry->isLoaded()) && (renderNetworkGeometry && renderNetworkGeometry->isLoaded())) { // we have both URLs AND both geometries AND they are both fully loaded. if (_needsInitialSimulation) { // the _model's offset will be wrong until _needsInitialSimulation is false PerformanceTimer perfTimer("_model->simulate"); _model->simulate(0.0f); _needsInitialSimulation = false; } return true; } // the model is still being downloaded. return false; } return true; }
void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties) { if (!_shouldSend) { return; // bail early } if (properties.getOwningAvatarID() != _myAvatar->getID()) { return; // don't send updates for someone else's avatarEntity } assert(properties.getClientOnly()); // this is an avatar-based entity. update our avatar-data rather than sending to the entity-server assert(_myAvatar); if (!entityTree) { qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; return; } EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); if (!entity) { qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage can't find entity."; return; } // the properties that get serialized into the avatar identity packet should be the entire set // rather than just the ones being edited. EntityItemProperties entityProperties = entity->getProperties(); entityProperties.merge(properties); QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); QVariant variantProperties = scriptProperties.toVariant(); QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); // the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar QJsonObject jsonObject = jsonProperties.object(); if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) { jsonObject["parentID"] = AVATAR_SELF_ID.toString(); } jsonProperties = QJsonDocument(jsonObject); QByteArray binaryProperties = jsonProperties.toBinaryData(); _myAvatar->updateAvatarEntity(entityItemID, binaryProperties); entity->setLastBroadcast(usecTimestampNow()); return; }
void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectionObject) { bool wantEditLogging = false; readOptionBool(QString("wantEditLogging"), settingsSectionObject, wantEditLogging); qDebug("wantEditLogging=%s", debug::valueOf(wantEditLogging)); bool wantTerseEditLogging = false; readOptionBool(QString("wantTerseEditLogging"), settingsSectionObject, wantTerseEditLogging); qDebug("wantTerseEditLogging=%s", debug::valueOf(wantTerseEditLogging)); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); int maxTmpEntityLifetime; if (readOptionInt("maxTmpLifetime", settingsSectionObject, maxTmpEntityLifetime)) { tree->setEntityMaxTmpLifetime(maxTmpEntityLifetime); } else { tree->setEntityMaxTmpLifetime(EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME); } int minTime; if (readOptionInt("dynamicDomainVerificationTimeMin", settingsSectionObject, minTime)) { _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = minTime * 1000; } int maxTime; if (readOptionInt("dynamicDomainVerificationTimeMax", settingsSectionObject, maxTime)) { _MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS = maxTime * 1000; } startDynamicDomainVerification(); tree->setWantEditLogging(wantEditLogging); tree->setWantTerseEditLogging(wantTerseEditLogging); QString entityScriptSourceWhitelist; if (readOptionString("entityScriptSourceWhitelist", settingsSectionObject, entityScriptSourceWhitelist)) { tree->setEntityScriptSourceWhitelist(entityScriptSourceWhitelist); } else { tree->setEntityScriptSourceWhitelist(""); } auto entityEditFilters = DependencyManager::get<EntityEditFilters>(); QString filterURL; if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) { // connect the filterAdded signal, and block edits until you hear back connect(entityEditFilters.data(), &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); entityEditFilters->addFilter(EntityItemID(), filterURL); } }
SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const override { SpatiallyNestableWeakPointer parent; if (parentID.isNull()) { success = true; return parent; } // search entities if (entityTree) { parent = entityTree->findByID(parentID); } else { parent = _tree ? _tree->findEntityByEntityItemID(parentID) : nullptr; } if (!parent.expired()) { success = true; return parent; } success = false; return parent; }
void OverlayPanel::applyTransformTo(Transform& transform, bool force) { if (force || usecTimestampNow() > _transformExpiry) { PanelAttachable::applyTransformTo(transform, true); if (!getParentPanel()) { if (_anchorPositionBindMyAvatar) { transform.setTranslation(DependencyManager::get<AvatarManager>()->getMyAvatar() ->getPosition()); } else if (!_anchorPositionBindEntity.isNull()) { EntityTreePointer entityTree = DependencyManager::get<EntityScriptingInterface>()->getEntityTree(); entityTree->withReadLock([&] { EntityItemPointer foundEntity = entityTree->findEntityByID(_anchorPositionBindEntity); if (foundEntity) { transform.setTranslation(foundEntity->getPosition()); } }); } else { transform.setTranslation(getAnchorPosition()); } if (_anchorRotationBindMyAvatar) { transform.setRotation(DependencyManager::get<AvatarManager>()->getMyAvatar() ->getOrientation()); } else if (!_anchorRotationBindEntity.isNull()) { EntityTreePointer entityTree = DependencyManager::get<EntityScriptingInterface>()->getEntityTree(); entityTree->withReadLock([&] { EntityItemPointer foundEntity = entityTree->findEntityByID(_anchorRotationBindEntity); if (foundEntity) { transform.setRotation(foundEntity->getRotation()); } }); } else { transform.setRotation(getAnchorRotation()); } transform.setScale(getAnchorScale()); transform.postTranslate(getOffsetPosition()); transform.postRotate(getOffsetRotation()); transform.postScale(getOffsetScale()); } pointTransformAtCamera(transform, getOffsetRotation()); } }
void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << "Starting Dynamic Domain Verification..."; QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainID().remove(QRegExp("\\{|\\}")); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap()); QHashIterator<QString, EntityItemID> i(localMap); qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap"; while (i.hasNext()) { i.next(); EntityItemPointer entity = tree->findEntityByEntityItemID(i.value()); if (entity) { if (!entity->getProperties().verifyStaticCertificateProperties()) { qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed" << "static certificate verification."; // Delete the entity if it doesn't pass static certificate verification tree->deleteEntity(i.value(), true); } else { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); QJsonObject request; request["certificate_id"] = i.key(); networkRequest.setUrl(requestURL); QNetworkReply* networkReply = NULL; networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); connect(networkReply, &QNetworkReply::finished, [=]() { QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); jsonObject = jsonObject["data"].toObject(); if (networkReply->error() == QNetworkReply::NoError) { if (jsonObject["domain_id"].toString() != thisDomainID) { qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value(); tree->deleteEntity(i.value(), true); } else { qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value(); } } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << i.value() << "More info:" << jsonObject; tree->deleteEntity(i.value(), true); } networkReply->deleteLater(); }); } } else { qCWarning(entities) << "During DDV, an entity with ID" << i.value() << "was NOT found in the Entity Tree!"; } } int nextInterval = qrand() % ((_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS + 1) - _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS) + _MINIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS; qCDebug(entities) << "Restarting Dynamic Domain Verification timer for" << nextInterval / 1000 << "seconds"; _dynamicDomainVerificationTimer.start(nextInterval); }
void EntityServer::nodeKilled(SharedNodePointer node) { EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); tree->deleteDescendantsOfAvatar(node->getUUID()); tree->forgetAvatarID(node->getUUID()); OctreeServer::nodeKilled(node); }
void EntityServer::nodeAdded(SharedNodePointer node) { EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); tree->knowAvatarID(node->getUUID()); OctreeServer::nodeAdded(node); }
// FIXME - most of the old code for this was encapsulated in EntityTree, I liked that design from a data // hiding and object oriented perspective. But that didn't really allow us to handle the case of lots // of entities being deleted at the same time. I'd like to look to move this back into EntityTree but // for now this works and addresses the bug. int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { int totalBytes = 0; EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData()); if (nodeData) { quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); quint64 considerEntitiesSince = EntityTree::getAdjustedConsiderSince(deletedEntitiesSentAt); quint64 deletePacketSentAt = usecTimestampNow(); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); auto recentlyDeleted = tree->getRecentlyDeletedEntityIDs(); packetsSent = 0; // create a new special packet std::unique_ptr<NLPacket> deletesPacket = NLPacket::create(PacketType::EntityErase); // pack in flags OCTREE_PACKET_FLAGS flags = 0; deletesPacket->writePrimitive(flags); // pack in sequence number auto sequenceNumber = queryNode->getSequenceNumber(); deletesPacket->writePrimitive(sequenceNumber); // pack in timestamp OCTREE_PACKET_SENT_TIME now = usecTimestampNow(); deletesPacket->writePrimitive(now); // figure out where we are now and pack a temporary number of IDs uint16_t numberOfIDs = 0; qint64 numberOfIDsPos = deletesPacket->pos(); deletesPacket->writePrimitive(numberOfIDs); // we keep a multi map of entity IDs to timestamps, we only want to include the entity IDs that have been // deleted since we last sent to this node auto it = recentlyDeleted.constBegin(); while (it != recentlyDeleted.constEnd()) { // if the timestamp is more recent then out last sent time, include it if (it.key() > considerEntitiesSince) { // get all the IDs for this timestamp const auto& entityIDsFromTime = recentlyDeleted.values(it.key()); for (const auto& entityID : entityIDsFromTime) { // check to make sure we have room for one more ID, if we don't have more // room, then send out this packet and create another one if (NUM_BYTES_RFC4122_UUID > deletesPacket->bytesAvailableForWrite()) { // replace the count for the number of included IDs deletesPacket->seek(numberOfIDsPos); deletesPacket->writePrimitive(numberOfIDs); // Send the current packet queryNode->packetSent(*deletesPacket); auto thisPacketSize = deletesPacket->getDataSize(); totalBytes += thisPacketSize; packetsSent++; DependencyManager::get<NodeList>()->sendPacket(std::move(deletesPacket), *node); #ifdef EXTRA_ERASE_DEBUGGING qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize; #endif // create another packet deletesPacket = NLPacket::create(PacketType::EntityErase); // pack in flags deletesPacket->writePrimitive(flags); // pack in sequence number sequenceNumber = queryNode->getSequenceNumber(); deletesPacket->writePrimitive(sequenceNumber); // pack in timestamp deletesPacket->writePrimitive(now); // figure out where we are now and pack a temporary number of IDs numberOfIDs = 0; numberOfIDsPos = deletesPacket->pos(); deletesPacket->writePrimitive(numberOfIDs); } // FIXME - we still seem to see cases where incorrect EntityIDs get sent from the server // to the client. These were causing "lost" entities like flashlights and laser pointers // now that we keep around some additional history of the erased entities and resend that // history for a longer time window, these entities are not "lost". But we haven't yet // found/fixed the underlying issue that caused bad UUIDs to be sent to some users. deletesPacket->write(entityID.toRfc4122()); ++numberOfIDs; #ifdef EXTRA_ERASE_DEBUGGING qDebug() << "EntityTree::encodeEntitiesDeletedSince() including:" << entityID; #endif } // end for (ids) } // end if (it.val > sinceLast) ++it; } // end while // replace the count for the number of included IDs deletesPacket->seek(numberOfIDsPos); deletesPacket->writePrimitive(numberOfIDs); // Send the current packet queryNode->packetSent(*deletesPacket); auto thisPacketSize = deletesPacket->getDataSize(); totalBytes += thisPacketSize; packetsSent++; DependencyManager::get<NodeList>()->sendPacket(std::move(deletesPacket), *node); #ifdef EXTRA_ERASE_DEBUGGING qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize; #endif nodeData->setLastDeletedEntitiesSentAt(deletePacketSentAt); } #ifdef EXTRA_ERASE_DEBUGGING if (packetsSent > 0) { qDebug() << "EntityServer::sendSpecialPackets() sent " << packetsSent << "special packets of " << totalBytes << " total bytes to node:" << node->getUUID(); } #endif // TODO: caller is expecting a packetLength, what if we send more than one packet?? return totalBytes; }