diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index 93d6725994..812252663a 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -41,8 +41,8 @@ public class InterfaceActivity extends QtActivity { private native void nativeOnDestroy(); private native void nativeGotoUrl(String url); private native void nativeGoBackFromAndroidActivity(); - //private native void nativeOnStop(); - //private native void nativeOnStart(); + private native void nativeEnterBackground(); + private native void nativeEnterForeground(); //private native void saveRealScreenSize(int width, int height); //private native void setAppVersion(String version); private native long nativeOnExitVr(); @@ -124,14 +124,13 @@ public class InterfaceActivity extends QtActivity { @Override protected void onStart() { super.onStart(); -// nativeOnStart(); + nativeEnterForeground(); } @Override protected void onStop() { - Log.d("[Background]","Calling nativeOnStop from InterfaceActivity"); -// nativeOnStop(); super.onStop(); + nativeEnterBackground(); } @Override diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 288652715a..268aba62d6 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -33,7 +33,7 @@ uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) return 0; } -void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) { +void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time) { std::unordered_map::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar); if (itr != _lastOtherAvatarEncodeTime.end()) { itr->second = time; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 7a7210a0e8..3c2e660cbc 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -113,7 +113,7 @@ public: ViewFrustum getViewFrustum() const { return _currentViewFrustum; } uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const; - void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time); + void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, uint64_t time); QVector& getLastOtherAvatarSentJoints(QUuid otherAvatar) { auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar]; diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index 1c66fb213f..4d0773f5f5 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66.zip - URL_MD5 91edfde96e06efc847ca327ab97f4c74 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v2.zip + URL_MD5 d76bdb3e2bf7ae5d20115bd97b0c44a8 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" diff --git a/interface/resources/icons/+android/backward.svg b/interface/resources/icons/+android/backward.svg old mode 100644 new mode 100755 index ae0893fc66..ad102b886e --- a/interface/resources/icons/+android/backward.svg +++ b/interface/resources/icons/+android/backward.svg @@ -1,70 +1,15 @@ - - - -image/svg+xml \ No newline at end of file + + + + + + + + + + + diff --git a/interface/resources/icons/+android/myview-a.svg b/interface/resources/icons/+android/myview-a.svg index 307b559c95..9964678074 100755 --- a/interface/resources/icons/+android/myview-a.svg +++ b/interface/resources/icons/+android/myview-a.svg @@ -1,56 +1,19 @@ - - - -image/svg+xmlAsset 3 \ No newline at end of file + + + + + + + + + + + diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index efc8519c1e..8e7db44b7d 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -122,9 +122,21 @@ Item { newViewRequestedCallback(request) } + // Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false + // as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns + // loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed + // when QT fixes this. + property bool safeLoading: false + property bool loadingLatched: false + property var loadingRequest: null onLoadingChanged: { - flick.onLoadingChanged(loadRequest) - loadingChangedCallback(loadRequest) + webViewCore.loadingRequest = loadRequest; + webViewCore.safeLoading = webViewCore.loading && !loadingLatched; + webViewCore.loadingLatched |= webViewCore.loading; + } + onSafeLoadingChanged: { + flick.onLoadingChanged(webViewCore.loadingRequest) + loadingChangedCallback(webViewCore.loadingRequest) } } @@ -133,7 +145,7 @@ Item { x: flick.width/2 - width/2 y: flick.height/2 - height/2 source: "../../icons/loader-snake-64-w.gif" - visible: webViewCore.loading && /^(http.*|)$/i.test(webViewCore.url.toString()) + visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString()) playing: visible z: 10000 } diff --git a/interface/resources/qml/hifi/+android/ActionBar.qml b/interface/resources/qml/hifi/+android/ActionBar.qml new file mode 100644 index 0000000000..7152c829bc --- /dev/null +++ b/interface/resources/qml/hifi/+android/ActionBar.qml @@ -0,0 +1,71 @@ +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.3 +import Qt.labs.settings 1.0 +import "../../styles-uit" +import "../../controls-uit" as HifiControlsUit +import "../../controls" as HifiControls +import ".." + +Item { + id: actionBar + x:0 + y:0 + width: 300 + height: 300 + z: -1 + + signal sendToScript(var message); + signal windowClosed(); + + property bool shown: true + + onShownChanged: { + actionBar.visible = shown; + } + + Rectangle { + anchors.fill : parent + color: "transparent" + Flow { + id: flowMain + spacing: 10 + flow: Flow.TopToBottom + layoutDirection: Flow.TopToBottom + anchors.fill: parent + anchors.margins: 4 + } + } + + Component.onCompleted: { + // put on bottom + x = 50; + y = 0; + width = 300; + height = 300; + } + + function addButton(properties) { + var component = Qt.createComponent("button.qml"); + if (component.status == Component.Ready) { + var button = component.createObject(flowMain); + // copy all properites to button + var keys = Object.keys(properties).forEach(function (key) { + button[key] = properties[key]; + }); + return button; + } else if( component.status == Component.Error) { + console.log("Load button errors " + component.errorString()); + } + } + + function urlHelper(src) { + if (src.match(/\bhttp/)) { + return src; + } else { + return "../../../" + src; + } + } + +} diff --git a/interface/resources/qml/hifi/+android/AudioBar.qml b/interface/resources/qml/hifi/+android/AudioBar.qml index f524595ef5..a6d4e28813 100644 --- a/interface/resources/qml/hifi/+android/AudioBar.qml +++ b/interface/resources/qml/hifi/+android/AudioBar.qml @@ -40,7 +40,7 @@ Item { Component.onCompleted: { // put on bottom - x = 0; + x = parent.width-300; y = 0; width = 300; height = 300; diff --git a/interface/resources/qml/hifi/+android/modesbar.qml b/interface/resources/qml/hifi/+android/modesbar.qml index fe71314ece..451921f155 100644 --- a/interface/resources/qml/hifi/+android/modesbar.qml +++ b/interface/resources/qml/hifi/+android/modesbar.qml @@ -10,7 +10,7 @@ import ".." Item { id: modesbar - y:60 + y:20 Rectangle { anchors.fill : parent color: "transparent" @@ -25,9 +25,9 @@ Item { } Component.onCompleted: { - width = 300 + 30; // That 30 is extra regardless the qty of items shown - height = 300 + 30; - x=Window.innerWidth - width; + width = 300; // That 30 is extra regardless the qty of items shown + height = 300; + x=parent.width - 540; } function addButton(properties) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3e16c3e287..0b68263ba4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -242,6 +242,7 @@ extern "C" { #if defined(Q_OS_ANDROID) #include +#include #endif enum ApplicationEvent { @@ -3046,28 +3047,20 @@ void Application::handleSandboxStatus(QNetworkReply* reply) { static const QString SENT_TO_PREVIOUS_LOCATION = "previous_location"; static const QString SENT_TO_ENTRY = "entry"; - static const QString SENT_TO_SANDBOX = "sandbox"; QString sentTo; // If this is a first run we short-circuit the address passed in if (firstRun.get()) { #if defined(Q_OS_ANDROID) - qCDebug(interfaceapp) << "First run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("default location") : addressLookupString); - DependencyManager::get()->loadSettings(addressLookupString); + qCDebug(interfaceapp) << "First run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("default location") : addressLookupString); + DependencyManager::get()->loadSettings(addressLookupString); #else - showHelp(); - if (sandboxIsRunning) { - qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; - DependencyManager::get()->goToLocalSandbox(); - sentTo = SENT_TO_SANDBOX; - } else { - qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; - DependencyManager::get()->goToEntry(); - sentTo = SENT_TO_ENTRY; - } + showHelp(); + DependencyManager::get()->goToEntry(); + sentTo = SENT_TO_ENTRY; #endif - firstRun.set(false); + firstRun.set(false); } else { qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); @@ -4682,7 +4675,7 @@ void Application::init() { auto entityScriptingInterface = DependencyManager::get(); // connect the _entityCollisionSystem to our EntityTreeRenderer since that's what handles running entity scripts - connect(_entitySimulation.get(), &EntitySimulation::entityCollisionWithEntity, + connect(_entitySimulation.get(), &PhysicalEntitySimulation::entityCollisionWithEntity, getEntities().data(), &EntityTreeRenderer::entityCollisionWithEntity); // connect the _entities (EntityTreeRenderer) to our script engine's EntityScriptingInterface for firing @@ -5277,11 +5270,13 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(simulation_physics, "PreStep"); PerformanceTimer perfTimer("preStep)"); - static VectorOfMotionStates motionStates; - _entitySimulation->getObjectsToRemoveFromPhysics(motionStates); - _physicsEngine->removeObjects(motionStates); - _entitySimulation->deleteObjectsRemovedFromPhysics(); + { + const VectorOfMotionStates& motionStates = _entitySimulation->getObjectsToRemoveFromPhysics(); + _physicsEngine->removeObjects(motionStates); + _entitySimulation->deleteObjectsRemovedFromPhysics(); + } + VectorOfMotionStates motionStates; getEntities()->getTree()->withReadLock([&] { _entitySimulation->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); @@ -5295,7 +5290,7 @@ void Application::update(float deltaTime) { _entitySimulation->applyDynamicChanges(); - avatarManager->getObjectsToRemoveFromPhysics(motionStates); + avatarManager->getObjectsToRemoveFromPhysics(motionStates); _physicsEngine->removeObjects(motionStates); avatarManager->getObjectsToAddToPhysics(motionStates); _physicsEngine->addObjects(motionStates); @@ -7868,7 +7863,8 @@ void Application::saveNextPhysicsStats(QString filename) { void Application::openAndroidActivity(const QString& activityName) { #if defined(Q_OS_ANDROID) - getActiveDisplayPlugin()->deactivate(); + qDebug() << "[Background-HIFI] Application::openAndroidActivity"; + //getActiveDisplayPlugin()->deactivate(); AndroidHelper::instance().requestActivity(activityName); connect(&AndroidHelper::instance(), &AndroidHelper::backFromAndroidActivity, this, &Application::restoreAfterAndroidActivity); #endif @@ -7876,11 +7872,63 @@ void Application::openAndroidActivity(const QString& activityName) { void Application::restoreAfterAndroidActivity() { #if defined(Q_OS_ANDROID) - if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { + qDebug() << "[Background-HIFI] restoreAfterAndroidActivity: this wouldn't be needed"; + + /*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { qWarning() << "Could not re-activate display plugin"; - } + }*/ disconnect(&AndroidHelper::instance(), &AndroidHelper::backFromAndroidActivity, this, &Application::restoreAfterAndroidActivity); #endif } +#if defined(Q_OS_ANDROID) +void Application::enterBackground() { + qDebug() << "[Background-HIFI] enterBackground begin"; + QMetaObject::invokeMethod(DependencyManager::get().data(), + "stop", Qt::BlockingQueuedConnection); + qDebug() << "[Background-HIFI] deactivating display plugin"; + getActiveDisplayPlugin()->deactivate(); + qDebug() << "[Background-HIFI] enterBackground end"; +} +void Application::enterForeground() { + qDebug() << "[Background-HIFI] enterForeground qApp?" << (qApp?"yeah":"false"); + if (qApp && DependencyManager::isSet()) { + qDebug() << "[Background-HIFI] audioclient.start()"; + QMetaObject::invokeMethod(DependencyManager::get().data(), + "start", Qt::BlockingQueuedConnection); + } else { + qDebug() << "[Background-HIFI] audioclient.start() not done"; + } + if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) { + qWarning() << "[Background-HIFI] Could not re-activate display plugin"; + } + +} + +extern "C" { + + +JNIEXPORT void +Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) { + qDebug() << "[Background-HIFI] nativeEnterBackground"; + if (qApp) { + qDebug() << "[Background-HIFI] nativeEnterBackground begin (qApp)"; + qApp->enterBackground(); + } +} + +JNIEXPORT void +Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) { + qDebug() << "[Background-HIFI] nativeEnterForeground"; + if (qApp) { + qDebug() << "[Background-HIFI] nativeEnterForeground begin (qApp)"; + qApp->enterForeground(); + } +} + + +} + +#endif + #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index ab55861930..ec49aa055f 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -292,6 +292,11 @@ public: void replaceDomainContent(const QString& url); +#if defined(Q_OS_ANDROID) + void enterBackground(); + void enterForeground(); +#endif + signals: void svoImportRequested(const QString& url); diff --git a/interface/src/DiscoverabilityManager.cpp b/interface/src/DiscoverabilityManager.cpp index e2d47bf844..33cfc481d7 100644 --- a/interface/src/DiscoverabilityManager.cpp +++ b/interface/src/DiscoverabilityManager.cpp @@ -129,7 +129,7 @@ void DiscoverabilityManager::updateLocation() { // Update Steam if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { - steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingShareableAddress()); + steamClient->updateLocation(domainHandler.getHostname(), addressManager->currentFacingPublicAddress()); } } diff --git a/interface/src/scripting/SelectionScriptingInterface.cpp b/interface/src/scripting/SelectionScriptingInterface.cpp index f8a62e848c..5f762ab511 100644 --- a/interface/src/scripting/SelectionScriptingInterface.cpp +++ b/interface/src/scripting/SelectionScriptingInterface.cpp @@ -110,6 +110,7 @@ bool SelectionScriptingInterface::enableListHighlight(const QString& listName, c } if (!(*highlightStyle).isBoundToList()) { + enableListToScene(listName); (*highlightStyle).setBoundToList(true); } @@ -133,6 +134,7 @@ bool SelectionScriptingInterface::disableListHighlight(const QString& listName) auto highlightStyle = _highlightStyleMap.find(listName); if (highlightStyle != _highlightStyleMap.end()) { if ((*highlightStyle).isBoundToList()) { + disableListToScene(listName); } _highlightStyleMap.erase(highlightStyle); @@ -476,4 +478,4 @@ QVariantMap SelectionHighlightStyle::toVariantMap() const { properties["isOutlineSmooth"] = _style._isOutlineSmooth; return properties; -} \ No newline at end of file +} diff --git a/interface/src/scripting/SelectionScriptingInterface.h b/interface/src/scripting/SelectionScriptingInterface.h index 3046ac371e..ed6efb39c6 100644 --- a/interface/src/scripting/SelectionScriptingInterface.h +++ b/interface/src/scripting/SelectionScriptingInterface.h @@ -165,6 +165,8 @@ public: * @param listName {string} name of the selection * @param highlightStyle {jsObject} highlight style fields (see Selection.getListHighlightStyle for a detailed description of the highlightStyle). * @returns {bool} true if the selection was successfully enabled for highlight. + * + * Note: This function will implicitly call Selection.enableListToScene */ Q_INVOKABLE bool enableListHighlight(const QString& listName, const QVariantMap& highlightStyle); @@ -175,8 +177,10 @@ public: * @function Selection.disableListHighlight * @param listName {string} name of the selection * @returns {bool} true if the selection was successfully disabled for highlight, false otherwise. + * + * Note: This function will implicitly call Selection.disableListToScene */ - Q_INVOKABLE bool disableListHighlight(const QString& listName); + Q_INVOKABLE bool disableListHighlight(const QString& listName); /**jsdoc * Enable scene selection for the named selection. * If the Selection doesn't exist, it will be created. @@ -246,7 +250,7 @@ private: void setupHandler(const QString& selectionName); void removeHandler(const QString& selectionName); - + }; #endif // hifi_SelectionScriptingInterface_h diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index 9b3089d78d..69103a40b5 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -95,7 +95,7 @@ QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) { QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QString& userSelectedFilename) { // adding URL to snapshot - QUrl currentURL = DependencyManager::get()->currentShareableAddress(); + QUrl currentURL = DependencyManager::get()->currentPublicAddress(); shot.setText(URL, currentURL.toString()); QString username = DependencyManager::get()->getAccountInfo().getUsername(); diff --git a/interface/src/ui/SnapshotUploader.cpp b/interface/src/ui/SnapshotUploader.cpp index 3408cb8512..37505db629 100644 --- a/interface/src/ui/SnapshotUploader.cpp +++ b/interface/src/ui/SnapshotUploader.cpp @@ -65,7 +65,7 @@ void SnapshotUploader::uploadSuccess(QNetworkReply& reply) { } else { emit DependencyManager::get()->snapshotShared(true, contents); - delete this; + this->deleteLater(); } } @@ -75,23 +75,27 @@ void SnapshotUploader::uploadFailure(QNetworkReply& reply) { if (replyString.size() == 0) { replyString = reply.errorString(); } + replyString = replyString.left(1000); // Only print first 1000 characters of error + qDebug() << "Snapshot upload reply error (truncated):" << replyString; emit DependencyManager::get()->snapshotShared(true, replyString); // maybe someday include _inWorldLocation, _filename? - delete this; + this->deleteLater(); } void SnapshotUploader::createStorySuccess(QNetworkReply& reply) { QString replyString = reply.readAll(); emit DependencyManager::get()->snapshotShared(false, replyString); - delete this; + this->deleteLater(); } void SnapshotUploader::createStoryFailure(QNetworkReply& reply) { QString replyString = reply.readAll(); - qDebug() << "Error " << reply.errorString() << " uploading snapshot " << _pathname << " from " << _inWorldLocation; + qDebug() << "Error " << reply.errorString() << " uploading snapshot story " << _pathname << " from " << _inWorldLocation; if (replyString.size() == 0) { replyString = reply.errorString(); } + replyString = replyString.left(1000); // Only print first 1000 characters of error + qDebug() << "Snapshot story upload reply error (truncated):" << replyString; emit DependencyManager::get()->snapshotShared(true, replyString); - delete this; + this->deleteLater(); } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5d056e17d8..aab8777862 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1911,7 +1911,7 @@ void EntityItem::computeCollisionGroupAndFinalMask(int16_t& group, int16_t& mask } } -void EntityItem::setSimulationOwner(const QUuid& id, quint8 priority) { +void EntityItem::setSimulationOwner(const QUuid& id, uint8_t priority) { if (wantTerseEditLogging() && (id != _simulationOwner.getID() || priority != _simulationOwner.getPriority())) { qCDebug(entities) << "sim ownership for" << getDebugName() << "is now" << id << priority; } @@ -1942,7 +1942,7 @@ void EntityItem::clearSimulationOwnership() { } -void EntityItem::setPendingOwnershipPriority(quint8 priority, const quint64& timestamp) { +void EntityItem::setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp) { _simulationOwner.setPendingPriority(priority, timestamp); } @@ -2962,13 +2962,6 @@ void EntityItem::retrieveMarketplacePublicKey() { } void EntityItem::preDelete() { - // clear out any left-over actions - EntityTreeElementPointer element = _element; // use local copy of _element for logic below - EntityTreePointer entityTree = element ? element->getTree() : nullptr; - EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr; - if (simulation) { - clearActions(simulation); - } } void EntityItem::addMaterial(graphics::MaterialLayer material, const std::string& parentMaterialName) { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 0303964e18..ebbeaaa254 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -304,14 +304,14 @@ public: // FIXME not thread safe? const SimulationOwner& getSimulationOwner() const { return _simulationOwner; } - void setSimulationOwner(const QUuid& id, quint8 priority); + void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const SimulationOwner& owner); - void promoteSimulationPriority(quint8 priority); + void promoteSimulationPriority(uint8_t priority); - quint8 getSimulationPriority() const { return _simulationOwner.getPriority(); } + uint8_t getSimulationPriority() const { return _simulationOwner.getPriority(); } QUuid getSimulatorID() const { return _simulationOwner.getID(); } void clearSimulationOwnership(); - void setPendingOwnershipPriority(quint8 priority, const quint64& timestamp); + void setPendingOwnershipPriority(uint8_t priority, const quint64& timestamp); uint8_t getPendingOwnershipPriority() const { return _simulationOwner.getPendingPriority(); } void rememberHasSimulationOwnershipBid() const; diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 9e5d6ddc79..0568a859ab 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -326,7 +326,7 @@ public: void clearSimulationOwner(); void setSimulationOwner(const QUuid& id, uint8_t priority); void setSimulationOwner(const QByteArray& data); - void promoteSimulationPriority(quint8 priority) { _simulationOwner.promotePriority(priority); } + void promoteSimulationPriority(uint8_t priority) { _simulationOwner.promotePriority(priority); } void setActionDataDirty() { _actionDataChanged = true; } diff --git a/libraries/entities/src/EntitySimulation.cpp b/libraries/entities/src/EntitySimulation.cpp index 36b0d8ab2d..d034ddedbe 100644 --- a/libraries/entities/src/EntitySimulation.cpp +++ b/libraries/entities/src/EntitySimulation.cpp @@ -20,7 +20,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { if (_entityTree && _entityTree != tree) { _mortalEntities.clear(); - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); @@ -30,7 +30,7 @@ void EntitySimulation::setEntityTree(EntityTreePointer tree) { void EntitySimulation::updateEntities() { QMutexLocker lock(&_mutex); - quint64 now = usecTimestampNow(); + uint64_t now = usecTimestampNow(); // these methods may accumulate entries in _entitiesToBeDeleted expireMortalEntities(now); @@ -40,18 +40,14 @@ void EntitySimulation::updateEntities() { sortEntitiesThatMoved(); } -void EntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { +void EntitySimulation::takeDeadEntities(SetOfEntities& entitiesToDelete) { QMutexLocker lock(&_mutex); - for (auto entity : _entitiesToDelete) { - // push this entity onto the external list - entitiesToDelete.push_back(entity); - } - _entitiesToDelete.clear(); + entitiesToDelete.swap(_deadEntities); + _deadEntities.clear(); } void EntitySimulation::removeEntityInternal(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - // remove from all internal lists except _entitiesToDelete + // remove from all internal lists except _deadEntities _mortalEntities.remove(entity); _entitiesToUpdate.remove(entity); _entitiesToSort.remove(entity); @@ -67,42 +63,23 @@ void EntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { QMutexLocker lock(&_mutex); entity->clearActions(getThisPointer()); removeEntityInternal(entity); - _entitiesToDelete.insert(entity); - } -} - -void EntitySimulation::addEntityInternal(EntityItemPointer entity) { - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { - QMutexLocker lock(&_mutex); - _simpleKinematicEntities.insert(entity); - entity->setLastSimulated(usecTimestampNow()); - } -} - -void EntitySimulation::changeEntityInternal(EntityItemPointer entity) { - QMutexLocker lock(&_mutex); - if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { - int numKinematicEntities = _simpleKinematicEntities.size(); - _simpleKinematicEntities.insert(entity); - if (numKinematicEntities != _simpleKinematicEntities.size()) { - entity->setLastSimulated(usecTimestampNow()); + if (entity->getElement()) { + _deadEntities.insert(entity); } - } else { - _simpleKinematicEntities.remove(entity); } } // protected -void EntitySimulation::expireMortalEntities(const quint64& now) { +void EntitySimulation::expireMortalEntities(uint64_t now) { if (now > _nextExpiry) { PROFILE_RANGE_EX(simulation_physics, "ExpireMortals", 0xffff00ff, (uint64_t)_mortalEntities.size()); // only search for expired entities if we expect to find one - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _mortalEntities.begin(); while (itemItr != _mortalEntities.end()) { EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < now) { itemItr = _mortalEntities.erase(itemItr); entity->die(); @@ -122,7 +99,7 @@ void EntitySimulation::expireMortalEntities(const quint64& now) { } // protected -void EntitySimulation::callUpdateOnEntitiesThatNeedIt(const quint64& now) { +void EntitySimulation::callUpdateOnEntitiesThatNeedIt(uint64_t now) { PerformanceTimer perfTimer("updatingEntities"); QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _entitiesToUpdate.begin(); @@ -176,7 +153,7 @@ void EntitySimulation::addEntity(EntityItemPointer entity) { entity->deserializeActions(); if (entity->isMortal()) { _mortalEntities.insert(entity); - quint64 expiry = entity->getExpiry(); + uint64_t expiry = entity->getExpiry(); if (expiry < _nextExpiry) { _nextExpiry = expiry; } @@ -207,7 +184,6 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { // Although it is not the responsibility of the EntitySimulation to sort the tree for EXTERNAL changes // it IS responsibile for triggering deletes for entities that leave the bounds of the domain, hence // we must check for that case here, however we rely on the change event to have set DIRTY_POSITION flag. - bool wasRemoved = false; uint32_t dirtyFlags = entity->getDirtyFlags(); if (dirtyFlags & Simulation::DIRTY_POSITION) { AACube domainBounds(glm::vec3((float)-HALF_TREE_SCALE), (float)TREE_SCALE); @@ -217,50 +193,45 @@ void EntitySimulation::changeEntity(EntityItemPointer entity) { qCDebug(entities) << "Entity " << entity->getEntityItemID() << " moved out of domain bounds."; entity->die(); prepareEntityForDelete(entity); - wasRemoved = true; + return; } } - if (!wasRemoved) { - if (dirtyFlags & Simulation::DIRTY_LIFETIME) { - if (entity->isMortal()) { - _mortalEntities.insert(entity); - quint64 expiry = entity->getExpiry(); - if (expiry < _nextExpiry) { - _nextExpiry = expiry; - } - } else { - _mortalEntities.remove(entity); + + if (dirtyFlags & Simulation::DIRTY_LIFETIME) { + if (entity->isMortal()) { + _mortalEntities.insert(entity); + uint64_t expiry = entity->getExpiry(); + if (expiry < _nextExpiry) { + _nextExpiry = expiry; } - entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME); - } - if (entity->needsToCallUpdate()) { - _entitiesToUpdate.insert(entity); } else { - _entitiesToUpdate.remove(entity); + _mortalEntities.remove(entity); } - changeEntityInternal(entity); + entity->clearDirtyFlags(Simulation::DIRTY_LIFETIME); } + if (entity->needsToCallUpdate()) { + _entitiesToUpdate.insert(entity); + } else { + _entitiesToUpdate.remove(entity); + } + changeEntityInternal(entity); } void EntitySimulation::clearEntities() { QMutexLocker lock(&_mutex); _mortalEntities.clear(); - _nextExpiry = quint64(-1); + _nextExpiry = std::numeric_limits::max(); _entitiesToUpdate.clear(); _entitiesToSort.clear(); _simpleKinematicEntities.clear(); clearEntitiesInternal(); - for (auto entity : _allEntities) { - entity->setSimulated(false); - entity->die(); - } _allEntities.clear(); - _entitiesToDelete.clear(); + _deadEntities.clear(); } -void EntitySimulation::moveSimpleKinematics(const quint64& now) { +void EntitySimulation::moveSimpleKinematics(uint64_t now) { PROFILE_RANGE_EX(simulation_physics, "MoveSimples", 0xffff00ff, (uint64_t)_simpleKinematicEntities.size()); SetOfEntities::iterator itemItr = _simpleKinematicEntities.begin(); while (itemItr != _simpleKinematicEntities.end()) { diff --git a/libraries/entities/src/EntitySimulation.h b/libraries/entities/src/EntitySimulation.h index 1c633aa9dc..b19e1c33d3 100644 --- a/libraries/entities/src/EntitySimulation.h +++ b/libraries/entities/src/EntitySimulation.h @@ -12,6 +12,8 @@ #ifndef hifi_EntitySimulation_h #define hifi_EntitySimulation_h +#include + #include #include #include @@ -43,9 +45,8 @@ const int DIRTY_SIMULATION_FLAGS = Simulation::DIRTY_SIMULATOR_ID; class EntitySimulation : public QObject, public std::enable_shared_from_this { -Q_OBJECT public: - EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(quint64(-1)) { } + EntitySimulation() : _mutex(QMutex::Recursive), _entityTree(NULL), _nextExpiry(std::numeric_limits::max()) { } virtual ~EntitySimulation() { setEntityTree(NULL); } inline EntitySimulationPointer getThisPointer() const { @@ -57,8 +58,6 @@ public: void updateEntities(); -// friend class EntityTree; - virtual void addDynamic(EntityDynamicPointer dynamic); virtual void removeDynamic(const QUuid dynamicID); virtual void removeDynamics(QList dynamicIDsToRemove); @@ -74,29 +73,26 @@ public: void clearEntities(); - void moveSimpleKinematics(const quint64& now); + void moveSimpleKinematics(uint64_t now); EntityTreePointer getEntityTree() { return _entityTree; } - virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete); + virtual void takeDeadEntities(SetOfEntities& entitiesToDelete); /// \param entity pointer to EntityItem that needs to be put on the entitiesToDelete list and removed from others. virtual void prepareEntityForDelete(EntityItemPointer entity); -signals: - void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); - protected: // These pure virtual methods are protected because they are not to be called will-nilly. The base class // calls them in the right places. - virtual void updateEntitiesInternal(const quint64& now) = 0; - virtual void addEntityInternal(EntityItemPointer entity); - virtual void removeEntityInternal(EntityItemPointer entity) = 0; - virtual void changeEntityInternal(EntityItemPointer entity); + virtual void updateEntitiesInternal(uint64_t now) = 0; + virtual void addEntityInternal(EntityItemPointer entity) = 0; + virtual void removeEntityInternal(EntityItemPointer entity); + virtual void changeEntityInternal(EntityItemPointer entity) = 0; virtual void clearEntitiesInternal() = 0; - void expireMortalEntities(const quint64& now); - void callUpdateOnEntitiesThatNeedIt(const quint64& now); + void expireMortalEntities(uint64_t now); + void callUpdateOnEntitiesThatNeedIt(uint64_t now); virtual void sortEntitiesThatMoved(); QMutex _mutex{ QMutex::Recursive }; @@ -108,7 +104,7 @@ protected: QMutex _dynamicsMutex { QMutex::Recursive }; protected: - SetOfEntities _entitiesToDelete; // entities simulation decided needed to be deleted (EntityTree will actually delete) + SetOfEntities _deadEntities; private: void moveSimpleKinematics(); @@ -120,11 +116,10 @@ private: // An entity may be in more than one list. SetOfEntities _allEntities; // tracks all entities added the simulation SetOfEntities _mortalEntities; // entities that have an expiry - quint64 _nextExpiry; + uint64_t _nextExpiry; SetOfEntities _entitiesToUpdate; // entities that need to call EntityItem::update() - }; #endif // hifi_EntitySimulation_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 2cf66911a4..7f6a7087cf 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -94,7 +94,6 @@ OctreeElementPointer EntityTree::createNewElement(unsigned char* octalCode) { void EntityTree::eraseAllOctreeElements(bool createNewRoot) { emit clearingEntities(); - // this would be a good place to clean up our entities... if (_simulation) { _simulation->clearEntities(); } @@ -260,7 +259,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { return; } } - + // check to see if we need to simulate this entity.. if (_simulation) { _simulation->addEntity(entity); @@ -453,12 +452,13 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti uint32_t newFlags = entity->getDirtyFlags() & ~preFlags; if (newFlags) { - if (_simulation) { + if (entity->isSimulated()) { + assert((bool)_simulation); if (newFlags & DIRTY_SIMULATION_FLAGS) { _simulation->changeEntity(entity); } } else { - // normally the _simulation clears ALL updateFlags, but since there is none we do it explicitly + // normally the _simulation clears ALL dirtyFlags, but when not possible we do it explicitly entity->clearDirtyFlags(); } } @@ -469,7 +469,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti if (entityScriptBefore != entityScriptAfter || reload) { emitEntityScriptChanging(entity->getEntityItemID(), reload); // the entity script has changed } - } + } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). if (!entity->getElement()) { @@ -551,8 +551,6 @@ void EntityTree::setSimulation(EntitySimulationPointer simulation) { assert(simulation->getEntityTree().get() == this); } if (_simulation && _simulation != simulation) { - // It's important to clearEntities() on the simulation since taht will update each - // EntityItem::_simulationState correctly so as to not confuse the next _simulation. _simulation->clearEntities(); } _simulation = simulation; @@ -650,7 +648,7 @@ void EntityTree::deleteEntities(QSet entityIDs, bool force, bool i emit deletingEntityPointer(existingEntity.get()); } - if (theOperator.getEntities().size() > 0) { + if (!theOperator.getEntities().empty()) { recurseTreeWithOperator(&theOperator); processRemovedEntities(theOperator); _isDirty = true; @@ -692,7 +690,7 @@ void EntityTree::processRemovedEntities(const DeleteEntityOperator& theOperator) trackDeletedEntity(theEntity->getEntityItemID()); } - if (_simulation) { + if (theEntity->isSimulated()) { _simulation->prepareEntityForDelete(theEntity); } } @@ -1688,7 +1686,7 @@ void EntityTree::releaseSceneEncodeData(OctreeElementExtraEncodeData* extraEncod } void EntityTree::entityChanged(EntityItemPointer entity) { - if (_simulation) { + if (entity->isSimulated()) { _simulation->changeEntity(entity); } } @@ -1802,13 +1800,13 @@ void EntityTree::update(bool simulate) { _simulation->updateEntities(); { PROFILE_RANGE(simulation_physics, "Deletes"); - VectorOfEntities pendingDeletes; - _simulation->takeEntitiesToDelete(pendingDeletes); - if (pendingDeletes.size() > 0) { + SetOfEntities deadEntities; + _simulation->takeDeadEntities(deadEntities); + if (!deadEntities.empty()) { // translate into list of ID's QSet idsToDelete; - for (auto entity : pendingDeletes) { + for (auto entity : deadEntities) { idsToDelete.insert(entity->getEntityItemID()); } diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index 137d6ef396..8b595bf69d 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -267,8 +267,11 @@ void MaterialEntityItem::setOwningAvatarID(const QUuid& owningAvatarID) { void MaterialEntityItem::removeMaterial() { graphics::MaterialPointer material = getMaterial(); + if (!material) { + return; + } QUuid parentID = getClientOnly() ? getOwningAvatarID() : getParentID(); - if (!material || parentID.isNull()) { + if (parentID.isNull()) { return; } @@ -336,4 +339,4 @@ void MaterialEntityItem::update(const quint64& now) { } EntityItem::update(now); -} \ No newline at end of file +} diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 3203c2968c..a2aba0169d 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -18,7 +18,7 @@ #include "EntityItem.h" #include "EntitiesLogging.h" -const quint64 MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; +const uint64_t MAX_OWNERLESS_PERIOD = 2 * USECS_PER_SECOND; void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { QMutexLocker lock(&_mutex); @@ -33,7 +33,7 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { if (entity->getDynamic() && entity->hasLocalVelocity()) { // it is still moving dynamically --> add to orphaned list _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; if (expiry < _nextOwnerlessExpiry) { _nextOwnerlessExpiry = expiry; } @@ -50,15 +50,15 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { } } -void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { +void SimpleEntitySimulation::updateEntitiesInternal(uint64_t now) { if (now > _nextOwnerlessExpiry) { // search for ownerless objects that have expired QMutexLocker lock(&_mutex); - _nextOwnerlessExpiry = -1; + _nextOwnerlessExpiry = std::numeric_limits::max(); SetOfEntities::iterator itemItr = _entitiesThatNeedSimulationOwner.begin(); while (itemItr != _entitiesThatNeedSimulationOwner.end()) { EntityItemPointer entity = *itemItr; - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; if (expiry < now) { // no simulators have volunteered ownership --> remove from list itemItr = _entitiesThatNeedSimulationOwner.erase(itemItr); @@ -85,14 +85,18 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { } void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { - EntitySimulation::addEntityInternal(entity); + if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + QMutexLocker lock(&_mutex); + _simpleKinematicEntities.insert(entity); + entity->setLastSimulated(usecTimestampNow()); + } if (!entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.insert(entity); } else if (entity->getDynamic() && entity->hasLocalVelocity()) { QMutexLocker lock(&_mutex); _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; if (expiry < _nextOwnerlessExpiry) { _nextOwnerlessExpiry = expiry; } @@ -101,19 +105,29 @@ void SimpleEntitySimulation::addEntityInternal(EntityItemPointer entity) { void SimpleEntitySimulation::removeEntityInternal(EntityItemPointer entity) { EntitySimulation::removeEntityInternal(entity); - QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.remove(entity); _entitiesThatNeedSimulationOwner.remove(entity); } void SimpleEntitySimulation::changeEntityInternal(EntityItemPointer entity) { - EntitySimulation::changeEntityInternal(entity); + { + QMutexLocker lock(&_mutex); + if (entity->isMovingRelativeToParent() && !entity->getPhysicsInfo()) { + int numKinematicEntities = _simpleKinematicEntities.size(); + _simpleKinematicEntities.insert(entity); + if (numKinematicEntities != _simpleKinematicEntities.size()) { + entity->setLastSimulated(usecTimestampNow()); + } + } else { + _simpleKinematicEntities.remove(entity); + } + } if (entity->getSimulatorID().isNull()) { QMutexLocker lock(&_mutex); _entitiesWithSimulationOwner.remove(entity); if (entity->getDynamic() && entity->hasLocalVelocity()) { _entitiesThatNeedSimulationOwner.insert(entity); - quint64 expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; + uint64_t expiry = entity->getLastChangedOnServer() + MAX_OWNERLESS_PERIOD; if (expiry < _nextOwnerlessExpiry) { _nextOwnerlessExpiry = expiry; } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 9c7c9a374e..95c996a920 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -28,7 +28,7 @@ public: void clearOwnership(const QUuid& ownerID); protected: - virtual void updateEntitiesInternal(const quint64& now) override; + virtual void updateEntitiesInternal(uint64_t now) override; virtual void addEntityInternal(EntityItemPointer entity) override; virtual void removeEntityInternal(EntityItemPointer entity) override; virtual void changeEntityInternal(EntityItemPointer entity) override; @@ -38,7 +38,7 @@ protected: SetOfEntities _entitiesWithSimulationOwner; SetOfEntities _entitiesThatNeedSimulationOwner; - quint64 _nextOwnerlessExpiry { 0 }; + uint64_t _nextOwnerlessExpiry { 0 }; }; #endif // hifi_SimpleEntitySimulation_h diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp index 4398582673..b312c6deb9 100644 --- a/libraries/entities/src/SimulationOwner.cpp +++ b/libraries/entities/src/SimulationOwner.cpp @@ -16,9 +16,9 @@ #include -const quint8 PENDING_STATE_NOTHING = 0; -const quint8 PENDING_STATE_TAKE = 1; -const quint8 PENDING_STATE_RELEASE = 2; +const uint8_t PENDING_STATE_NOTHING = 0; +const uint8_t PENDING_STATE_TAKE = 1; +const uint8_t PENDING_STATE_RELEASE = 2; // static const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; @@ -33,7 +33,7 @@ SimulationOwner::SimulationOwner() : { } -SimulationOwner::SimulationOwner(const QUuid& id, quint8 priority) : +SimulationOwner::SimulationOwner(const QUuid& id, uint8_t priority) : _id(id), _expiry(0), _pendingBidTimestamp(0), @@ -67,11 +67,11 @@ void SimulationOwner::clear() { _pendingState = PENDING_STATE_NOTHING; } -void SimulationOwner::setPriority(quint8 priority) { +void SimulationOwner::setPriority(uint8_t priority) { _priority = priority; } -void SimulationOwner::promotePriority(quint8 priority) { +void SimulationOwner::promotePriority(uint8_t priority) { if (priority > _priority) { _priority = priority; } @@ -89,7 +89,7 @@ bool SimulationOwner::setID(const QUuid& id) { return false; } -bool SimulationOwner::set(const QUuid& id, quint8 priority) { +bool SimulationOwner::set(const QUuid& id, uint8_t priority) { uint8_t oldPriority = _priority; setPriority(priority); return setID(id) || oldPriority != _priority; @@ -101,22 +101,22 @@ bool SimulationOwner::set(const SimulationOwner& owner) { return setID(owner._id) || oldPriority != _priority; } -void SimulationOwner::setPendingPriority(quint8 priority, const quint64& timestamp) { +void SimulationOwner::setPendingPriority(uint8_t priority, uint64_t timestamp) { _pendingBidPriority = priority; _pendingBidTimestamp = timestamp; _pendingState = (_pendingBidPriority == 0) ? PENDING_STATE_RELEASE : PENDING_STATE_TAKE; } void SimulationOwner::updateExpiry() { - const quint64 OWNERSHIP_LOCKOUT_EXPIRY = USECS_PER_SECOND / 5; + const uint64_t OWNERSHIP_LOCKOUT_EXPIRY = 200 * USECS_PER_MSEC; _expiry = usecTimestampNow() + OWNERSHIP_LOCKOUT_EXPIRY; } -bool SimulationOwner::pendingRelease(const quint64& timestamp) { +bool SimulationOwner::pendingRelease(uint64_t timestamp) { return _pendingBidPriority == 0 && _pendingState == PENDING_STATE_RELEASE && _pendingBidTimestamp >= timestamp; } -bool SimulationOwner::pendingTake(const quint64& timestamp) { +bool SimulationOwner::pendingTake(uint64_t timestamp) { return _pendingBidPriority > 0 && _pendingState == PENDING_STATE_TAKE && _pendingBidTimestamp >= timestamp; } @@ -142,7 +142,7 @@ void SimulationOwner::test() { { // test set constructor QUuid id = QUuid::createUuid(); - quint8 priority = 128; + uint8_t priority = 128; SimulationOwner simOwner(id, priority); if (simOwner.isNull()) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR : SimulationOwner should NOT be NULL" << std::endl; @@ -164,7 +164,7 @@ void SimulationOwner::test() { { // test set() QUuid id = QUuid::createUuid(); - quint8 priority = 1; + uint8_t priority = 1; SimulationOwner simOwner; simOwner.set(id, priority); if (simOwner.isNull()) { diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index 94ed1d9a08..cc2069dcc8 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -18,20 +18,88 @@ #include #include -// Simulation observers will bid to simulate unowned active objects at the lowest possible priority -// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it -// to RECRUIT priority so that other volunteers don't accidentally take over. -const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01; -const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; +// HighFidelity uses a distributed physics simulation where multiple "participants" simulate portions +// of the same world. When portions overlap only one participant is allowed to be the authority for any +// particular object. For a simulated entity the authoritative participant is called the simulation "owner" and +// their duty is to send transform/velocity updates for the entity to the central entity-server. +// The entity-server relays updates to other participants who apply them as "state synchronization" +// to their own simulation. +// +// Participants acquire ownership by sending a "bid" to the entity-server. The bid is a properties update: +// { +// "simulationOwner": { "ownerID" : sessionID, "priority" : priority }, +// transform/velocity properties +// } +// +// The entity-server is the authority as to who owns what and may reject a bid. +// The rules for handling a bid are as follows: +// +// (1) A bid may be refused for special ownership restrictions, but otherwise... +// +// (2) A bid at higher priority is accepted +// +// (3) A bid at equal priority is rejected if receieved within a grace-period (200msec) +// of the last ownership transition, otherwise it is accepted +// +// (4) The current owner is the only participant allowed to clear ownership (entity-server can override). +// +// (5) The current owner is the only participant allowed to adjust priority (entity-server can override). +// +// (6) If an owner does not update the transform or velocities of an owned entity within some period +// (5 seconds) then ownership is cleared and the entity's velocities are zeroed. This to handle +// the case when an owner drops off the network. +// +// The priority of a participant's bid depends on how "interested" it is in the entity's motion. The rules +// for bidding are as follows: +// +// (7) A participant (almost) never assumes that a bid is accepted by the entity-server. It packs the +// simulation owner and priority as if they really did change but doesn't actually modify them +// locally. Thus, if the bid packet is lost the participant will re-send after some period. +// The participant only updates its knowledge of who owns what when it recieves an update from the +// entity-server. An exception is when the participant creates a moving entity: it assumes it starts +// off owning any moving entities it creates. +// +// (8) When an unowned entity becomes active in the physics simulation the participant will +// start a timer and if the entity is still unowned after some period (0.5 seconds) +// it will bid at priority = VOLUNTEER (=2). The entity-server never grants ownership at VOLUNTEER +// priority: when a VOLUNTEER bid is accepted the entity-server always promotes the priority to +// RECRUIT (VOLUNTEER + 1); this to avoid a race-condition which might rapidly transition ownership +// when multiple participants (with variable ping-times to the server) bid simultaneously for a +// recently activated entity. +// +// (9) When a participant changes an entity's transform/velocity it will bid at priority = POKE (=127) +// +// (10) When an entity touches MyAvatar the participant it will bid at priority = POKE. +// +// (11) When a participant grabs an entity it will bid at priority = GRAB (=128). +// +// (12) When entityA, locally owned at priority = N, collides with an unowned entityB the owner will +// also bid for entityB at priority = N-1 (or VOLUNTEER, whichever is larger). +// +// (13) When an entity comes to rest and is deactivated in the physics simulation the owner will +// send an update to: clear their ownerhsip, set priority to zero, and set the object's +// velocities to be zero. As per a normal bid, the owner does NOT assume that its ownership +// has been cleared until it hears from the entity-server. This, if the packet is lost the +// owner will re-send after some period. +// +// (14) When an entity's ownership priority drops below VOLUNTEER other participants may bid for it +// immediately at priority = VOLUNTEER. +// +// (15) When an entity is still active but the owner no longer wants to own it, it will drop its priority +// to YIELD (=1, less than VOLUNTEER) thereby signalling to other participants to bid for it. +// +const uint8_t YIELD_SIMULATION_PRIORITY = 1; +const uint8_t VOLUNTEER_SIMULATION_PRIORITY = YIELD_SIMULATION_PRIORITY + 1; +const uint8_t RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; // When poking objects with scripts an observer will bid at SCRIPT_EDIT priority. -const quint8 SCRIPT_GRAB_SIMULATION_PRIORITY = 0x80; -const quint8 SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; -const quint8 AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; +const uint8_t SCRIPT_GRAB_SIMULATION_PRIORITY = 128; +const uint8_t SCRIPT_POKE_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY - 1; +const uint8_t AVATAR_ENTITY_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY + 1; // PERSONAL priority (needs a better name) is the level at which a simulation observer owns its own avatar // which really just means: things that collide with it will be bid at a priority level one lower -const quint8 PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY; +const uint8_t PERSONAL_SIMULATION_PRIORITY = SCRIPT_GRAB_SIMULATION_PRIORITY; class SimulationOwner { @@ -39,25 +107,25 @@ public: static const int NUM_BYTES_ENCODED; SimulationOwner(); - SimulationOwner(const QUuid& id, quint8 priority); + SimulationOwner(const QUuid& id, uint8_t priority); const QUuid& getID() const { return _id; } - const quint64& getExpiry() const { return _expiry; } - quint8 getPriority() const { return _priority; } + const uint64_t& getExpiry() const { return _expiry; } + uint8_t getPriority() const { return _priority; } QByteArray toByteArray() const; bool fromByteArray(const QByteArray& data); void clear(); - void setPriority(quint8 priority); - void promotePriority(quint8 priority); + void setPriority(uint8_t priority); + void promotePriority(uint8_t priority); // return true if id is changed bool setID(const QUuid& id); - bool set(const QUuid& id, quint8 priority); + bool set(const QUuid& id, uint8_t priority); bool set(const SimulationOwner& owner); - void setPendingPriority(quint8 priority, const quint64& timestamp); + void setPendingPriority(uint8_t priority, uint64_t timestamp); bool isNull() const { return _id.isNull(); } bool matchesValidID(const QUuid& id) const { return _id == id && !_id.isNull(); } @@ -67,11 +135,11 @@ public: bool hasExpired() const { return usecTimestampNow() > _expiry; } uint8_t getPendingPriority() const { return _pendingBidPriority; } - bool pendingRelease(const quint64& timestamp); // return true if valid pending RELEASE - bool pendingTake(const quint64& timestamp); // return true if valid pending TAKE + bool pendingRelease(uint64_t timestamp); // return true if valid pending RELEASE + bool pendingTake(uint64_t timestamp); // return true if valid pending TAKE void clearCurrentOwner(); - bool operator>=(quint8 priority) const { return _priority >= priority; } + bool operator>=(uint8_t priority) const { return _priority >= priority; } bool operator==(const SimulationOwner& other) { return (_id == other._id && _priority == other._priority); } bool operator!=(const SimulationOwner& other); @@ -84,11 +152,11 @@ public: private: QUuid _id; // owner - quint64 _expiry; // time when ownership can transition at equal priority - quint64 _pendingBidTimestamp; // time when pending bid was set - quint8 _priority; // priority of current owner - quint8 _pendingBidPriority; // priority at which we'd like to own it - quint8 _pendingState; // NOTHING, TAKE, or RELEASE + uint64_t _expiry; // time when ownership can transition at equal priority + uint64_t _pendingBidTimestamp; // time when pending bid was set + uint8_t _priority; // priority of current owner + uint8_t _pendingBidPriority; // priority at which we'd like to own it + uint8_t _pendingState; // NOTHING, TAKE, or RELEASE }; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index f7cb2c4f85..dc9e678a9e 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -77,6 +77,17 @@ QUrl AddressManager::currentShareableAddress(bool domainOnly) const { } } +QUrl AddressManager::currentPublicAddress(bool domainOnly) const { + // return an address that can be used by others to visit this client's current location. If + // in a serverless domain (which can't be visited) return an empty URL. + QUrl shareableAddress = currentShareableAddress(domainOnly); + if (shareableAddress.scheme() != URL_SCHEME_HIFI) { + return QUrl(); // file: urls aren't public + } + return shareableAddress; +} + + QUrl AddressManager::currentFacingShareableAddress() const { auto hifiURL = currentShareableAddress(); if (hifiURL.scheme() == URL_SCHEME_HIFI) { @@ -86,6 +97,17 @@ QUrl AddressManager::currentFacingShareableAddress() const { return hifiURL; } +QUrl AddressManager::currentFacingPublicAddress() const { + // return an address that can be used by others to visit this client's current location. If + // in a serverless domain (which can't be visited) return an empty URL. + QUrl shareableAddress = currentFacingShareableAddress(); + if (shareableAddress.scheme() != URL_SCHEME_HIFI) { + return QUrl(); // file: urls aren't public + } + return shareableAddress; +} + + void AddressManager::loadSettings(const QString& lookupString) { #if defined(USE_GLES) && defined(Q_OS_WIN) handleUrl(QUrl("hifi://127.0.0.0"), LookupTrigger::StartupFromSettings); @@ -286,6 +308,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { // lookupUrl.scheme() == URL_SCHEME_HTTP || // lookupUrl.scheme() == URL_SCHEME_HTTPS || _previousLookup.clear(); + _shareablePlaceName.clear(); QUrl domainURL = PathUtils::expandToLocalDataAbsolutePath(lookupUrl); setDomainInfo(domainURL, trigger); emit lookupResultsFinished(); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index dc1046bf51..4add1e9414 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -150,7 +150,9 @@ public: QUrl currentAddress(bool domainOnly = false) const; QUrl currentFacingAddress() const; QUrl currentShareableAddress(bool domainOnly = false) const; + QUrl currentPublicAddress(bool domainOnly = false) const; QUrl currentFacingShareableAddress() const; + QUrl currentFacingPublicAddress() const; QString currentPath(bool withOrientation = true) const; QString currentFacingPath() const; diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 7c5b7eb45c..e6c28f75e8 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -192,17 +192,12 @@ bool OctreePersistThread::process() { QString lockFileName = _filename + ".lock"; std::ifstream lockFile(qPrintable(lockFileName), std::ios::in | std::ios::binary | std::ios::ate); if (lockFile.is_open()) { - qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName - << "-- Attempting to restore from previous backup file."; - - // This is where we should attempt to find the most recent backup and restore from - // that file as our persist file. - restoreFromMostRecentBackup(); + qCDebug(octree) << "WARNING: Octree lock file detected at startup:" << lockFileName; lockFile.close(); - qCDebug(octree) << "Loading Octree... lock file closed:" << lockFileName; + qCDebug(octree) << "Removing lock file:" << lockFileName; remove(qPrintable(lockFileName)); - qCDebug(octree) << "Loading Octree... lock file removed:" << lockFileName; + qCDebug(octree) << "Lock file removed:" << lockFileName; } persistentFileRead = _tree->readFromFile(qPrintable(_filename.toLocal8Bit())); diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 420da5a1e0..6a25700ce8 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -26,12 +26,7 @@ #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS #include "EntityTree.h" -#endif -const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; -const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; - -#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool EntityMotionState::entityTreeIsLocked() const { EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -46,11 +41,13 @@ bool entityTreeIsLocked() { } #endif +const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; +const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; + EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : ObjectMotionState(nullptr), - _entityPtr(entity), - _entity(entity.get()), + _entity(entity), _serverPosition(0.0f), _serverRotation(), _serverVelocity(0.0f), @@ -60,7 +57,7 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _serverActionData(QByteArray()), _lastVelocity(0.0f), _measuredAcceleration(0.0f), - _nextOwnershipBid(0), + _nextBidExpiry(0), _measuredDeltaTime(0.0f), _lastMeasureStep(0), _lastStep(0), @@ -68,6 +65,12 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _accelerationNearlyGravityCount(0), _numInactiveUpdates(1) { + // Why is _numInactiveUpdates initialied to 1? + // Because: when an entity is first created by a LOCAL operatioin its local simulation ownership is assumed, + // which causes it to be immediately placed on the 'owned' list, but in this case an "update" already just + // went out for the object's creation and there is no need to send another. By initializing _numInactiveUpdates + // to 1 here we trick remoteSimulationOutOfSync() to return "false" the first time through for this case. + _type = MOTIONSTATE_TYPE_ENTITY; assert(_entity); assert(entityTreeIsLocked()); @@ -76,77 +79,89 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer // rather than pass the legit shape pointer to the ObjectMotionState ctor above. setShape(shape); - _outgoingPriority = _entity->getPendingOwnershipPriority(); -} - -EntityMotionState::~EntityMotionState() { - assert(_entity); - _entity = nullptr; -} - -void EntityMotionState::updateServerPhysicsVariables() { - assert(entityTreeIsLocked()); - if (isLocallyOwned()) { - // don't slam these values if we are the simulation owner - return; + _bidPriority = _entity->getPendingOwnershipPriority(); + if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { + // client-only entities are always thus, so we cache this fact in _ownershipState + _ownershipState = EntityMotionState::OwnershipState::Unownable; } Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); - _serverVariablesSet = true; _serverPosition = localTransform.getTranslation(); _serverRotation = localTransform.getRotation(); _serverAcceleration = _entity->getAcceleration(); _serverActionData = _entity->getDynamicData(); + +} + +EntityMotionState::~EntityMotionState() { + if (_entity) { + assert(_entity->getPhysicsInfo() == this); + _entity->setPhysicsInfo(nullptr); + _entity.reset(); + } +} + +void EntityMotionState::updateServerPhysicsVariables() { + if (_ownershipState != EntityMotionState::OwnershipState::LocallyOwned) { + // only slam these values if we are NOT the simulation owner + Transform localTransform; + _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); + _serverPosition = localTransform.getTranslation(); + _serverRotation = localTransform.getRotation(); + _serverAcceleration = _entity->getAcceleration(); + _serverActionData = _entity->getDynamicData(); + _lastStep = ObjectMotionState::getWorldSimulationStep(); + } } void EntityMotionState::handleDeactivation() { - if (_serverVariablesSet) { - // copy _server data to entity - Transform localTransform = _entity->getLocalTransform(); - localTransform.setTranslation(_serverPosition); - localTransform.setRotation(_serverRotation); - _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); - // and also to RigidBody - btTransform worldTrans; - worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); - worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); - _body->setWorldTransform(worldTrans); - // no need to update velocities... should already be zero - } + // copy _server data to entity + Transform localTransform = _entity->getLocalTransform(); + localTransform.setTranslation(_serverPosition); + localTransform.setRotation(_serverRotation); + _entity->setLocalTransformAndVelocities(localTransform, ENTITY_ITEM_ZERO_VEC3, ENTITY_ITEM_ZERO_VEC3); + // and also to RigidBody + btTransform worldTrans; + worldTrans.setOrigin(glmToBullet(_entity->getWorldPosition())); + worldTrans.setRotation(glmToBullet(_entity->getWorldOrientation())); + _body->setWorldTransform(worldTrans); + // no need to update velocities... should already be zero } // virtual void EntityMotionState::handleEasyChanges(uint32_t& flags) { - assert(_entity); assert(entityTreeIsLocked()); updateServerPhysicsVariables(); ObjectMotionState::handleEasyChanges(flags); if (flags & Simulation::DIRTY_SIMULATOR_ID) { if (_entity->getSimulatorID().isNull()) { - // simulation ownership has been removed by an external simulator + // simulation ownership has been removed if (glm::length2(_entity->getWorldVelocity()) == 0.0f) { - // this object is coming to rest --> clear the ACTIVATION flag and _outgoingPriority + // this object is coming to rest --> clear the ACTIVATION flag and _bidPriority flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; _body->setActivationState(WANTS_DEACTIVATION); - _outgoingPriority = 0; + _bidPriority = 0; const float ACTIVATION_EXPIRY = 3.0f; // something larger than the 2.0 hard coded in Bullet _body->setDeactivationTime(ACTIVATION_EXPIRY); } else { // disowned object is still moving --> start timer for ownership bid // TODO? put a delay in here proportional to distance from object? - upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); + _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; } _loopsWithoutOwner = 0; _numInactiveUpdates = 0; } else if (isLocallyOwned()) { // we just inherited ownership, make sure our desired priority matches what we have - upgradeOutgoingPriority(_entity->getSimulationPriority()); + upgradeBidPriority(_entity->getSimulationPriority()); } else { - _outgoingPriority = 0; - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; + // the entity is owned by someone else, so we clear _bidPriority here + // but _bidPriority may be updated to non-zero value if this object interacts with locally owned simulation + // in which case we may try to bid again + _bidPriority = 0; + _nextBidExpiry = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; _numInactiveUpdates = 0; } } @@ -155,10 +170,10 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // (1) we own it but may need to change the priority OR... // (2) we don't own it but should bid (because a local script has been changing physics properties) uint8_t newPriority = isLocallyOwned() ? _entity->getSimulationOwner().getPriority() : _entity->getSimulationOwner().getPendingPriority(); - upgradeOutgoingPriority(newPriority); + upgradeBidPriority(newPriority); // reset bid expiry so that we bid ASAP - _nextOwnershipBid = 0; + _nextBidExpiry = 0; } if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { if (_body->isKinematicObject()) { @@ -175,7 +190,6 @@ void EntityMotionState::handleEasyChanges(uint32_t& flags) { // virtual bool EntityMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) { - assert(_entity); updateServerPhysicsVariables(); return ObjectMotionState::handleHardAndEasyChanges(flags, engine); } @@ -253,7 +267,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { // This callback is invoked by the physics simulation at the end of each simulation step... // iff the corresponding RigidBody is DYNAMIC and ACTIVE. void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { - assert(_entity); assert(entityTreeIsLocked()); measureBodyAcceleration(); @@ -285,21 +298,10 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { if (_entity->getSimulatorID().isNull()) { _loopsWithoutOwner++; - if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { - upgradeOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); + if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextBidExpiry) { + upgradeBidPriority(VOLUNTEER_SIMULATION_PRIORITY); } } - - #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); - qCDebug(physics) << "EntityMotionState::setWorldTransform()... changed entity:" << _entity->getEntityItemID(); - qCDebug(physics) << " last edited:" << _entity->getLastEdited() - << formatUsecTime(now - _entity->getLastEdited()) << "ago"; - qCDebug(physics) << " last simulated:" << _entity->getLastSimulated() - << formatUsecTime(now - _entity->getLastSimulated()) << "ago"; - qCDebug(physics) << " last updated:" << _entity->getLastUpdated() - << formatUsecTime(now - _entity->getLastUpdated()) << "ago"; - #endif } @@ -323,19 +325,13 @@ void EntityMotionState::setShape(const btCollisionShape* shape) { } } -bool EntityMotionState::isCandidateForOwnership() const { - assert(_body); - assert(_entity); - assert(entityTreeIsLocked()); - return _outgoingPriority != 0 - || isLocallyOwned() - || _entity->dynamicDataNeedsTransmit(); -} - bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { - DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); // NOTE: we only get here if we think we own the simulation - assert(_body); + DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync"); + + // Since we own the simulation: make sure _bidPriority is not less than current owned priority + // because: an _bidPriority of zero indicates that we should drop ownership when we have it. + upgradeBidPriority(_entity->getSimulationPriority()); bool parentTransformSuccess; Transform localToWorld = _entity->getParentTransform(parentTransformSuccess); @@ -347,27 +343,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { worldVelocityToLocal.setTranslation(glm::vec3(0.0f)); } - // if we've never checked before, our _lastStep will be 0, and we need to initialize our state - if (_lastStep == 0) { - btTransform xform = _body->getWorldTransform(); - _serverVariablesSet = true; - _serverPosition = worldToLocal.transform(bulletToGLM(xform.getOrigin())); - _serverRotation = worldToLocal.getRotation() * bulletToGLM(xform.getRotation()); - _serverVelocity = worldVelocityToLocal.transform(getBodyLinearVelocityGTSigma()); - _serverAcceleration = Vectors::ZERO; - _serverAngularVelocity = worldVelocityToLocal.transform(bulletToGLM(_body->getAngularVelocity())); - _lastStep = simulationStep; - _serverActionData = _entity->getDynamicData(); - _numInactiveUpdates = 1; - return false; - } - - #ifdef WANT_DEBUG - glm::vec3 wasPosition = _serverPosition; - glm::quat wasRotation = _serverRotation; - glm::vec3 wasAngularVelocity = _serverAngularVelocity; - #endif - int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; @@ -378,9 +353,9 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _entity->clearSimulationOwnership(); return false; } - // we resend the inactive update every INACTIVE_UPDATE_PERIOD - // until it is removed from the outgoing updates - // (which happens when we don't own the simulation and it isn't touching our simulation) + // we resend the inactive update with a growing delay: every INACTIVE_UPDATE_PERIOD * _numInactiveUpdates + // until it is removed from the owned list + // (which happens when we no longer own the simulation) const float INACTIVE_UPDATE_PERIOD = 0.5f; return (dt > INACTIVE_UPDATE_PERIOD * (float)_numInactiveUpdates); } @@ -411,14 +386,10 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { if (_entity->dynamicDataNeedsTransmit()) { uint8_t priority = _entity->hasActions() ? SCRIPT_GRAB_SIMULATION_PRIORITY : SCRIPT_POKE_SIMULATION_PRIORITY; - upgradeOutgoingPriority(priority); + upgradeBidPriority(priority); return true; } - if (_entity->shouldSuppressLocationEdits()) { - return false; - } - // Else we measure the error between current and extrapolated transform (according to expected behavior // of remote EntitySimulation) and return true if the error is significant. @@ -440,13 +411,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { const float MIN_ERROR_RATIO_SQUARED = 0.0025f; // corresponds to 5% error in 1 second const float MIN_SPEED_SQUARED = 1.0e-6f; // corresponds to 1mm/sec if (speed2 < MIN_SPEED_SQUARED || dx2 / speed2 > MIN_ERROR_RATIO_SQUARED) { - #ifdef WANT_DEBUG - qCDebug(physics) << ".... (dx2 > MAX_POSITION_ERROR_SQUARED) ...."; - qCDebug(physics) << "wasPosition:" << wasPosition; - qCDebug(physics) << "bullet position:" << position; - qCDebug(physics) << "_serverPosition:" << _serverPosition; - qCDebug(physics) << "dx2:" << dx2; - #endif return true; } } @@ -466,22 +430,6 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { const float MIN_ROTATION_DOT = 0.99999f; // This corresponds to about 0.5 degrees of rotation glm::quat actualRotation = worldToLocal.getRotation() * bulletToGLM(worldTrans.getRotation()); - #ifdef WANT_DEBUG - if ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) { - qCDebug(physics) << ".... ((fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT)) ...."; - - qCDebug(physics) << "wasAngularVelocity:" << wasAngularVelocity; - qCDebug(physics) << "_serverAngularVelocity:" << _serverAngularVelocity; - - qCDebug(physics) << "length wasAngularVelocity:" << glm::length(wasAngularVelocity); - qCDebug(physics) << "length _serverAngularVelocity:" << glm::length(_serverAngularVelocity); - - qCDebug(physics) << "wasRotation:" << wasRotation; - qCDebug(physics) << "bullet actualRotation:" << actualRotation; - qCDebug(physics) << "_serverRotation:" << _serverRotation; - } - #endif - return (fabsf(glm::dot(actualRotation, _serverRotation)) < MIN_ROTATION_DOT); } @@ -489,14 +437,10 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { DETAILED_PROFILE_RANGE(simulation_physics, "ShouldSend"); // NOTE: we expect _entity and _body to be valid in this context, since shouldSendUpdate() is only called // after doesNotNeedToSendUpdate() returns false and that call should return 'true' if _entity or _body are NULL. - assert(_entity); - assert(_body); assert(entityTreeIsLocked()); - if (_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID()) { - // don't send updates for someone else's avatarEntities - return false; - } + // this case is prevented by setting _ownershipState to UNOWNABLE in EntityMotionState::ctor + assert(!(_entity->getClientOnly() && _entity->getOwningAvatarID() != Physics::getSessionUUID())); if (_entity->dynamicDataNeedsTransmit() || _entity->queryAACubeNeedsUpdate()) { return true; @@ -506,44 +450,19 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep) { return false; } - if (!isLocallyOwned()) { - // we don't own the simulation - - // NOTE: we do not volunteer to own kinematic or static objects - uint8_t volunteerPriority = _body->isStaticOrKinematicObject() ? VOLUNTEER_SIMULATION_PRIORITY : 0; - - bool shouldBid = _outgoingPriority > volunteerPriority && // but we would like to own it AND - usecTimestampNow() > _nextOwnershipBid; // it is time to bid again - if (shouldBid && _outgoingPriority < _entity->getSimulationPriority()) { - // we are insufficiently interested so clear _outgoingPriority - // and reset the bid expiry - _outgoingPriority = 0; - _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; - } - return shouldBid; - } else { - // When we own the simulation: make sure _outgoingPriority is not less than current owned priority - // because: an _outgoingPriority of zero indicates that we should drop ownership when we have it. - upgradeOutgoingPriority(_entity->getSimulationPriority()); - } - return remoteSimulationOutOfSync(simulationStep); } -void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { - DETAILED_PROFILE_RANGE(simulation_physics, "Send"); - assert(_entity); - assert(entityTreeIsLocked()); - +void EntityMotionState::updateSendVelocities() { if (!_body->isActive()) { // make sure all derivatives are zero - zeroCleanObjectVelocities(); - _numInactiveUpdates++; + clearObjectVelocities(); + _numInactiveUpdates = 1; } else { glm::vec3 gravity = _entity->getGravity(); - // if this entity has been accelerated at close to gravity for a certain number of simulation-steps, let - // the entity server's estimates include gravity. + // if this entity has been accelerated at close to gravity for a certain number of simulation-steps + // let the entity server's estimates include gravity. const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4; if (_accelerationNearlyGravityCount >= STEPS_TO_DECIDE_BALLISTIC) { _entity->setAcceleration(gravity); @@ -564,13 +483,82 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ if (movingSlowly) { // velocities might not be zero, but we'll fake them as such, which will hopefully help convince // other simulating observers to deactivate their own copies - zeroCleanObjectVelocities(); + clearObjectVelocities(); } } _numInactiveUpdates = 0; } +} - // remember properties for local server prediction +void EntityMotionState::sendBid(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Bid"); + assert(entityTreeIsLocked()); + + updateSendVelocities(); + + EntityItemProperties properties; + Transform localTransform; + glm::vec3 linearVelocity; + glm::vec3 angularVelocity; + _entity->getLocalTransformAndVelocities(localTransform, linearVelocity, angularVelocity); + properties.setPosition(localTransform.getTranslation()); + properties.setRotation(localTransform.getRotation()); + properties.setVelocity(linearVelocity); + properties.setAcceleration(_entity->getAcceleration()); + properties.setAngularVelocity(angularVelocity); + if (_entity->dynamicDataNeedsTransmit()) { + _entity->setDynamicDataNeedsTransmit(false); + properties.setActionData(_entity->getDynamicData()); + } + + if (_entity->updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); + } + + // set the LastEdited of the properties but NOT the entity itself + quint64 now = usecTimestampNow(); + properties.setLastEdited(now); + + // we don't own the simulation for this entity yet, but we're sending a bid for it + uint8_t bidPriority = glm::max(_bidPriority, VOLUNTEER_SIMULATION_PRIORITY); + properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); + // copy _bidPriority into pendingPriority... + _entity->setPendingOwnershipPriority(_bidPriority, now); + // don't forget to remember that we have made a bid + _entity->rememberHasSimulationOwnershipBid(); + + EntityTreeElementPointer element = _entity->getElement(); + EntityTreePointer tree = element ? element->getTree() : nullptr; + + properties.setClientOnly(_entity->getClientOnly()); + properties.setOwningAvatarID(_entity->getOwningAvatarID()); + + EntityItemID id(_entity->getID()); + EntityEditPacketSender* entityPacketSender = static_cast(packetSender); + entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); + _entity->setLastBroadcast(now); // for debug/physics status icons + + // NOTE: we don't descend to children for ownership bid. Instead, if we win ownership of the parent + // then in sendUpdate() we'll walk descendents and send updates for their QueryAACubes if necessary. + + _lastStep = step; + _nextBidExpiry = now + USECS_BETWEEN_OWNERSHIP_BIDS; + + // finally: clear _bidPriority + // which will may get promoted before next bid + // or maybe we'll win simulation ownership + _bidPriority = 0; +} + +void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step) { + DETAILED_PROFILE_RANGE(simulation_physics, "Send"); + assert(entityTreeIsLocked()); + assert(isLocallyOwned()); + + updateSendVelocities(); + + // remember _serverFoo data for local prediction of server state Transform localTransform; _entity->getLocalTransformAndVelocities(localTransform, _serverVelocity, _serverAngularVelocity); _serverPosition = localTransform.getTranslation(); @@ -579,11 +567,8 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ _serverActionData = _entity->getDynamicData(); EntityItemProperties properties; - - // explicitly set the properties that changed so that they will be packed properties.setPosition(_entity->getLocalPosition()); properties.setRotation(_entity->getLocalOrientation()); - properties.setVelocity(_serverVelocity); properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); @@ -592,61 +577,35 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setActionData(_serverActionData); } - if (properties.transformChanged()) { - if (_entity->updateQueryAACube()) { - // due to parenting, the server may not know where something is in world-space, so include the bounding cube. - properties.setQueryAACube(_entity->getQueryAACube()); - } + if (_entity->updateQueryAACube()) { + // due to parenting, the server may not know where something is in world-space, so include the bounding cube. + properties.setQueryAACube(_entity->getQueryAACube()); } // set the LastEdited of the properties but NOT the entity itself quint64 now = usecTimestampNow(); properties.setLastEdited(now); - #ifdef WANT_DEBUG - quint64 lastSimulated = _entity->getLastSimulated(); - qCDebug(physics) << "EntityMotionState::sendUpdate()"; - qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() - << "---------------------------------------------"; - qCDebug(physics) << " lastSimulated:" << debugTime(lastSimulated, now); - #endif //def WANT_DEBUG - if (_numInactiveUpdates > 0) { - // we own the simulation but the entity has stopped so we tell the server we're clearing simulatorID + // the entity is stopped and inactive so we tell the server we're clearing simulatorID // but we remember we do still own it... and rely on the server to tell us we don't properties.clearSimulationOwner(); - _outgoingPriority = 0; - _entity->setPendingOwnershipPriority(_outgoingPriority, now); - } else if (!isLocallyOwned()) { - // we don't own the simulation for this entity yet, but we're sending a bid for it - quint8 bidPriority = glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY); - properties.setSimulationOwner(Physics::getSessionUUID(), bidPriority); - _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; - // copy _outgoingPriority into pendingPriority... - _entity->setPendingOwnershipPriority(_outgoingPriority, now); - // don't forget to remember that we have made a bid - _entity->rememberHasSimulationOwnershipBid(); - // ...then reset _outgoingPriority - _outgoingPriority = 0; - // _outgoingPrioriuty will be re-computed before next bid, - // or will be set to agree with ownership priority should we win the bid - } else if (_outgoingPriority != _entity->getSimulationPriority()) { - // we own the simulation but our desired priority has changed - if (_outgoingPriority == 0) { + _bidPriority = 0; + _entity->setPendingOwnershipPriority(_bidPriority, now); + } else if (_bidPriority != _entity->getSimulationPriority()) { + // our desired priority has changed + if (_bidPriority == 0) { // we should release ownership properties.clearSimulationOwner(); } else { // we just need to change the priority - properties.setSimulationOwner(Physics::getSessionUUID(), _outgoingPriority); + properties.setSimulationOwner(Physics::getSessionUUID(), _bidPriority); } - _entity->setPendingOwnershipPriority(_outgoingPriority, now); + _entity->setPendingOwnershipPriority(_bidPriority, now); } EntityItemID id(_entity->getID()); EntityEditPacketSender* entityPacketSender = static_cast(packetSender); - #ifdef WANT_DEBUG - qCDebug(physics) << "EntityMotionState::sendUpdate()... calling queueEditEntityMessage()..."; - #endif EntityTreeElementPointer element = _entity->getElement(); EntityTreePointer tree = element ? element->getTree() : nullptr; @@ -655,7 +614,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ properties.setOwningAvatarID(_entity->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, id, properties); - _entity->setLastBroadcast(now); + _entity->setLastBroadcast(now); // for debug/physics status icons // if we've moved an entity with children, check/update the queryAACube of all descendents and tell the server // if they've changed. @@ -666,13 +625,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, uint32_ EntityItemProperties newQueryCubeProperties; newQueryCubeProperties.setQueryAACube(descendant->getQueryAACube()); newQueryCubeProperties.setLastEdited(properties.getLastEdited()); - newQueryCubeProperties.setClientOnly(entityDescendant->getClientOnly()); newQueryCubeProperties.setOwningAvatarID(entityDescendant->getOwningAvatarID()); entityPacketSender->queueEditEntityMessage(PacketType::EntityPhysics, tree, descendant->getID(), newQueryCubeProperties); - entityDescendant->setLastBroadcast(now); + entityDescendant->setLastBroadcast(now); // for debug/physics status icons } } }); @@ -723,6 +681,10 @@ uint8_t EntityMotionState::getSimulationPriority() const { return _entity->getSimulationPriority(); } +void EntityMotionState::slaveBidPriority() { + upgradeBidPriority(_entity->getSimulationPriority()); +} + // virtual QUuid EntityMotionState::getSimulatorID() const { assert(entityTreeIsLocked()); @@ -731,7 +693,7 @@ QUuid EntityMotionState::getSimulatorID() const { void EntityMotionState::bump(uint8_t priority) { assert(priority != 0); - upgradeOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); + upgradeBidPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); } void EntityMotionState::resetMeasuredBodyAcceleration() { @@ -755,13 +717,14 @@ void EntityMotionState::measureBodyAcceleration() { _lastMeasureStep = thisStep; _measuredDeltaTime = dt; - // Note: the integration equation for velocity uses damping: v1 = (v0 + a * dt) * (1 - D)^dt + // Note: the integration equation for velocity uses damping (D): v1 = (v0 + a * dt) * (1 - D)^dt // hence the equation for acceleration is: a = (v1 / (1 - D)^dt - v0) / dt glm::vec3 velocity = getBodyLinearVelocityGTSigma(); _measuredAcceleration = (velocity / powf(1.0f - _body->getLinearDamping(), dt) - _lastVelocity) * invDt; _lastVelocity = velocity; if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { + // we fall in here when _lastMeasureStep is old: the body has just become active _loopsWithoutOwner = 0; _lastStep = ObjectMotionState::getWorldSimulationStep(); _numInactiveUpdates = 0; @@ -805,24 +768,44 @@ QString EntityMotionState::getName() const { // virtual void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const { - assert(_entity); _entity->computeCollisionGroupAndFinalMask(group, mask); } +bool EntityMotionState::shouldSendBid() { + if (_bidPriority >= glm::max(_entity->getSimulationPriority(), VOLUNTEER_SIMULATION_PRIORITY)) { + return true; + } else { + // NOTE: this 'else' case has a side-effect: it clears _bidPriority + // which may be updated next simulation step (via collision or script event) + _bidPriority = 0; + return false; + } +} + bool EntityMotionState::isLocallyOwned() const { return _entity->getSimulatorID() == Physics::getSessionUUID(); } -bool EntityMotionState::shouldBeLocallyOwned() const { - return (_outgoingPriority > VOLUNTEER_SIMULATION_PRIORITY && _outgoingPriority > _entity->getSimulationPriority()) || +bool EntityMotionState::isLocallyOwnedOrShouldBe() const { + return (_bidPriority > VOLUNTEER_SIMULATION_PRIORITY && _bidPriority > _entity->getSimulationPriority()) || _entity->getSimulatorID() == Physics::getSessionUUID(); } -void EntityMotionState::upgradeOutgoingPriority(uint8_t priority) { - _outgoingPriority = glm::max(_outgoingPriority, priority); +void EntityMotionState::initForBid() { + assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); + _ownershipState = EntityMotionState::OwnershipState::PendingBid; } -void EntityMotionState::zeroCleanObjectVelocities() const { +void EntityMotionState::initForOwned() { + assert(_ownershipState != EntityMotionState::OwnershipState::Unownable); + _ownershipState = EntityMotionState::OwnershipState::LocallyOwned; +} + +void EntityMotionState::upgradeBidPriority(uint8_t priority) { + _bidPriority = glm::max(_bidPriority, priority); +} + +void EntityMotionState::clearObjectVelocities() const { // If transform or velocities are flagged as dirty it means a network or scripted change // occured between the beginning and end of the stepSimulation() and we DON'T want to apply // these physics simulation results. diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 784273d600..807acbfe80 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -24,11 +24,17 @@ class EntityMotionState : public ObjectMotionState { public: + enum class OwnershipState { + NotLocallyOwned = 0, + PendingBid, + LocallyOwned, + Unownable + }; + EntityMotionState() = delete; EntityMotionState(btCollisionShape* shape, EntityItemPointer item); virtual ~EntityMotionState(); - void updateServerPhysicsVariables(); void handleDeactivation(); virtual void handleEasyChanges(uint32_t& flags) override; virtual bool handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine* engine) override; @@ -44,9 +50,8 @@ public: // this relays outgoing position/rotation to the EntityItem virtual void setWorldTransform(const btTransform& worldTrans) override; - bool isCandidateForOwnership() const; - bool remoteSimulationOutOfSync(uint32_t simulationStep); bool shouldSendUpdate(uint32_t simulationStep); + void sendBid(OctreeEditPacketSender* packetSender, uint32_t step); void sendUpdate(OctreeEditPacketSender* packetSender, uint32_t step); virtual uint32_t getIncomingDirtyFlags() override; @@ -70,7 +75,9 @@ public: virtual QUuid getSimulatorID() const override; virtual void bump(uint8_t priority) override; - EntityItemPointer getEntity() const { return _entityPtr.lock(); } + // getEntity() returns a smart-pointer by reference because it is only ever used + // to insert into lists of smart pointers, and the lists will make their own copies + const EntityItemPointer& getEntity() const { return _entity; } void resetMeasuredBodyAcceleration(); void measureBodyAcceleration(); @@ -79,15 +86,29 @@ public: virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; + bool shouldSendBid(); bool isLocallyOwned() const override; - bool shouldBeLocallyOwned() const override; + bool isLocallyOwnedOrShouldBe() const override; // aka shouldEmitCollisionEvents() friend class PhysicalEntitySimulation; + OwnershipState getOwnershipState() const { return _ownershipState; } protected: - // changes _outgoingPriority only if priority is larger - void upgradeOutgoingPriority(uint8_t priority); - void zeroCleanObjectVelocities() const; + void updateSendVelocities(); + uint64_t getNextBidExpiry() const { return _nextBidExpiry; } + void initForBid(); + void initForOwned(); + void clearOwnershipState() { _ownershipState = OwnershipState::NotLocallyOwned; } + void updateServerPhysicsVariables(); + bool remoteSimulationOutOfSync(uint32_t simulationStep); + + // changes _bidPriority only if priority is larger + void upgradeBidPriority(uint8_t priority); + + // upgradeBidPriority to value stored in _entity + void slaveBidPriority(); + + void clearObjectVelocities() const; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS bool entityTreeIsLocked() const; @@ -98,17 +119,21 @@ protected: void setShape(const btCollisionShape* shape) override; void setMotionType(PhysicsMotionType motionType) override; - // In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be - // properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that - // state of affairs we can't keep a real EntityItemPointer as data member (it would produce a - // recursive dependency). Instead we keep a EntityItemWeakPointer to break that dependency while - // still granting us the capability to generate EntityItemPointers as necessary (for external data - // structures that use the MotionState to get to the EntityItem). - EntityItemWeakPointer _entityPtr; - // Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid. - EntityItem* _entity; + // EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR + // and is only cleared in the DTOR + EntityItemPointer _entity; - bool _serverVariablesSet { false }; + // These "_serverFoo" variables represent what we think the server knows. + // They are used in two different modes: + // + // (1) For remotely owned simulation: we store the last values recieved from the server. + // When the body comes to rest and goes inactive we slam its final transforms to agree with the last server + // update. This to reduce state synchronization errors when the local simulation deviated from remote. + // + // (2) For locally owned simulation: we store the last values sent to the server, integrated forward over time + // according to how we think the server doing it. We calculate the error between the true local transform + // and the remote to decide when to send another update. + // glm::vec3 _serverPosition; // in simulation-frame (not world-frame) glm::quat _serverRotation; glm::vec3 _serverVelocity; @@ -119,16 +144,18 @@ protected: glm::vec3 _lastVelocity; glm::vec3 _measuredAcceleration; - quint64 _nextOwnershipBid { 0 }; + quint64 _nextBidExpiry { 0 }; float _measuredDeltaTime; uint32_t _lastMeasureStep; uint32_t _lastStep; // last step of server extrapolation + OwnershipState _ownershipState { OwnershipState::NotLocallyOwned }; uint8_t _loopsWithoutOwner; mutable uint8_t _accelerationNearlyGravityCount; uint8_t _numInactiveUpdates { 1 }; - uint8_t _outgoingPriority { 0 }; + uint8_t _bidPriority { 0 }; + bool _serverVariablesSet { false }; }; #endif // hifi_EntityMotionState_h diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 7f583ca9ca..fbda9366fc 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -148,9 +148,9 @@ public: virtual const QUuid getObjectID() const = 0; - virtual quint8 getSimulationPriority() const { return 0; } + virtual uint8_t getSimulationPriority() const { return 0; } virtual QUuid getSimulatorID() const = 0; - virtual void bump(quint8 priority) {} + virtual void bump(uint8_t priority) {} virtual QString getName() const { return ""; } @@ -164,7 +164,7 @@ public: void clearInternalKinematicChanges() { _hasInternalKinematicChanges = false; } virtual bool isLocallyOwned() const { return false; } - virtual bool shouldBeLocallyOwned() const { return false; } + virtual bool isLocallyOwnedOrShouldBe() const { return false; } // aka shouldEmitCollisionEvents() friend class PhysicsEngine; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index e4ba47e205..d799577fc2 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -40,7 +40,7 @@ void PhysicalEntitySimulation::init( } // begin EntitySimulation overrides -void PhysicalEntitySimulation::updateEntitiesInternal(const quint64& now) { +void PhysicalEntitySimulation::updateEntitiesInternal(uint64_t now) { // Do nothing here because the "internal" update the PhysicsEngine::stepSimualtion() which is done elsewhere. } @@ -61,33 +61,58 @@ void PhysicalEntitySimulation::addEntityInternal(EntityItemPointer entity) { void PhysicalEntitySimulation::removeEntityInternal(EntityItemPointer entity) { if (entity->isSimulated()) { EntitySimulation::removeEntityInternal(entity); - QMutexLocker lock(&_mutex); _entitiesToAddToPhysics.remove(entity); EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { - _outgoingChanges.remove(motionState); + removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); - } else { - _entitiesToDelete.insert(entity); + } else if (entity->isDead() && entity->getElement()) { + _deadEntities.insert(entity); } } } -void PhysicalEntitySimulation::takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) { - QMutexLocker lock(&_mutex); - for (auto entity : _entitiesToDelete) { - // this entity is still in its tree, so we insert into the external list - entitiesToDelete.push_back(entity); +void PhysicalEntitySimulation::removeOwnershipData(EntityMotionState* motionState) { + assert(motionState); + if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::LocallyOwned) { + for (uint32_t i = 0; i < _owned.size(); ++i) { + if (_owned[i] == motionState) { + _owned[i]->clearOwnershipState(); + _owned.remove(i); + } + } + } else if (motionState->getOwnershipState() == EntityMotionState::OwnershipState::PendingBid) { + for (uint32_t i = 0; i < _bids.size(); ++i) { + if (_bids[i] == motionState) { + _bids[i]->clearOwnershipState(); + _bids.remove(i); + } + } + } +} - // Someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo - // rather than do it here +void PhysicalEntitySimulation::clearOwnershipData() { + for (uint32_t i = 0; i < _owned.size(); ++i) { + _owned[i]->clearOwnershipState(); + } + _owned.clear(); + for (uint32_t i = 0; i < _bids.size(); ++i) { + _bids[i]->clearOwnershipState(); + } + _bids.clear(); +} + +void PhysicalEntitySimulation::takeDeadEntities(SetOfEntities& deadEntities) { + QMutexLocker lock(&_mutex); + for (auto entity : _deadEntities) { EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); if (motionState) { _entitiesToRemoveFromPhysics.insert(entity); } } - _entitiesToDelete.clear(); + _deadEntities.swap(deadEntities); + _deadEntities.clear(); } void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { @@ -98,15 +123,15 @@ void PhysicalEntitySimulation::changeEntityInternal(EntityItemPointer entity) { if (motionState) { if (!entity->shouldBePhysical()) { // the entity should be removed from the physical simulation - _pendingChanges.remove(motionState); + _incomingChanges.remove(motionState); _physicalObjects.remove(motionState); - _outgoingChanges.remove(motionState); + removeOwnershipData(motionState); _entitiesToRemoveFromPhysics.insert(entity); if (entity->isMovingRelativeToParent()) { _simpleKinematicEntities.insert(entity); } } else { - _pendingChanges.insert(motionState); + _incomingChanges.insert(motionState); } } else if (entity->shouldBePhysical()) { // The intent is for this object to be in the PhysicsEngine, but it has no MotionState yet. @@ -125,80 +150,69 @@ void PhysicalEntitySimulation::clearEntitiesInternal() { // while it is in the middle of a simulation step. As it is, we're probably in shutdown mode // anyway, so maybe the simulation was already properly shutdown? Cross our fingers... - // copy everything into _entitiesToDelete - for (auto stateItr : _physicalObjects) { - EntityMotionState* motionState = static_cast(&(*stateItr)); - _entitiesToDelete.insert(motionState->getEntity()); - } - - // then remove the objects (aka MotionStates) from physics + // remove the objects (aka MotionStates) from physics _physicsEngine->removeSetOfObjects(_physicalObjects); // delete the MotionStates - // TODO: after we invert the entities/physics lib dependencies we will let EntityItem delete - // its own PhysicsInfo rather than do it here - for (auto entity : _entitiesToDelete) { - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState) { - entity->setPhysicsInfo(nullptr); - delete motionState; - } + for (auto stateItr : _physicalObjects) { + EntityMotionState* motionState = static_cast(&(*stateItr)); + assert(motionState); + EntityItemPointer entity = motionState->getEntity(); + entity->setPhysicsInfo(nullptr); + // TODO: someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo + // until then we must do it here + delete motionState; } - - // finally clear all lists maintained by this class _physicalObjects.clear(); + + // clear all other lists specific to this derived class + clearOwnershipData(); _entitiesToRemoveFromPhysics.clear(); - _entitiesToRelease.clear(); _entitiesToAddToPhysics.clear(); - _pendingChanges.clear(); - _outgoingChanges.clear(); + _incomingChanges.clear(); } // virtual void PhysicalEntitySimulation::prepareEntityForDelete(EntityItemPointer entity) { assert(entity); assert(entity->isDead()); + QMutexLocker lock(&_mutex); entity->clearActions(getThisPointer()); removeEntityInternal(entity); } // end EntitySimulation overrides -void PhysicalEntitySimulation::getObjectsToRemoveFromPhysics(VectorOfMotionStates& result) { - result.clear(); +const VectorOfMotionStates& PhysicalEntitySimulation::getObjectsToRemoveFromPhysics() { QMutexLocker lock(&_mutex); for (auto entity: _entitiesToRemoveFromPhysics) { - // make sure it isn't on any side lists - _entitiesToAddToPhysics.remove(entity); - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - if (motionState) { - _pendingChanges.remove(motionState); - _outgoingChanges.remove(motionState); - _physicalObjects.remove(motionState); - result.push_back(motionState); - _entitiesToRelease.insert(entity); + assert(motionState); + + _entitiesToAddToPhysics.remove(entity); + if (entity->isDead() && entity->getElement()) { + _deadEntities.insert(entity); } - if (entity->isDead()) { - _entitiesToDelete.insert(entity); - } + _incomingChanges.remove(motionState); + removeOwnershipData(motionState); + _physicalObjects.remove(motionState); + + // remember this motionState and delete it later (after removing its RigidBody from the PhysicsEngine) + _objectsToDelete.push_back(motionState); } _entitiesToRemoveFromPhysics.clear(); + return _objectsToDelete; } void PhysicalEntitySimulation::deleteObjectsRemovedFromPhysics() { QMutexLocker lock(&_mutex); - for (auto entity: _entitiesToRelease) { - EntityMotionState* motionState = static_cast(entity->getPhysicsInfo()); - assert(motionState); - entity->setPhysicsInfo(nullptr); + for (auto motionState : _objectsToDelete) { + // someday when we invert the entities/physics lib dependencies we can let EntityItem delete its own PhysicsInfo + // until then we must do it here + // NOTE: a reference to the EntityItemPointer is released in the EntityMotionState::dtor delete motionState; - - if (entity->isDead()) { - _entitiesToDelete.insert(entity); - } } - _entitiesToRelease.clear(); + _objectsToDelete.clear(); } void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& result) { @@ -248,18 +262,18 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re void PhysicalEntitySimulation::setObjectsToChange(const VectorOfMotionStates& objectsToChange) { QMutexLocker lock(&_mutex); for (auto object : objectsToChange) { - _pendingChanges.insert(static_cast(object)); + _incomingChanges.insert(static_cast(object)); } } void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) { result.clear(); QMutexLocker lock(&_mutex); - for (auto stateItr : _pendingChanges) { + for (auto stateItr : _incomingChanges) { EntityMotionState* motionState = &(*stateItr); result.push_back(motionState); } - _pendingChanges.clear(); + _incomingChanges.clear(); } void PhysicalEntitySimulation::handleDeactivatedMotionStates(const VectorOfMotionStates& motionStates) { @@ -279,20 +293,22 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta PROFILE_RANGE_EX(simulation_physics, "ChangedEntities", 0x00000000, (uint64_t)motionStates.size()); QMutexLocker lock(&_mutex); - // walk the motionStates looking for those that correspond to entities - { - PROFILE_RANGE_EX(simulation_physics, "Filter", 0x00000000, (uint64_t)motionStates.size()); - for (auto stateItr : motionStates) { - ObjectMotionState* state = &(*stateItr); - assert(state); - if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { - EntityMotionState* entityState = static_cast(state); - EntityItemPointer entity = entityState->getEntity(); - assert(entity.get()); - if (entityState->isCandidateForOwnership()) { - _outgoingChanges.insert(entityState); + for (auto stateItr : motionStates) { + ObjectMotionState* state = &(*stateItr); + assert(state); + if (state->getType() == MOTIONSTATE_TYPE_ENTITY) { + EntityMotionState* entityState = static_cast(state); + _entitiesToSort.insert(entityState->getEntity()); + if (entityState->getOwnershipState() == EntityMotionState::OwnershipState::NotLocallyOwned) { + // NOTE: entityState->getOwnershipState() reflects what ownership list (_bids or _owned) it is in + // and is distinct from entityState->isLocallyOwned() which checks the simulation ownership + // properties of the corresponding EntityItem. It is possible for the two states to be out + // of sync. In fact, we're trying to put them back into sync here. + if (entityState->isLocallyOwned()) { + addOwnership(entityState); + } else if (entityState->shouldSendBid()) { + addOwnershipBid(entityState); } - _entitiesToSort.insert(entity); } } } @@ -302,26 +318,78 @@ void PhysicalEntitySimulation::handleChangedMotionStates(const VectorOfMotionSta _lastStepSendPackets = numSubsteps; if (Physics::getSessionUUID().isNull()) { - // usually don't get here, but if so --> nothing to do - _outgoingChanges.clear(); - return; + // usually don't get here, but if so clear all ownership + clearOwnershipData(); } + // send updates before bids, because this simplifies the logic thasuccessful bids will immediately send an update when added to the 'owned' list + sendOwnedUpdates(numSubsteps); + sendOwnershipBids(numSubsteps); + } +} - // look for entities to prune or update - PROFILE_RANGE_EX(simulation_physics, "Prune/Send", 0x00000000, (uint64_t)_outgoingChanges.size()); - QSet::iterator stateItr = _outgoingChanges.begin(); - while (stateItr != _outgoingChanges.end()) { - EntityMotionState* state = *stateItr; - if (!state->isCandidateForOwnership()) { - // prune - stateItr = _outgoingChanges.erase(stateItr); - } else if (state->shouldSendUpdate(numSubsteps)) { - // update - state->sendUpdate(_entityPacketSender, numSubsteps); - ++stateItr; - } else { - ++stateItr; +void PhysicalEntitySimulation::addOwnershipBid(EntityMotionState* motionState) { + motionState->initForBid(); + motionState->sendBid(_entityPacketSender, _physicsEngine->getNumSubsteps()); + _bids.push_back(motionState); + _nextBidExpiry = glm::min(_nextBidExpiry, motionState->getNextBidExpiry()); +} + +void PhysicalEntitySimulation::addOwnership(EntityMotionState* motionState) { + motionState->initForOwned(); + _owned.push_back(motionState); +} + +void PhysicalEntitySimulation::sendOwnershipBids(uint32_t numSubsteps) { + uint64_t now = usecTimestampNow(); + if (now > _nextBidExpiry) { + PROFILE_RANGE_EX(simulation_physics, "Bid", 0x00000000, (uint64_t)_bids.size()); + _nextBidExpiry = std::numeric_limits::max(); + uint32_t i = 0; + while (i < _bids.size()) { + bool removeBid = false; + if (_bids[i]->isLocallyOwned()) { + // when an object transitions from 'bid' to 'owned' we are changing the "mode" of data stored + // in the EntityMotionState::_serverFoo variables (please see comments in EntityMotionState.h) + // therefore we need to immediately send an update so that the values stored are what we're + // "telling" the server rather than what we've been "hearing" from the server. + _bids[i]->slaveBidPriority(); + _bids[i]->sendUpdate(_entityPacketSender, numSubsteps); + + addOwnership(_bids[i]); + removeBid = true; + } else if (!_bids[i]->shouldSendBid()) { + removeBid = true; + _bids[i]->clearOwnershipState(); } + if (removeBid) { + _bids.remove(i); + } else { + if (now > _bids[i]->getNextBidExpiry()) { + _bids[i]->sendBid(_entityPacketSender, numSubsteps); + _nextBidExpiry = glm::min(_nextBidExpiry, _bids[i]->getNextBidExpiry()); + } + ++i; + } + } + } +} + +void PhysicalEntitySimulation::sendOwnedUpdates(uint32_t numSubsteps) { + PROFILE_RANGE_EX(simulation_physics, "Update", 0x00000000, (uint64_t)_owned.size()); + uint32_t i = 0; + while (i < _owned.size()) { + if (!_owned[i]->isLocallyOwned()) { + if (_owned[i]->shouldSendBid()) { + addOwnershipBid(_owned[i]); + } else { + _owned[i]->clearOwnershipState(); + } + _owned.remove(i); + } else { + if (_owned[i]->shouldSendUpdate(numSubsteps)) { + _owned[i]->sendUpdate(_entityPacketSender, numSubsteps); + } + ++i; } } } @@ -336,7 +404,6 @@ void PhysicalEntitySimulation::handleCollisionEvents(const CollisionEvents& coll } } - void PhysicalEntitySimulation::addDynamic(EntityDynamicPointer dynamic) { if (_physicsEngine) { // FIXME put fine grain locking into _physicsEngine diff --git a/libraries/physics/src/PhysicalEntitySimulation.h b/libraries/physics/src/PhysicalEntitySimulation.h index b9acf4cace..7b6fe221fb 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.h +++ b/libraries/physics/src/PhysicalEntitySimulation.h @@ -27,7 +27,19 @@ class PhysicalEntitySimulation; using PhysicalEntitySimulationPointer = std::shared_ptr; using SetOfEntityMotionStates = QSet; +class VectorOfEntityMotionStates: public std::vector { +public: + void remove(uint32_t index) { + assert(index < size()); + if (index < size() - 1) { + (*this)[index] = back(); + } + pop_back(); + } +}; + class PhysicalEntitySimulation : public EntitySimulation { + Q_OBJECT public: PhysicalEntitySimulation(); ~PhysicalEntitySimulation(); @@ -37,21 +49,28 @@ public: virtual void addDynamic(EntityDynamicPointer dynamic) override; virtual void applyDynamicChanges() override; - virtual void takeEntitiesToDelete(VectorOfEntities& entitiesToDelete) override; + virtual void takeDeadEntities(SetOfEntities& deadEntities) override; + +signals: + void entityCollisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); protected: // only called by EntitySimulation // overrides for EntitySimulation - virtual void updateEntitiesInternal(const quint64& now) override; + virtual void updateEntitiesInternal(uint64_t now) override; virtual void addEntityInternal(EntityItemPointer entity) override; virtual void removeEntityInternal(EntityItemPointer entity) override; virtual void changeEntityInternal(EntityItemPointer entity) override; virtual void clearEntitiesInternal() override; + void removeOwnershipData(EntityMotionState* motionState); + void clearOwnershipData(); + public: virtual void prepareEntityForDelete(EntityItemPointer entity) override; - void getObjectsToRemoveFromPhysics(VectorOfMotionStates& result); + const VectorOfMotionStates& getObjectsToRemoveFromPhysics(); void deleteObjectsRemovedFromPhysics(); + void getObjectsToAddToPhysics(VectorOfMotionStates& result); void setObjectsToChange(const VectorOfMotionStates& objectsToChange); void getObjectsToChange(VectorOfMotionStates& result); @@ -62,19 +81,28 @@ public: EntityEditPacketSender* getPacketSender() { return _entityPacketSender; } -private: - SetOfEntities _entitiesToRemoveFromPhysics; - SetOfEntities _entitiesToRelease; - SetOfEntities _entitiesToAddToPhysics; + void addOwnershipBid(EntityMotionState* motionState); + void addOwnership(EntityMotionState* motionState); + void sendOwnershipBids(uint32_t numSubsteps); + void sendOwnedUpdates(uint32_t numSubsteps); - SetOfEntityMotionStates _pendingChanges; // EntityMotionStates already in PhysicsEngine that need their physics changed - SetOfEntityMotionStates _outgoingChanges; // EntityMotionStates for which we may need to send updates to entity-server +private: + SetOfEntities _entitiesToAddToPhysics; + SetOfEntities _entitiesToRemoveFromPhysics; + + VectorOfMotionStates _objectsToDelete; + + SetOfEntityMotionStates _incomingChanges; // EntityMotionStates that have changed from external sources + // and need their RigidBodies updated SetOfMotionStates _physicalObjects; // MotionStates of entities in PhysicsEngine PhysicsEnginePointer _physicsEngine = nullptr; EntityEditPacketSender* _entityPacketSender = nullptr; + VectorOfEntityMotionStates _owned; + VectorOfEntityMotionStates _bids; + uint64_t _nextBidExpiry; uint32_t _lastStepSendPackets { 0 }; }; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 2d84f0cef1..1d4c385f07 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -571,7 +571,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { // modify the logic below. // // We only create events when at least one of the objects is (or should be) owned in the local simulation. - if (motionStateA && (motionStateA->shouldBeLocallyOwned())) { + if (motionStateA && (motionStateA->isLocallyOwnedOrShouldBe())) { QUuid idA = motionStateA->getObjectID(); QUuid idB; if (motionStateB) { @@ -582,7 +582,7 @@ const CollisionEvents& PhysicsEngine::getCollisionEvents() { (motionStateB ? motionStateB->getObjectLinearVelocityChange() : glm::vec3(0.0f)); glm::vec3 penetration = bulletToGLM(contact.distance * contact.normalWorldOnB); _collisionEvents.push_back(Collision(type, idA, idB, position, penetration, velocityChange)); - } else if (motionStateB && (motionStateB->shouldBeLocallyOwned())) { + } else if (motionStateB && (motionStateB->isLocallyOwnedOrShouldBe())) { QUuid idB = motionStateB->getObjectID(); QUuid idA; if (motionStateA) { diff --git a/libraries/render-utils/src/RenderPipelines.cpp b/libraries/render-utils/src/RenderPipelines.cpp index ef4b0a5683..a3abb24afe 100644 --- a/libraries/render-utils/src/RenderPipelines.cpp +++ b/libraries/render-utils/src/RenderPipelines.cpp @@ -32,6 +32,7 @@ #include "model_lightmap_fade_vert.h" #include "model_lightmap_normal_map_fade_vert.h" #include "model_translucent_vert.h" +#include "model_translucent_normal_map_vert.h" #include "skin_model_fade_vert.h" #include "skin_model_normal_map_fade_vert.h" #include "skin_model_fade_dq_vert.h" @@ -68,11 +69,13 @@ #include "model_lightmap_normal_map_frag.h" #include "model_translucent_frag.h" #include "model_translucent_unlit_frag.h" +#include "model_translucent_normal_map_frag.h" #include "model_lightmap_fade_frag.h" #include "model_lightmap_normal_map_fade_frag.h" #include "model_translucent_fade_frag.h" #include "model_translucent_unlit_fade_frag.h" +#include "model_translucent_normal_map_fade_frag.h" #include "overlay3D_vert.h" #include "overlay3D_frag.h" @@ -187,6 +190,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelLightmapVertex = model_lightmap_vert::getShader(); auto modelLightmapNormalMapVertex = model_lightmap_normal_map_vert::getShader(); auto modelTranslucentVertex = model_translucent_vert::getShader(); + auto modelTranslucentNormalMapVertex = model_translucent_normal_map_vert::getShader(); auto modelShadowVertex = model_shadow_vert::getShader(); auto modelLightmapFadeVertex = model_lightmap_fade_vert::getShader(); @@ -227,6 +231,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelNormalMapPixel = model_normal_map_frag::getShader(); auto modelTranslucentPixel = model_translucent_frag::getShader(); auto modelTranslucentUnlitPixel = model_translucent_unlit_frag::getShader(); + auto modelTranslucentNormalMapPixel = model_translucent_normal_map_frag::getShader(); auto modelShadowPixel = model_shadow_frag::getShader(); auto modelLightmapPixel = model_lightmap_frag::getShader(); auto modelLightmapNormalMapPixel = model_lightmap_normal_map_frag::getShader(); @@ -239,6 +244,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip auto modelShadowFadePixel = model_shadow_fade_frag::getShader(); auto modelTranslucentFadePixel = model_translucent_fade_frag::getShader(); auto modelTranslucentUnlitFadePixel = model_translucent_unlit_fade_frag::getShader(); + auto modelTranslucentNormalMapFadePixel = model_translucent_normal_map_fade_frag::getShader(); auto simpleFadePixel = simple_textured_fade_frag::getShader(); auto simpleUnlitFadePixel = simple_textured_unlit_fade_frag::getShader(); auto simpleTranslucentFadePixel = simple_transparent_textured_fade_frag::getShader(); @@ -296,7 +302,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip simpleVertex, simpleTranslucentUnlitPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents(), - modelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + modelTranslucentNormalMapVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap(), @@ -316,7 +322,7 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip simpleFadeVertex, simpleTranslucentUnlitFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withTranslucent().withTangents().withFade(), - modelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + modelTranslucentNormalMapVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); addPipeline( // FIXME: Ignore lightmap for translucents meshpart Key::Builder().withMaterial().withTranslucent().withLightmap().withFade(), @@ -358,14 +364,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip skinModelTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents(), - skinModelNormalMapTranslucentVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelNormalMapTranslucentVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withFade(), skinModelFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withTranslucent().withTangents().withFade(), - skinModelNormalMapFadeVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + skinModelNormalMapFadeVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); // dual quaternion skinned addPipeline( @@ -388,14 +394,14 @@ void initDeferredPipelines(render::ShapePlumber& plumber, const render::ShapePip skinModelTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents(), - skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentPixel, nullptr, nullptr); + skinModelNormalMapTranslucentDualQuatVertex, modelTranslucentNormalMapPixel, nullptr, nullptr); // Same thing but with Fade on addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withFade(), skinModelFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter); addPipeline( Key::Builder().withMaterial().withSkinned().withDualQuatSkinned().withTranslucent().withTangents().withFade(), - skinModelNormalMapFadeDualQuatVertex, modelTranslucentFadePixel, batchSetter, itemSetter); + skinModelNormalMapFadeDualQuatVertex, modelTranslucentNormalMapFadePixel, batchSetter, itemSetter); // Depth-only addPipeline( diff --git a/scripts/+android/defaultScripts.js b/scripts/+android/defaultScripts.js index 11aee6a9d2..98fbb4b1a7 100644 --- a/scripts/+android/defaultScripts.js +++ b/scripts/+android/defaultScripts.js @@ -14,7 +14,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/progress.js", "system/+android/touchscreenvirtualpad.js", - "system/+android/bottombar.js", + "system/+android/actionbar.js", "system/+android/audio.js" , "system/+android/modes.js", "system/+android/stats.js"/*, diff --git a/scripts/developer/utilities/render/debugHighlight.js b/scripts/developer/utilities/render/debugHighlight.js index c2173f6e2a..664af836a9 100644 --- a/scripts/developer/utilities/render/debugHighlight.js +++ b/scripts/developer/utilities/render/debugHighlight.js @@ -157,7 +157,7 @@ }) function cleanup() { - Pointers.removePointer(ray); + Pointers.removePointer(laser); Selection.disableListHighlight(HoveringList) Selection.removeListFromMap(HoveringList) diff --git a/scripts/system/+android/actionbar.js b/scripts/system/+android/actionbar.js new file mode 100644 index 0000000000..9aed1e6cf9 --- /dev/null +++ b/scripts/system/+android/actionbar.js @@ -0,0 +1,53 @@ +"use strict"; +// +// backbutton.js +// scripts/system/+android +// +// Created by Gabriel Calero & Cristian Duarte on Apr 06, 2018 +// Copyright 2018 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 +// +(function() { // BEGIN LOCAL_SCOPE + +var actionbar; +var backButton; + +var logEnabled = true; + +function printd(str) { + if (logEnabled) + print("[actionbar.js] " + str); +} + +function init() { + actionbar = new QmlFragment({ + qml: "hifi/ActionBar.qml" + }); + backButton = actionbar.addButton({ + icon: "icons/+android/backward.svg", + activeIcon: "icons/+android/backward.svg", + text: "", + bgOpacity: 0.0, + activeBgOpacity: 0.0, + bgColor: "#FFFFFF" + }); + + backButton.clicked.connect(onBackPressed); +} + +function onBackPressed() { + App.openAndroidActivity("Goto"); +} + + +Script.scriptEnding.connect(function () { + if(backButton) { + backButton.clicked.disconnect(onBackPressed); + } +}); + +init(); + +}()); // END LOCAL_SCOPE diff --git a/scripts/system/help.js b/scripts/system/help.js index e29fc59e59..aaeb82721c 100644 --- a/scripts/system/help.js +++ b/scripts/system/help.js @@ -40,7 +40,7 @@ } function onScreenChanged(type, url) { - onHelpScreen = type === "Web" && url.startsWith(HELP_URL); + onHelpScreen = type === "Web" && (url.indexOf(HELP_URL) === 0); button.editProperties({ isActive: onHelpScreen }); } diff --git a/scripts/system/snapshot.js b/scripts/system/snapshot.js index ae8ef52a15..658d1c3ced 100644 --- a/scripts/system/snapshot.js +++ b/scripts/system/snapshot.js @@ -411,8 +411,6 @@ function snapshotUploaded(isError, reply) { } else { print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID); } - } else { - print(reply); } isUploadingPrintableStill = false; } diff --git a/tools/auto-tester/src/Test.cpp b/tools/auto-tester/src/Test.cpp index 0bbd35e280..5d8b9115b8 100644 --- a/tools/auto-tester/src/Test.cpp +++ b/tools/auto-tester/src/Test.cpp @@ -554,7 +554,7 @@ ExtractedText Test::getTestScriptLines(QString testFileName) { // The folder selected must contain a script named "test.js", the file produced is named "test.md" void Test::createMDFile() { // Folder selection - QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test images", ".", QFileDialog::ShowDirsOnly); + QString testDirectory = QFileDialog::getExistingDirectory(nullptr, "Please select folder containing the test", ".", QFileDialog::ShowDirsOnly); if (testDirectory == "") { return; } @@ -571,7 +571,7 @@ void Test::createMDFile() { QString mdFilename(testDirectory + "/" + "test.md"); QFile mdFile(mdFilename); - if (!mdFile.open(QIODevice::ReadWrite)) { + if (!mdFile.open(QIODevice::WriteOnly)) { messageBox.critical(0, "Internal error: " + QString(__FILE__) + ":" + QString::number(__LINE__), "Failed to create file " + mdFilename); exit(-1); } @@ -640,7 +640,7 @@ void Test::createMDFile() { int snapShotIndex { 0 }; for (size_t i = 0; i < testScriptLines.stepList.size(); ++i) { stream << "### Step " << QString::number(i) << "\n"; - stream << "- " << testScriptLines.stepList[i]->text << "\n"; + stream << "- " << testScriptLines.stepList[i + 1]->text << "\n"; if (testScriptLines.stepList[i]->takeSnapshot) { stream << "- ![](./ExpectedImage_" << QString::number(snapShotIndex).rightJustified(5, '0') << ".png)\n"; ++snapShotIndex;