diff --git a/INSTALL.md b/INSTALL.md index e07d28a43d..ac210c887f 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -19,6 +19,7 @@ To produce an executable installer on Windows, the following are required: - [nsProcess Plug-in for Nullsoft](http://nsis.sourceforge.net/NsProcess_plugin) - 1.6 - [Inetc Plug-in for Nullsoft](http://nsis.sourceforge.net/Inetc_plug-in) - 1.0 - [NSISpcre Plug-in for Nullsoft](http://nsis.sourceforge.net/NSISpcre_plug-in) - 1.0 +- [nsisSlideshow Plug-in for Nullsoft](http://nsis.sourceforge.net/NsisSlideshow_plug-in) - 1.7 Run the `package` target to create an executable installer using the Nullsoft Scriptable Install System. diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 28ac320e42..bfedccdd2e 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -87,6 +87,10 @@ ;-------------------------------- ;-------------------------------- ;General + + ; hide install details since we show an image slideshow in their place + ShowInstDetails nevershow + ; leverage the UAC NSIS plugin to promote uninstaller to elevated privileges !include UAC.nsh @@ -446,6 +450,7 @@ SectionEnd Page custom PostInstallOptionsPage ReadPostInstallOptions !define MUI_PAGE_CUSTOMFUNCTION_PRE PageInstallFilesPre + !define MUI_PAGE_CUSTOMFUNCTION_SHOW StartInstallSlideshow !insertmacro MUI_PAGE_INSTFILES !insertmacro MUI_UNPAGE_CONFIRM @@ -544,11 +549,33 @@ Var Express ${EndIf} !macroend +!macro DownloadSlideshowImages + InitPluginsDir + + Push $0 + + ; figure out where to download installer slideshow images from + StrCpy $0 "http://cdn.highfidelity.com/installer/slideshow" + + ${If} $CampaignName == "" + StrCpy $0 "$0/default" + ${Else} + StrCpy $0 "$0/$CampaignName" + ${EndIf} + + NSISdl::download_quiet $0/1.jpg "$PLUGINSDIR\1.jpg" + NSISdl::download_quiet $0/2.jpg "$PLUGINSDIR\2.jpg" + NSISdl::download_quiet $0/3.jpg "$PLUGINSDIR\3.jpg" + + Pop $0 +!macroend + Function OnUserAbort !insertmacro GoogleAnalytics "Installer" "Abort" "User Abort" "" FunctionEnd Function PageWelcomePre !insertmacro GoogleAnalytics "Installer" "Welcome" "" "" + !insertmacro DownloadSlideshowImages FunctionEnd Function PageLicensePre !insertmacro GoogleAnalytics "Installer" "License" "" "" @@ -640,6 +667,56 @@ Function ChangeCustomLabel Pop $R1 FunctionEnd +!macro AddImageToSlideshowFile ImageFilename + ${If} ${FileExists} "$PLUGINSDIR\${ImageFilename}.jpg" + FileWrite $0 "= ${ImageFilename}.jpg,500,5000,$\"$\"$\r$\n" + StrCpy $1 "1" + ${EndIf} +!macroend + +Function StartInstallSlideshow + ; create a slideshow file based on what files we have available + + ; stash $0 and $1 + Push $0 + Push $1 + + ; start $1 as 0, indicating we have no images present + StrCpy $1 "0" + + FileOpen $0 "$PLUGINSDIR\slideshow.dat" w + + ; write the language value to the slideshow file for english + FileWrite $0 "[1033]$\r$\n" + + ; for each of 1.jpg, 2.jpg, 3.jpg + ; if the image is present add it to the dat file and set our flag + ; to show we found at least one image + !insertmacro AddImageToSlideshowFile "1" + !insertmacro AddImageToSlideshowFile "2" + !insertmacro AddImageToSlideshowFile "3" + + FileClose $0 + + ; NOTE: something inside of nsisSlideshow::show isn't keeping the stack clean + ; so we need to push things back BEFORE we call it + + ${If} $1 == "1" + Pop $1 + Pop $0 + + ; show the slideshow using the created data file + nsisSlideshow::show /NOUNLOAD "/auto=$PLUGINSDIR\slideshow.dat" + ${Else} + Pop $1 + Pop $0 + + ; show the install details because we didn't end up with slideshow images to show + SetDetailsView show + ${EndIf} + +FunctionEnd + Function PostInstallOptionsPage !insertmacro MaybeSkipPage !insertmacro GoogleAnalytics "Installer" "Post Install Options" "" "" @@ -932,6 +1009,7 @@ FunctionEnd ;Installer Sections Section "-Core installation" + ;The following delete blocks are temporary and can be removed once users who had the initial installer have updated ;Delete any server-console files installed before it was placed in sub-folder @@ -983,11 +1061,13 @@ Section "-Core installation" WriteRegStr HKLM "Software\@CPACK_PACKAGE_VENDOR@\@CPACK_PACKAGE_INSTALL_REGISTRY_KEY@" "" $INSTDIR ;Write some information about this install to the installation folder + Push $0 FileOpen $0 "$INSTDIR\installer.ini" w FileWrite $0 "type=@INSTALLER_TYPE@$\r$\n" FileWrite $0 "campaign=$CampaignName$\r$\n" FileWrite $0 "exepath=$EXEPATH$\r$\n" FileClose $0 + Pop $0 ;Package the signed uninstaller produced by the inner loop !ifndef INNER diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ac157979bb..baeac043e4 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -617,7 +617,7 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) { } } else { HIFI_FDEBUG("Packet of type" << headerType - << "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceNode->getUUID())); + << "received from unknown node with Local ID" << localSourceID); return false; } } diff --git a/interface/resources/qml/controls-uit/Slider.qml b/interface/resources/qml/controls-uit/Slider.qml index 3726d3f260..5ddd97f3f6 100644 --- a/interface/resources/qml/controls-uit/Slider.qml +++ b/interface/resources/qml/controls-uit/Slider.qml @@ -24,6 +24,7 @@ Slider { property alias minimumValue: slider.from property alias maximumValue: slider.to + property bool tickmarksEnabled: false height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control. y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0 diff --git a/interface/resources/qml/controls-uit/SpinBox.qml b/interface/resources/qml/controls-uit/SpinBox.qml index 83c30ce162..9d63122dbc 100644 --- a/interface/resources/qml/controls-uit/SpinBox.qml +++ b/interface/resources/qml/controls-uit/SpinBox.qml @@ -20,6 +20,7 @@ SpinBox { property int colorScheme: hifi.colorSchemes.light readonly property bool isLightColorScheme: colorScheme === hifi.colorSchemes.light property string label: "" + property string suffix: "" property string labelInside: "" property color colorLabelInside: hifi.colors.white property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0) @@ -34,8 +35,11 @@ SpinBox { property real realTo: 100.0 property real realStepSize: 1.0 + signal editingFinished() + implicitHeight: height implicitWidth: width + editable: true padding: 0 leftPadding: 0 @@ -68,16 +72,16 @@ SpinBox { } validator: DoubleValidator { - bottom: Math.min(spinBox.from, spinBox.to)*spinBox.factor - top: Math.max(spinBox.from, spinBox.to)*spinBox.factor + bottom: Math.min(spinBox.from, spinBox.to) + top: Math.max(spinBox.from, spinBox.to) } textFromValue: function(value, locale) { - return parseFloat(value*1.0/factor).toFixed(decimals); + return parseFloat(value/factor).toFixed(decimals); } valueFromText: function(text, locale) { - return Number.fromLocaleString(locale, text); + return Number.fromLocaleString(locale, text)*factor; } @@ -88,12 +92,14 @@ SpinBox { : (spinBox.activeFocus ? hifi.colors.white : hifi.colors.lightGrayText) selectedTextColor: hifi.colors.black selectionColor: hifi.colors.primaryHighlight - text: spinBox.textFromValue(spinBox.value, spinBox.locale) + text: spinBox.textFromValue(spinBox.value, spinBox.locale) + suffix verticalAlignment: Qt.AlignVCenter leftPadding: spinBoxLabelInside.visible ? 30 : hifi.dimensions.textPadding //rightPadding: hifi.dimensions.spinnerSize width: spinBox.width - hifi.dimensions.spinnerSize + onEditingFinished: spinBox.editingFinished() } + up.indicator: Item { x: spinBox.width - implicitWidth - 5 y: 1 diff --git a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml index e670cd37c4..89e1096a04 100644 --- a/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinBoxPreference.qml @@ -18,11 +18,11 @@ Preference { height: control.height + hifi.dimensions.controlInterlineHeight Component.onCompleted: { - spinner.value = preference.value; + spinner.realValue = preference.value; } function save() { - preference.value = spinner.value; + preference.value = spinner.realValue; preference.save(); } diff --git a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml index 3cba67bc82..731acc7e5b 100644 --- a/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml +++ b/interface/resources/qml/dialogs/preferences/SpinnerSliderPreference.qml @@ -21,7 +21,7 @@ Preference { Component.onCompleted: { slider.value = preference.value; - spinner.value = preference.value; + spinner.realValue = preference.value; } function save() { @@ -60,7 +60,7 @@ Preference { maximumValue: MyAvatar.getDomainMaxScale() stepSize: preference.step onValueChanged: { - spinner.value = value + spinner.realValue = value } anchors { right: spinner.left @@ -73,12 +73,12 @@ Preference { SpinBox { id: spinner decimals: preference.decimals - value: preference.value + realValue: preference.value minimumValue: MyAvatar.getDomainMinScale() maximumValue: MyAvatar.getDomainMaxScale() width: 100 onValueChanged: { - slider.value = value; + slider.value = realValue; } anchors { right: button.left @@ -92,10 +92,10 @@ Preference { id: button onClicked: { if (spinner.maximumValue >= 1) { - spinner.value = 1 + spinner.realValue = 1 slider.value = 1 } else { - spinner.value = spinner.maximumValue + spinner.realValue = spinner.maximumValue slider.value = spinner.maximumValue } } @@ -108,4 +108,4 @@ Preference { colorScheme: hifi.colorSchemes.dark } } -} \ No newline at end of file +} diff --git a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml index d93e077b5a..30e03bd02e 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Attachment.qml @@ -181,11 +181,11 @@ Item { minimumValue: 0.01 maximumValue: 10 realStepSize: 0.05; - value: attachment ? attachment.scale : 1.0 + realValue: attachment ? attachment.scale : 1.0 colorScheme: hifi.colorSchemes.dark - onValueChanged: { - if (completed && attachment && attachment.scale !== value) { - attachment.scale = value; + onRealValueChanged: { + if (completed && attachment && attachment.scale !== realValue) { + attachment.scale = realValue; updateAttachment(); } } diff --git a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml index 228d71fe6f..311492858b 100644 --- a/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml +++ b/interface/resources/qml/hifi/dialogs/attachments/Vector3.qml @@ -51,7 +51,7 @@ Item { id: xspinner width: root.spinboxWidth anchors { left: parent.left } - value: root.vector.x + realValue: root.vector.x labelInside: "X:" colorScheme: hifi.colorSchemes.dark colorLabelInside: hifi.colors.redHighlight @@ -72,17 +72,17 @@ Item { id: yspinner width: root.spinboxWidth anchors { horizontalCenter: parent.horizontalCenter } - value: root.vector.y + realValue: root.vector.y labelInside: "Y:" colorLabelInside: hifi.colors.greenHighlight colorScheme: hifi.colorSchemes.dark decimals: root.decimals - stepSize: root.stepSize + realStepSize: root.stepSize maximumValue: root.maximumValue minimumValue: root.minimumValue - onValueChanged: { - if (value !== vector.y) { - vector.y = value + onRealValueChanged: { + if (realValue !== vector.y) { + vector.y = realValue root.valueChanged(); } } @@ -93,17 +93,17 @@ Item { id: zspinner width: root.spinboxWidth anchors { right: parent.right; } - value: root.vector.z + realValue: root.vector.z labelInside: "Z:" colorLabelInside: hifi.colors.primaryHighlight colorScheme: hifi.colorSchemes.dark decimals: root.decimals - stepSize: root.stepSize + realStepSize: root.stepSize maximumValue: root.maximumValue minimumValue: root.minimumValue - onValueChanged: { - if (value !== vector.z) { - vector.z = value + onRealValueChanged: { + if (realValue !== vector.z) { + vector.z = realValue root.valueChanged(); } } diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index bd3a95bca0..572d3a55f0 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -178,8 +178,8 @@ Rectangle { label: "Y Offset" suffix: " cm" minimumValue: -10 - stepSize: 1 - value: -5 + realStepSize: 1 + realValue: -5 colorScheme: hifi.colorSchemes.dark onEditingFinished: { @@ -193,10 +193,10 @@ Rectangle { width: 112 label: "Z Offset" minimumValue: -10 - stepSize: 1 + realStepSize: 1 decimals: 1 suffix: " cm" - value: -5 + realValue: -5 colorScheme: hifi.colorSchemes.dark onEditingFinished: { @@ -288,7 +288,7 @@ Rectangle { suffix: " cm" label: "Y Offset" minimumValue: -10 - stepSize: 1 + realStepSize: 1 colorScheme: hifi.colorSchemes.dark onEditingFinished: { @@ -303,7 +303,7 @@ Rectangle { label: "Z Offset" suffix: " cm" minimumValue: -10 - stepSize: 1 + realStepSize: 1 decimals: 1 colorScheme: hifi.colorSchemes.dark @@ -535,9 +535,9 @@ Rectangle { suffix: " cm" label: "Arm Circumference" minimumValue: 0 - stepSize: 1.0 + realStepSize: 1.0 colorScheme: hifi.colorSchemes.dark - value: 33.0 + realValue: 33.0 onEditingFinished: { sendConfigurationSettings(); @@ -550,10 +550,10 @@ Rectangle { label: "Shoulder Width" suffix: " cm" minimumValue: 0 - stepSize: 1.0 + realStepSize: 1.0 decimals: 1 colorScheme: hifi.colorSchemes.dark - value: 48 + realValue: 48 onEditingFinished: { sendConfigurationSettings(); @@ -659,13 +659,13 @@ Rectangle { InputConfiguration.uncalibratePlugin(pluginName); updateCalibrationButton(); } else { - calibrationTimer.interval = timeToCalibrate.value * 1000 - openVrConfiguration.countDown = timeToCalibrate.value; + calibrationTimer.interval = timeToCalibrate.realValue * 1000 + openVrConfiguration.countDown = timeToCalibrate.realValue; var calibratingScreen = screen.createObject(); stack.push(calibratingScreen); calibratingScreen.canceled.connect(cancelCalibration); calibratingScreen.restart.connect(restartCalibration); - calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.value); + calibratingScreen.start(calibrationTimer.interval, timeToCalibrate.realValue); calibrationTimer.start(); } } @@ -728,12 +728,12 @@ Rectangle { anchors.leftMargin: leftMargin minimumValue: 5 - value: 5 + realValue: 5 colorScheme: hifi.colorSchemes.dark onEditingFinished: { - calibrationTimer.interval = value * 1000; - openVrConfiguration.countDown = value; + calibrationTimer.interval = realValue * 1000; + openVrConfiguration.countDown = realValue; numberAnimation.duration = calibrationTimer.interval; } } @@ -910,8 +910,8 @@ Rectangle { var desktopMode = settings["desktopMode"]; var hmdDesktopPosition = settings["hmdDesktopTracking"]; - armCircumference.value = settings.armCircumference; - shoulderWidth.value = settings.shoulderWidth; + armCircumference.realValue = settings.armCircumference; + shoulderWidth.realValue = settings.shoulderWidth; if (HmdHead) { headBox.checked = true; @@ -1075,22 +1075,22 @@ Rectangle { var headObject = { "override": overrideHead, - "Y": headYOffset.value, - "Z": headZOffset.value + "Y": headYOffset.realValue, + "Z": headZOffset.realValue } var handObject = { "override": overrideHandController, - "Y": handYOffset.value, - "Z": handZOffset.value + "Y": handYOffset.realValue, + "Z": handZOffset.realValue } var settingsObject = { "bodyConfiguration": trackerConfiguration, "headConfiguration": headObject, "handConfiguration": handObject, - "armCircumference": armCircumference.value, - "shoulderWidth": shoulderWidth.value, + "armCircumference": armCircumference.realValue, + "shoulderWidth": shoulderWidth.realValue, "desktopMode": viveInDesktop.checked, "hmdDesktopTracking": hmdInDesktop.checked } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index dc1653ba3c..f19422c442 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -4753,7 +4753,7 @@ void Application::updateLOD(float deltaTime) const { } } -void Application::pushPostUpdateLambda(void* key, std::function func) { +void Application::pushPostUpdateLambda(void* key, const std::function& func) { std::unique_lock guard(_postUpdateLambdasLock); _postUpdateLambdas[key] = func; } @@ -7377,7 +7377,7 @@ void Application::windowMinimizedChanged(bool minimized) { } } -void Application::postLambdaEvent(std::function f) { +void Application::postLambdaEvent(const std::function& f) { if (this->thread() == QThread::currentThread()) { f(); } else { @@ -7385,6 +7385,15 @@ void Application::postLambdaEvent(std::function f) { } } +void Application::sendLambdaEvent(const std::function& f) { + if (this->thread() == QThread::currentThread()) { + f(); + } else { + LambdaEvent event(f); + QCoreApplication::sendEvent(this, &event); + } +} + void Application::initPlugins(const QStringList& arguments) { QCommandLineOption display("display", "Preferred displays", "displays"); QCommandLineOption disableDisplays("disable-displays", "Displays to disable", "displays"); diff --git a/interface/src/Application.h b/interface/src/Application.h index a17aff8fac..38254ec81b 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -138,7 +138,8 @@ public: Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runningMarkerExisted); ~Application(); - void postLambdaEvent(std::function f) override; + void postLambdaEvent(const std::function& f) override; + void sendLambdaEvent(const std::function& f) override; QString getPreviousScriptLocation(); void setPreviousScriptLocation(const QString& previousScriptLocation); @@ -242,7 +243,7 @@ public: qint64 getCurrentSessionRuntime() const { return _sessionRunTimer.elapsed(); } - bool isAboutToQuit() const override { return _aboutToQuit; } + bool isAboutToQuit() const { return _aboutToQuit; } bool isPhysicsEnabled() const { return _physicsEnabled; } // the isHMDMode is true whenever we use the interface from an HMD and not a standard flat display @@ -266,10 +267,9 @@ public: render::EnginePointer getRenderEngine() override { return _renderEngine; } gpu::ContextPointer getGPUContext() const { return _gpuContext; } - const GameWorkload& getGameWorkload() const { return _gameWorkload; } - virtual void pushPostUpdateLambda(void* key, std::function func) override; + virtual void pushPostUpdateLambda(void* key, const std::function& func) override; void updateMyAvatarLookAtPosition(); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index 387900b2ae..b27cc344c3 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -147,7 +147,7 @@ void Audio::setInputVolume(float volume) { } float Audio::getInputLevel() const { - return resultWithReadLock([&] { + return resultWithReadLock([&] { return _inputLevel; }); } diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 59ec80c17f..edec2e2cbd 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -65,14 +65,10 @@ const QString Web3DOverlay::TYPE = "web3d"; const QString Web3DOverlay::QML = "Web3DOverlay.qml"; static auto qmlSurfaceDeleter = [](OffscreenQmlSurface* surface) { - AbstractViewStateInterface::instance()->postLambdaEvent([surface] { - if (AbstractViewStateInterface::instance()->isAboutToQuit()) { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete surface; - } else { - surface->deleteLater(); - } + AbstractViewStateInterface::instance()->sendLambdaEvent([surface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete surface; }); }; diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index f647082d73..2b87cbae58 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -25,7 +25,7 @@ #include #include "EntitiesRendererLogging.h" - +#include using namespace render; using namespace render::entities; @@ -45,6 +45,7 @@ static int DEFAULT_MAX_FPS = 10; static int YOUTUBE_MAX_FPS = 30; static QTouchDevice _touchDevice; +static const char* URL_PROPERTY = "url"; WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& urlString) { if (urlString.isEmpty()) { @@ -52,7 +53,7 @@ WebEntityRenderer::ContentType WebEntityRenderer::getContentType(const QString& } const QUrl url(urlString); - if (url.scheme() == "http" || url.scheme() == "https" || + if (url.scheme() == URL_SCHEME_HTTP || url.scheme() == URL_SCHEME_HTTPS || urlString.toLower().endsWith(".htm") || urlString.toLower().endsWith(".html")) { return ContentType::HtmlContent; } @@ -164,6 +165,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (urlChanged) { if (newContentType != ContentType::HtmlContent || currentContentType != ContentType::HtmlContent) { destroyWebSurface(); + // If we destroyed the surface, the URL change will be implicitly handled by the re-creation + urlChanged = false; } withWriteLock([&] { @@ -185,8 +188,8 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene return; } - if (urlChanged) { - _webSurface->getRootItem()->setProperty("url", _lastSourceUrl); + if (urlChanged && _contentType == ContentType::HtmlContent) { + _webSurface->getRootItem()->setProperty(URL_PROPERTY, _lastSourceUrl); } if (_contextPosition != entity->getWorldPosition()) { @@ -254,6 +257,14 @@ bool WebEntityRenderer::hasWebSurface() { return (bool)_webSurface && _webSurface->getRootItem(); } +static const auto WebSurfaceDeleter = [](OffscreenQmlSurface* webSurface) { + AbstractViewStateInterface::instance()->sendLambdaEvent([webSurface] { + // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown + // if the application has already stopped its event loop, delete must be explicit + delete webSurface; + }); +}; + bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) { qWarning() << "Too many concurrent web views to create new view"; @@ -261,20 +272,9 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { } ++_currentWebCount; - auto deleter = [](OffscreenQmlSurface* webSurface) { - AbstractViewStateInterface::instance()->postLambdaEvent([webSurface] { - if (AbstractViewStateInterface::instance()->isAboutToQuit()) { - // WebEngineView may run other threads (wasapi), so they must be deleted for a clean shutdown - // if the application has already stopped its event loop, delete must be explicit - delete webSurface; - } else { - webSurface->deleteLater(); - } - }); - }; // FIXME use the surface cache instead of explicit creation - _webSurface = QSharedPointer(new OffscreenQmlSurface(), deleter); + _webSurface = QSharedPointer(new OffscreenQmlSurface(), WebSurfaceDeleter); // FIXME, the max FPS could be better managed by being dynamic (based on the number of current surfaces // and the current rendering load) _webSurface->setMaxFps(DEFAULT_MAX_FPS); @@ -302,7 +302,7 @@ bool WebEntityRenderer::buildWebSurface(const TypedEntityPointer& entity) { _webSurface->setMaxFps(DEFAULT_MAX_FPS); } _webSurface->load("controls/WebEntityView.qml", [this](QQmlContext* context, QObject* item) { - item->setProperty("url", _lastSourceUrl); + item->setProperty(URL_PROPERTY, _lastSourceUrl); }); } else if (_contentType == ContentType::QmlContent) { _webSurface->load(_lastSourceUrl, [this](QQmlContext* context, QObject* item) { @@ -327,6 +327,11 @@ void WebEntityRenderer::destroyWebSurface() { if (webSurface) { --_currentWebCount; QQuickItem* rootItem = webSurface->getRootItem(); + // Explicitly set the web URL to an empty string, in an effort to get a + // faster shutdown of any chromium processes interacting with audio + if (rootItem && _contentType == ContentType::HtmlContent) { + rootItem->setProperty(URL_PROPERTY, ""); + } if (rootItem && rootItem->objectName() == "tabletRoot") { auto tabletScriptingInterface = DependencyManager::get(); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index a64d10ca9c..295126c2e7 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -942,11 +942,10 @@ void EntityItem::setMass(float mass) { float volume = _volumeMultiplier * dimensions.x * dimensions.y * dimensions.z; // compute new density - const float MIN_VOLUME = 1.0e-6f; // 0.001mm^3 float newDensity = 1.0f; - if (volume < 1.0e-6f) { + if (volume < ENTITY_ITEM_MIN_VOLUME) { // avoid divide by zero - newDensity = glm::min(mass / MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY); + newDensity = glm::min(mass / ENTITY_ITEM_MIN_VOLUME, ENTITY_ITEM_MAX_DENSITY); } else { newDensity = glm::max(glm::min(mass / volume, ENTITY_ITEM_MAX_DENSITY), ENTITY_ITEM_MIN_DENSITY); } @@ -1688,7 +1687,7 @@ void EntityItem::setScaledDimensions(const glm::vec3& value) { } void EntityItem::setUnscaledDimensions(const glm::vec3& value) { - glm::vec3 newDimensions = glm::max(value, glm::vec3(0.0f)); // can never have negative dimensions + glm::vec3 newDimensions = glm::max(value, glm::vec3(ENTITY_ITEM_MIN_DIMENSION)); if (getUnscaledDimensions() != newDimensions) { withWriteLock([&] { _unscaledDimensions = newDimensions; diff --git a/libraries/entities/src/EntityItemPropertiesDefaults.h b/libraries/entities/src/EntityItemPropertiesDefaults.h index 0e0c2994cd..d2ddd687dd 100644 --- a/libraries/entities/src/EntityItemPropertiesDefaults.h +++ b/libraries/entities/src/EntityItemPropertiesDefaults.h @@ -60,8 +60,10 @@ const float ENTITY_ITEM_DEFAULT_LIFETIME = ENTITY_ITEM_IMMORTAL_LIFETIME; const glm::vec3 ENTITY_ITEM_DEFAULT_POSITION = ENTITY_ITEM_ZERO_VEC3; const glm::quat ENTITY_ITEM_DEFAULT_ROTATION; const float ENTITY_ITEM_DEFAULT_WIDTH = 0.1f; +const float ENTITY_ITEM_MIN_DIMENSION = 0.001f; const glm::vec3 ENTITY_ITEM_DEFAULT_DIMENSIONS = glm::vec3(ENTITY_ITEM_DEFAULT_WIDTH); const float ENTITY_ITEM_DEFAULT_VOLUME = ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH * ENTITY_ITEM_DEFAULT_WIDTH; +const float ENTITY_ITEM_MIN_VOLUME = ENTITY_ITEM_MIN_DIMENSION * ENTITY_ITEM_MIN_DIMENSION * ENTITY_ITEM_MIN_DIMENSION; const float ENTITY_ITEM_MAX_DENSITY = 10000.0f; // kg/m^3 density of silver const float ENTITY_ITEM_MIN_DENSITY = 100.0f; // kg/m^3 density of balsa wood diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index aaf1b58a0a..8d177ca534 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -358,7 +358,7 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe } else { HIFI_FCDEBUG(networking(), - "Packet of type" << headerType << "received from unknown node with UUID" << uuidStringWithoutCurlyBraces(sourceID)); + "Packet of type" << headerType << "received from unknown node with Local ID" << sourceLocalID); } } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index cfe81a58af..04e32f50cb 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -638,7 +638,7 @@ void NodeList::processDomainServerList(QSharedPointer message) // if this was the first domain-server list from this domain, we've now connected if (!_domainHandler.isConnected()) { - _domainHandler.setLocalID(newLocalID); + _domainHandler.setLocalID(domainLocalID); _domainHandler.setUUID(domainUUID); _domainHandler.setIsConnected(true); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 2da1c41340..688f3fdb5f 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -66,14 +66,10 @@ OffscreenSurface::OffscreenSurface() } OffscreenSurface::~OffscreenSurface() { - disconnect(qApp); - _sharedObject->destroy(); + delete _sharedObject; } bool OffscreenSurface::fetchTexture(TextureAndFence& textureAndFence) { - if (!_sharedObject) { - return false; - } hifi::qml::impl::TextureAndFence typedTextureAndFence; bool result = _sharedObject->fetchTexture(typedTextureAndFence); textureAndFence = typedTextureAndFence; diff --git a/libraries/qml/src/qml/impl/RenderEventHandler.cpp b/libraries/qml/src/qml/impl/RenderEventHandler.cpp index 6b66ce9314..945a469611 100644 --- a/libraries/qml/src/qml/impl/RenderEventHandler.cpp +++ b/libraries/qml/src/qml/impl/RenderEventHandler.cpp @@ -49,8 +49,8 @@ RenderEventHandler::RenderEventHandler(SharedObject* shared, QThread* targetThre qFatal("Unable to create new offscreen GL context"); } - moveToThread(targetThread); _canvas.moveToThreadWithContext(targetThread); + moveToThread(targetThread); } void RenderEventHandler::onInitalize() { @@ -160,11 +160,8 @@ void RenderEventHandler::onQuit() { } _shared->shutdownRendering(_canvas, _currentSize); - // Release the reference to the shared object. This will allow it to - // be destroyed (should happen on it's own thread). - _shared->deleteLater(); - - deleteLater(); - + _canvas.doneCurrent(); + _canvas.moveToThreadWithContext(qApp->thread()); + moveToThread(qApp->thread()); QThread::currentThread()->quit(); } diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index b2057117f6..9253c41b39 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -72,26 +72,35 @@ SharedObject::SharedObject() { QObject::connect(qApp, &QCoreApplication::aboutToQuit, this, &SharedObject::onAboutToQuit); } + SharedObject::~SharedObject() { - if (_quickWindow) { - _quickWindow->destroy(); - _quickWindow = nullptr; + // After destroy returns, the rendering thread should be gone + destroy(); + + // _renderTimer is created with `this` as the parent, so need no explicit destruction + + // Destroy the event hand + if (_renderObject) { + delete _renderObject; + _renderObject = nullptr; } if (_renderControl) { - _renderControl->deleteLater(); + delete _renderControl; _renderControl = nullptr; } - if (_renderThread) { - _renderThread->quit(); - _renderThread->deleteLater(); + if (_quickWindow) { + _quickWindow->destroy(); + delete _quickWindow; + _quickWindow = nullptr; } - if (_rootItem) { - _rootItem->deleteLater(); - _rootItem = nullptr; - } + // _rootItem is parented to the quickWindow, so needs no explicit destruction + //if (_rootItem) { + // delete _rootItem; + // _rootItem = nullptr; + //} releaseEngine(_qmlContext->engine()); } @@ -119,6 +128,10 @@ void SharedObject::create(OffscreenSurface* surface) { } void SharedObject::setRootItem(QQuickItem* rootItem) { + if (_quit) { + return; + } + _rootItem = rootItem; _rootItem->setSize(_quickWindow->size()); @@ -127,7 +140,6 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { _renderThread->setObjectName(objectName()); _renderThread->start(); - // Create event handler for the render thread _renderObject = new RenderEventHandler(this, _renderThread); QCoreApplication::postEvent(this, new OffscreenEvent(OffscreenEvent::Initialize)); @@ -137,35 +149,43 @@ void SharedObject::setRootItem(QQuickItem* rootItem) { } void SharedObject::destroy() { + // `destroy` is idempotent, it can be called multiple times without issues if (_quit) { return; } if (!_rootItem) { - deleteLater(); return; } - _paused = true; if (_renderTimer) { + _renderTimer->stop(); QObject::disconnect(_renderTimer); - _renderTimer->deleteLater(); } - QObject::disconnect(_renderControl); + if (_renderControl) { + QObject::disconnect(_renderControl); + } + QObject::disconnect(qApp); { QMutexLocker lock(&_mutex); _quit = true; - QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + if (_renderObject) { + QCoreApplication::postEvent(_renderObject, new OffscreenEvent(OffscreenEvent::Quit), Qt::HighEventPriority); + } } // Block until the rendering thread has stopped // FIXME this is undesirable because this is blocking the main thread, // but I haven't found a reliable way to do this only at application // shutdown - _renderThread->wait(); + if (_renderThread) { + _renderThread->wait(); + delete _renderThread; + _renderThread = nullptr; + } } diff --git a/libraries/render-utils/src/AbstractViewStateInterface.h b/libraries/render-utils/src/AbstractViewStateInterface.h index 96e9f4d222..54fdc903ca 100644 --- a/libraries/render-utils/src/AbstractViewStateInterface.h +++ b/libraries/render-utils/src/AbstractViewStateInterface.h @@ -37,15 +37,25 @@ public: virtual glm::vec3 getAvatarPosition() const = 0; - virtual bool isAboutToQuit() const = 0; - virtual void postLambdaEvent(std::function f) = 0; + // Unfortunately, having this here is a bad idea. Lots of objects connect to + // the aboutToQuit signal, and it's impossible to know the order in which + // the receivers will be called, so this might return false negatives + //virtual bool isAboutToQuit() const = 0; + + // Queue code to execute on the main thread. + // If called from the main thread, the lambda will execute synchronously + virtual void postLambdaEvent(const std::function& f) = 0; + // Synchronously execute code on the main thread. This function will + // not return until the code is executed, regardles of which thread it + // is called from + virtual void sendLambdaEvent(const std::function& f) = 0; virtual qreal getDevicePixelRatio() = 0; virtual render::ScenePointer getMain3DScene() = 0; virtual render::EnginePointer getRenderEngine() = 0; - virtual void pushPostUpdateLambda(void* key, std::function func) = 0; + virtual void pushPostUpdateLambda(void* key, const std::function& func) = 0; virtual bool isHMDMode() const = 0; @@ -54,5 +64,4 @@ public: static void setInstance(AbstractViewStateInterface* instance); }; - -#endif // hifi_AbstractViewStateInterface_h +#endif // hifi_AbstractViewStateInterface_h diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 70f873734a..65b12ac0d4 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -182,9 +182,7 @@ const float SCALE_CHANGE_EPSILON = 0.0000001f; void Model::setScaleInternal(const glm::vec3& scale) { if (glm::distance(_scale, scale) > SCALE_CHANGE_EPSILON) { _scale = scale; - if (_scale.x == 0.0f || _scale.y == 0.0f || _scale.z == 0.0f) { - assert(false); - } + assert(_scale.x != 0.0f && scale.y != 0.0f && scale.z != 0.0f); simulate(0.0f, true); } } diff --git a/libraries/shared/src/ColorUtils.h b/libraries/shared/src/ColorUtils.h index fd0bbdd8ab..e113449db3 100644 --- a/libraries/shared/src/ColorUtils.h +++ b/libraries/shared/src/ColorUtils.h @@ -102,7 +102,7 @@ inline float ColorUtils::tosRGBFloat(const float &linear) { } else if (0 < linear && linear < SRGB_ELBOW_INV) { sRGBValue = 12.92f * linear; } else if (SRGB_ELBOW_INV <= linear && linear < 1) { - sRGBValue = 1.055f * powf(linear, 0.41666f - 0.055f); + sRGBValue = 1.055f * powf(linear, 0.41666f) - 0.055f; } else { sRGBValue = 1.0f; } diff --git a/libraries/shared/src/Preferences.h b/libraries/shared/src/Preferences.h index a243a6d58d..76d61fe3f6 100644 --- a/libraries/shared/src/Preferences.h +++ b/libraries/shared/src/Preferences.h @@ -198,6 +198,7 @@ class IntPreference : public TypedPreference { Q_PROPERTY(float min READ getMin CONSTANT) Q_PROPERTY(float max READ getMax CONSTANT) Q_PROPERTY(float step READ getStep CONSTANT) + Q_PROPERTY(int decimals READ getDecimals CONSTANT) public: IntPreference(const QString& category, const QString& name, Getter getter, Setter setter) @@ -212,6 +213,9 @@ public: float getStep() const { return _step; } void setStep(float step) { _step = step; }; + int getDecimals() const { return _decimals; } + void setDecimals(int decimals) { _decimals = decimals; }; + signals: void valueChanged(); @@ -221,6 +225,7 @@ protected: int _min { std::numeric_limits::min() }; int _max { std::numeric_limits::max() }; int _step { 1 }; + int _decimals { 0 }; }; class StringPreference : public TypedPreference { diff --git a/scripts/system/controllers/controllerModules/equipEntity.js b/scripts/system/controllers/controllerModules/equipEntity.js index 10bd5d28d3..6f3169f5cf 100644 --- a/scripts/system/controllers/controllerModules/equipEntity.js +++ b/scripts/system/controllers/controllerModules/equipEntity.js @@ -176,7 +176,10 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var TRIGGER_OFF_VALUE = 0.1; var TRIGGER_ON_VALUE = TRIGGER_OFF_VALUE + 0.05; // Squeezed just enough to activate search or near grab var BUMPER_ON_VALUE = 0.5; - + + var EMPTY_PARENT_ID = "{00000000-0000-0000-0000-000000000000}"; + + var UNEQUIP_KEY = "u"; function getWearableData(props) { var wearable = {}; @@ -270,6 +273,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.shouldSendStart = false; this.equipedWithSecondary = false; this.handHasBeenRightsideUp = false; + this.mouseEquip = false; this.parameters = makeDispatcherModuleParameters( 300, @@ -279,10 +283,11 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipHotspotBuddy = new EquipHotspotBuddy(); - this.setMessageGrabData = function(entityProperties) { + this.setMessageGrabData = function(entityProperties, mouseEquip) { if (entityProperties) { this.messageGrabEntity = true; this.grabEntityProps = entityProperties; + this.mouseEquip = mouseEquip; } }; @@ -580,6 +585,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa this.targetEntityID = null; this.messageGrabEntity = false; this.grabEntityProps = null; + this.mouseEquip = false; }; this.updateInputs = function (controllerData) { @@ -656,7 +662,7 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var timestamp = Date.now(); this.updateInputs(controllerData); - if (!this.isTargetIDValid(controllerData)) { + if (!this.mouseEquip && !this.isTargetIDValid(controllerData)) { this.endEquipEntity(); return makeRunningValues(false, [], []); } @@ -757,7 +763,8 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa var equipModule = (data.hand === "left") ? leftEquipEntity : rightEquipEntity; var entityProperties = Entities.getEntityProperties(data.entityID, DISPATCHER_PROPERTIES); entityProperties.id = data.entityID; - equipModule.setMessageGrabData(entityProperties); + var mouseEquip = false; + equipModule.setMessageGrabData(entityProperties, mouseEquip); } catch (e) { print("WARNING: equipEntity.js -- error parsing Hifi-Hand-Grab message: " + message); @@ -774,10 +781,68 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa } } }; - + + var clearGrabActions = function(entityID) { + var actionIDs = Entities.getActionIDs(entityID); + var myGrabTag = "grab-" + MyAvatar.sessionUUID; + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments.tag; + if (tag === myGrabTag) { + Entities.deleteAction(entityID, actionID); + } + } + }; + + var onMousePress = function(event) { + if (isInEditMode()) { // don't consider any mouse clicks on the entity while in edit + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Entities.findRayIntersection(pickRay, true); + if (intersection.intersects) { + var entityID = intersection.entityID; + var entityProperties = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES); + var hasEquipData = getWearableData(entityProperties).joints || getEquipHotspotsData(entityProperties).length > 0; + if (hasEquipData && entityProperties.parentID === EMPTY_PARENT_ID && !entityIsFarGrabbedByOther(entityID)) { + entityProperties.id = entityID; + var rightHandPosition = MyAvatar.getJointPosition("RightHand"); + var leftHandPosition = MyAvatar.getJointPosition("LeftHand"); + var distanceToRightHand = Vec3.distance(entityProperties.position, rightHandPosition); + var distanceToLeftHand = Vec3.distance(entityProperties.position, leftHandPosition); + var leftHandAvailable = leftEquipEntity.targetEntityID === null; + var rightHandAvailable = rightEquipEntity.targetEntityID === null; + var mouseEquip = true; + if (rightHandAvailable && (distanceToRightHand < distanceToLeftHand || !leftHandAvailable)) { + // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) + clearGrabActions(entityID); + rightEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + } else if (leftHandAvailable && (distanceToLeftHand < distanceToRightHand || !rightHandAvailable)) { + // clear any existing grab actions on the entity now (their later removal could affect bootstrapping flags) + clearGrabActions(entityID); + leftEquipEntity.setMessageGrabData(entityProperties, mouseEquip); + } + } + } + }; + + var onKeyPress = function(event) { + if (event.text === UNEQUIP_KEY) { + if (rightEquipEntity.targetEntityID) { + rightEquipEntity.endEquipEntity(); + } + if (leftEquipEntity.targetEntityID) { + leftEquipEntity.endEquipEntity(); + } + } + }; + Messages.subscribe('Hifi-Hand-Grab'); Messages.subscribe('Hifi-Hand-Drop'); Messages.messageReceived.connect(handleMessage); + Controller.mousePressEvent.connect(onMousePress); + Controller.keyPressEvent.connect(onKeyPress); var leftEquipEntity = new EquipEntity(LEFT_HAND); var rightEquipEntity = new EquipEntity(RIGHT_HAND); @@ -791,6 +856,9 @@ EquipHotspotBuddy.prototype.update = function(deltaTime, timestamp, controllerDa disableDispatcherModule("LeftEquipEntity"); disableDispatcherModule("RightEquipEntity"); clearAttachPoints(); + Messages.messageReceived.disconnect(handleMessage); + Controller.mousePressEvent.disconnect(onMousePress); + Controller.keyPressEvent.disconnect(onKeyPress); } Script.scriptEnding.connect(cleanup); }()); diff --git a/scripts/system/controllers/grab.js b/scripts/system/controllers/grab.js index 36dd9314bb..8ae94a4caa 100644 --- a/scripts/system/controllers/grab.js +++ b/scripts/system/controllers/grab.js @@ -569,7 +569,7 @@ Grabber.prototype.moveEventProcess = function() { } if (!this.actionID) { - if (!entityIsGrabbedByOther(this.entityID)) { + if (!entityIsGrabbedByOther(this.entityID) && !entityIsEquipped(this.entityID)) { this.actionID = Entities.addAction("far-grab", this.entityID, actionArgs); } } else { diff --git a/scripts/system/libraries/controllerDispatcherUtils.js b/scripts/system/libraries/controllerDispatcherUtils.js index 9edd6d006a..71dc5e4273 100644 --- a/scripts/system/libraries/controllerDispatcherUtils.js +++ b/scripts/system/libraries/controllerDispatcherUtils.js @@ -415,6 +415,34 @@ distanceBetweenPointAndEntityBoundingBox = function(point, entityProps) { return Vec3.distance(v, localPoint); }; +entityIsEquipped = function(entityID) { + var rightEquipEntity = getEnabledModuleByName("RightEquipEntity"); + var leftEquipEntity = getEnabledModuleByName("LeftEquipEntity"); + var equippedInRightHand = rightEquipEntity ? rightEquipEntity.targetEntityID === entityID : false; + var equippedInLeftHand = leftEquipEntity ? leftEquipEntity.targetEntityID === entityID : false; + return equippedInRightHand || equippedInLeftHand; +}; + +entityIsFarGrabbedByOther = function(entityID) { + // by convention, a far grab sets the tag of its action to be far-grab-*owner-session-id*. + var actionIDs = Entities.getActionIDs(entityID); + var myFarGrabTag = "far-grab-" + MyAvatar.sessionUUID; + for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) { + var actionID = actionIDs[actionIndex]; + var actionArguments = Entities.getActionArguments(entityID, actionID); + var tag = actionArguments.tag; + if (tag == myFarGrabTag) { + // we see a far-grab-*uuid* shaped tag, but it's our tag, so that's okay. + continue; + } + if (tag.slice(0, 9) == "far-grab-") { + // we see a far-grab-*uuid* shaped tag and it's not ours, so someone else is grabbing it. + return true; + } + } + return false; +}; + if (typeof module !== 'undefined') { module.exports = { makeDispatcherModuleParameters: makeDispatcherModuleParameters, diff --git a/scripts/system/pal.js b/scripts/system/pal.js index c24e34cc1b..0a01007ee9 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -715,7 +715,6 @@ function onTabletScreenChanged(type, url) { ContextOverlay.enabled = false; Users.requestsDomainListData = true; - populateNearbyUserList(); audioTimer = createAudioInterval(conserveResources ? AUDIO_LEVEL_CONSERVED_UPDATE_INTERVAL_MS : AUDIO_LEVEL_UPDATE_INTERVAL_MS); @@ -726,6 +725,7 @@ function onTabletScreenChanged(type, url) { Users.usernameFromIDReply.connect(usernameFromIDReply); triggerMapping.enable(); triggerPressMapping.enable(); + populateNearbyUserList(); } else { off(); ContextOverlay.enabled = true; diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 93672cc5a2..9249b3d957 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -453,8 +453,8 @@ protected: return vec3(); } - bool isAboutToQuit() const override { return false; } - void postLambdaEvent(std::function f) override {} + void postLambdaEvent(const std::function& f) override {} + void sendLambdaEvent(const std::function& f) override {} qreal getDevicePixelRatio() override { return 1.0f; @@ -469,7 +469,7 @@ protected: } std::map> _postUpdateLambdas; - void pushPostUpdateLambda(void* key, std::function func) override { + void pushPostUpdateLambda(void* key, const std::function& func) override { _postUpdateLambdas[key] = func; }