diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 1954e373f7..60ac9a2119 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6757,7 +6757,7 @@ void Application::updateWindowTitle() const { DependencyManager::get< MessagesClient >()->sendLocalMessage("Toolbar-DomainChanged", ""); } -void Application::clearDomainOctreeDetails() { +void Application::clearDomainOctreeDetails(bool clearAll) { // before we delete all entities get MyAvatar's AvatarEntityData ready getMyAvatar()->prepareAvatarEntityDataForReload(); @@ -6776,7 +6776,7 @@ void Application::clearDomainOctreeDetails() { }); // reset the model renderer - getEntities()->clear(); + clearAll ? getEntities()->clear() : getEntities()->clearNonLocalEntities(); auto skyStage = DependencyManager::get()->getSkyStage(); @@ -6814,7 +6814,7 @@ void Application::goToErrorDomainURL(QUrl errorDomainURL) { void Application::resettingDomain() { _notifiedPacketVersionMismatchThisDomain = false; - clearDomainOctreeDetails(); + clearDomainOctreeDetails(false); } void Application::nodeAdded(SharedNodePointer node) const { @@ -6900,7 +6900,7 @@ void Application::nodeKilled(SharedNodePointer node) { QMetaObject::invokeMethod(DependencyManager::get().data(), "audioMixerKilled"); } else if (node->getType() == NodeType::EntityServer) { // we lost an entity server, clear all of the domain octree details - clearDomainOctreeDetails(); + clearDomainOctreeDetails(false); } else if (node->getType() == NodeType::AssetServer) { // asset server going away - check if we have the asset browser showing diff --git a/interface/src/Application.h b/interface/src/Application.h index 4c6d45b8c3..05d6135a93 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -469,7 +469,7 @@ private slots: void onDesktopRootItemCreated(QQuickItem* qmlContext); void onDesktopRootContextCreated(QQmlContext* qmlContext); void showDesktop(); - void clearDomainOctreeDetails(); + void clearDomainOctreeDetails(bool clearAll = true); void onAboutToQuit(); void onPresent(quint32 frameCount); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 380998321f..c71b296a74 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -197,9 +197,57 @@ void EntityTreeRenderer::resetEntitiesScriptEngine() { }); } +void EntityTreeRenderer::stopNonLocalEntityScripts() { + leaveNonLocalEntities(); + // unload and stop the engine + if (_entitiesScriptEngine) { + QList entitiesWithEntityScripts = _entitiesScriptEngine->getListOfEntityScriptIDs(); + + foreach (const EntityItemID& entityID, entitiesWithEntityScripts) { + EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); + + if (entityItem) { + if (!entityItem->isLocalEntity()) { + _entitiesScriptEngine->unloadEntityScript(entityID, true); + } + } + } + } +} + +void EntityTreeRenderer::clearNonLocalEntities() { + stopNonLocalEntityScripts(); + + std::unordered_map savedEntities; + // remove all entities from the scene + _space->clear(); + auto scene = _viewState->getMain3DScene(); + if (scene) { + render::Transaction transaction; + for (const auto& entry : _entitiesInScene) { + const auto& renderer = entry.second; + const EntityItemPointer& entityItem = renderer->getEntity(); + if (!entityItem->isLocalEntity()) { + renderer->removeFromScene(scene, transaction); + } else { + savedEntities[entry.first] = entry.second; + } + } + scene->enqueueTransaction(transaction); + } else { + qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; + } + + _renderablesToUpdate = savedEntities; + _entitiesInScene = savedEntities; + + _layeredZones.clearNonLocalLayeredZones(); + + OctreeProcessor::clearNonLocalEntities(); +} + void EntityTreeRenderer::clear() { leaveAllEntities(); - // unload and stop the engine if (_entitiesScriptEngine) { // do this here (instead of in deleter) to avoid marshalling unload signals back to this thread @@ -211,8 +259,8 @@ void EntityTreeRenderer::clear() { if (_wantScripts && !_shuttingDown) { resetEntitiesScriptEngine(); } - // remove all entities from the scene + _space->clear(); auto scene = _viewState->getMain3DScene(); if (scene) { @@ -507,8 +555,7 @@ bool EntityTreeRenderer::findBestZoneAndMaybeContainingEntities(QVector(_tree); // FIXME - if EntityTree had a findEntitiesContainingPoint() this could theoretically be a little faster - entityTree->evalEntitiesInSphere(_avatarPosition, radius, - PickFilter(PickFilter::getBitMask(PickFilter::FlagBit::DOMAIN_ENTITIES) | PickFilter::getBitMask(PickFilter::FlagBit::AVATAR_ENTITIES)), entityIDs); + entityTree->evalEntitiesInSphere(_avatarPosition, radius, PickFilter(), entityIDs); LayeredZones oldLayeredZones(std::move(_layeredZones)); _layeredZones.clear(); @@ -614,6 +661,26 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { return didUpdate; } +void EntityTreeRenderer::leaveNonLocalEntities() { + if (_tree && !_shuttingDown) { + QVector currentLocalEntitiesInside; + foreach (const EntityItemID& entityID, _currentEntitiesInside) { + EntityItemPointer entityItem = getTree()->findEntityByEntityItemID(entityID); + if (!entityItem->isLocalEntity()) { + emit leaveEntity(entityID); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } + } else { + currentLocalEntitiesInside.push_back(entityID); + } + } + + _currentEntitiesInside = currentLocalEntitiesInside; + forceRecheckEntities(); + } +} + void EntityTreeRenderer::leaveAllEntities() { if (_tree && !_shuttingDown) { @@ -1136,6 +1203,29 @@ EntityTreeRenderer::LayeredZones::LayeredZones(LayeredZones&& other) { } } +void EntityTreeRenderer::LayeredZones::clearNonLocalLayeredZones() { + std::set localLayeredZones; + std::map newMap; + + for (auto iter = begin(); iter != end(); iter++) { + LayeredZone layeredZone = *iter; + + if (layeredZone.zone->isLocalEntity()) { + bool success; + iterator it; + std::tie(it, success) = localLayeredZones.insert(layeredZone); + + if (success) { + newMap.emplace(layeredZone.id, it); + } + } + } + + std::set::operator=(localLayeredZones); + _map = newMap; + _skyboxLayer = empty() ? end() : begin(); +} + void EntityTreeRenderer::LayeredZones::clear() { std::set::clear(); _map.clear(); diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index b4f0bda703..d9f594a20b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -86,6 +86,7 @@ public: virtual void init() override; /// clears the tree + virtual void clearNonLocalEntities() override; virtual void clear() override; /// reloads the entity scripts, calling unload and preload @@ -161,6 +162,7 @@ private: bool findBestZoneAndMaybeContainingEntities(QVector* entitiesContainingAvatar = nullptr); bool applyLayeredZones(); + void stopNonLocalEntityScripts(); void checkAndCallPreload(const EntityItemID& entityID, bool reload = false, bool unloadFirst = false); @@ -169,6 +171,7 @@ private: QScriptValueList createEntityArgs(const EntityItemID& entityID); bool checkEnterLeaveEntities(); + void leaveNonLocalEntities(); void leaveAllEntities(); void forceRecheckEntities(); @@ -219,6 +222,7 @@ private: LayeredZones& operator=(LayeredZones&&) = delete; void clear(); + void clearNonLocalLayeredZones(); std::pair insert(const LayeredZone& layer); void update(std::shared_ptr zone); bool contains(const LayeredZones& other); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 697d583de7..286f0dd650 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -986,7 +986,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { shouldSendDeleteToServer = false; } else { // only delete local entities, server entities will round trip through the server filters - if (entity->isAvatarEntity() || _entityTree->isServerlessMode()) { + if (!entity->isDomainEntity() || _entityTree->isServerlessMode()) { shouldSendDeleteToServer = false; _entityTree->deleteEntity(entityID); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index fb1a11d43f..b8babc60b8 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -70,6 +70,49 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { return std::static_pointer_cast(newElement); } +void EntityTree::eraseNonLocalEntities() { + emit clearingEntities(); + + if (_simulation) { + // This will clear all entities host types including local entities, because local entities + // are not in the physics simulation + _simulation->clearEntities(); + } + _staleProxies.clear(); + QHash localMap; + localMap.swap(_entityMap); + QHash savedEntities; + this->withWriteLock([&] { + foreach(EntityItemPointer entity, localMap) { + EntityTreeElementPointer element = entity->getElement(); + if (element) { + element->cleanupNonLocalEntities(); + } + + if (entity->isLocalEntity()) { + savedEntities[entity->getEntityItemID()] = entity; + } + } + }); + localMap.clear(); + _entityMap = savedEntities; + + resetClientEditStats(); + clearDeletedEntities(); + + { + QWriteLocker locker(&_needsParentFixupLock); + QVector localEntitiesNeedsParentFixup; + + foreach (EntityItemWeakPointer entityItem, _needsParentFixup) { + if (!entityItem.expired() && entityItem.lock()->isLocalEntity()) { + localEntitiesNeedsParentFixup.push_back(entityItem); + } + } + + _needsParentFixup = localEntitiesNeedsParentFixup; + } +} void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 9181a4851c..f9b7b8d67f 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -74,6 +74,8 @@ public: return std::static_pointer_cast(_rootElement); } + + virtual void eraseNonLocalEntities() override; virtual void eraseAllOctreeElements(bool createNewRoot = true) override; virtual void readBitstreamToTree(const unsigned char* bitstream, diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp index 2ece6835ea..d51ffcd10a 100644 --- a/libraries/entities/src/EntityTreeElement.cpp +++ b/libraries/entities/src/EntityTreeElement.cpp @@ -683,6 +683,23 @@ EntityItemPointer EntityTreeElement::getEntityWithEntityItemID(const EntityItemI return foundEntity; } +void EntityTreeElement::cleanupNonLocalEntities() { + withWriteLock([&] { + EntityItems savedEntities; + foreach(EntityItemPointer entity, _entityItems) { + if (!entity->isLocalEntity()) { + entity->preDelete(); + entity->_element = NULL; + } else { + savedEntities.push_back(entity); + } + } + + _entityItems = savedEntities; + }); + bumpChangedContent(); +} + void EntityTreeElement::cleanupEntities() { withWriteLock([&] { foreach(EntityItemPointer entity, _entityItems) { diff --git a/libraries/entities/src/EntityTreeElement.h b/libraries/entities/src/EntityTreeElement.h index aed19eed15..3f1fda57bd 100644 --- a/libraries/entities/src/EntityTreeElement.h +++ b/libraries/entities/src/EntityTreeElement.h @@ -189,6 +189,7 @@ public: EntityItemPointer getEntityWithEntityItemID(const EntityItemID& id) const; void getEntitiesInside(const AACube& box, QVector& foundEntities); + void cleanupNonLocalEntities(); void cleanupEntities(); /// called by EntityTree on cleanup this will free all entities bool removeEntityItem(EntityItemPointer entity, bool deletion = false); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index eef23493f6..aac29201f1 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -149,6 +149,7 @@ public: OctreeElementPointer getRoot() { return _rootElement; } + virtual void eraseNonLocalEntities() { _isDirty = true; }; virtual void eraseAllOctreeElements(bool createNewRoot = true); virtual void readBitstreamToTree(const unsigned char* bitstream, uint64_t bufferSizeBytes, ReadBitstreamToTreeParams& args); diff --git a/libraries/octree/src/OctreeProcessor.cpp b/libraries/octree/src/OctreeProcessor.cpp index 206ff399d9..18c8630391 100644 --- a/libraries/octree/src/OctreeProcessor.cpp +++ b/libraries/octree/src/OctreeProcessor.cpp @@ -51,15 +51,15 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe bool showTimingDetails = false; // Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showTimingDetails, "OctreeProcessor::processDatagram()", showTimingDetails); - + if (message.getType() == getExpectedPacketType()) { PerformanceWarning warn(showTimingDetails, "OctreeProcessor::processDatagram expected PacketType", showTimingDetails); // if we are getting inbound packets, then our tree is also viewing, and we should remember that fact. _tree->setIsViewing(true); - + OCTREE_PACKET_FLAGS flags; message.readPrimitive(&flags); - + OCTREE_PACKET_SEQUENCE sequence; message.readPrimitive(&sequence); @@ -68,7 +68,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe bool packetIsColored = oneAtBit(flags, PACKET_IS_COLOR_BIT); bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); - + OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); qint64 clockSkew = sourceNode ? sourceNode->getClockSkewUsec() : 0; qint64 flightTime = arrivedAt - sentAt + clockSkew; @@ -79,27 +79,27 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe qCDebug(octree) << "OctreeProcessor::processDatagram() ... " "Got Packet Section color:" << packetIsColored << "compressed:" << packetIsCompressed << - "sequence: " << sequence << + "sequence: " << sequence << "flight: " << flightTime << " usec" << "size:" << message.getSize() << "data:" << message.getBytesLeftToRead(); } - + _packetsInLastWindow++; - + int elementsPerPacket = 0; int entitiesPerPacket = 0; - + quint64 totalWaitingForLock = 0; quint64 totalUncompress = 0; quint64 totalReadBitsteam = 0; const QUuid& sourceUUID = sourceNode->getUUID(); - + int subsection = 1; - + bool error = false; - + while (message.getBytesLeftToRead() > 0 && !error) { if (packetIsCompressed) { if (message.getBytesLeftToRead() > (qint64) sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE)) { @@ -111,7 +111,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } else { sectionLength = message.getBytesLeftToRead(); } - + if (sectionLength) { // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(WANT_EXISTS_BITS, NULL, @@ -149,7 +149,7 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe qCDebug(octree) << "OctreeProcessor::processDatagram() ******* END _tree->readBitstreamToTree()..."; } }); - + // seek forwards in packet message.seek(message.getPosition() + sectionLength); @@ -172,13 +172,13 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe _waitLockPerPacket.updateAverage(totalWaitingForLock); _uncompressPerPacket.updateAverage(totalUncompress); _readBitstreamPerPacket.updateAverage(totalReadBitsteam); - + quint64 now = usecTimestampNow(); if (_lastWindowAt == 0) { _lastWindowAt = now; } quint64 sinceLastWindow = now - _lastWindowAt; - + if (sinceLastWindow > USECS_PER_SECOND) { float packetsPerSecondInWindow = (float)_packetsInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); float elementsPerSecondInWindow = (float)_elementsInLastWindow / (float)(sinceLastWindow / USECS_PER_SECOND); @@ -197,6 +197,14 @@ void OctreeProcessor::processDatagram(ReceivedMessage& message, SharedNodePointe } } + +void OctreeProcessor::clearNonLocalEntities() { + if (_tree) { + _tree->withWriteLock([&] { + _tree->eraseNonLocalEntities(); + }); + } +} void OctreeProcessor::clear() { if (_tree) { _tree->withWriteLock([&] { diff --git a/libraries/octree/src/OctreeProcessor.h b/libraries/octree/src/OctreeProcessor.h index 1bc3bd10f9..bc5618e657 100644 --- a/libraries/octree/src/OctreeProcessor.h +++ b/libraries/octree/src/OctreeProcessor.h @@ -43,6 +43,7 @@ public: virtual void init(); /// clears the tree + virtual void clearNonLocalEntities(); virtual void clear(); float getAverageElementsPerPacket() const { return _elementsPerPacket.getAverage(); } diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 47ce29f9a3..fa7a8e1114 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -2407,6 +2407,11 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID, bool shouldR } } +QList ScriptEngine::getListOfEntityScriptIDs() { + QReadLocker locker{ &_entityScriptsLock }; + return _entityScripts.keys(); +} + void ScriptEngine::unloadAllEntityScripts() { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 0d6d45e594..8753010089 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -145,6 +145,9 @@ public: QString getFilename() const; + + QList getListOfEntityScriptIDs(); + /**jsdoc * Stop the current script. * @function Script.stop