From 55eed376980dd5f8e485887176f9e5911bab4abb Mon Sep 17 00:00:00 2001 From: vladest Date: Tue, 29 Aug 2017 20:47:09 +0200 Subject: [PATCH 01/42] Start working on autocompletion --- interface/resources/qml/Browser.qml | 2 +- .../qml/controls-uit/BaseWebView.qml | 4 +- interface/resources/qml/hifi/Desktop.qml | 2 +- interface/resources/qml/hifi/WebBrowser.qml | 72 ++++++++++++------- 4 files changed, 49 insertions(+), 31 deletions(-) diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index 55927fda24..2809f91923 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -1,7 +1,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.2 import QtWebChannel 1.0 -import QtWebEngine 1.2 +import QtWebEngine 1.5 import "controls-uit" import "styles" as HifiStyles diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml index 3ca57f03bf..fdd9c12220 100644 --- a/interface/resources/qml/controls-uit/BaseWebView.qml +++ b/interface/resources/qml/controls-uit/BaseWebView.qml @@ -8,8 +8,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtWebEngine 1.2 +import QtQuick 2.7 +import QtWebEngine 1.5 WebEngineView { id: root diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index ea9ec2f6c9..96bb359aea 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -1,6 +1,6 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import QtWebEngine 1.1; +import QtWebEngine 1.5; import Qt.labs.settings 1.0 import "../desktop" as OriginalDesktop diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index af54c86bf4..972c03355c 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -9,12 +9,11 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.5 as QQControls +import QtQuick 2.7 +import QtQuick.Controls 2.2 as QQControls import QtQuick.Layouts 1.3 -import QtQuick.Controls.Styles 1.4 -import QtWebEngine 1.2 +import QtWebEngine 1.5 import QtWebChannel 1.0 import "../styles-uit" @@ -66,9 +65,27 @@ Rectangle { } } - QQControls.TextField { + QQControls.ComboBox { id: addressBar + //selectByMouse: true + focus: true + //placeholderText: "Enter URL" + editable: true + flat: true + indicator: Item {} + Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") + + Keys.onPressed: { + if (event.key === Qt.Key_Return) { + if (editText.indexOf("http") != 0) { + editText = "http://" + editText; + } + webEngineView.url = editText + event.accepted = true; + } + } + Image { anchors.verticalCenter: addressBar.verticalCenter; x: 5 @@ -94,21 +111,18 @@ Rectangle { } } - style: TextFieldStyle { - padding { - left: 26; - right: 26 - } - } - focus: true + leftPadding: 26 + rightPadding: 26 + Layout.fillWidth: true - text: webEngineView.url - onAccepted: webEngineView.url = text + editText: webEngineView.url + onAccepted: webEngineView.url = editText } + HifiControls.WebGlyphButton { checkable: true //only QtWebEngine 1.3 - //checked: webEngineView.audioMuted + checked: webEngineView.audioMuted glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted anchors.verticalCenter: parent.verticalCenter; width: hifi.dimensions.controlLineHeight @@ -120,18 +134,24 @@ Rectangle { QQControls.ProgressBar { id: loadProgressBar - style: ProgressBarStyle { - background: Rectangle { - color: "#6A6A6A" - } - progress: Rectangle{ + background: Rectangle { + implicitHeight: 2 + color: "#6A6A6A" + } + + contentItem: Item { + implicitHeight: 2 + + Rectangle { + width: loadProgressBar.visualPosition * parent.width + height: parent.height color: "#00B4EF" } } width: parent.width; - minimumValue: 0 - maximumValue: 100 + from: 0 + to: 100 value: webEngineView.loadProgress height: 2 } @@ -183,8 +203,8 @@ Rectangle { settings.pluginsEnabled: true settings.fullScreenSupportEnabled: false //from WebEngine 1.3 - // settings.autoLoadIconsForPage: false - // settings.touchIconsEnabled: false + settings.autoLoadIconsForPage: false + settings.touchIconsEnabled: false onCertificateError: { error.defer(); @@ -201,9 +221,7 @@ Rectangle { } onNewViewRequested: { - if (!request.userInitiated) { - print("Warning: Blocked a popup window."); - } + request.openIn(webEngineView); } onRenderProcessTerminated: { From 748ef8bb87663201a260bb71139512f44fe81677 Mon Sep 17 00:00:00 2001 From: vladest Date: Sun, 3 Sep 2017 18:10:53 +0200 Subject: [PATCH 02/42] Enable edit in combobox --- interface/resources/qml/hifi/WebBrowser.qml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 972c03355c..98ed0f10be 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -72,7 +72,7 @@ Rectangle { focus: true //placeholderText: "Enter URL" editable: true - flat: true + //flat: true indicator: Item {} Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") @@ -158,14 +158,15 @@ Rectangle { HifiControls.BaseWebView { id: webEngineView + width: parent.width; + property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 + height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight + + focus: true objectName: "tabletWebEngineView" url: "http://www.highfidelity.com" - property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 - - width: parent.width; - height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight profile: HFWebEngineProfile; @@ -258,6 +259,7 @@ Rectangle { } } + HifiControls.Keyboard { id: keyboard raised: parent.keyboardEnabled && parent.keyboardRaised From 6de00c796989ba26a6bdf48a89185e2e9d1a1b48 Mon Sep 17 00:00:00 2001 From: vladest Date: Tue, 5 Sep 2017 13:11:52 +0200 Subject: [PATCH 03/42] Implemented suggestions popup. Cleanup --- interface/resources/qml/hifi/WebBrowser.qml | 143 ++-- interface/src/Application.cpp | 3 + interface/src/opensearch/opensearchengine.cpp | 650 ++++++++++++++++++ interface/src/opensearch/opensearchengine.h | 128 ++++ .../opensearch/opensearchenginedelegate.cpp | 43 ++ .../src/opensearch/opensearchenginedelegate.h | 18 + 6 files changed, 946 insertions(+), 39 deletions(-) create mode 100644 interface/src/opensearch/opensearchengine.cpp create mode 100644 interface/src/opensearch/opensearchengine.h create mode 100644 interface/src/opensearch/opensearchenginedelegate.cpp create mode 100644 interface/src/opensearch/opensearchenginedelegate.h diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 98ed0f10be..582e26bbeb 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -21,6 +21,8 @@ import "../controls-uit" as HifiControls import "../windows" import "../controls" +import HifiWeb 1.0 + Rectangle { id: root; @@ -31,17 +33,61 @@ Rectangle { property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false property bool keyboardRaised: false property bool punctuationMode: false + property var suggestionsList: [] + OpenSearchEngine { + id: searchEngine + name: "Google"; + //icon: ":icons/sites/google.png" + searchUrlTemplate: "https://www.google.com/search?client=qupzilla&q=%s"; + suggestionsUrlTemplate: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; + suggestionsUrl: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; + + onSuggestions: { + if (suggestions.length > 0) { + console.log("suggestions:", suggestions) + suggestionsList = [] + suggestionsList.push(addressBar.editText) //do not overwrite edit text + for(var i = 0; i < suggestions.length; i++) { + suggestionsList.push(suggestions[i]) + } + addressBar.model = suggestionsList + if (!addressBar.popup.visible) { + addressBar.popup.open() + } + } + } + } + + Timer { + id: suggestionRequestTimer + interval: 200 + repeat: false + onTriggered: { + if (addressBar.editText !== "") { + searchEngine.requestSuggestions(addressBar.editText) + } + } + } color: hifi.colors.baseGray; - // only show the title if loaded through a "loader" + function goTo(url) { + if (url.indexOf("http") !== 0) { + url = "http://" + url; + } + webEngineView.url = url + event.accepted = true; + suggestionRequestTimer.stop() + addressBar.popup.close() + } Column { spacing: 2 width: parent.width; RowLayout { + id: addressBarRow width: parent.width; height: 48 @@ -70,50 +116,65 @@ Rectangle { //selectByMouse: true focus: true - //placeholderText: "Enter URL" + editable: true //flat: true indicator: Item {} + background: Item {} + onActivated: { + goTo(textAt(index)) + } + + contentItem: QQControls.TextField { + id: addressBarInput + leftPadding: 26 + rightPadding: hifi.dimensions.controlLineHeight + text: addressBar.editText + placeholderText: qsTr("Enter URL") + font: addressBar.font + horizontalAlignment: Text.AlignLeft + verticalAlignment: Text.AlignVCenter + + Image { + anchors.verticalCenter: parent.verticalCenter; + x: 5 + z: 2 + id: faviconImage + width: 16; height: 16 + sourceSize: Qt.size(width, height) + source: webEngineView.icon + onSourceChanged: console.log("web icon", source) + } + + HifiControls.WebGlyphButton { + glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; + anchors.verticalCenter: parent.verticalCenter; + width: hifi.dimensions.controlLineHeight + z: 2 + x: addressBarInput.width - implicitWidth + onClicked: { + if (webEngineView.loading) { + webEngineView.stop() + } else { + reloadTimer.start() + } + } + } + } + Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") Keys.onPressed: { if (event.key === Qt.Key_Return) { - if (editText.indexOf("http") != 0) { - editText = "http://" + editText; - } - webEngineView.url = editText - event.accepted = true; + goTo(editText) } } - Image { - anchors.verticalCenter: addressBar.verticalCenter; - x: 5 - z: 2 - id: faviconImage - width: 16; height: 16 - sourceSize: Qt.size(width, height) - source: webEngineView.icon + onEditTextChanged: { + console.log("edit text", addressBar.editText) + suggestionRequestTimer.restart() } - HifiControls.WebGlyphButton { - glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; - anchors.verticalCenter: parent.verticalCenter; - width: hifi.dimensions.controlLineHeight - z: 2 - x: addressBar.width - 28 - onClicked: { - if (webEngineView.loading) { - webEngineView.stop() - } else { - reloadTimer.start() - } - } - } - - leftPadding: 26 - rightPadding: 26 - Layout.fillWidth: true editText: webEngineView.url onAccepted: webEngineView.url = editText @@ -121,7 +182,6 @@ Rectangle { HifiControls.WebGlyphButton { checkable: true - //only QtWebEngine 1.3 checked: webEngineView.audioMuted glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted anchors.verticalCenter: parent.verticalCenter; @@ -162,7 +222,6 @@ Rectangle { property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - focus: true objectName: "tabletWebEngineView" @@ -172,6 +231,13 @@ Rectangle { property string userScriptUrl: "" + onLoadingChanged: { + if (!loading) { + suggestionRequestTimer.stop() + addressBar.popup.close() + } + } + // creates a global EventBridge object. WebEngineScript { id: createGlobalEventBridge @@ -202,10 +268,9 @@ Rectangle { settings.javascriptEnabled: true settings.errorPageEnabled: true settings.pluginsEnabled: true - settings.fullScreenSupportEnabled: false - //from WebEngine 1.3 - settings.autoLoadIconsForPage: false - settings.touchIconsEnabled: false + settings.fullScreenSupportEnabled: true + settings.autoLoadIconsForPage: true + settings.touchIconsEnabled: true onCertificateError: { error.defer(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fe6ea2a0b3..726e5ba6e4 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -201,6 +201,8 @@ #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" +#include "opensearch/opensearchengine.h" + // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. #if defined(Q_OS_WIN) @@ -2224,6 +2226,7 @@ void Application::initializeUi() { QmlCommerce::registerType(); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); + qmlRegisterType("HifiWeb", 1, 0, "OpenSearchEngine"); auto offscreenUi = DependencyManager::get(); offscreenUi->create(); diff --git a/interface/src/opensearch/opensearchengine.cpp b/interface/src/opensearch/opensearchengine.cpp new file mode 100644 index 0000000000..28cacab256 --- /dev/null +++ b/interface/src/opensearch/opensearchengine.cpp @@ -0,0 +1,650 @@ +#include "opensearchengine.h" +#include "qregexp.h" +#include "opensearchenginedelegate.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + + +/*! + \class OpenSearchEngine + \brief A class representing a single search engine described in OpenSearch format + + OpenSearchEngine is a class that represents a single search engine based on + the OpenSearch format. + For more information about the format, see http://www.opensearch.org/. + + Instances of the class hold all the data associated with the corresponding search + engines, such as name(), description() and also URL templates that are used + to construct URLs, which can be used later to perform search queries. Search engine + can also have an image, even an external one, in this case it will be downloaded + automatically from the network. + + OpenSearchEngine instances can be constructed from scratch but also read from + external sources and written back to them. OpenSearchReader and OpenSearchWriter + are the classes provided for reading and writing OpenSearch descriptions. + + Default constructed engines need to be filled with the necessary information before + they can be used to peform search requests. First of all, a search engine should have + the metadata including the name and the description. + However, the most important are URL templates, which are the construction of URLs + but can also contain template parameters, that are replaced with corresponding values + at the time of constructing URLs. + + There are two types of URL templates: search URL template and suggestions URL template. + Search URL template is needed for constructing search URLs, which point directly to + search results. Suggestions URL template is necessary to construct suggestion queries + URLs, which are then used for requesting contextual suggestions, a popular service + offered along with search results that provides search terms related to what has been + supplied by the user. + + Both types of URLs are constructed by the class, by searchUrl() and suggestionsUrl() + functions respectively. However, search requests are supposed to be performed outside + the class, while suggestion queries can be executed using the requestSuggestions() + method. The class will take care of peforming the network request and parsing the + JSON response. + + Both the image request and suggestion queries need network access. The class can + perform network requests on its own, though the client application needs to provide + a network access manager, which then will to be used for network operations. + Without that, both images delivered from remote locations and contextual suggestions + will be disabled. + + \sa OpenSearchReader, OpenSearchWriter +*/ + +/*! + Constructs an engine with a given \a parent. +*/ +OpenSearchEngine::OpenSearchEngine(QObject* parent) + : QObject(parent) + , m_searchMethod(QLatin1String("get")) + , m_suggestionsMethod(QLatin1String("get")) + , m_suggestionsReply(0) + , m_delegate(0) +{ + m_requestMethods.insert(QLatin1String("get"), QNetworkAccessManager::GetOperation); + m_requestMethods.insert(QLatin1String("post"), QNetworkAccessManager::PostOperation); +} + +/*! + A destructor. +*/ +OpenSearchEngine::~OpenSearchEngine() +{ +} + +QString OpenSearchEngine::parseTemplate(const QString &searchTerm, const QString &searchTemplate) +{ + QString language = QLocale().name(); + // Simple conversion to RFC 3066. + language = language.replace(QLatin1Char('_'), QLatin1Char('-')); + + QString result = searchTemplate; + result.replace(QLatin1String("{count}"), QLatin1String("20")); + result.replace(QLatin1String("{startIndex}"), QLatin1String("0")); + result.replace(QLatin1String("{startPage}"), QLatin1String("0")); + result.replace(QLatin1String("{language}"), language); + result.replace(QLatin1String("{inputEncoding}"), QLatin1String("UTF-8")); + result.replace(QLatin1String("{outputEncoding}"), QLatin1String("UTF-8")); + result.replace(QRegExp(QLatin1String("\\{([^\\}]*:|)source\\??\\}")), QCoreApplication::applicationName()); + result.replace(QLatin1String("{searchTerms}"), QLatin1String(QUrl::toPercentEncoding(searchTerm))); + + return result; +} + +/*! + \property OpenSearchEngine::name + \brief the name of the engine + + \sa description() +*/ +QString OpenSearchEngine::name() const +{ + return m_name; +} + +void OpenSearchEngine::setName(const QString &name) +{ + m_name = name; +} + +/*! + \property OpenSearchEngine::description + \brief the description of the engine + + \sa name() +*/ +QString OpenSearchEngine::description() const +{ + return m_description; +} + +void OpenSearchEngine::setDescription(const QString &description) +{ + m_description = description; +} + +/*! + \property OpenSearchEngine::searchUrlTemplate + \brief the template of the search URL + + \sa searchUrl(), searchParameters(), suggestionsUrlTemplate() +*/ +QString OpenSearchEngine::searchUrlTemplate() const +{ + return m_searchUrlTemplate; +} + +void OpenSearchEngine::setSearchUrlTemplate(const QString &searchUrlTemplate) +{ + if (!searchUrlTemplate.startsWith(QLatin1String("http://")) && !searchUrlTemplate.startsWith(QLatin1String("https://"))) { + return; + } + + m_searchUrlTemplate = searchUrlTemplate; +} + +/*! + Constructs and returns a search URL with a given \a searchTerm. + + The URL template is processed according to the specification: + http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax + + A list of template parameters currently supported and what they are replaced with: + \table + \header \o parameter + \o value + \row \o "{count}" + \o "20" + \row \o "{startIndex}" + \o "0" + \row \o "{startPage}" + \o "0" + \row \o "{language}" + \o "the default language code (RFC 3066)" + \row \o "{inputEncoding}" + \o "UTF-8" + \row \o "{outputEncoding}" + \o "UTF-8" + \row \o "{*:source}" + \o "application name, QCoreApplication::applicationName()" + \row \o "{searchTerms}" + \o "the string supplied by the user" + \endtable + + \sa searchUrlTemplate(), searchParameters(), suggestionsUrl() +*/ +QUrl OpenSearchEngine::searchUrl(const QString &searchTerm) const +{ + if (m_searchUrlTemplate.isEmpty()) { + return QUrl(); + } + + QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_searchUrlTemplate).toUtf8()); + + QUrlQuery query(retVal); + if (m_searchMethod != QLatin1String("post")) { + Parameters::const_iterator end = m_searchParameters.constEnd(); + Parameters::const_iterator i = m_searchParameters.constBegin(); + for (; i != end; ++i) { + query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); + } + retVal.setQuery(query); + } + + return retVal; +} + +QByteArray OpenSearchEngine::getPostData(const QString &searchTerm) const +{ + if (m_searchMethod != QLatin1String("post")) { + return QByteArray(); + } + + QUrl retVal = QUrl("http://foo.bar"); + + QUrlQuery query(retVal); + Parameters::const_iterator end = m_searchParameters.constEnd(); + Parameters::const_iterator i = m_searchParameters.constBegin(); + for (; i != end; ++i) { + query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); + } + retVal.setQuery(query); + + QByteArray data = retVal.toEncoded(QUrl::RemoveScheme); + return data.contains('?') ? data.mid(data.lastIndexOf('?') + 1) : QByteArray(); +} + +/*! + \property providesSuggestions + \brief indicates whether the engine supports contextual suggestions +*/ +bool OpenSearchEngine::providesSuggestions() const +{ + return (!m_suggestionsUrlTemplate.isEmpty() || !m_preparedSuggestionsUrl.isEmpty()); +} + +/*! + \property OpenSearchEngine::suggestionsUrlTemplate + \brief the template of the suggestions URL + + \sa suggestionsUrl(), suggestionsParameters(), searchUrlTemplate() +*/ +QString OpenSearchEngine::suggestionsUrlTemplate() const +{ + return m_suggestionsUrlTemplate; +} + +void OpenSearchEngine::setSuggestionsUrlTemplate(const QString &suggestionsUrlTemplate) +{ + if (!suggestionsUrlTemplate.startsWith(QLatin1String("http://")) && !suggestionsUrlTemplate.startsWith(QLatin1String("https://"))) { + return; + } + + m_suggestionsUrlTemplate = suggestionsUrlTemplate; +} + +/*! + Constructs a suggestions URL with a given \a searchTerm. + + The URL template is processed according to the specification: + http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax + + See searchUrl() for more information about processing template parameters. + + \sa suggestionsUrlTemplate(), suggestionsParameters(), searchUrl() +*/ +QUrl OpenSearchEngine::suggestionsUrl(const QString &searchTerm) const +{ + if (!m_preparedSuggestionsUrl.isEmpty()) { + QString s = m_preparedSuggestionsUrl; + s.replace(QLatin1String("%s"), searchTerm); + return QUrl(s); + } + + if (m_suggestionsUrlTemplate.isEmpty()) { + return QUrl(); + } + + QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_suggestionsUrlTemplate).toUtf8()); + + QUrlQuery query(retVal); + if (m_suggestionsMethod != QLatin1String("post")) { + Parameters::const_iterator end = m_suggestionsParameters.constEnd(); + Parameters::const_iterator i = m_suggestionsParameters.constBegin(); + for (; i != end; ++i) { + query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); + } + retVal.setQuery(query); + } + + return retVal; +} + +/*! + \property searchParameters + \brief additional parameters that will be included in the search URL + + For more information see: + http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0 +*/ +OpenSearchEngine::Parameters OpenSearchEngine::searchParameters() const +{ + return m_searchParameters; +} + +void OpenSearchEngine::setSearchParameters(const Parameters &searchParameters) +{ + m_searchParameters = searchParameters; +} + +/*! + \property suggestionsParameters + \brief additional parameters that will be included in the suggestions URL + + For more information see: + http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0 +*/ +OpenSearchEngine::Parameters OpenSearchEngine::suggestionsParameters() const +{ + return m_suggestionsParameters; +} + +void OpenSearchEngine::setSuggestionsParameters(const Parameters &suggestionsParameters) +{ + m_suggestionsParameters = suggestionsParameters; +} + +/*! + \property searchMethod + \brief HTTP request method that will be used to perform search requests +*/ +QString OpenSearchEngine::searchMethod() const +{ + return m_searchMethod; +} + +void OpenSearchEngine::setSearchMethod(const QString &method) +{ + QString requestMethod = method.toLower(); + if (!m_requestMethods.contains(requestMethod)) { + return; + } + + m_searchMethod = requestMethod; +} + +/*! + \property suggestionsMethod + \brief HTTP request method that will be used to perform suggestions requests +*/ +QString OpenSearchEngine::suggestionsMethod() const +{ + return m_suggestionsMethod; +} + +void OpenSearchEngine::setSuggestionsMethod(const QString &method) +{ + QString requestMethod = method.toLower(); + if (!m_requestMethods.contains(requestMethod)) { + return; + } + + m_suggestionsMethod = requestMethod; +} + +/*! + \property imageUrl + \brief the image URL of the engine + + When setting a new image URL, it won't be loaded immediately. The first request will be + deferred until image() is called for the first time. + + \note To be able to request external images, you need to provide a network access manager, + which will be used for network operations. + + \sa image(), networkAccessManager() +*/ +QString OpenSearchEngine::imageUrl() const +{ + return m_imageUrl; +} + +void OpenSearchEngine::setImageUrl(const QString &imageUrl) +{ + m_imageUrl = imageUrl; +} + +void OpenSearchEngine::loadImage() const +{ + if (m_imageUrl.isEmpty()) { + return; + } + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(QUrl::fromEncoded(m_imageUrl.toUtf8()))); + connect(reply, SIGNAL(finished()), this, SLOT(imageObtained())); +} + +void OpenSearchEngine::imageObtained() +{ + QNetworkReply* reply = qobject_cast(sender()); + + if (!reply) { + return; + } + + QByteArray response = reply->readAll(); + + reply->close(); + reply->deleteLater(); + + if (response.isEmpty()) { + return; + } + + m_image.loadFromData(response); + emit imageChanged(); +} + +/*! + \property image + \brief the image of the engine + + When no image URL has been set and an image will be set explicitly, a new data URL + will be constructed, holding the image data encoded with Base64. + + \sa imageUrl() +*/ +QImage OpenSearchEngine::image() const +{ + if (m_image.isNull()) { + loadImage(); + } + return m_image; +} + +void OpenSearchEngine::setImage(const QImage &image) +{ + if (m_imageUrl.isEmpty()) { + QBuffer imageBuffer; + imageBuffer.open(QBuffer::ReadWrite); + if (image.save(&imageBuffer, "PNG")) { + m_imageUrl = QString(QLatin1String("data:image/png;base64,%1")) + .arg(QLatin1String(imageBuffer.buffer().toBase64())); + } + } + + m_image = image; + emit imageChanged(); +} + +/*! + \property valid + \brief indicates whether the engine is valid i.e. the description was properly formed and included all necessary information +*/ +bool OpenSearchEngine::isValid() const +{ + return (!m_name.isEmpty() && !m_searchUrlTemplate.isEmpty()); +} + +bool OpenSearchEngine::operator==(const OpenSearchEngine &other) const +{ + return (m_name == other.m_name + && m_description == other.m_description + && m_imageUrl == other.m_imageUrl + && m_searchUrlTemplate == other.m_searchUrlTemplate + && m_suggestionsUrlTemplate == other.m_suggestionsUrlTemplate + && m_searchParameters == other.m_searchParameters + && m_suggestionsParameters == other.m_suggestionsParameters); +} + +bool OpenSearchEngine::operator<(const OpenSearchEngine &other) const +{ + return (m_name < other.m_name); +} + +/*! + Requests contextual suggestions on the search engine, for a given \a searchTerm. + + If succeeded, suggestions() signal will be emitted once the suggestions are received. + + \note To be able to request suggestions, you need to provide a network access manager, + which will be used for network operations. + + \sa requestSearchResults() +*/ + +void OpenSearchEngine::setSuggestionsParameters(const QByteArray ¶meters) +{ + m_preparedSuggestionsParameters = parameters; +} + +void OpenSearchEngine::setSuggestionsUrl(const QString &string) +{ + m_preparedSuggestionsUrl = string; +} + +QString OpenSearchEngine::getSuggestionsUrl() +{ + return suggestionsUrl("searchstring").toString().replace(QLatin1String("searchstring"), QLatin1String("%s")); +} + +QByteArray OpenSearchEngine::getSuggestionsParameters() +{ + QStringList parameters; + Parameters::const_iterator end = m_suggestionsParameters.constEnd(); + Parameters::const_iterator i = m_suggestionsParameters.constBegin(); + for (; i != end; ++i) { + parameters.append(i->first + QLatin1String("=") + i->second); + } + + QByteArray data = parameters.join(QLatin1String("&")).toUtf8(); + + return data; +} + +void OpenSearchEngine::requestSuggestions(const QString &searchTerm) +{ + if (searchTerm.isEmpty() || !providesSuggestions()) { + return; + } + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + + if (m_suggestionsReply) { + m_suggestionsReply->disconnect(this); + m_suggestionsReply->abort(); + m_suggestionsReply->deleteLater(); + m_suggestionsReply = 0; + } + + Q_ASSERT(m_requestMethods.contains(m_suggestionsMethod)); + if (m_suggestionsMethod == QLatin1String("get")) { + m_suggestionsReply = networkAccessManager.get(QNetworkRequest(suggestionsUrl(searchTerm))); + } + else { + QStringList parameters; + Parameters::const_iterator end = m_suggestionsParameters.constEnd(); + Parameters::const_iterator i = m_suggestionsParameters.constBegin(); + for (; i != end; ++i) { + parameters.append(i->first + QLatin1String("=") + i->second); + } + + QByteArray data = parameters.join(QLatin1String("&")).toUtf8(); + m_suggestionsReply = networkAccessManager.post(QNetworkRequest(suggestionsUrl(searchTerm)), data); + } + + connect(m_suggestionsReply, SIGNAL(finished()), this, SLOT(suggestionsObtained())); +} + +/*! + Requests search results on the search engine, for a given \a searchTerm. + + The default implementation does nothing, to supply your own you need to create your own + OpenSearchEngineDelegate subclass and supply it to the engine. Then the function will call + the performSearchRequest() method of the delegate, which can then handle the request + in a custom way. + + \sa requestSuggestions(), delegate() +*/ +void OpenSearchEngine::requestSearchResults(const QString &searchTerm) +{ + if (!m_delegate || searchTerm.isEmpty()) { + return; + } + + Q_ASSERT(m_requestMethods.contains(m_searchMethod)); + + QNetworkRequest request(QUrl(searchUrl(searchTerm))); + QByteArray data; + QNetworkAccessManager::Operation operation = m_requestMethods.value(m_searchMethod); + + if (operation == QNetworkAccessManager::PostOperation) { + QStringList parameters; + Parameters::const_iterator end = m_searchParameters.constEnd(); + Parameters::const_iterator i = m_searchParameters.constBegin(); + for (; i != end; ++i) { + parameters.append(i->first + QLatin1String("=") + i->second); + } + + data = parameters.join(QLatin1String("&")).toUtf8(); + } + + m_delegate->performSearchRequest(request, operation, data); +} + +void OpenSearchEngine::suggestionsObtained() +{ + const QByteArray response = m_suggestionsReply->readAll(); + + m_suggestionsReply->close(); + m_suggestionsReply->deleteLater(); + m_suggestionsReply = 0; + + QJsonParseError err; + QJsonDocument json = QJsonDocument::fromJson(response, &err); + const QVariant res = json.toVariant(); + + if (err.error != QJsonParseError::NoError || res.type() != QVariant::List) + return; + + const QVariantList list = res.toList(); + + if (list.size() < 2) + return; + + QStringList out; + + foreach (const QVariant &v, list.at(1).toList()) + out.append(v.toString()); + + emit suggestions(out); +} + + +/*! + \property delegate + \brief the delegate that is used to perform specific tasks. + + It can be currently supplied to provide a custom behaviour ofthe requetSearchResults() method. + The default implementation does nothing. +*/ +OpenSearchEngineDelegate* OpenSearchEngine::delegate() const +{ + return m_delegate; +} + +void OpenSearchEngine::setDelegate(OpenSearchEngineDelegate* delegate) +{ + m_delegate = delegate; +} + +/*! + \fn void OpenSearchEngine::imageChanged() + + This signal is emitted whenever the image of the engine changes. + + \sa image(), imageUrl() +*/ + +/*! + \fn void OpenSearchEngine::suggestions(const QStringList &suggestions) + + This signal is emitted whenever new contextual suggestions have been provided + by the search engine. To request suggestions, use requestSuggestions(). + The suggestion set is specified by \a suggestions. + + \sa requestSuggestions() +*/ diff --git a/interface/src/opensearch/opensearchengine.h b/interface/src/opensearch/opensearchengine.h new file mode 100644 index 0000000000..2b717fab7e --- /dev/null +++ b/interface/src/opensearch/opensearchengine.h @@ -0,0 +1,128 @@ +#ifndef OPENSEARCHENGINE_H +#define OPENSEARCHENGINE_H + +#include +#include +#include +#include +#include +#include + +class QNetworkReply; + +class OpenSearchEngineDelegate; +class OpenSearchEngine : public QObject +{ + Q_OBJECT + +signals: + void imageChanged(); + void suggestions(const QStringList &suggestions); + +public: + typedef QPair Parameter; + typedef QList Parameters; + + Q_PROPERTY(QString name READ name WRITE setName) + Q_PROPERTY(QString description READ description WRITE setDescription) + Q_PROPERTY(QString searchUrlTemplate READ searchUrlTemplate WRITE setSearchUrlTemplate) + Q_PROPERTY(Parameters searchParameters READ searchParameters WRITE setSearchParameters) + Q_PROPERTY(QString searchMethod READ searchMethod WRITE setSearchMethod) + Q_PROPERTY(QString suggestionsUrlTemplate READ suggestionsUrlTemplate WRITE setSuggestionsUrlTemplate) + Q_PROPERTY(QString suggestionsUrl READ getSuggestionsUrl WRITE setSuggestionsUrl) + Q_PROPERTY(Parameters suggestionsParameters READ suggestionsParameters WRITE setSuggestionsParameters) + Q_PROPERTY(QString suggestionsMethod READ suggestionsMethod WRITE setSuggestionsMethod) + Q_PROPERTY(bool providesSuggestions READ providesSuggestions) + Q_PROPERTY(QString imageUrl READ imageUrl WRITE setImageUrl) + Q_PROPERTY(bool valid READ isValid) + + OpenSearchEngine(QObject* parent = 0); + ~OpenSearchEngine(); + + QString name() const; + void setName(const QString &name); + + QString description() const; + void setDescription(const QString &description); + + QString searchUrlTemplate() const; + void setSearchUrlTemplate(const QString &searchUrl); + QUrl searchUrl(const QString &searchTerm) const; + + QByteArray getPostData(const QString &searchTerm) const; + + bool providesSuggestions() const; + + QString suggestionsUrlTemplate() const; + void setSuggestionsUrlTemplate(const QString &suggestionsUrl); + QUrl suggestionsUrl(const QString &searchTerm) const; + + Parameters searchParameters() const; + void setSearchParameters(const Parameters &searchParameters); + + Parameters suggestionsParameters() const; + void setSuggestionsParameters(const Parameters &suggestionsParameters); + + QString searchMethod() const; + void setSearchMethod(const QString &method); + + QString suggestionsMethod() const; + void setSuggestionsMethod(const QString &method); + + QString imageUrl() const; + void setImageUrl(const QString &url); + + QImage image() const; + void setImage(const QImage &image); + + bool isValid() const; + + void setSuggestionsUrl(const QString &string); + void setSuggestionsParameters(const QByteArray ¶meters); + QString getSuggestionsUrl(); + QByteArray getSuggestionsParameters(); + + OpenSearchEngineDelegate* delegate() const; + void setDelegate(OpenSearchEngineDelegate* delegate); + + bool operator==(const OpenSearchEngine &other) const; + bool operator<(const OpenSearchEngine &other) const; + +public slots: + void requestSuggestions(const QString &searchTerm); + void requestSearchResults(const QString &searchTerm); + +protected: + static QString parseTemplate(const QString &searchTerm, const QString &searchTemplate); + void loadImage() const; + +private slots: + void imageObtained(); + void suggestionsObtained(); + +private: + QString m_name; + QString m_description; + + QString m_imageUrl; + QImage m_image; + + QString m_searchUrlTemplate; + QString m_suggestionsUrlTemplate; + Parameters m_searchParameters; + Parameters m_suggestionsParameters; + QString m_searchMethod; + QString m_suggestionsMethod; + + QByteArray m_preparedSuggestionsParameters; + QString m_preparedSuggestionsUrl; + + QMap m_requestMethods; + + QNetworkReply* m_suggestionsReply; + + OpenSearchEngineDelegate* m_delegate; +}; + +#endif // OPENSEARCHENGINE_H + diff --git a/interface/src/opensearch/opensearchenginedelegate.cpp b/interface/src/opensearch/opensearchenginedelegate.cpp new file mode 100644 index 0000000000..2c9c18ece0 --- /dev/null +++ b/interface/src/opensearch/opensearchenginedelegate.cpp @@ -0,0 +1,43 @@ +#include "opensearchenginedelegate.h" + +/*! + \class OpenSearchEngineDelegate + \brief An abstract class providing custom processing of specific activities. + + OpenSearchEngineDelegate is an abstract class that can be subclassed and set on + an OpenSearchEngine. It allows to customize some parts of the default implementation + or even extend it with missing bits. + + Currently subclasses can only provide a custom way of handling search requests by + reimplementing the performSearchRequest() method. + + \sa OpenSearchEngine +*/ + +/*! + Constructs the delegate. +*/ +OpenSearchEngineDelegate::OpenSearchEngineDelegate() +{ +} + +/*! + Destructs the delegate. +*/ +OpenSearchEngineDelegate::~OpenSearchEngineDelegate() +{ +} + +/*! + \fn void performSearchRequest(const QNetworkRequest &request, + QNetworkAccessManager::Operation operation, const QByteArray &data) = 0 + + This method will be used after OpenSearchEngine::requestResults() is called. + + For example, a console application that uses the OpenSearchEngine class to generate + a search URL for a search term supplied by the user would reimplement the function + and forward the request to e.g. a web browser. + + Likewise, a web browser that uses the OpenSearchEngine class to support multiple search + engines e.g. in a toolbar would perform the request and navigate to the search results site. +*/ diff --git a/interface/src/opensearch/opensearchenginedelegate.h b/interface/src/opensearch/opensearchenginedelegate.h new file mode 100644 index 0000000000..ae7b5ef876 --- /dev/null +++ b/interface/src/opensearch/opensearchenginedelegate.h @@ -0,0 +1,18 @@ +#ifndef OPENSEARCHENGINEDELEGATE_H +#define OPENSEARCHENGINEDELEGATE_H + +#include +#include + +class OpenSearchEngineDelegate +{ +public: + OpenSearchEngineDelegate(); + virtual ~OpenSearchEngineDelegate(); + + virtual void performSearchRequest(const QNetworkRequest &request, + QNetworkAccessManager::Operation operation, + const QByteArray &data) = 0; +}; + +#endif // OPENSEARCHENGINEDELEGATE_H From 33767862888359beeb85fa2a41e7e24c3bbff933 Mon Sep 17 00:00:00 2001 From: vladest Date: Wed, 20 Sep 2017 17:02:54 +0200 Subject: [PATCH 04/42] Added text selection on focus. Added basic check for text within url input field to decide is this something for search or for goto. Limited popup list height to do not cover virtual keyboard --- interface/resources/qml/hifi/WebBrowser.qml | 37 ++++++++++++++++++--- interface/src/opensearch/opensearchengine.h | 2 +- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 582e26bbeb..f1c6efde9a 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -39,7 +39,7 @@ Rectangle { id: searchEngine name: "Google"; //icon: ":icons/sites/google.png" - searchUrlTemplate: "https://www.google.com/search?client=qupzilla&q=%s"; + searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q={searchTerms}"; suggestionsUrlTemplate: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; suggestionsUrl: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; @@ -73,11 +73,14 @@ Rectangle { color: hifi.colors.baseGray; function goTo(url) { - if (url.indexOf("http") !== 0) { + //must be valid attempt to open an site with dot + if (url.indexOf("http") <= 0 && url.indexOf(".") > 0) { url = "http://" + url; + } else { + url = searchEngine.searchUrl(url) } + webEngineView.url = url - event.accepted = true; suggestionRequestTimer.stop() addressBar.popup.close() } @@ -125,6 +128,14 @@ Rectangle { goTo(textAt(index)) } + popup.bottomPadding: keyboard.height + + onFocusChanged: { + if (focus) { + addressBarInput.selectAll() + } + } + contentItem: QQControls.TextField { id: addressBarInput leftPadding: 26 @@ -132,8 +143,25 @@ Rectangle { text: addressBar.editText placeholderText: qsTr("Enter URL") font: addressBar.font + selectByMouse: true horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter + onFocusChanged: { + if (focus) { + selectAll() + } + } + + Keys.onDeletePressed: { + editText = "" + } + + Keys.onPressed: { + if (event.key === Qt.Key_Return) { + goTo(editText) + event.accepted = true; + } + } Image { anchors.verticalCenter: parent.verticalCenter; @@ -167,6 +195,7 @@ Rectangle { Keys.onPressed: { if (event.key === Qt.Key_Return) { goTo(editText) + event.accepted = true; } } @@ -177,7 +206,7 @@ Rectangle { Layout.fillWidth: true editText: webEngineView.url - onAccepted: webEngineView.url = editText + onAccepted: goTo(editText) } HifiControls.WebGlyphButton { diff --git a/interface/src/opensearch/opensearchengine.h b/interface/src/opensearch/opensearchengine.h index 2b717fab7e..fa714b85d3 100644 --- a/interface/src/opensearch/opensearchengine.h +++ b/interface/src/opensearch/opensearchengine.h @@ -47,7 +47,6 @@ public: QString searchUrlTemplate() const; void setSearchUrlTemplate(const QString &searchUrl); - QUrl searchUrl(const QString &searchTerm) const; QByteArray getPostData(const QString &searchTerm) const; @@ -89,6 +88,7 @@ public: bool operator<(const OpenSearchEngine &other) const; public slots: + QUrl searchUrl(const QString &searchTerm) const; void requestSuggestions(const QString &searchTerm); void requestSearchResults(const QString &searchTerm); From 45f1f074dff350da0762b180884383e7ae8f8851 Mon Sep 17 00:00:00 2001 From: vladest Date: Thu, 12 Oct 2017 20:52:40 +0200 Subject: [PATCH 05/42] Reduce combobox dropdown height taking keyboard in account --- interface/resources/qml/hifi/WebBrowser.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index f1c6efde9a..1b49701bdf 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -38,14 +38,12 @@ Rectangle { OpenSearchEngine { id: searchEngine name: "Google"; - //icon: ":icons/sites/google.png" searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q={searchTerms}"; suggestionsUrlTemplate: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; suggestionsUrl: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; onSuggestions: { if (suggestions.length > 0) { - console.log("suggestions:", suggestions) suggestionsList = [] suggestionsList.push(addressBar.editText) //do not overwrite edit text for(var i = 0; i < suggestions.length; i++) { @@ -128,7 +126,7 @@ Rectangle { goTo(textAt(index)) } - popup.bottomPadding: keyboard.height + popup.height: webEngineView.height onFocusChanged: { if (focus) { From 83c14bba5ca412c88468a5bcf8ba3fdcd04f17a3 Mon Sep 17 00:00:00 2001 From: vladest Date: Fri, 20 Oct 2017 11:55:41 +0200 Subject: [PATCH 06/42] Fix popups misbehavior, wrong links with double http etc --- interface/resources/qml/hifi/WebBrowser.qml | 35 +++++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 1b49701bdf..6a1d61de11 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -1,3 +1,4 @@ + // // WebBrowser.qml // @@ -45,7 +46,7 @@ Rectangle { onSuggestions: { if (suggestions.length > 0) { suggestionsList = [] - suggestionsList.push(addressBar.editText) //do not overwrite edit text + suggestionsList.push(addressBarInput.text) //do not overwrite edit text for(var i = 0; i < suggestions.length; i++) { suggestionsList.push(suggestions[i]) } @@ -63,7 +64,7 @@ Rectangle { repeat: false onTriggered: { if (addressBar.editText !== "") { - searchEngine.requestSuggestions(addressBar.editText) + searchEngine.requestSuggestions(addressBarInput.text) } } } @@ -72,12 +73,16 @@ Rectangle { function goTo(url) { //must be valid attempt to open an site with dot - if (url.indexOf("http") <= 0 && url.indexOf(".") > 0) { - url = "http://" + url; + + if (url.indexOf(".") > 0) { + if (url.indexOf("http") < 0) { + url = "http://" + url; + } } else { url = searchEngine.searchUrl(url) } + addressBar.model = [] webEngineView.url = url suggestionRequestTimer.stop() addressBar.popup.close() @@ -151,12 +156,12 @@ Rectangle { } Keys.onDeletePressed: { - editText = "" + addressBarInput.text = "" } Keys.onPressed: { if (event.key === Qt.Key_Return) { - goTo(editText) + goTo(addressBarInput.text) event.accepted = true; } } @@ -192,19 +197,24 @@ Rectangle { Keys.onPressed: { if (event.key === Qt.Key_Return) { - goTo(editText) + goTo(addressBarInput.text) event.accepted = true; } } onEditTextChanged: { - console.log("edit text", addressBar.editText) - suggestionRequestTimer.restart() + if (addressBar.editText !== "" && addressBar.editText !== webEngineView.url.toString()) { + suggestionRequestTimer.restart(); + } else { + addressBar.model = [] + addressBar.popup.close() + } + } Layout.fillWidth: true editText: webEngineView.url - onAccepted: goTo(editText) + onAccepted: goTo(addressBarInput.text) } HifiControls.WebGlyphButton { @@ -265,6 +275,11 @@ Rectangle { } } + onLinkHovered: { + //TODO: change cursor shape? + console.error("hoveredUrl:", hoveredUrl) + } + // creates a global EventBridge object. WebEngineScript { id: createGlobalEventBridge From fd41299efc5e6ed42d2ec7f6336fc57dac2f86da Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 23 Oct 2017 22:34:22 +0200 Subject: [PATCH 07/42] Disable profile and custom user agent which broke nav bars --- interface/resources/qml/hifi/WebBrowser.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 6a1d61de11..edd0523aa6 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -264,7 +264,7 @@ Rectangle { url: "http://www.highfidelity.com" - profile: HFWebEngineProfile; + //profile: HFWebEngineProfile; property string userScriptUrl: "" @@ -321,7 +321,7 @@ Rectangle { Component.onCompleted: { webChannel.registerObject("eventBridge", eventBridge); webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; } onFeaturePermissionRequested: { From 874b9149e7a204eff07d5c48c637d6793e27f812 Mon Sep 17 00:00:00 2001 From: vladest Date: Tue, 24 Oct 2017 22:04:25 +0200 Subject: [PATCH 08/42] Popupd support reworked --- interface/resources/qml/hifi/WebBrowser.qml | 255 ++++++++++++-------- 1 file changed, 159 insertions(+), 96 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index edd0523aa6..c96d05d6a7 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -253,115 +253,178 @@ Rectangle { height: 2 } - HifiControls.BaseWebView { - id: webEngineView + Component { + id: webDialogComponent + Rectangle { + property alias webDialogView: webDialogView + color: "white" + HifiControls.BaseWebView { + id: webDialogView + anchors.fill: parent + + settings.autoLoadImages: true + settings.javascriptEnabled: true + settings.errorPageEnabled: true + settings.pluginsEnabled: true + settings.fullScreenSupportEnabled: true + settings.autoLoadIconsForPage: true + settings.touchIconsEnabled: true + + onWindowCloseRequested: { + webDialog.active = false + webDialog.request = null + } + } + } + } + + Item { width: parent.width; property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - focus: true - objectName: "tabletWebEngineView" + HifiControls.BaseWebView { + id: webEngineView + anchors.fill: parent - url: "http://www.highfidelity.com" + focus: true + objectName: "tabletWebEngineView" - //profile: HFWebEngineProfile; + url: "https://www.highfidelity.com" - property string userScriptUrl: "" + //profile: HFWebEngineProfile; - onLoadingChanged: { - if (!loading) { - suggestionRequestTimer.stop() - addressBar.popup.close() + property string userScriptUrl: "" + + onLoadingChanged: { + if (!loading) { + suggestionRequestTimer.stop() + addressBar.popup.close() + } + } + + onLinkHovered: { + //TODO: change cursor shape? + console.error("hoveredUrl:", hoveredUrl) + } + + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: webEngineView.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] + + settings.autoLoadImages: true + settings.javascriptEnabled: true + settings.errorPageEnabled: true + settings.pluginsEnabled: true + settings.fullScreenSupportEnabled: true + settings.autoLoadIconsForPage: true + settings.touchIconsEnabled: true + + onCertificateError: { + error.defer(); + } + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onNewViewRequested: { + console.error("new view requested:", request.destination) + if (request.destination == WebEngineView.NewViewInDialog) { + webDialog.request = request + webDialog.active = true + } else { + request.openIn(webEngineView); + } + } + + onRenderProcessTerminated: { + var status = ""; + switch (terminationStatus) { + case WebEngineView.NormalTerminationStatus: + status = "(normal exit)"; + break; + case WebEngineView.AbnormalTerminationStatus: + status = "(abnormal exit)"; + break; + case WebEngineView.CrashedTerminationStatus: + status = "(crashed)"; + break; + case WebEngineView.KilledTerminationStatus: + status = "(killed)"; + break; + } + + console.error("Render process exited with code " + exitCode + " " + status); + reloadTimer.running = true; + } + + onFullScreenRequested: { + console.error("FS requested:", request.destination) + if (request.toggleOn) { + webEngineView.state = "FullScreen"; + } else { + webEngineView.state = ""; + } + request.accept(); + } + + onWindowCloseRequested: { + console.error("window close requested:", request.destination) + } + + Timer { + id: reloadTimer + interval: 0 + running: false + repeat: false + onTriggered: webEngineView.reload() } } - onLinkHovered: { - //TODO: change cursor shape? - console.error("hoveredUrl:", hoveredUrl) - } + Loader { + id: webDialog + property WebEngineNewViewRequest request: null + anchors.fill: parent + anchors.margins: 10 - // creates a global EventBridge object. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // detects when to raise and lower virtual keyboard - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webEngineView.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - settings.autoLoadImages: true - settings.javascriptEnabled: true - settings.errorPageEnabled: true - settings.pluginsEnabled: true - settings.fullScreenSupportEnabled: true - settings.autoLoadIconsForPage: true - settings.touchIconsEnabled: true - - onCertificateError: { - error.defer(); - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; - } - - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } - - onNewViewRequested: { - request.openIn(webEngineView); - } - - onRenderProcessTerminated: { - var status = ""; - switch (terminationStatus) { - case WebEngineView.NormalTerminationStatus: - status = "(normal exit)"; - break; - case WebEngineView.AbnormalTerminationStatus: - status = "(abnormal exit)"; - break; - case WebEngineView.CrashedTerminationStatus: - status = "(crashed)"; - break; - case WebEngineView.KilledTerminationStatus: - status = "(killed)"; - break; + active: false + sourceComponent: webDialogComponent + onStatusChanged: { + if (Loader.Ready === status) { + focus = true + item.webDialogView.profile = webEngineView.profile + request.openIn(item.webDialogView) + } } - - print("Render process exited with code " + exitCode + " " + status); - reloadTimer.running = true; - } - - onWindowCloseRequested: { - } - - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: webEngineView.reload() } } } From 320ab2e68211fc5753de3e1e3b9b4d74cc502db4 Mon Sep 17 00:00:00 2001 From: vladest Date: Thu, 26 Oct 2017 21:14:44 +0200 Subject: [PATCH 09/42] Rework using StackView. Now backward takes in account popups as well --- interface/resources/qml/hifi/WebBrowser.qml | 356 ++++++++++---------- 1 file changed, 174 insertions(+), 182 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index c96d05d6a7..79932dbcdd 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -13,6 +13,7 @@ import QtQuick 2.7 import QtQuick.Controls 2.2 as QQControls import QtQuick.Layouts 1.3 +import QtGraphicalEffects 1.0 import QtWebEngine 1.5 import QtWebChannel 1.0 @@ -46,13 +47,13 @@ Rectangle { onSuggestions: { if (suggestions.length > 0) { suggestionsList = [] - suggestionsList.push(addressBarInput.text) //do not overwrite edit text + suggestionsList.push(addressBarInput.text); //do not overwrite edit text for(var i = 0; i < suggestions.length; i++) { - suggestionsList.push(suggestions[i]) + suggestionsList.push(suggestions[i]); } addressBar.model = suggestionsList if (!addressBar.popup.visible) { - addressBar.popup.open() + addressBar.popup.open(); } } } @@ -64,7 +65,7 @@ Rectangle { repeat: false onTriggered: { if (addressBar.editText !== "") { - searchEngine.requestSuggestions(addressBarInput.text) + searchEngine.requestSuggestions(addressBarInput.text); } } } @@ -83,9 +84,9 @@ Rectangle { } addressBar.model = [] - webEngineView.url = url - suggestionRequestTimer.stop() - addressBar.popup.close() + webStack.currentItem.webEngineView.url = url + suggestionRequestTimer.stop(); + addressBar.popup.close(); } Column { @@ -98,22 +99,26 @@ Rectangle { height: 48 HifiControls.WebGlyphButton { - enabled: webEngineView.canGoBack + enabled: webStack.currentItem.webEngineView.canGoBack || webStack.depth > 1 glyph: hifi.glyphs.backward; anchors.verticalCenter: parent.verticalCenter; size: 38; onClicked: { - webEngineView.goBack() + if (webStack.currentItem.webEngineView.canGoBack) { + webStack.currentItem.webEngineView.goBack(); + } else if (webStack.depth > 1) { + webStack.pop(); + } } } HifiControls.WebGlyphButton { - enabled: webEngineView.canGoForward + enabled: webStack.currentItem.webEngineView.canGoForward glyph: hifi.glyphs.forward; anchors.verticalCenter: parent.verticalCenter; size: 38; onClicked: { - webEngineView.goForward() + webStack.currentItem.webEngineView.goForward(); } } @@ -128,21 +133,21 @@ Rectangle { indicator: Item {} background: Item {} onActivated: { - goTo(textAt(index)) + goTo(textAt(index)); } - popup.height: webEngineView.height + popup.height: webStack.height onFocusChanged: { if (focus) { - addressBarInput.selectAll() + addressBarInput.selectAll(); } } contentItem: QQControls.TextField { id: addressBarInput leftPadding: 26 - rightPadding: hifi.dimensions.controlLineHeight + rightPadding: hifi.dimensions.controlLineHeight + 5 text: addressBar.editText placeholderText: qsTr("Enter URL") font: addressBar.font @@ -151,7 +156,7 @@ Rectangle { verticalAlignment: Text.AlignVCenter onFocusChanged: { if (focus) { - selectAll() + selectAll(); } } @@ -161,7 +166,7 @@ Rectangle { Keys.onPressed: { if (event.key === Qt.Key_Return) { - goTo(addressBarInput.text) + goTo(addressBarInput.text); event.accepted = true; } } @@ -173,58 +178,57 @@ Rectangle { id: faviconImage width: 16; height: 16 sourceSize: Qt.size(width, height) - source: webEngineView.icon - onSourceChanged: console.log("web icon", source) + source: webStack.currentItem.webEngineView.icon } HifiControls.WebGlyphButton { - glyph: webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; + glyph: webStack.currentItem.webEngineView.loading ? hifi.glyphs.closeSmall : hifi.glyphs.reloadSmall; anchors.verticalCenter: parent.verticalCenter; width: hifi.dimensions.controlLineHeight z: 2 x: addressBarInput.width - implicitWidth onClicked: { - if (webEngineView.loading) { - webEngineView.stop() + if (webStack.currentItem.webEngineView.loading) { + webStack.currentItem.webEngineView.stop(); } else { - reloadTimer.start() + webStack.currentItem.webEngineView.reloadTimer.start(); } } } } - Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i") + Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i"); Keys.onPressed: { if (event.key === Qt.Key_Return) { - goTo(addressBarInput.text) + goTo(addressBarInput.text); event.accepted = true; } } onEditTextChanged: { - if (addressBar.editText !== "" && addressBar.editText !== webEngineView.url.toString()) { + if (addressBar.editText !== "" && addressBar.editText !== webStack.currentItem.webEngineView.url.toString()) { suggestionRequestTimer.restart(); } else { addressBar.model = [] - addressBar.popup.close() + addressBar.popup.close(); } } Layout.fillWidth: true - editText: webEngineView.url - onAccepted: goTo(addressBarInput.text) + editText: webStack.currentItem.webEngineView.url + onAccepted: goTo(addressBarInput.text); } HifiControls.WebGlyphButton { checkable: true - checked: webEngineView.audioMuted + checked: webStack.currentItem.webEngineView.audioMuted glyph: checked ? hifi.glyphs.unmuted : hifi.glyphs.muted anchors.verticalCenter: parent.verticalCenter; width: hifi.dimensions.controlLineHeight onClicked: { - webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute) + webStack.currentItem.webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute); } } } @@ -249,18 +253,88 @@ Rectangle { width: parent.width; from: 0 to: 100 - value: webEngineView.loadProgress + value: webStack.currentItem.webEngineView.loadProgress height: 2 } Component { - id: webDialogComponent + id: webViewComponent Rectangle { - property alias webDialogView: webDialogView - color: "white" + property alias webEngineView: webEngineView + property WebEngineNewViewRequest request: null + + property bool isDialog: QQControls.StackView.index > 0 + property real margins: isDialog ? 10 : 0 + + color: "#d1d1d1" + + QQControls.StackView.onActivated: { + addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; }); + } + + onRequestChanged: { + if (isDialog && request !== null && request !== undefined) {//is Dialog ? + request.openIn(webEngineView); + } + } + HifiControls.BaseWebView { - id: webDialogView + id: webEngineView anchors.fill: parent + anchors.margins: parent.margins + + layer.enabled: parent.isDialog + layer.effect: DropShadow { + verticalOffset: 8 + horizontalOffset: 8 + color: "#330066ff" + samples: 10 + spread: 0.5 + } + + focus: true + objectName: "tabletWebEngineView" + + //profile: HFWebEngineProfile; + + property string userScriptUrl: "" + + onLoadingChanged: { + if (!loading) { + suggestionRequestTimer.stop(); + addressBar.popup.close(); + } + } + + onLinkHovered: { + //TODO: change cursor shape? + } + + // creates a global EventBridge object. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // detects when to raise and lower virtual keyboard + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + // User script. + WebEngineScript { + id: userScript + sourceUrl: webEngineView.userScriptUrl + injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] settings.autoLoadImages: true settings.javascriptEnabled: true @@ -270,162 +344,80 @@ Rectangle { settings.autoLoadIconsForPage: true settings.touchIconsEnabled: true + onCertificateError: { + error.defer(); + } + + Component.onCompleted: { + webChannel.registerObject("eventBridge", eventBridge); + webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); + //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; + } + + onFeaturePermissionRequested: { + grantFeaturePermission(securityOrigin, feature, true); + } + + onNewViewRequested: { + if (request.destination == WebEngineView.NewViewInDialog) { + webStack.push(webViewComponent, {"request": request}); + } else { + request.openIn(webEngineView); + } + } + + onRenderProcessTerminated: { + var status = ""; + switch (terminationStatus) { + case WebEngineView.NormalTerminationStatus: + status = "(normal exit)"; + break; + case WebEngineView.AbnormalTerminationStatus: + status = "(abnormal exit)"; + break; + case WebEngineView.CrashedTerminationStatus: + status = "(crashed)"; + break; + case WebEngineView.KilledTerminationStatus: + status = "(killed)"; + break; + } + + console.error("Render process exited with code " + exitCode + " " + status); + reloadTimer.running = true; + } + + onFullScreenRequested: { + if (request.toggleOn) { + webEngineView.state = "FullScreen"; + } else { + webEngineView.state = ""; + } + request.accept(); + } + onWindowCloseRequested: { - webDialog.active = false - webDialog.request = null + webStack.pop(); + } + + Timer { + id: reloadTimer + interval: 0 + running: false + repeat: false + onTriggered: webEngineView.reload() } } } } - Item { + QQControls.StackView { + id: webStack width: parent.width; property real webViewHeight: root.height - loadProgressBar.height - 48 - 4 height: keyboardEnabled && keyboardRaised ? webViewHeight - keyboard.height : webViewHeight - HifiControls.BaseWebView { - id: webEngineView - anchors.fill: parent - - focus: true - objectName: "tabletWebEngineView" - - url: "https://www.highfidelity.com" - - //profile: HFWebEngineProfile; - - property string userScriptUrl: "" - - onLoadingChanged: { - if (!loading) { - suggestionRequestTimer.stop() - addressBar.popup.close() - } - } - - onLinkHovered: { - //TODO: change cursor shape? - console.error("hoveredUrl:", hoveredUrl) - } - - // creates a global EventBridge object. - WebEngineScript { - id: createGlobalEventBridge - sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation - worldId: WebEngineScript.MainWorld - } - - // detects when to raise and lower virtual keyboard - WebEngineScript { - id: raiseAndLowerKeyboard - injectionPoint: WebEngineScript.Deferred - sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js" - worldId: WebEngineScript.MainWorld - } - - // User script. - WebEngineScript { - id: userScript - sourceUrl: webEngineView.userScriptUrl - injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished. - worldId: WebEngineScript.MainWorld - } - - userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ] - - settings.autoLoadImages: true - settings.javascriptEnabled: true - settings.errorPageEnabled: true - settings.pluginsEnabled: true - settings.fullScreenSupportEnabled: true - settings.autoLoadIconsForPage: true - settings.touchIconsEnabled: true - - onCertificateError: { - error.defer(); - } - - Component.onCompleted: { - webChannel.registerObject("eventBridge", eventBridge); - webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; - } - - onFeaturePermissionRequested: { - grantFeaturePermission(securityOrigin, feature, true); - } - - onNewViewRequested: { - console.error("new view requested:", request.destination) - if (request.destination == WebEngineView.NewViewInDialog) { - webDialog.request = request - webDialog.active = true - } else { - request.openIn(webEngineView); - } - } - - onRenderProcessTerminated: { - var status = ""; - switch (terminationStatus) { - case WebEngineView.NormalTerminationStatus: - status = "(normal exit)"; - break; - case WebEngineView.AbnormalTerminationStatus: - status = "(abnormal exit)"; - break; - case WebEngineView.CrashedTerminationStatus: - status = "(crashed)"; - break; - case WebEngineView.KilledTerminationStatus: - status = "(killed)"; - break; - } - - console.error("Render process exited with code " + exitCode + " " + status); - reloadTimer.running = true; - } - - onFullScreenRequested: { - console.error("FS requested:", request.destination) - if (request.toggleOn) { - webEngineView.state = "FullScreen"; - } else { - webEngineView.state = ""; - } - request.accept(); - } - - onWindowCloseRequested: { - console.error("window close requested:", request.destination) - } - - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: webEngineView.reload() - } - } - - Loader { - id: webDialog - property WebEngineNewViewRequest request: null - anchors.fill: parent - anchors.margins: 10 - - active: false - sourceComponent: webDialogComponent - onStatusChanged: { - if (Loader.Ready === status) { - focus = true - item.webDialogView.profile = webEngineView.profile - request.openIn(item.webDialogView) - } - } - } + Component.onCompleted: webStack.push(webViewComponent, {"webEngineView.url": "https://www.highfidelity.com"}); } } From 950ac3540ab0af9a10b6188e190f8ec9cd16fb1b Mon Sep 17 00:00:00 2001 From: vladest Date: Sun, 29 Oct 2017 14:05:47 +0100 Subject: [PATCH 10/42] Fix mute unmute. Scrolling thru suggestions popup now set popup's contains into url. Fixed reload timer --- interface/resources/qml/hifi/WebBrowser.qml | 30 +++++++++++++-------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index 79932dbcdd..ec5594e74d 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -85,6 +85,7 @@ Rectangle { addressBar.model = [] webStack.currentItem.webEngineView.url = url + addressBar.editText = webStack.currentItem.webEngineView.url suggestionRequestTimer.stop(); addressBar.popup.close(); } @@ -136,6 +137,12 @@ Rectangle { goTo(textAt(index)); } + onHighlightedIndexChanged: { + if (highlightedIndex >= 0) { + addressBar.editText = textAt(highlightedIndex) + } + } + popup.height: webStack.height onFocusChanged: { @@ -191,7 +198,7 @@ Rectangle { if (webStack.currentItem.webEngineView.loading) { webStack.currentItem.webEngineView.stop(); } else { - webStack.currentItem.webEngineView.reloadTimer.start(); + webStack.currentItem.reloadTimer.start(); } } } @@ -228,7 +235,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter; width: hifi.dimensions.controlLineHeight onClicked: { - webStack.currentItem.webEngineView.triggerWebAction(WebEngineView.ToggleMediaMute); + webStack.currentItem.webEngineView.audioMuted = !webStack.currentItem.webEngineView.audioMuted } } } @@ -261,6 +268,8 @@ Rectangle { id: webViewComponent Rectangle { property alias webEngineView: webEngineView + property alias reloadTimer: reloadTimer + property WebEngineNewViewRequest request: null property bool isDialog: QQControls.StackView.index > 0 @@ -314,7 +323,7 @@ Rectangle { WebEngineScript { id: createGlobalEventBridge sourceCode: eventBridgeJavaScriptToInject - injectionPoint: WebEngineScript.DocumentCreation + injectionPoint: WebEngineScript.Deferred worldId: WebEngineScript.MainWorld } @@ -399,14 +408,13 @@ Rectangle { onWindowCloseRequested: { webStack.pop(); } - - Timer { - id: reloadTimer - interval: 0 - running: false - repeat: false - onTriggered: webEngineView.reload() - } + } + Timer { + id: reloadTimer + interval: 0 + running: false + repeat: false + onTriggered: webEngineView.reload() } } } From 586dde7dd70e0846b2b6644a8abb0275f7503683 Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 18:15:42 +0100 Subject: [PATCH 11/42] Removed GPL code. Added own implementation of suggestions engine. Fixed Google maps different issues --- interface/resources/qml/hifi/WebBrowser.qml | 24 +- interface/src/Application.cpp | 4 +- interface/src/opensearch/opensearchengine.cpp | 650 ------------------ interface/src/opensearch/opensearchengine.h | 128 ---- .../opensearch/opensearchenginedelegate.cpp | 43 -- .../src/opensearch/opensearchenginedelegate.h | 18 - .../webbrowsersuggestionsengine.cpp | 87 +++ .../webbrowser/webbrowsersuggestionsengine.h | 46 ++ 8 files changed, 147 insertions(+), 853 deletions(-) delete mode 100644 interface/src/opensearch/opensearchengine.cpp delete mode 100644 interface/src/opensearch/opensearchengine.h delete mode 100644 interface/src/opensearch/opensearchenginedelegate.cpp delete mode 100644 interface/src/opensearch/opensearchenginedelegate.h create mode 100644 interface/src/webbrowser/webbrowsersuggestionsengine.cpp create mode 100644 interface/src/webbrowser/webbrowsersuggestionsengine.h diff --git a/interface/resources/qml/hifi/WebBrowser.qml b/interface/resources/qml/hifi/WebBrowser.qml index ec5594e74d..ab93752d92 100644 --- a/interface/resources/qml/hifi/WebBrowser.qml +++ b/interface/resources/qml/hifi/WebBrowser.qml @@ -36,13 +36,11 @@ Rectangle { property bool keyboardRaised: false property bool punctuationMode: false property var suggestionsList: [] + readonly property string searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q="; - OpenSearchEngine { + + WebBrowserSuggestionsEngine { id: searchEngine - name: "Google"; - searchUrlTemplate: "https://www.google.com/search?client=hifibrowser&q={searchTerms}"; - suggestionsUrlTemplate: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; - suggestionsUrl: "https://suggestqueries.google.com/complete/search?output=firefox&q=%s"; onSuggestions: { if (suggestions.length > 0) { @@ -65,7 +63,7 @@ Rectangle { repeat: false onTriggered: { if (addressBar.editText !== "") { - searchEngine.requestSuggestions(addressBarInput.text); + searchEngine.querySuggestions(addressBarInput.text); } } } @@ -74,18 +72,19 @@ Rectangle { function goTo(url) { //must be valid attempt to open an site with dot - + var urlNew = url if (url.indexOf(".") > 0) { if (url.indexOf("http") < 0) { - url = "http://" + url; + urlNew = "http://" + url; } } else { - url = searchEngine.searchUrl(url) + urlNew = searchUrlTemplate + url } addressBar.model = [] - webStack.currentItem.webEngineView.url = url - addressBar.editText = webStack.currentItem.webEngineView.url + //need to rebind if binfing was broken by selecting from suggestions + addressBar.editText = Qt.binding( function() { return webStack.currentItem.webEngineView.url; }); + webStack.currentItem.webEngineView.url = urlNew suggestionRequestTimer.stop(); addressBar.popup.close(); } @@ -305,11 +304,13 @@ Rectangle { objectName: "tabletWebEngineView" //profile: HFWebEngineProfile; + profile.httpUserAgent: "Mozilla/5.0 (Android; Mobile; rv:13.0) Gecko/13.0 Firefox/13.0" property string userScriptUrl: "" onLoadingChanged: { if (!loading) { + addressBarInput.cursorPosition = 0 //set input field cursot to beginning suggestionRequestTimer.stop(); addressBar.popup.close(); } @@ -360,7 +361,6 @@ Rectangle { Component.onCompleted: { webChannel.registerObject("eventBridge", eventBridge); webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper); - //webEngineView.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; } onFeaturePermissionRequested: { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 0b9e8833e4..bf2754917b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -203,7 +203,7 @@ #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" -#include "opensearch/opensearchengine.h" +#include "webbrowser/webbrowsersuggestionsengine.h" // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. @@ -2225,7 +2225,7 @@ void Application::initializeUi() { QmlCommerce::registerType(); qmlRegisterType("Hifi", 1, 0, "ResourceImageItem"); qmlRegisterType("Hifi", 1, 0, "Preference"); - qmlRegisterType("HifiWeb", 1, 0, "OpenSearchEngine"); + qmlRegisterType("HifiWeb", 1, 0, "WebBrowserSuggestionsEngine"); auto offscreenUi = DependencyManager::get(); offscreenUi->create(); diff --git a/interface/src/opensearch/opensearchengine.cpp b/interface/src/opensearch/opensearchengine.cpp deleted file mode 100644 index 28cacab256..0000000000 --- a/interface/src/opensearch/opensearchengine.cpp +++ /dev/null @@ -1,650 +0,0 @@ -#include "opensearchengine.h" -#include "qregexp.h" -#include "opensearchenginedelegate.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - - -/*! - \class OpenSearchEngine - \brief A class representing a single search engine described in OpenSearch format - - OpenSearchEngine is a class that represents a single search engine based on - the OpenSearch format. - For more information about the format, see http://www.opensearch.org/. - - Instances of the class hold all the data associated with the corresponding search - engines, such as name(), description() and also URL templates that are used - to construct URLs, which can be used later to perform search queries. Search engine - can also have an image, even an external one, in this case it will be downloaded - automatically from the network. - - OpenSearchEngine instances can be constructed from scratch but also read from - external sources and written back to them. OpenSearchReader and OpenSearchWriter - are the classes provided for reading and writing OpenSearch descriptions. - - Default constructed engines need to be filled with the necessary information before - they can be used to peform search requests. First of all, a search engine should have - the metadata including the name and the description. - However, the most important are URL templates, which are the construction of URLs - but can also contain template parameters, that are replaced with corresponding values - at the time of constructing URLs. - - There are two types of URL templates: search URL template and suggestions URL template. - Search URL template is needed for constructing search URLs, which point directly to - search results. Suggestions URL template is necessary to construct suggestion queries - URLs, which are then used for requesting contextual suggestions, a popular service - offered along with search results that provides search terms related to what has been - supplied by the user. - - Both types of URLs are constructed by the class, by searchUrl() and suggestionsUrl() - functions respectively. However, search requests are supposed to be performed outside - the class, while suggestion queries can be executed using the requestSuggestions() - method. The class will take care of peforming the network request and parsing the - JSON response. - - Both the image request and suggestion queries need network access. The class can - perform network requests on its own, though the client application needs to provide - a network access manager, which then will to be used for network operations. - Without that, both images delivered from remote locations and contextual suggestions - will be disabled. - - \sa OpenSearchReader, OpenSearchWriter -*/ - -/*! - Constructs an engine with a given \a parent. -*/ -OpenSearchEngine::OpenSearchEngine(QObject* parent) - : QObject(parent) - , m_searchMethod(QLatin1String("get")) - , m_suggestionsMethod(QLatin1String("get")) - , m_suggestionsReply(0) - , m_delegate(0) -{ - m_requestMethods.insert(QLatin1String("get"), QNetworkAccessManager::GetOperation); - m_requestMethods.insert(QLatin1String("post"), QNetworkAccessManager::PostOperation); -} - -/*! - A destructor. -*/ -OpenSearchEngine::~OpenSearchEngine() -{ -} - -QString OpenSearchEngine::parseTemplate(const QString &searchTerm, const QString &searchTemplate) -{ - QString language = QLocale().name(); - // Simple conversion to RFC 3066. - language = language.replace(QLatin1Char('_'), QLatin1Char('-')); - - QString result = searchTemplate; - result.replace(QLatin1String("{count}"), QLatin1String("20")); - result.replace(QLatin1String("{startIndex}"), QLatin1String("0")); - result.replace(QLatin1String("{startPage}"), QLatin1String("0")); - result.replace(QLatin1String("{language}"), language); - result.replace(QLatin1String("{inputEncoding}"), QLatin1String("UTF-8")); - result.replace(QLatin1String("{outputEncoding}"), QLatin1String("UTF-8")); - result.replace(QRegExp(QLatin1String("\\{([^\\}]*:|)source\\??\\}")), QCoreApplication::applicationName()); - result.replace(QLatin1String("{searchTerms}"), QLatin1String(QUrl::toPercentEncoding(searchTerm))); - - return result; -} - -/*! - \property OpenSearchEngine::name - \brief the name of the engine - - \sa description() -*/ -QString OpenSearchEngine::name() const -{ - return m_name; -} - -void OpenSearchEngine::setName(const QString &name) -{ - m_name = name; -} - -/*! - \property OpenSearchEngine::description - \brief the description of the engine - - \sa name() -*/ -QString OpenSearchEngine::description() const -{ - return m_description; -} - -void OpenSearchEngine::setDescription(const QString &description) -{ - m_description = description; -} - -/*! - \property OpenSearchEngine::searchUrlTemplate - \brief the template of the search URL - - \sa searchUrl(), searchParameters(), suggestionsUrlTemplate() -*/ -QString OpenSearchEngine::searchUrlTemplate() const -{ - return m_searchUrlTemplate; -} - -void OpenSearchEngine::setSearchUrlTemplate(const QString &searchUrlTemplate) -{ - if (!searchUrlTemplate.startsWith(QLatin1String("http://")) && !searchUrlTemplate.startsWith(QLatin1String("https://"))) { - return; - } - - m_searchUrlTemplate = searchUrlTemplate; -} - -/*! - Constructs and returns a search URL with a given \a searchTerm. - - The URL template is processed according to the specification: - http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax - - A list of template parameters currently supported and what they are replaced with: - \table - \header \o parameter - \o value - \row \o "{count}" - \o "20" - \row \o "{startIndex}" - \o "0" - \row \o "{startPage}" - \o "0" - \row \o "{language}" - \o "the default language code (RFC 3066)" - \row \o "{inputEncoding}" - \o "UTF-8" - \row \o "{outputEncoding}" - \o "UTF-8" - \row \o "{*:source}" - \o "application name, QCoreApplication::applicationName()" - \row \o "{searchTerms}" - \o "the string supplied by the user" - \endtable - - \sa searchUrlTemplate(), searchParameters(), suggestionsUrl() -*/ -QUrl OpenSearchEngine::searchUrl(const QString &searchTerm) const -{ - if (m_searchUrlTemplate.isEmpty()) { - return QUrl(); - } - - QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_searchUrlTemplate).toUtf8()); - - QUrlQuery query(retVal); - if (m_searchMethod != QLatin1String("post")) { - Parameters::const_iterator end = m_searchParameters.constEnd(); - Parameters::const_iterator i = m_searchParameters.constBegin(); - for (; i != end; ++i) { - query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); - } - retVal.setQuery(query); - } - - return retVal; -} - -QByteArray OpenSearchEngine::getPostData(const QString &searchTerm) const -{ - if (m_searchMethod != QLatin1String("post")) { - return QByteArray(); - } - - QUrl retVal = QUrl("http://foo.bar"); - - QUrlQuery query(retVal); - Parameters::const_iterator end = m_searchParameters.constEnd(); - Parameters::const_iterator i = m_searchParameters.constBegin(); - for (; i != end; ++i) { - query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); - } - retVal.setQuery(query); - - QByteArray data = retVal.toEncoded(QUrl::RemoveScheme); - return data.contains('?') ? data.mid(data.lastIndexOf('?') + 1) : QByteArray(); -} - -/*! - \property providesSuggestions - \brief indicates whether the engine supports contextual suggestions -*/ -bool OpenSearchEngine::providesSuggestions() const -{ - return (!m_suggestionsUrlTemplate.isEmpty() || !m_preparedSuggestionsUrl.isEmpty()); -} - -/*! - \property OpenSearchEngine::suggestionsUrlTemplate - \brief the template of the suggestions URL - - \sa suggestionsUrl(), suggestionsParameters(), searchUrlTemplate() -*/ -QString OpenSearchEngine::suggestionsUrlTemplate() const -{ - return m_suggestionsUrlTemplate; -} - -void OpenSearchEngine::setSuggestionsUrlTemplate(const QString &suggestionsUrlTemplate) -{ - if (!suggestionsUrlTemplate.startsWith(QLatin1String("http://")) && !suggestionsUrlTemplate.startsWith(QLatin1String("https://"))) { - return; - } - - m_suggestionsUrlTemplate = suggestionsUrlTemplate; -} - -/*! - Constructs a suggestions URL with a given \a searchTerm. - - The URL template is processed according to the specification: - http://www.opensearch.org/Specifications/OpenSearch/1.1#OpenSearch_URL_template_syntax - - See searchUrl() for more information about processing template parameters. - - \sa suggestionsUrlTemplate(), suggestionsParameters(), searchUrl() -*/ -QUrl OpenSearchEngine::suggestionsUrl(const QString &searchTerm) const -{ - if (!m_preparedSuggestionsUrl.isEmpty()) { - QString s = m_preparedSuggestionsUrl; - s.replace(QLatin1String("%s"), searchTerm); - return QUrl(s); - } - - if (m_suggestionsUrlTemplate.isEmpty()) { - return QUrl(); - } - - QUrl retVal = QUrl::fromEncoded(parseTemplate(searchTerm, m_suggestionsUrlTemplate).toUtf8()); - - QUrlQuery query(retVal); - if (m_suggestionsMethod != QLatin1String("post")) { - Parameters::const_iterator end = m_suggestionsParameters.constEnd(); - Parameters::const_iterator i = m_suggestionsParameters.constBegin(); - for (; i != end; ++i) { - query.addQueryItem(i->first, parseTemplate(searchTerm, i->second)); - } - retVal.setQuery(query); - } - - return retVal; -} - -/*! - \property searchParameters - \brief additional parameters that will be included in the search URL - - For more information see: - http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0 -*/ -OpenSearchEngine::Parameters OpenSearchEngine::searchParameters() const -{ - return m_searchParameters; -} - -void OpenSearchEngine::setSearchParameters(const Parameters &searchParameters) -{ - m_searchParameters = searchParameters; -} - -/*! - \property suggestionsParameters - \brief additional parameters that will be included in the suggestions URL - - For more information see: - http://www.opensearch.org/Specifications/OpenSearch/Extensions/Parameter/1.0 -*/ -OpenSearchEngine::Parameters OpenSearchEngine::suggestionsParameters() const -{ - return m_suggestionsParameters; -} - -void OpenSearchEngine::setSuggestionsParameters(const Parameters &suggestionsParameters) -{ - m_suggestionsParameters = suggestionsParameters; -} - -/*! - \property searchMethod - \brief HTTP request method that will be used to perform search requests -*/ -QString OpenSearchEngine::searchMethod() const -{ - return m_searchMethod; -} - -void OpenSearchEngine::setSearchMethod(const QString &method) -{ - QString requestMethod = method.toLower(); - if (!m_requestMethods.contains(requestMethod)) { - return; - } - - m_searchMethod = requestMethod; -} - -/*! - \property suggestionsMethod - \brief HTTP request method that will be used to perform suggestions requests -*/ -QString OpenSearchEngine::suggestionsMethod() const -{ - return m_suggestionsMethod; -} - -void OpenSearchEngine::setSuggestionsMethod(const QString &method) -{ - QString requestMethod = method.toLower(); - if (!m_requestMethods.contains(requestMethod)) { - return; - } - - m_suggestionsMethod = requestMethod; -} - -/*! - \property imageUrl - \brief the image URL of the engine - - When setting a new image URL, it won't be loaded immediately. The first request will be - deferred until image() is called for the first time. - - \note To be able to request external images, you need to provide a network access manager, - which will be used for network operations. - - \sa image(), networkAccessManager() -*/ -QString OpenSearchEngine::imageUrl() const -{ - return m_imageUrl; -} - -void OpenSearchEngine::setImageUrl(const QString &imageUrl) -{ - m_imageUrl = imageUrl; -} - -void OpenSearchEngine::loadImage() const -{ - if (m_imageUrl.isEmpty()) { - return; - } - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - - QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(QUrl::fromEncoded(m_imageUrl.toUtf8()))); - connect(reply, SIGNAL(finished()), this, SLOT(imageObtained())); -} - -void OpenSearchEngine::imageObtained() -{ - QNetworkReply* reply = qobject_cast(sender()); - - if (!reply) { - return; - } - - QByteArray response = reply->readAll(); - - reply->close(); - reply->deleteLater(); - - if (response.isEmpty()) { - return; - } - - m_image.loadFromData(response); - emit imageChanged(); -} - -/*! - \property image - \brief the image of the engine - - When no image URL has been set and an image will be set explicitly, a new data URL - will be constructed, holding the image data encoded with Base64. - - \sa imageUrl() -*/ -QImage OpenSearchEngine::image() const -{ - if (m_image.isNull()) { - loadImage(); - } - return m_image; -} - -void OpenSearchEngine::setImage(const QImage &image) -{ - if (m_imageUrl.isEmpty()) { - QBuffer imageBuffer; - imageBuffer.open(QBuffer::ReadWrite); - if (image.save(&imageBuffer, "PNG")) { - m_imageUrl = QString(QLatin1String("data:image/png;base64,%1")) - .arg(QLatin1String(imageBuffer.buffer().toBase64())); - } - } - - m_image = image; - emit imageChanged(); -} - -/*! - \property valid - \brief indicates whether the engine is valid i.e. the description was properly formed and included all necessary information -*/ -bool OpenSearchEngine::isValid() const -{ - return (!m_name.isEmpty() && !m_searchUrlTemplate.isEmpty()); -} - -bool OpenSearchEngine::operator==(const OpenSearchEngine &other) const -{ - return (m_name == other.m_name - && m_description == other.m_description - && m_imageUrl == other.m_imageUrl - && m_searchUrlTemplate == other.m_searchUrlTemplate - && m_suggestionsUrlTemplate == other.m_suggestionsUrlTemplate - && m_searchParameters == other.m_searchParameters - && m_suggestionsParameters == other.m_suggestionsParameters); -} - -bool OpenSearchEngine::operator<(const OpenSearchEngine &other) const -{ - return (m_name < other.m_name); -} - -/*! - Requests contextual suggestions on the search engine, for a given \a searchTerm. - - If succeeded, suggestions() signal will be emitted once the suggestions are received. - - \note To be able to request suggestions, you need to provide a network access manager, - which will be used for network operations. - - \sa requestSearchResults() -*/ - -void OpenSearchEngine::setSuggestionsParameters(const QByteArray ¶meters) -{ - m_preparedSuggestionsParameters = parameters; -} - -void OpenSearchEngine::setSuggestionsUrl(const QString &string) -{ - m_preparedSuggestionsUrl = string; -} - -QString OpenSearchEngine::getSuggestionsUrl() -{ - return suggestionsUrl("searchstring").toString().replace(QLatin1String("searchstring"), QLatin1String("%s")); -} - -QByteArray OpenSearchEngine::getSuggestionsParameters() -{ - QStringList parameters; - Parameters::const_iterator end = m_suggestionsParameters.constEnd(); - Parameters::const_iterator i = m_suggestionsParameters.constBegin(); - for (; i != end; ++i) { - parameters.append(i->first + QLatin1String("=") + i->second); - } - - QByteArray data = parameters.join(QLatin1String("&")).toUtf8(); - - return data; -} - -void OpenSearchEngine::requestSuggestions(const QString &searchTerm) -{ - if (searchTerm.isEmpty() || !providesSuggestions()) { - return; - } - - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - - if (m_suggestionsReply) { - m_suggestionsReply->disconnect(this); - m_suggestionsReply->abort(); - m_suggestionsReply->deleteLater(); - m_suggestionsReply = 0; - } - - Q_ASSERT(m_requestMethods.contains(m_suggestionsMethod)); - if (m_suggestionsMethod == QLatin1String("get")) { - m_suggestionsReply = networkAccessManager.get(QNetworkRequest(suggestionsUrl(searchTerm))); - } - else { - QStringList parameters; - Parameters::const_iterator end = m_suggestionsParameters.constEnd(); - Parameters::const_iterator i = m_suggestionsParameters.constBegin(); - for (; i != end; ++i) { - parameters.append(i->first + QLatin1String("=") + i->second); - } - - QByteArray data = parameters.join(QLatin1String("&")).toUtf8(); - m_suggestionsReply = networkAccessManager.post(QNetworkRequest(suggestionsUrl(searchTerm)), data); - } - - connect(m_suggestionsReply, SIGNAL(finished()), this, SLOT(suggestionsObtained())); -} - -/*! - Requests search results on the search engine, for a given \a searchTerm. - - The default implementation does nothing, to supply your own you need to create your own - OpenSearchEngineDelegate subclass and supply it to the engine. Then the function will call - the performSearchRequest() method of the delegate, which can then handle the request - in a custom way. - - \sa requestSuggestions(), delegate() -*/ -void OpenSearchEngine::requestSearchResults(const QString &searchTerm) -{ - if (!m_delegate || searchTerm.isEmpty()) { - return; - } - - Q_ASSERT(m_requestMethods.contains(m_searchMethod)); - - QNetworkRequest request(QUrl(searchUrl(searchTerm))); - QByteArray data; - QNetworkAccessManager::Operation operation = m_requestMethods.value(m_searchMethod); - - if (operation == QNetworkAccessManager::PostOperation) { - QStringList parameters; - Parameters::const_iterator end = m_searchParameters.constEnd(); - Parameters::const_iterator i = m_searchParameters.constBegin(); - for (; i != end; ++i) { - parameters.append(i->first + QLatin1String("=") + i->second); - } - - data = parameters.join(QLatin1String("&")).toUtf8(); - } - - m_delegate->performSearchRequest(request, operation, data); -} - -void OpenSearchEngine::suggestionsObtained() -{ - const QByteArray response = m_suggestionsReply->readAll(); - - m_suggestionsReply->close(); - m_suggestionsReply->deleteLater(); - m_suggestionsReply = 0; - - QJsonParseError err; - QJsonDocument json = QJsonDocument::fromJson(response, &err); - const QVariant res = json.toVariant(); - - if (err.error != QJsonParseError::NoError || res.type() != QVariant::List) - return; - - const QVariantList list = res.toList(); - - if (list.size() < 2) - return; - - QStringList out; - - foreach (const QVariant &v, list.at(1).toList()) - out.append(v.toString()); - - emit suggestions(out); -} - - -/*! - \property delegate - \brief the delegate that is used to perform specific tasks. - - It can be currently supplied to provide a custom behaviour ofthe requetSearchResults() method. - The default implementation does nothing. -*/ -OpenSearchEngineDelegate* OpenSearchEngine::delegate() const -{ - return m_delegate; -} - -void OpenSearchEngine::setDelegate(OpenSearchEngineDelegate* delegate) -{ - m_delegate = delegate; -} - -/*! - \fn void OpenSearchEngine::imageChanged() - - This signal is emitted whenever the image of the engine changes. - - \sa image(), imageUrl() -*/ - -/*! - \fn void OpenSearchEngine::suggestions(const QStringList &suggestions) - - This signal is emitted whenever new contextual suggestions have been provided - by the search engine. To request suggestions, use requestSuggestions(). - The suggestion set is specified by \a suggestions. - - \sa requestSuggestions() -*/ diff --git a/interface/src/opensearch/opensearchengine.h b/interface/src/opensearch/opensearchengine.h deleted file mode 100644 index fa714b85d3..0000000000 --- a/interface/src/opensearch/opensearchengine.h +++ /dev/null @@ -1,128 +0,0 @@ -#ifndef OPENSEARCHENGINE_H -#define OPENSEARCHENGINE_H - -#include -#include -#include -#include -#include -#include - -class QNetworkReply; - -class OpenSearchEngineDelegate; -class OpenSearchEngine : public QObject -{ - Q_OBJECT - -signals: - void imageChanged(); - void suggestions(const QStringList &suggestions); - -public: - typedef QPair Parameter; - typedef QList Parameters; - - Q_PROPERTY(QString name READ name WRITE setName) - Q_PROPERTY(QString description READ description WRITE setDescription) - Q_PROPERTY(QString searchUrlTemplate READ searchUrlTemplate WRITE setSearchUrlTemplate) - Q_PROPERTY(Parameters searchParameters READ searchParameters WRITE setSearchParameters) - Q_PROPERTY(QString searchMethod READ searchMethod WRITE setSearchMethod) - Q_PROPERTY(QString suggestionsUrlTemplate READ suggestionsUrlTemplate WRITE setSuggestionsUrlTemplate) - Q_PROPERTY(QString suggestionsUrl READ getSuggestionsUrl WRITE setSuggestionsUrl) - Q_PROPERTY(Parameters suggestionsParameters READ suggestionsParameters WRITE setSuggestionsParameters) - Q_PROPERTY(QString suggestionsMethod READ suggestionsMethod WRITE setSuggestionsMethod) - Q_PROPERTY(bool providesSuggestions READ providesSuggestions) - Q_PROPERTY(QString imageUrl READ imageUrl WRITE setImageUrl) - Q_PROPERTY(bool valid READ isValid) - - OpenSearchEngine(QObject* parent = 0); - ~OpenSearchEngine(); - - QString name() const; - void setName(const QString &name); - - QString description() const; - void setDescription(const QString &description); - - QString searchUrlTemplate() const; - void setSearchUrlTemplate(const QString &searchUrl); - - QByteArray getPostData(const QString &searchTerm) const; - - bool providesSuggestions() const; - - QString suggestionsUrlTemplate() const; - void setSuggestionsUrlTemplate(const QString &suggestionsUrl); - QUrl suggestionsUrl(const QString &searchTerm) const; - - Parameters searchParameters() const; - void setSearchParameters(const Parameters &searchParameters); - - Parameters suggestionsParameters() const; - void setSuggestionsParameters(const Parameters &suggestionsParameters); - - QString searchMethod() const; - void setSearchMethod(const QString &method); - - QString suggestionsMethod() const; - void setSuggestionsMethod(const QString &method); - - QString imageUrl() const; - void setImageUrl(const QString &url); - - QImage image() const; - void setImage(const QImage &image); - - bool isValid() const; - - void setSuggestionsUrl(const QString &string); - void setSuggestionsParameters(const QByteArray ¶meters); - QString getSuggestionsUrl(); - QByteArray getSuggestionsParameters(); - - OpenSearchEngineDelegate* delegate() const; - void setDelegate(OpenSearchEngineDelegate* delegate); - - bool operator==(const OpenSearchEngine &other) const; - bool operator<(const OpenSearchEngine &other) const; - -public slots: - QUrl searchUrl(const QString &searchTerm) const; - void requestSuggestions(const QString &searchTerm); - void requestSearchResults(const QString &searchTerm); - -protected: - static QString parseTemplate(const QString &searchTerm, const QString &searchTemplate); - void loadImage() const; - -private slots: - void imageObtained(); - void suggestionsObtained(); - -private: - QString m_name; - QString m_description; - - QString m_imageUrl; - QImage m_image; - - QString m_searchUrlTemplate; - QString m_suggestionsUrlTemplate; - Parameters m_searchParameters; - Parameters m_suggestionsParameters; - QString m_searchMethod; - QString m_suggestionsMethod; - - QByteArray m_preparedSuggestionsParameters; - QString m_preparedSuggestionsUrl; - - QMap m_requestMethods; - - QNetworkReply* m_suggestionsReply; - - OpenSearchEngineDelegate* m_delegate; -}; - -#endif // OPENSEARCHENGINE_H - diff --git a/interface/src/opensearch/opensearchenginedelegate.cpp b/interface/src/opensearch/opensearchenginedelegate.cpp deleted file mode 100644 index 2c9c18ece0..0000000000 --- a/interface/src/opensearch/opensearchenginedelegate.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "opensearchenginedelegate.h" - -/*! - \class OpenSearchEngineDelegate - \brief An abstract class providing custom processing of specific activities. - - OpenSearchEngineDelegate is an abstract class that can be subclassed and set on - an OpenSearchEngine. It allows to customize some parts of the default implementation - or even extend it with missing bits. - - Currently subclasses can only provide a custom way of handling search requests by - reimplementing the performSearchRequest() method. - - \sa OpenSearchEngine -*/ - -/*! - Constructs the delegate. -*/ -OpenSearchEngineDelegate::OpenSearchEngineDelegate() -{ -} - -/*! - Destructs the delegate. -*/ -OpenSearchEngineDelegate::~OpenSearchEngineDelegate() -{ -} - -/*! - \fn void performSearchRequest(const QNetworkRequest &request, - QNetworkAccessManager::Operation operation, const QByteArray &data) = 0 - - This method will be used after OpenSearchEngine::requestResults() is called. - - For example, a console application that uses the OpenSearchEngine class to generate - a search URL for a search term supplied by the user would reimplement the function - and forward the request to e.g. a web browser. - - Likewise, a web browser that uses the OpenSearchEngine class to support multiple search - engines e.g. in a toolbar would perform the request and navigate to the search results site. -*/ diff --git a/interface/src/opensearch/opensearchenginedelegate.h b/interface/src/opensearch/opensearchenginedelegate.h deleted file mode 100644 index ae7b5ef876..0000000000 --- a/interface/src/opensearch/opensearchenginedelegate.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef OPENSEARCHENGINEDELEGATE_H -#define OPENSEARCHENGINEDELEGATE_H - -#include -#include - -class OpenSearchEngineDelegate -{ -public: - OpenSearchEngineDelegate(); - virtual ~OpenSearchEngineDelegate(); - - virtual void performSearchRequest(const QNetworkRequest &request, - QNetworkAccessManager::Operation operation, - const QByteArray &data) = 0; -}; - -#endif // OPENSEARCHENGINEDELEGATE_H diff --git a/interface/src/webbrowser/webbrowsersuggestionsengine.cpp b/interface/src/webbrowser/webbrowsersuggestionsengine.cpp new file mode 100644 index 0000000000..546d9c68b0 --- /dev/null +++ b/interface/src/webbrowser/webbrowsersuggestionsengine.cpp @@ -0,0 +1,87 @@ +// +// webbrowsersuggestionsengine.cpp +// interface/src/webbrowser +// +// Created by Vlad Stelmahovsky Kapolka on 30/10/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "webbrowsersuggestionsengine.h" +#include "qregexp.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1"; + +WebBrowserSuggestionsEngine::WebBrowserSuggestionsEngine(QObject* parent) + : QObject(parent) + , _suggestionsReply(0) { + _currentNAM = &NetworkAccessManager::getInstance(); + connect(_currentNAM, &QNetworkAccessManager::finished, this, &WebBrowserSuggestionsEngine::suggestionsFinished); +} + + +WebBrowserSuggestionsEngine::~WebBrowserSuggestionsEngine() { + disconnect(_currentNAM, &QNetworkAccessManager::finished, this, &WebBrowserSuggestionsEngine::suggestionsFinished); +} + +void WebBrowserSuggestionsEngine::querySuggestions(const QString &searchString) { + if (_suggestionsReply) { + _suggestionsReply->disconnect(this); + _suggestionsReply->abort(); + _suggestionsReply->deleteLater(); + _suggestionsReply = 0; + } + QString url = QString(GoogleSuggestionsUrl).arg(searchString); + _suggestionsReply = _currentNAM->get(QNetworkRequest(url)); +} + +void WebBrowserSuggestionsEngine::suggestionsFinished(QNetworkReply *reply) { + + if (reply != _suggestionsReply) { + return; //invalid reply. ignore + } + + const QByteArray response = _suggestionsReply->readAll(); + + _suggestionsReply->close(); + _suggestionsReply->deleteLater(); + _suggestionsReply = 0; + + QJsonParseError err; + QJsonDocument json = QJsonDocument::fromJson(response, &err); + const QVariant res = json.toVariant(); + + if (err.error != QJsonParseError::NoError || res.type() != QVariant::List) { + return; + } + + const QVariantList list = res.toList(); + + if (list.size() < 2) { + return; + } + + QStringList out; + const QVariantList& suggList = list.at(1).toList(); + + foreach (const QVariant &v, suggList) { + out.append(v.toString()); + } + + emit suggestions(out); +} diff --git a/interface/src/webbrowser/webbrowsersuggestionsengine.h b/interface/src/webbrowser/webbrowsersuggestionsengine.h new file mode 100644 index 0000000000..fa0f7f8e2e --- /dev/null +++ b/interface/src/webbrowser/webbrowsersuggestionsengine.h @@ -0,0 +1,46 @@ +#ifndef WEBBROWSERSUGGESTIONSENGINE_H +#define WEBBROWSERSUGGESTIONSENGINE_H + +#include +#include +#include +// +// webbrowsersuggestionsengine.h +// interface/src/webbrowser +// +// Created by Vlad Stelmahovsky Kapolka on 30/10/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +class QNetworkReply; + +class WebBrowserSuggestionsEngine : public QObject +{ + Q_OBJECT + +public: + WebBrowserSuggestionsEngine(QObject* parent = 0); + virtual ~WebBrowserSuggestionsEngine(); + +public slots: + void querySuggestions(const QString& searchString); + +signals: + void suggestions(const QStringList& suggestions); + +private slots: + void suggestionsFinished(QNetworkReply *reply); +private: + QNetworkReply* _suggestionsReply; + QNetworkAccessManager* _currentNAM; +}; + +#endif // WEBBROWSERSUGGESTIONSENGINE_H + From d9c6f17e42b3dc4d23e0bbf81594665f68b8a83d Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:06:17 +0100 Subject: [PATCH 12/42] Coding standards --- interface/src/Application.cpp | 2 +- ...ggestionsengine.cpp => WebBrowserSuggestionsEngine.cpp} | 7 ++++--- ...ersuggestionsengine.h => WebBrowserSuggestionsEngine.h} | 0 3 files changed, 5 insertions(+), 4 deletions(-) rename interface/src/webbrowser/{webbrowsersuggestionsengine.cpp => WebBrowserSuggestionsEngine.cpp} (92%) rename interface/src/webbrowser/{webbrowsersuggestionsengine.h => WebBrowserSuggestionsEngine.h} (100%) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bf2754917b..ab1884f67a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -203,7 +203,7 @@ #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" -#include "webbrowser/webbrowsersuggestionsengine.h" +#include "webbrowser/WebBrowserSuggestionsEngine.h" // On Windows PC, NVidia Optimus laptop, we want to enable NVIDIA GPU // FIXME seems to be broken. diff --git a/interface/src/webbrowser/webbrowsersuggestionsengine.cpp b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp similarity index 92% rename from interface/src/webbrowser/webbrowsersuggestionsengine.cpp rename to interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp index 546d9c68b0..40af21753b 100644 --- a/interface/src/webbrowser/webbrowsersuggestionsengine.cpp +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp @@ -9,7 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include "webbrowsersuggestionsengine.h" +#include "WebBrowserSuggestionsEngine.h" #include "qregexp.h" #include @@ -26,6 +26,7 @@ #include const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1"; +const int SUGGESTIONS_LIST_INDEX = 2; WebBrowserSuggestionsEngine::WebBrowserSuggestionsEngine(QObject* parent) : QObject(parent) @@ -72,12 +73,12 @@ void WebBrowserSuggestionsEngine::suggestionsFinished(QNetworkReply *reply) { const QVariantList list = res.toList(); - if (list.size() < 2) { + if (list.size() <= SUGGESTIONS_LIST_INDEX) { return; } QStringList out; - const QVariantList& suggList = list.at(1).toList(); + const QVariantList& suggList = list.at(SUGGESTIONS_LIST_INDEX).toList(); foreach (const QVariant &v, suggList) { out.append(v.toString()); diff --git a/interface/src/webbrowser/webbrowsersuggestionsengine.h b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h similarity index 100% rename from interface/src/webbrowser/webbrowsersuggestionsengine.h rename to interface/src/webbrowser/WebBrowserSuggestionsEngine.h From 7b57fe1a5da214b6b621b17fc5b49d3128f96b04 Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:21:34 +0100 Subject: [PATCH 13/42] type fixed --- interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp index 40af21753b..a21badb0a8 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp @@ -25,8 +25,8 @@ #include -const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1"; -const int SUGGESTIONS_LIST_INDEX = 2; +static const QString GoogleSuggestionsUrl = "https://suggestqueries.google.com/complete/search?output=firefox&q=%1"; +static const int SUGGESTIONS_LIST_INDEX = 1; WebBrowserSuggestionsEngine::WebBrowserSuggestionsEngine(QObject* parent) : QObject(parent) From ea55b5407ea66541f435e193d7a8c4d4ec524b7b Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:22:47 +0100 Subject: [PATCH 14/42] bracket fix --- interface/src/webbrowser/WebBrowserSuggestionsEngine.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.h b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h index fa0f7f8e2e..0a94ea75c4 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.h +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h @@ -21,8 +21,7 @@ class QNetworkReply; -class WebBrowserSuggestionsEngine : public QObject -{ +class WebBrowserSuggestionsEngine : public QObject { Q_OBJECT public: From aa9ae6c8381829f598d8b8231d286fa756022564 Mon Sep 17 00:00:00 2001 From: vladest Date: Mon, 30 Oct 2017 20:29:44 +0100 Subject: [PATCH 15/42] License header fix --- .../WebBrowserSuggestionsEngine.cpp | 4 ++-- .../webbrowser/WebBrowserSuggestionsEngine.h | 21 ++++++++++--------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp index a21badb0a8..4e7b135cdf 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.cpp @@ -1,8 +1,8 @@ // -// webbrowsersuggestionsengine.cpp +// WebBrowserSuggestionsEngine.cpp // interface/src/webbrowser // -// Created by Vlad Stelmahovsky Kapolka on 30/10/17. +// Created by Vlad Stelmahovsky on 30/10/17. // Copyright 2017 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. diff --git a/interface/src/webbrowser/WebBrowserSuggestionsEngine.h b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h index 0a94ea75c4..976631f109 100644 --- a/interface/src/webbrowser/WebBrowserSuggestionsEngine.h +++ b/interface/src/webbrowser/WebBrowserSuggestionsEngine.h @@ -1,19 +1,20 @@ +// +// WebBrowserSuggestionsEngine.h +// interface/src/webbrowser +// +// Created by Vlad Stelmahovsky on 30/10/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + #ifndef WEBBROWSERSUGGESTIONSENGINE_H #define WEBBROWSERSUGGESTIONSENGINE_H #include #include #include -// -// webbrowsersuggestionsengine.h -// interface/src/webbrowser -// -// Created by Vlad Stelmahovsky Kapolka on 30/10/17. -// Copyright 2017 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// #include #include From c6947dd165691887ac64e0fae778be96af0d6b4e Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 9 Nov 2017 18:20:06 -0800 Subject: [PATCH 16/42] some side by side plumbing for NLPackets and NLPacketLists --- libraries/networking/src/NodeList.h | 3 ++ libraries/networking/src/PacketSender.cpp | 38 ++++++++++++---- libraries/networking/src/PacketSender.h | 5 ++- .../octree/src/OctreeEditPacketSender.cpp | 44 +++++++++++++++++-- libraries/octree/src/OctreeEditPacketSender.h | 5 ++- 5 files changed, 80 insertions(+), 15 deletions(-) diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index b3a12153e5..0ebc1f0b22 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -40,6 +40,9 @@ const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000; const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; +using PacketOrPacketList = std::pair, std::unique_ptr>; +using NodePacketOrPacketListPair = std::pair; + using NodePacketPair = std::pair>; using NodeSharedPacketPair = std::pair>; using NodeSharedReceivedMessagePair = std::pair>; diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index 0cfd67cc4e..01b78585c2 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -53,7 +53,19 @@ void PacketSender::queuePacketForSending(const SharedNodePointer& destinationNod _totalBytesQueued += packet->getDataSize(); lock(); - _packets.push_back({destinationNode, std::move(packet)}); + _packets.push_back({destinationNode, PacketOrPacketList { std::move(packet), nullptr} }); + unlock(); + + // Make sure to wake our actual processing thread because we now have packets for it to process. + _hasPackets.wakeAll(); +} + +void PacketSender::queuePacketListForSending(const SharedNodePointer& destinationNode, std::unique_ptr packetList) { + _totalPacketsQueued += packetList->getNumPackets(); + _totalBytesQueued += packetList->getMessageSize(); + + lock(); + _packets.push_back({ destinationNode, PacketOrPacketList { nullptr, std::move(packetList)} }); unlock(); // Make sure to wake our actual processing thread because we now have packets for it to process. @@ -178,7 +190,7 @@ bool PacketSender::nonThreadedProcess() { float averagePacketsPerCall = 0; // might be less than 1, if our caller calls us more frequently than the target PPS - int packetsSentThisCall = 0; + size_t packetsSentThisCall = 0; int packetsToSendThisCall = 0; // Since we're in non-threaded mode, we need to determine how many packets to send per call to process @@ -265,23 +277,31 @@ bool PacketSender::nonThreadedProcess() { while ((packetsSentThisCall < packetsToSendThisCall) && (packetsLeft > 0)) { lock(); - NodePacketPair packetPair = std::move(_packets.front()); + NodePacketOrPacketListPair packetPair = std::move(_packets.front()); _packets.pop_front(); packetsLeft = _packets.size(); unlock(); // send the packet through the NodeList... - DependencyManager::get()->sendUnreliablePacket(*packetPair.second, *packetPair.first); + //PacketOrPacketList packetOrList = packetPair.second; + bool sendAsPacket = packetPair.second.first.get(); + if (sendAsPacket) { + DependencyManager::get()->sendUnreliablePacket(*packetPair.second.first, *packetPair.first); + } else { + DependencyManager::get()->sendPacketList(*packetPair.second.second, *packetPair.first); + } - packetsSentThisCall++; - _packetsOverCheckInterval++; - _totalPacketsSent++; + size_t packetSize = sendAsPacket ? packetPair.second.first->getDataSize() : packetPair.second.second->getMessageSize(); + size_t packetCount = sendAsPacket ? 1 : packetPair.second.second->getNumPackets(); + + packetsSentThisCall += packetCount; + _packetsOverCheckInterval += packetCount; + _totalPacketsSent += packetCount; - int packetSize = packetPair.second->getDataSize(); _totalBytesSent += packetSize; - emit packetSent(packetSize); + emit packetSent(packetSize); // FIXME should include number of packets? _lastSendTime = now; } diff --git a/libraries/networking/src/PacketSender.h b/libraries/networking/src/PacketSender.h index 68faeaca47..fead49df72 100644 --- a/libraries/networking/src/PacketSender.h +++ b/libraries/networking/src/PacketSender.h @@ -39,6 +39,7 @@ public: /// Add packet to outbound queue. void queuePacketForSending(const SharedNodePointer& destinationNode, std::unique_ptr packet); + void queuePacketListForSending(const SharedNodePointer& destinationNode, std::unique_ptr packetList); void setPacketsPerSecond(int packetsPerSecond); int getPacketsPerSecond() const { return _packetsPerSecond; } @@ -99,14 +100,14 @@ protected: SimpleMovingAverage _averageProcessCallTime; private: - std::list _packets; + std::list _packets; quint64 _lastSendTime; bool threadedProcess(); bool nonThreadedProcess(); quint64 _lastPPSCheck; - int _packetsOverCheckInterval; + size_t _packetsOverCheckInterval; quint64 _started; quint64 _totalPacketsSent; diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index f3c9ece9fe..7b612a828c 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -115,6 +115,27 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::uniqu }); } +// This method is called when the edit packet layer has determined that it has a fully formed packet destined for +// a known nodeID. +void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr packetList) { + + bool wantDebug = false; + DependencyManager::get()->eachNode([&](const SharedNodePointer& node) { + // only send to the NodeTypes that are getMyNodeType() + if (node->getType() == getMyNodeType() + && ((node->getUUID() == nodeUUID) || (nodeUUID.isNull())) + && node->getActiveSocket()) { + + // NOTE: unlike packets, the packet lists don't get rewritten sequence numbers. + + // add packet to history -- we don't keep track of sent PacketLists + //_sentPacketHistories[nodeUUID].packetSent(sequence, *packet); + + queuePacketListForSending(node, std::move(packetList)); + } + }); +} + void OctreeEditPacketSender::processPreServerExistsPackets() { assert(serversExist()); // we should only be here if we have jurisdictions @@ -247,7 +268,7 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray& }); } if (isMyJurisdiction) { - std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID]; + std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID].first; //only a NLPacket for now if (!bufferedPacket) { bufferedPacket = initializePacket(type, node->getClockSkewUsec()); @@ -291,15 +312,24 @@ void OctreeEditPacketSender::releaseQueuedMessages() { } else { _packetsQueueLock.lock(); for (auto& i : _pendingEditPackets) { - if (i.second) { + if (i.second.first) { // construct a null unique_ptr to an NL packet std::unique_ptr releasedPacket; // swap the null ptr with the packet we want to release - i.second.swap(releasedPacket); + i.second.first.swap(releasedPacket); // move and release the queued packet releaseQueuedPacket(i.first, std::move(releasedPacket)); + } else if (i.second.second) { + // construct a null unique_ptr to an NLPacketList + std::unique_ptr releasedPacketList; + + // swap the null ptr with the NLPacketList we want to release + i.second.second.swap(releasedPacketList); + + // move and release the queued NLPacketList + releaseQueuedPacketList(i.first, std::move(releasedPacketList)); } } @@ -315,6 +345,14 @@ void OctreeEditPacketSender::releaseQueuedPacket(const QUuid& nodeID, std::uniqu _releaseQueuedPacketMutex.unlock(); } +void OctreeEditPacketSender::releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr packetList) { + _releaseQueuedPacketMutex.lock(); + if (packetList->getMessageSize() > 0 && packetList->getType() != PacketType::Unknown) { + queuePacketListToNode(nodeID, std::move(packetList)); + } + _releaseQueuedPacketMutex.unlock(); +} + std::unique_ptr OctreeEditPacketSender::initializePacket(PacketType type, qint64 nodeClockSkew) { auto newPacket = NLPacket::create(type); diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index fd8cc85f91..79c363bec5 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -87,15 +87,18 @@ protected: bool _shouldSend; void queuePacketToNode(const QUuid& nodeID, std::unique_ptr packet); + void queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr packetList); + void queuePendingPacketToNodes(std::unique_ptr packet); void queuePacketToNodes(std::unique_ptr packet); std::unique_ptr initializePacket(PacketType type, qint64 nodeClockSkew); void releaseQueuedPacket(const QUuid& nodeUUID, std::unique_ptr packetBuffer); // releases specific queued packet + void releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr packetList); void processPreServerExistsPackets(); // These are packets which are destined from know servers but haven't been released because they're still too small - std::unordered_map> _pendingEditPackets; + std::unordered_map _pendingEditPackets; // These are packets that are waiting to be processed because we don't yet know if there are servers int _maxPendingMessages; From 908faa13345797dd37102cf324f3aade3ba9f459 Mon Sep 17 00:00:00 2001 From: beholder Date: Fri, 10 Nov 2017 19:38:07 +0300 Subject: [PATCH 17/42] refactoring: simplify code & fix the issue with menu-es sometimes losing checked state --- interface/resources/qml/desktop/Desktop.qml | 24 +++++++++++-------- .../qml/hifi/tablet/TabletMenuStack.qml | 23 ------------------ 2 files changed, 14 insertions(+), 33 deletions(-) diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index 847111b8a0..e7c68b2a47 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -51,19 +51,23 @@ FocusScope { // The VR version of the primary menu property var rootMenu: Menu { + id: rootMenuId objectName: "rootMenu" - // for some reasons it is not possible to use just '({})' here as it gets empty when passed to TableRoot/DesktopRoot - property var exclusionGroupsByMenuItem : ListModel {} + property var exclusionGroups: ({}); + property Component exclusiveGroupMaker: Component { + ExclusiveGroup { + } + } - function addExclusionGroup(menuItem, exclusionGroup) - { - exclusionGroupsByMenuItem.append( - { - 'menuItem' : menuItem.toString(), - 'exclusionGroup' : exclusionGroup.toString() - } - ); + function addExclusionGroup(qmlAction, exclusionGroup) { + + var exclusionGroupId = exclusionGroup.toString(); + if(!exclusionGroups[exclusionGroupId]) { + exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(rootMenuId); + } + + qmlAction.exclusiveGroup = exclusionGroups[exclusionGroupId] } } diff --git a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml index ce4fac3bd5..8cd696a41b 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuStack.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuStack.qml @@ -66,7 +66,6 @@ Item { function toModel(items, newMenu) { var result = modelMaker.createObject(tabletMenu); - var exclusionGroups = {}; for (var i = 0; i < items.length; ++i) { var item = items[i]; @@ -78,28 +77,6 @@ Item { if (item.text !== "Users Online") { result.append({"name": item.text, "item": item}) } - - for(var j = 0; j < tabletMenu.rootMenu.exclusionGroupsByMenuItem.count; ++j) - { - var entry = tabletMenu.rootMenu.exclusionGroupsByMenuItem.get(j); - if(entry.menuItem == item.toString()) - { - var exclusionGroupId = entry.exclusionGroup; - console.debug('item exclusionGroupId: ', exclusionGroupId) - - if(!exclusionGroups[exclusionGroupId]) - { - exclusionGroups[exclusionGroupId] = exclusiveGroupMaker.createObject(newMenu); - console.debug('new exclusion group created: ', exclusionGroups[exclusionGroupId]) - } - - var exclusionGroup = exclusionGroups[exclusionGroupId]; - - item.exclusiveGroup = exclusionGroup - console.debug('item.exclusiveGroup: ', item.exclusiveGroup) - } - } - break; case MenuItemType.Separator: result.append({"name": "", "item": item}) From 87519cd26f0174ce273fc63ee40076bb02132a22 Mon Sep 17 00:00:00 2001 From: beholder Date: Fri, 10 Nov 2017 19:39:51 +0300 Subject: [PATCH 18/42] use conditional bindings to avoid intermediate binding states --- .../qml/hifi/tablet/TabletMenuItem.qml | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml index 11d3cab35e..520841b33f 100644 --- a/interface/resources/qml/hifi/tablet/TabletMenuItem.qml +++ b/interface/resources/qml/hifi/tablet/TabletMenuItem.qml @@ -40,37 +40,29 @@ Item { CheckBox { id: checkbox - // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 visible: source !== null ? source.visible && source.type === 1 && source.checkable && !source.exclusiveGroup : false - checked: setChecked() - function setChecked() { - if (!source || source.type !== 1 || !source.checkable) { - return false; - } - // FIXME this works for native QML menus but I don't think it will - // for proxied QML menus - return source.checked; + + Binding on checked { + value: source.checked; + when: source && source.type === 1 && source.checkable && !source.exclusiveGroup; } } RadioButton { id: radiobutton - // FIXME: Should use radio buttons if source.exclusiveGroup. + width: 20 visible: source !== null ? source.visible && source.type === 1 && source.checkable && source.exclusiveGroup : false - checked: setChecked() - function setChecked() { - if (!source || source.type !== 1 || !source.checkable) { - return false; - } - // FIXME this works for native QML menus but I don't think it will - // for proxied QML menus - return source.checked; + + Binding on checked { + value: source.checked; + when: source && source.type === 1 && source.checkable && source.exclusiveGroup; } } } From da49ee8bee498b9d1d9280afbc6ccb325d566d40 Mon Sep 17 00:00:00 2001 From: beholder Date: Fri, 10 Nov 2017 19:42:10 +0300 Subject: [PATCH 19/42] 9032 Current display mode can be deselected in the toolbar/tablet menu --- interface/src/Application.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b21588958e..e35407038b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7082,6 +7082,7 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const { return _displayPlugin; } +static const char* exclusionGroupKey = "exclusionGroup"; static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { auto menu = Menu::getInstance(); @@ -7117,6 +7118,8 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti action->setCheckable(true); action->setChecked(active); displayPluginGroup->addAction(action); + + action->setProperty(exclusionGroupKey, QVariant::fromValue(displayPluginGroup)); Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name)); } From 5050a554a2b2d99fdcb171c80f9682d25f4a56aa Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 7 Nov 2017 10:34:34 -0800 Subject: [PATCH 20/42] remove warning about signed/unsigned comparison --- libraries/render/src/render/IndexedContainer.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/render/src/render/IndexedContainer.h b/libraries/render/src/render/IndexedContainer.h index 4b51f8eb3c..2d21b5cc29 100644 --- a/libraries/render/src/render/IndexedContainer.h +++ b/libraries/render/src/render/IndexedContainer.h @@ -87,7 +87,7 @@ namespace indexed_container { if (index < (Index) _elements.size()) { _elements[index] = e; } else { - assert(index == _elements.size()); + assert(index == (Index)_elements.size()); _elements.emplace_back(e); } } @@ -159,4 +159,4 @@ namespace indexed_container { }; }; } -#endif \ No newline at end of file +#endif From 809ff7928e6c793efd803e4d38d11a5273bcb8b3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 7 Nov 2017 10:34:58 -0800 Subject: [PATCH 21/42] sort and throttle UpdateRenderables --- .../src/EntityTreeRenderer.cpp | 127 ++++++++++++++++-- .../src/EntityTreeRenderer.h | 15 ++- .../src/RenderableEntityItem.cpp | 5 +- .../src/RenderableEntityItem.h | 7 +- 4 files changed, 139 insertions(+), 15 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 07d8716656..527bf29cb4 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -12,6 +12,7 @@ #include "EntityTreeRenderer.h" #include +#include #include #include @@ -272,7 +273,7 @@ void EntityTreeRenderer::addPendingEntities(const render::ScenePointer& scene, r } } -void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, render::Transaction& transaction) { +void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction) { PROFILE_RANGE_EX(simulation_physics, "Change", 0xffff00ff, (uint64_t)_changedEntities.size()); PerformanceTimer pt("change"); std::unordered_set changedEntities; @@ -286,21 +287,129 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene #endif }); - for (const auto& entityId : changedEntities) { - auto renderable = renderableForEntityId(entityId); - if (!renderable) { - continue; + { + PROFILE_RANGE_EX(simulation_physics, "CopyRenderables", 0xffff00ff, (uint64_t)changedEntities.size()); + if (_renderablesToUpdate.empty()) { + for (const auto& entityId : changedEntities) { + auto renderable = renderableForEntityId(entityId); + if (!renderable) { + continue; + } + _renderablesToUpdate.insert({ entityId, renderable }); + } + } else { + // we weren't able to update all renderables last frame + // so we have to be more careful when processing changed renderables + std::unordered_map::iterator itr; + for (const auto& entityId : changedEntities) { + auto renderable = renderableForEntityId(entityId); + itr = _renderablesToUpdate.find(entityId); + if (itr != _renderablesToUpdate.end()) { + if (!renderable) { + _renderablesToUpdate.erase(itr); + continue; + } + _renderablesToUpdate.insert(itr, { entityId, renderable }); + } else { + _renderablesToUpdate.insert({ entityId, renderable }); + } + } } - _renderablesToUpdate.insert({ entityId, renderable }); } - if (!_renderablesToUpdate.empty()) { + float expectedUpdateCost = _avgRenderableUpdateCost * _renderablesToUpdate.size(); + const float MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2 * USECS_PER_MSEC; + if (expectedUpdateCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET) { + // we expect to update all renderables within available time budget PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + uint64_t updateStart = usecTimestampNow(); for (const auto& entry : _renderablesToUpdate) { const auto& renderable = entry.second; renderable->updateInScene(scene, transaction); } + size_t numRenderables = _renderablesToUpdate.size() + 1; // add one to avoid divide by zero _renderablesToUpdate.clear(); + + // compute average per-renderable update cost + float cost = (float)(usecTimestampNow() - updateStart) / (float)(numRenderables); + const float blend = 0.1f; + _avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost; + } else { + // we expect the cost to updating all renderables to exceed available time budget + // so we first sort by priority and update in order until out of time + uint64_t sortStart = usecTimestampNow(); + std::priority_queue sortedRenderables; + { + PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + std::unordered_map::iterator itr = _renderablesToUpdate.begin(); + glm::vec3 viewCenter = view.getPosition(); + glm::vec3 forward = view.getDirection(); + const float OUT_OF_VIEW_PENALTY = -10.0f; + while (itr != _renderablesToUpdate.end()) { + // priority = weighted linear combination of: + // (a) apparentSize + // (b) proximity to center of view + // (c) time since last update + EntityItemPointer entity = itr->second->getEntity(); + glm::vec3 entityPosition = entity->getPosition(); + glm::vec3 offset = entityPosition - viewCenter; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + + float diameter = entity->getQueryAACube().getScale(); + float apparentSize = diameter / distance; + float cosineAngle = glm::dot(offset, forward) / distance; + float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); + + // NOTE: we are adding values of different units to get a single measure of "priority". + // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. + // These weights are pure magic tuning and should be hard coded in the relation below, + // but are currently exposed for anyone who would like to explore fine tuning: + const float APPARENT_SIZE_COEFFICIENT = 1.0f; + const float CENTER_SORT_COEFFICIENT = 0.5f; + const float AGE_SORT_COEFFICIENT = 0.25f; + float priority = APPARENT_SIZE_COEFFICIENT * apparentSize + + CENTER_SORT_COEFFICIENT * cosineAngle + + AGE_SORT_COEFFICIENT * age; + + // decrement priority of things outside keyhole + if (distance > view.getCenterRadius()) { + if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { + priority += OUT_OF_VIEW_PENALTY; + } + } + + sortedRenderables.push(SortableEntityRenderer(itr->second, priority)); + ++itr; + } + } + { + PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + + // compute remaining time budget + uint64_t updateStart = usecTimestampNow(); + const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1 * USECS_PER_MSEC; + uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET; + uint64_t timeForSort = updateStart - sortStart; + if (timeForSort < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { + timeBudget = MAX_UPDATE_RENDERABLES_TIME_BUDGET - timeForSort; + } + uint64_t expiry = updateStart + timeBudget; + + std::unordered_map::iterator itr; + size_t numSorted = sortedRenderables.size(); + while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { + const EntityRendererPointer& renderer = sortedRenderables.top().renderer; + renderer->updateInScene(scene, transaction); + _renderablesToUpdate.erase(renderer->getEntity()->getID()); + sortedRenderables.pop(); + } + + // compute average per-renderable update cost + size_t numUpdated = numSorted - sortedRenderables.size() + 1; // add one to avoid divide by zero + float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated + 1); + const float blend = 0.1f; + _avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost; + } } } @@ -319,7 +428,9 @@ void EntityTreeRenderer::update(bool simulate) { if (scene) { render::Transaction transaction; addPendingEntities(scene, transaction); - updateChangedEntities(scene, transaction); + ViewFrustum view; + _viewState->copyCurrentViewFrustum(view); + updateChangedEntities(scene, view, transaction); scene->enqueueTransaction(transaction); } } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 1eb44f996a..e922dd83e9 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -51,6 +51,14 @@ using ModelWeakPointer = std::weak_ptr; using CalculateEntityLoadingPriority = std::function; +class SortableEntityRenderer { +public: + SortableEntityRenderer(EntityRendererPointer r, float p) : renderer(r), priority(p) {} + EntityRendererPointer renderer; + float priority; + bool operator<(const SortableEntityRenderer& other) const { return priority < other.priority; } +}; + // Generic client side Octree renderer class. class EntityTreeRenderer : public OctreeProcessor, public Dependency { Q_OBJECT @@ -144,7 +152,7 @@ protected: private: void addPendingEntities(const render::ScenePointer& scene, render::Transaction& transaction); - void updateChangedEntities(const render::ScenePointer& scene, render::Transaction& transaction); + void updateChangedEntities(const render::ScenePointer& scene, const ViewFrustum& view, render::Transaction& transaction); EntityRendererPointer renderableForEntity(const EntityItemPointer& entity) const { return renderableForEntityId(entity->getID()); } render::ItemID renderableIdForEntity(const EntityItemPointer& entity) const { return renderableIdForEntityId(entity->getID()); } @@ -235,11 +243,12 @@ private: NetworkTexturePointer _skyboxTexture; QString _ambientTextureURL; QString _skyboxTextureURL; + float _avgRenderableUpdateCost { 0.0f }; bool _pendingAmbientTexture { false }; bool _pendingSkyboxTexture { false }; - quint64 _lastZoneCheck { 0 }; - const quint64 ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz + uint64_t _lastZoneCheck { 0 }; + const uint64_t ZONE_CHECK_INTERVAL = USECS_PER_MSEC * 100; // ~10hz const float ZONE_CHECK_DISTANCE = 0.001f; ReadWriteLockable _changedEntitiesGuard; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index 9e4d832037..24de651247 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -59,7 +59,7 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St const QUuid& myNodeID = nodeList->getSessionUUID(); statusGetters.push_back([entity]() -> render::Item::Status::Value { - quint64 delta = usecTimestampNow() - entity->getLastEditedFromRemote(); + uint64_t delta = usecTimestampNow() - entity->getLastEditedFromRemote(); const float WAIT_THRESHOLD_INV = 1.0f / (0.2f * USECS_PER_SECOND); float normalizedDelta = delta * WAIT_THRESHOLD_INV; // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD @@ -71,7 +71,7 @@ void EntityRenderer::makeStatusGetters(const EntityItemPointer& entity, Item::St }); statusGetters.push_back([entity] () -> render::Item::Status::Value { - quint64 delta = usecTimestampNow() - entity->getLastBroadcast(); + uint64_t delta = usecTimestampNow() - entity->getLastBroadcast(); const float WAIT_THRESHOLD_INV = 1.0f / (0.4f * USECS_PER_SECOND); float normalizedDelta = delta * WAIT_THRESHOLD_INV; // Status icon will scale from 1.0f down to 0.0f after WAIT_THRESHOLD @@ -278,6 +278,7 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans if (!isValidRenderItem()) { return; } + _updateTime = usecTimestampNow(); // FIXME is this excessive? if (!needsRenderUpdate()) { diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index ed636ebf73..b134dddf8c 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -52,6 +52,8 @@ public: void clearSubRenderItemIDs(); void setSubRenderItemIDs(const render::ItemIDs& ids); + const uint64_t& getUpdateTime() const { return _updateTime; } + protected: virtual bool needsRenderUpdateFromEntity() const final { return needsRenderUpdateFromEntity(_entity); } virtual void onAddToScene(const EntityItemPointer& entity); @@ -100,7 +102,6 @@ protected: return result; } - signals: void requestRenderUpdate(); @@ -113,14 +114,16 @@ protected: static std::function _entitiesShouldFadeFunction; const Transform& getModelTransform() const; + Item::Bound _bound; SharedSoundPointer _collisionSound; QUuid _changeHandlerId; ItemID _renderItemID{ Item::INVALID_ITEM_ID }; ItemIDs _subRenderItemIDs; quint64 _fadeStartTime{ usecTimestampNow() }; + uint64_t _fadeStartTime{ usecTimestampNow() }; + uint64_t _updateTime{ usecTimestampNow() }; // used when sorting/throttling render updates bool _isFading{ _entitiesShouldFadeFunction() }; bool _prevIsTransparent { false }; - Item::Bound _bound; bool _visible { false }; bool _moving { false }; // Only touched on the rendering thread From c6bccb3de3b9c49482b179bcd78c436c8a473612 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 7 Nov 2017 18:58:25 -0800 Subject: [PATCH 22/42] avoid adding null renderables to list --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 527bf29cb4..aa4fe35bcd 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -310,7 +310,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene continue; } _renderablesToUpdate.insert(itr, { entityId, renderable }); - } else { + } else if (renderable) { _renderablesToUpdate.insert({ entityId, renderable }); } } From f47185b2f62308c42f077dac1d2b70e88e9afa2c Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 10:49:54 -0800 Subject: [PATCH 23/42] remove tabs from indentation --- .../src/EntityTreeRenderer.cpp | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index aa4fe35bcd..17d8a6e9be 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -346,37 +346,37 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene glm::vec3 forward = view.getDirection(); const float OUT_OF_VIEW_PENALTY = -10.0f; while (itr != _renderablesToUpdate.end()) { - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update + // priority = weighted linear combination of: + // (a) apparentSize + // (b) proximity to center of view + // (c) time since last update EntityItemPointer entity = itr->second->getEntity(); - glm::vec3 entityPosition = entity->getPosition(); - glm::vec3 offset = entityPosition - viewCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + glm::vec3 entityPosition = entity->getPosition(); + glm::vec3 offset = entityPosition - viewCenter; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float diameter = entity->getQueryAACube().getScale(); - float apparentSize = diameter / distance; - float cosineAngle = glm::dot(offset, forward) / distance; - float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); + float diameter = entity->getQueryAACube().getScale(); + float apparentSize = diameter / distance; + float cosineAngle = glm::dot(offset, forward) / distance; + float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. - // These weights are pure magic tuning and should be hard coded in the relation below, - // but are currently exposed for anyone who would like to explore fine tuning: + // NOTE: we are adding values of different units to get a single measure of "priority". + // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. + // These weights are pure magic tuning and should be hard coded in the relation below, + // but are currently exposed for anyone who would like to explore fine tuning: const float APPARENT_SIZE_COEFFICIENT = 1.0f; const float CENTER_SORT_COEFFICIENT = 0.5f; const float AGE_SORT_COEFFICIENT = 0.25f; - float priority = APPARENT_SIZE_COEFFICIENT * apparentSize - + CENTER_SORT_COEFFICIENT * cosineAngle - + AGE_SORT_COEFFICIENT * age; + float priority = APPARENT_SIZE_COEFFICIENT * apparentSize + + CENTER_SORT_COEFFICIENT * cosineAngle + + AGE_SORT_COEFFICIENT * age; - // decrement priority of things outside keyhole - if (distance > view.getCenterRadius()) { - if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { - priority += OUT_OF_VIEW_PENALTY; - } - } + // decrement priority of things outside keyhole + if (distance > view.getCenterRadius()) { + if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { + priority += OUT_OF_VIEW_PENALTY; + } + } sortedRenderables.push(SortableEntityRenderer(itr->second, priority)); ++itr; From 8707c76a6a20cacb50da84512627532d9a452c10 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 16:30:12 -0800 Subject: [PATCH 24/42] templatize the ViewFrustum-relative sort algorithm --- .../src/EntityTreeRenderer.cpp | 61 ++++----- libraries/shared/src/PrioritySortUtil.h | 126 ++++++++++++++++++ 2 files changed, 149 insertions(+), 38 deletions(-) create mode 100644 libraries/shared/src/PrioritySortUtil.h diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 17d8a6e9be..1efcd863ab 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -38,6 +38,22 @@ #include "RenderableWebEntityItem.h" +// implement these methods BEFORE including PrioritySortUtil.h +namespace PrioritySortUtil { + glm::vec3 getObjectPosition(const EntityRendererPointer& object) { + return object->getEntity()->getPosition(); + } + + float getObjectRadius(const EntityRendererPointer& object) { + return 0.5f * object->getEntity()->getQueryAACube().getScale(); + } + + uint64_t getObjectAge(const EntityRendererPointer& object) { + return object->getUpdateTime(); + } +} +#include + size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; @@ -338,47 +354,16 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // we expect the cost to updating all renderables to exceed available time budget // so we first sort by priority and update in order until out of time uint64_t sortStart = usecTimestampNow(); - std::priority_queue sortedRenderables; + using SortableRenderer = PrioritySortUtil::Sortable; + std::priority_queue< SortableRenderer > sortedRenderables; { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + PrioritySortUtil::PriorityCalculator priorityCalculator(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); - glm::vec3 viewCenter = view.getPosition(); - glm::vec3 forward = view.getDirection(); - const float OUT_OF_VIEW_PENALTY = -10.0f; while (itr != _renderablesToUpdate.end()) { - // priority = weighted linear combination of: - // (a) apparentSize - // (b) proximity to center of view - // (c) time since last update - EntityItemPointer entity = itr->second->getEntity(); - glm::vec3 entityPosition = entity->getPosition(); - glm::vec3 offset = entityPosition - viewCenter; - float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - - float diameter = entity->getQueryAACube().getScale(); - float apparentSize = diameter / distance; - float cosineAngle = glm::dot(offset, forward) / distance; - float age = (float)(sortStart - itr->second->getUpdateTime()) / (float)(USECS_PER_SECOND); - - // NOTE: we are adding values of different units to get a single measure of "priority". - // Thus we multiply each component by a conversion "weight" that scales its units relative to the others. - // These weights are pure magic tuning and should be hard coded in the relation below, - // but are currently exposed for anyone who would like to explore fine tuning: - const float APPARENT_SIZE_COEFFICIENT = 1.0f; - const float CENTER_SORT_COEFFICIENT = 0.5f; - const float AGE_SORT_COEFFICIENT = 0.25f; - float priority = APPARENT_SIZE_COEFFICIENT * apparentSize - + CENTER_SORT_COEFFICIENT * cosineAngle - + AGE_SORT_COEFFICIENT * age; - - // decrement priority of things outside keyhole - if (distance > view.getCenterRadius()) { - if (!view.sphereIntersectsFrustum(entityPosition, 0.5f * diameter)) { - priority += OUT_OF_VIEW_PENALTY; - } - } - - sortedRenderables.push(SortableEntityRenderer(itr->second, priority)); + float priority = priorityCalculator.computePriority(itr->second); + SortableRenderer entry(itr->second, priority); + sortedRenderables.push(entry); ++itr; } } @@ -398,7 +383,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().renderer; + const EntityRendererPointer& renderer = sortedRenderables.top().getObject(); renderer->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderer->getEntity()->getID()); sortedRenderables.pop(); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h new file mode 100644 index 0000000000..e084c6d1a5 --- /dev/null +++ b/libraries/shared/src/PrioritySortUtil.h @@ -0,0 +1,126 @@ +// +// PrioritySortUtil.h +// +// Created by Andrew Meadows on 2017-11-08 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#pragma once +#ifndef hifi_PrioritySortUtil_h +#define hifi_PrioritySortUtil_h + +#include +#include "ViewFrustum.h" + +namespace PrioritySortUtil { + // PrioritySortUtil is a helper template for sorting 3D objects relative to a ViewFrustum. + // To use this utility: + // + // (1) Declare and implent the following methods for your "object" type T: + // + // glm::vec3 PrioritySortUtil::getObjectPosition(const T&); + // float PrioritySortUtil::getObjectRadius(const T&); + // uint64_t PrioritySortUtil::getObjectAge(const T&); + // + // (2) Below the implementation in (1): + / + // #include + // + // (3) Create a PriorityCalculator instance: + // + // PrioritySortUtil::PriorityCalculator calculator(viewFrustum); + // + // (4) Loop over your objects and insert the into a priority_queue: + // + // std::priority_queue< PrioritySortUtil::Sortable > sortedObjects; + // for (T obj in objects) { + // float priority = calculator.computePriority(obj); + // PrioritySortUtil::Sortable entry(obj, priority); + // sortedObjects.push(entry); + // } + + template + class Sortable { + public: + Sortable(const T& object, float sortPriority) : _object(object), _priority(sortPriority) {} + const T& getObject() const { return _object; } + void setPriority(float priority) { _priority = priority; } + bool operator<(const Sortable& other) const { return _priority < other._priority; } + private: + T _object; + float _priority; + }; + + constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; + constexpr float DEFAULT_CENTER_COEF { 0.5f }; + constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; + + template + class PriorityCalculator { + public: + PriorityCalculator() = delete; + + PriorityCalculator(const ViewFrustum& view) : _view(view) { + cacheView(); + } + + PriorityCalculator(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) + : _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) + { + cacheView(); + } + + void setView(const ViewFrustum& view) { _view = view; } + + void setWeights(float angularWeight, float centerWeight, float ageWeight) { + _angularWeight = angularWeight; + _centerWeight = centerWeight; + _ageWeight = ageWeight; + } + + float computePriority(T& object) const { + // priority = weighted linear combination of multiple values: + // (a) angular size + // (b) proximity to center of view + // (c) time since last update + // where the relative "weights" are tuned to scale the contributing values into units of "priority". + + glm::vec3 position = PrioritySortUtil::getObjectPosition(object); + glm::vec3 offset = position - _viewPosition; + float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero + float radius = PrioritySortUtil::getObjectRadius(object); + + float priority = _angularWeight * (radius / distance) + + _centerWeight * (glm::dot(offset, _viewForward) / distance) + + _ageWeight * (float)(usecTimestampNow() - PrioritySortUtil::getObjectAge(object)); + + // decrement priority of things outside keyhole + if (distance + radius > _viewRadius) { + if (!_view.sphereIntersectsFrustum(position, radius)) { + constexpr float OUT_OF_VIEW_PENALTY = -10.0f; + priority += OUT_OF_VIEW_PENALTY; + } + } + return priority; + } + + private: + void cacheView() { + // assuming we'll prioritize many objects: cache these values + _viewPosition = _view.getPosition(); + _viewForward = _view.getDirection(); + _viewRadius = _view.getCenterRadius(); + } + + ViewFrustum _view; + glm::vec3 _viewPosition; + glm::vec3 _viewForward; + float _viewRadius; + float _angularWeight { DEFAULT_ANGULAR_COEF }; + float _centerWeight { DEFAULT_CENTER_COEF }; + float _ageWeight { DEFAULT_AGE_COEF }; + }; +} +#endif // hifi_PrioritySortUtil_h From e93c10b5eeaa6d77fe84b776228a33602fd32f51 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 16:32:19 -0800 Subject: [PATCH 25/42] remove cruft --- libraries/entities-renderer/src/EntityTreeRenderer.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index e922dd83e9..1f5e6c8d92 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -51,14 +51,6 @@ using ModelWeakPointer = std::weak_ptr; using CalculateEntityLoadingPriority = std::function; -class SortableEntityRenderer { -public: - SortableEntityRenderer(EntityRendererPointer r, float p) : renderer(r), priority(p) {} - EntityRendererPointer renderer; - float priority; - bool operator<(const SortableEntityRenderer& other) const { return priority < other.priority; } -}; - // Generic client side Octree renderer class. class EntityTreeRenderer : public OctreeProcessor, public Dependency { Q_OBJECT From 111480f6308751f81db0eb694bb5d6b1b8e69e39 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 17:16:13 -0800 Subject: [PATCH 26/42] fix bad conflict resolution --- libraries/entities-renderer/src/RenderableEntityItem.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index b134dddf8c..5deb69711d 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -119,7 +119,6 @@ protected: QUuid _changeHandlerId; ItemID _renderItemID{ Item::INVALID_ITEM_ID }; ItemIDs _subRenderItemIDs; - quint64 _fadeStartTime{ usecTimestampNow() }; uint64_t _fadeStartTime{ usecTimestampNow() }; uint64_t _updateTime{ usecTimestampNow() }; // used when sorting/throttling render updates bool _isFading{ _entitiesShouldFadeFunction() }; From d7b84f8a86417dd58c7dcba95044f7adcade813d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 8 Nov 2017 17:18:19 -0800 Subject: [PATCH 27/42] fix typo in comment --- libraries/shared/src/PrioritySortUtil.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index e084c6d1a5..666847a3e7 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -25,7 +25,7 @@ namespace PrioritySortUtil { // uint64_t PrioritySortUtil::getObjectAge(const T&); // // (2) Below the implementation in (1): - / + // // #include // // (3) Create a PriorityCalculator instance: @@ -124,3 +124,4 @@ namespace PrioritySortUtil { }; } #endif // hifi_PrioritySortUtil_h + From 99b4283cbcbd43cdc2488f63c91b2ea1153b5330 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 9 Nov 2017 15:18:32 -0800 Subject: [PATCH 28/42] fix typo in 'out-of-view' check --- libraries/shared/src/PrioritySortUtil.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 666847a3e7..4b32b6d977 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -18,7 +18,7 @@ namespace PrioritySortUtil { // PrioritySortUtil is a helper template for sorting 3D objects relative to a ViewFrustum. // To use this utility: // - // (1) Declare and implent the following methods for your "object" type T: + // (1) Declare and implement the following methods for your "object" type T: // // glm::vec3 PrioritySortUtil::getObjectPosition(const T&); // float PrioritySortUtil::getObjectRadius(const T&); @@ -97,7 +97,7 @@ namespace PrioritySortUtil { + _ageWeight * (float)(usecTimestampNow() - PrioritySortUtil::getObjectAge(object)); // decrement priority of things outside keyhole - if (distance + radius > _viewRadius) { + if (distance - radius > _viewRadius) { if (!_view.sphereIntersectsFrustum(position, radius)) { constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; From 55cc945c78fe2a2f9b7a63b34518741457029155 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 10 Nov 2017 10:09:41 -0800 Subject: [PATCH 29/42] organize PrioritySortUtil differently --- .../src/EntityTreeRenderer.cpp | 48 ++++--- libraries/shared/src/PrioritySortUtil.h | 118 +++++++++++------- 2 files changed, 94 insertions(+), 72 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 1efcd863ab..c62eafd9f2 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -19,18 +19,18 @@ #include #include -#include #include #include +#include +#include #include #include #include +#include +#include #include #include -#include -#include #include -#include #include #include "EntitiesRendererLogging.h" @@ -38,22 +38,6 @@ #include "RenderableWebEntityItem.h" -// implement these methods BEFORE including PrioritySortUtil.h -namespace PrioritySortUtil { - glm::vec3 getObjectPosition(const EntityRendererPointer& object) { - return object->getEntity()->getPosition(); - } - - float getObjectRadius(const EntityRendererPointer& object) { - return 0.5f * object->getEntity()->getQueryAACube().getScale(); - } - - uint64_t getObjectAge(const EntityRendererPointer& object) { - return object->getUpdateTime(); - } -} -#include - size_t std::hash::operator()(const EntityItemID& id) const { return qHash(id); } std::function EntityTreeRenderer::_entitiesShouldFadeFunction; @@ -353,17 +337,30 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } else { // we expect the cost to updating all renderables to exceed available time budget // so we first sort by priority and update in order until out of time + + // as per the instructions in PriortySortUtil we first derive from PrioritySortUtil::Prioritizable + class PrioritizableRenderer: public PrioritySortUtil::Prioritizable { + public: + PrioritizableRenderer(const EntityRendererPointer& renderer) : _renderer(renderer) { } + glm::vec3 getPosition() const override { return _renderer->getEntity()->getPosition(); } + float getRadius() const override { return 0.5f * _renderer->getEntity()->getQueryAACube().getScale(); } + uint64_t getTimestamp() const override { return _renderer->getUpdateTime(); } + private: + const EntityRendererPointer& _renderer; + }; + + // prioritize and sort the renderables uint64_t sortStart = usecTimestampNow(); using SortableRenderer = PrioritySortUtil::Sortable; std::priority_queue< SortableRenderer > sortedRenderables; { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); - PrioritySortUtil::PriorityCalculator priorityCalculator(view); + PrioritySortUtil::Prioritizer prioritizer(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); while (itr != _renderablesToUpdate.end()) { - float priority = priorityCalculator.computePriority(itr->second); - SortableRenderer entry(itr->second, priority); - sortedRenderables.push(entry); + float priority = prioritizer.computePriority(PrioritizableRenderer(itr->second)); + //SortableRenderer entry(itr->second, priority); + sortedRenderables.push(SortableRenderer(itr->second, priority)); ++itr; } } @@ -380,10 +377,11 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } uint64_t expiry = updateStart + timeBudget; + // process the sorted renderables std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().getObject(); + const EntityRendererPointer& renderer = sortedRenderables.top().getThing(); renderer->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderer->getEntity()->getID()); sortedRenderables.pop(); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 4b32b6d977..d03edc53ac 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -14,59 +14,82 @@ #include #include "ViewFrustum.h" -namespace PrioritySortUtil { - // PrioritySortUtil is a helper template for sorting 3D objects relative to a ViewFrustum. - // To use this utility: - // - // (1) Declare and implement the following methods for your "object" type T: - // - // glm::vec3 PrioritySortUtil::getObjectPosition(const T&); - // float PrioritySortUtil::getObjectRadius(const T&); - // uint64_t PrioritySortUtil::getObjectAge(const T&); - // - // (2) Below the implementation in (1): - // - // #include - // - // (3) Create a PriorityCalculator instance: - // - // PrioritySortUtil::PriorityCalculator calculator(viewFrustum); - // - // (4) Loop over your objects and insert the into a priority_queue: - // - // std::priority_queue< PrioritySortUtil::Sortable > sortedObjects; - // for (T obj in objects) { - // float priority = calculator.computePriority(obj); - // PrioritySortUtil::Sortable entry(obj, priority); - // sortedObjects.push(entry); - // } +/* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: - template - class Sortable { - public: - Sortable(const T& object, float sortPriority) : _object(object), _priority(sortPriority) {} - const T& getObject() const { return _object; } - void setPriority(float priority) { _priority = priority; } - bool operator<(const Sortable& other) const { return _priority < other._priority; } - private: - T _object; - float _priority; - }; +(1) Derive a class from pure-virtual PrioritySortUtil::Prioritizable + that wraps the Thing you want to prioritize and sort: + + class PrioritizableThing : public PrioritySortUtil::Prioritizable { + public: + PrioritizableThing(const Thing& thing) : _thing(thing) {} + glm::vec3 getPosition() const override { return _thing.getPosition(); } + float getRadius() const const override { return _thing.getBoundingRadius(); } + uint64_t getTimestamp() const override { return _thing.getLastUpdated(); } + private: + // Yes really: the data member is a const reference! + // PrioritizableThing only needs enough scope to compute a priority. + const Thing& _thing; + } + +(2) Loop over your things and insert each into a priority_queue: + + PrioritySortUtil::Prioritizer prioritizer(viewFrustum); + std::priority_queue< PrioritySortUtil::Sortable > sortedThings; + for (thing in things) { + float priority = prioritizer.computePriority(PrioritySortUtil::PrioritizableThing(thing)); + sortedThings.push(PrioritySortUtil::Sortable entry(thing, priority)); + } + +(4) Loop over your priority queue and do timeboxed work: + + uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; + while (!sortedThings.empty()) { + const Thing& thing = sortedThings.top(); + // ...do work on thing... + sortedThings.pop(); + if (usecTimestampNow() > cutoffTime) { + break; + } + } + +*/ + +namespace PrioritySortUtil { constexpr float DEFAULT_ANGULAR_COEF { 1.0f }; constexpr float DEFAULT_CENTER_COEF { 0.5f }; constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; template - class PriorityCalculator { + class Sortable { public: - PriorityCalculator() = delete; + Sortable(const T& thing, float sortPriority) : _thing(thing), _priority(sortPriority) {} + const T& getThing() const { return _thing; } + void setPriority(float priority) { _priority = priority; } + bool operator<(const Sortable& other) const { return _priority < other._priority; } + private: + T _thing; + float _priority; + }; - PriorityCalculator(const ViewFrustum& view) : _view(view) { + // Prioritizable isn't a template because templates can't have pure-virtual methods. + class Prioritizable { + public: + virtual glm::vec3 getPosition() const = 0; + virtual float getRadius() const = 0; + virtual uint64_t getTimestamp() const = 0; + }; + + template + class Prioritizer { + public: + Prioritizer() = delete; + + Prioritizer(const ViewFrustum& view) : _view(view) { cacheView(); } - PriorityCalculator(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) + Prioritizer(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) : _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) { cacheView(); @@ -80,21 +103,21 @@ namespace PrioritySortUtil { _ageWeight = ageWeight; } - float computePriority(T& object) const { + float computePriority(const Prioritizable& prioritizableThing) const { // priority = weighted linear combination of multiple values: // (a) angular size // (b) proximity to center of view // (c) time since last update // where the relative "weights" are tuned to scale the contributing values into units of "priority". - glm::vec3 position = PrioritySortUtil::getObjectPosition(object); + glm::vec3 position = prioritizableThing.getPosition(); glm::vec3 offset = position - _viewPosition; float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float radius = PrioritySortUtil::getObjectRadius(object); + float radius = prioritizableThing.getRadius(); float priority = _angularWeight * (radius / distance) + _centerWeight * (glm::dot(offset, _viewForward) / distance) - + _ageWeight * (float)(usecTimestampNow() - PrioritySortUtil::getObjectAge(object)); + + _ageWeight * (float)(usecTimestampNow() - prioritizableThing.getTimestamp()); // decrement priority of things outside keyhole if (distance - radius > _viewRadius) { @@ -108,7 +131,7 @@ namespace PrioritySortUtil { private: void cacheView() { - // assuming we'll prioritize many objects: cache these values + // assuming we'll prioritize many things: cache these values _viewPosition = _view.getPosition(); _viewForward = _view.getDirection(); _viewRadius = _view.getCenterRadius(); @@ -122,6 +145,7 @@ namespace PrioritySortUtil { float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; }; -} +} // namespace PrioritySortUtil + #endif // hifi_PrioritySortUtil_h From f67114a0a8c92a63aa496ff15c49de43facc78d3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 10 Nov 2017 14:45:46 -0800 Subject: [PATCH 30/42] PrioritySortUtil::Prioritizer doesn't need to be a template --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- libraries/shared/src/PrioritySortUtil.h | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index c62eafd9f2..11b9cb30bb 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -355,7 +355,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::priority_queue< SortableRenderer > sortedRenderables; { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); - PrioritySortUtil::Prioritizer prioritizer(view); + PrioritySortUtil::Prioritizer prioritizer(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); while (itr != _renderablesToUpdate.end()) { float priority = prioritizer.computePriority(PrioritizableRenderer(itr->second)); diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index d03edc53ac..d5fdbad31b 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -80,7 +80,6 @@ namespace PrioritySortUtil { virtual uint64_t getTimestamp() const = 0; }; - template class Prioritizer { public: Prioritizer() = delete; From ffe16a754e088b8d7648258c5dc5731042ba10cf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Nov 2017 15:42:57 -0800 Subject: [PATCH 31/42] another pass through the crucible --- .../src/EntityTreeRenderer.cpp | 30 +++--- libraries/shared/src/PrioritySortUtil.h | 99 ++++++++----------- 2 files changed, 57 insertions(+), 72 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 11b9cb30bb..9e4bfe22c8 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -338,42 +338,40 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // we expect the cost to updating all renderables to exceed available time budget // so we first sort by priority and update in order until out of time - // as per the instructions in PriortySortUtil we first derive from PrioritySortUtil::Prioritizable - class PrioritizableRenderer: public PrioritySortUtil::Prioritizable { + class SortableRenderer: public PrioritySortUtil::Sortable { public: - PrioritizableRenderer(const EntityRendererPointer& renderer) : _renderer(renderer) { } + SortableRenderer(const EntityRendererPointer& renderer) : _renderer(renderer) { } + glm::vec3 getPosition() const override { return _renderer->getEntity()->getPosition(); } float getRadius() const override { return 0.5f * _renderer->getEntity()->getQueryAACube().getScale(); } uint64_t getTimestamp() const override { return _renderer->getUpdateTime(); } + + const EntityRendererPointer& getRenderer() const { return _renderer; } private: - const EntityRendererPointer& _renderer; + EntityRendererPointer _renderer; }; // prioritize and sort the renderables uint64_t sortStart = usecTimestampNow(); - using SortableRenderer = PrioritySortUtil::Sortable; - std::priority_queue< SortableRenderer > sortedRenderables; + PrioritySortUtil::PriorityQueue sortedRenderables(view); { PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); - PrioritySortUtil::Prioritizer prioritizer(view); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); while (itr != _renderablesToUpdate.end()) { - float priority = prioritizer.computePriority(PrioritizableRenderer(itr->second)); - //SortableRenderer entry(itr->second, priority); - sortedRenderables.push(SortableRenderer(itr->second, priority)); + sortedRenderables.push(SortableRenderer(itr->second)); ++itr; } } { - PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); + PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, sortedRenderables.size()); // compute remaining time budget uint64_t updateStart = usecTimestampNow(); const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1 * USECS_PER_MSEC; uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET; - uint64_t timeForSort = updateStart - sortStart; - if (timeForSort < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { - timeBudget = MAX_UPDATE_RENDERABLES_TIME_BUDGET - timeForSort; + uint64_t sortCost = updateStart - sortStart; + if (sortCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { + timeBudget = MAX_UPDATE_RENDERABLES_TIME_BUDGET - sortCost; } uint64_t expiry = updateStart + timeBudget; @@ -381,7 +379,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().getThing(); + const EntityRendererPointer& renderer = sortedRenderables.top().getRenderer(); renderer->updateInScene(scene, transaction); _renderablesToUpdate.erase(renderer->getEntity()->getID()); sortedRenderables.pop(); @@ -389,7 +387,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // compute average per-renderable update cost size_t numUpdated = numSorted - sortedRenderables.size() + 1; // add one to avoid divide by zero - float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated + 1); + float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated); const float blend = 0.1f; _avgRenderableUpdateCost = (1.0f - blend) * _avgRenderableUpdateCost + blend * cost; } diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index d5fdbad31b..6f4897c7b4 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -16,22 +16,21 @@ /* PrioritySortUtil is a helper for sorting 3D things relative to a ViewFrustum. To use: -(1) Derive a class from pure-virtual PrioritySortUtil::Prioritizable - that wraps the Thing you want to prioritize and sort: +(1) Derive a class from pure-virtual PrioritySortUtil::Sortable that wraps a copy of + the Thing you want to prioritize and sort: - class PrioritizableThing : public PrioritySortUtil::Prioritizable { - public: - PrioritizableThing(const Thing& thing) : _thing(thing) {} - glm::vec3 getPosition() const override { return _thing.getPosition(); } - float getRadius() const const override { return _thing.getBoundingRadius(); } - uint64_t getTimestamp() const override { return _thing.getLastUpdated(); } - private: - // Yes really: the data member is a const reference! - // PrioritizableThing only needs enough scope to compute a priority. - const Thing& _thing; - } + class SortableWrapper: public PrioritySortUtil::Sortable { + public: + SortableWrapper(const Thing& thing) : _thing(thing) { } + glm::vec3 getPosition() const override { return _thing->getPosition(); } + float getRadius() const override { return 0.5f * _thing->getBoundingRadius(); } + uint64_t getTimestamp() const override { return _thing->getLastTime(); } + const Thing& getThing() const { return _thing; } + private: + Thing _thing; + }; -(2) Loop over your things and insert each into a priority_queue: +(2) Make a PrioritySortUtil::PriorityQueue and add them to the queue: PrioritySortUtil::Prioritizer prioritizer(viewFrustum); std::priority_queue< PrioritySortUtil::Sortable > sortedThings; @@ -40,7 +39,7 @@ sortedThings.push(PrioritySortUtil::Sortable entry(thing, priority)); } -(4) Loop over your priority queue and do timeboxed work: +(3) Loop over your priority queue and do timeboxed work: uint64_t cutoffTime = usecTimestampNow() + TIME_BUDGET; while (!sortedThings.empty()) { @@ -51,7 +50,6 @@ break; } } - */ namespace PrioritySortUtil { @@ -60,39 +58,28 @@ namespace PrioritySortUtil { constexpr float DEFAULT_CENTER_COEF { 0.5f }; constexpr float DEFAULT_AGE_COEF { 0.25f / (float)(USECS_PER_SECOND) }; - template class Sortable { - public: - Sortable(const T& thing, float sortPriority) : _thing(thing), _priority(sortPriority) {} - const T& getThing() const { return _thing; } - void setPriority(float priority) { _priority = priority; } - bool operator<(const Sortable& other) const { return _priority < other._priority; } - private: - T _thing; - float _priority; - }; - - // Prioritizable isn't a template because templates can't have pure-virtual methods. - class Prioritizable { public: virtual glm::vec3 getPosition() const = 0; virtual float getRadius() const = 0; virtual uint64_t getTimestamp() const = 0; + + void setPriority(float priority) { _priority = priority; } + bool operator<(const Sortable& other) const { return _priority < other._priority; } + private: + float _priority { 0.0f }; }; - class Prioritizer { + template + class PriorityQueue { public: - Prioritizer() = delete; + PriorityQueue() = delete; - Prioritizer(const ViewFrustum& view) : _view(view) { - cacheView(); - } + PriorityQueue(const ViewFrustum& view) : _view(view) { } - Prioritizer(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) + PriorityQueue(const ViewFrustum& view, float angularWeight, float centerWeight, float ageWeight) : _view(view), _angularWeight(angularWeight), _centerWeight(centerWeight), _ageWeight(ageWeight) - { - cacheView(); - } + { } void setView(const ViewFrustum& view) { _view = view; } @@ -102,24 +89,34 @@ namespace PrioritySortUtil { _ageWeight = ageWeight; } - float computePriority(const Prioritizable& prioritizableThing) const { + size_t size() const { return _queue.size(); } + void push(T thing) { + thing.setPriority(computePriority(thing)); + _queue.push(thing); + } + const T& top() const { return _queue.top(); } + void pop() { return _queue.pop(); } + bool empty() const { return _queue.empty(); } + + private: + float computePriority(const T& thing) const { // priority = weighted linear combination of multiple values: // (a) angular size // (b) proximity to center of view // (c) time since last update // where the relative "weights" are tuned to scale the contributing values into units of "priority". - glm::vec3 position = prioritizableThing.getPosition(); - glm::vec3 offset = position - _viewPosition; + glm::vec3 position = thing.getPosition(); + glm::vec3 offset = position - _view.getPosition(); float distance = glm::length(offset) + 0.001f; // add 1mm to avoid divide by zero - float radius = prioritizableThing.getRadius(); + float radius = thing.getRadius(); float priority = _angularWeight * (radius / distance) - + _centerWeight * (glm::dot(offset, _viewForward) / distance) - + _ageWeight * (float)(usecTimestampNow() - prioritizableThing.getTimestamp()); + + _centerWeight * (glm::dot(offset, _view.getDirection()) / distance) + + _ageWeight * (float)(usecTimestampNow() - thing.getTimestamp()); // decrement priority of things outside keyhole - if (distance - radius > _viewRadius) { + if (distance - radius > _view.getCenterRadius()) { if (!_view.sphereIntersectsFrustum(position, radius)) { constexpr float OUT_OF_VIEW_PENALTY = -10.0f; priority += OUT_OF_VIEW_PENALTY; @@ -128,18 +125,8 @@ namespace PrioritySortUtil { return priority; } - private: - void cacheView() { - // assuming we'll prioritize many things: cache these values - _viewPosition = _view.getPosition(); - _viewForward = _view.getDirection(); - _viewRadius = _view.getCenterRadius(); - } - ViewFrustum _view; - glm::vec3 _viewPosition; - glm::vec3 _viewForward; - float _viewRadius; + std::priority_queue _queue; float _angularWeight { DEFAULT_ANGULAR_COEF }; float _centerWeight { DEFAULT_CENTER_COEF }; float _ageWeight { DEFAULT_AGE_COEF }; From 500f38182799cc976020f3d461a00817b1ce3533 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Nov 2017 15:56:55 -0800 Subject: [PATCH 32/42] collect hard-coded time budgets in one spot --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 -- libraries/shared/src/PrioritySortUtil.h | 4 ++++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 9e4bfe22c8..13e5973e27 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -318,7 +318,6 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } float expectedUpdateCost = _avgRenderableUpdateCost * _renderablesToUpdate.size(); - const float MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2 * USECS_PER_MSEC; if (expectedUpdateCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET) { // we expect to update all renderables within available time budget PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); @@ -367,7 +366,6 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene // compute remaining time budget uint64_t updateStart = usecTimestampNow(); - const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1 * USECS_PER_MSEC; uint64_t timeBudget = MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET; uint64_t sortCost = updateStart - sortStart; if (sortCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET - MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET) { diff --git a/libraries/shared/src/PrioritySortUtil.h b/libraries/shared/src/PrioritySortUtil.h index 6f4897c7b4..1d11a04265 100644 --- a/libraries/shared/src/PrioritySortUtil.h +++ b/libraries/shared/src/PrioritySortUtil.h @@ -133,5 +133,9 @@ namespace PrioritySortUtil { }; } // namespace PrioritySortUtil +// for now we're keeping hard-coded sorted time budgets in one spot +const uint64_t MAX_UPDATE_RENDERABLES_TIME_BUDGET = 2000; // usec +const uint64_t MIN_SORTED_UPDATE_RENDERABLES_TIME_BUDGET = 1000; // usec + #endif // hifi_PrioritySortUtil_h From 2f5b7f32c6bd6ed9f6a4e4e80164bb430a6a1ee0 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 14 Nov 2017 17:08:12 -0800 Subject: [PATCH 33/42] only add valid renderables to _renderablesToUpdate --- .../src/EntityTreeRenderer.cpp | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 13e5973e27..35f4b2aa80 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -184,6 +184,7 @@ void EntityTreeRenderer::clear() { qCWarning(entitiesrenderer) << "EntitityTreeRenderer::clear(), Unexpected null scene, possibly during application shutdown"; } _entitiesInScene.clear(); + _renderablesToUpdate.clear(); // reset the zone to the default (while we load the next scene) _layeredZones.clear(); @@ -289,31 +290,12 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene { PROFILE_RANGE_EX(simulation_physics, "CopyRenderables", 0xffff00ff, (uint64_t)changedEntities.size()); - if (_renderablesToUpdate.empty()) { - for (const auto& entityId : changedEntities) { - auto renderable = renderableForEntityId(entityId); - if (!renderable) { - continue; - } + for (const auto& entityId : changedEntities) { + auto renderable = renderableForEntityId(entityId); + if (renderable) { + // only add valid renderables _renderablesToUpdate _renderablesToUpdate.insert({ entityId, renderable }); } - } else { - // we weren't able to update all renderables last frame - // so we have to be more careful when processing changed renderables - std::unordered_map::iterator itr; - for (const auto& entityId : changedEntities) { - auto renderable = renderableForEntityId(entityId); - itr = _renderablesToUpdate.find(entityId); - if (itr != _renderablesToUpdate.end()) { - if (!renderable) { - _renderablesToUpdate.erase(itr); - continue; - } - _renderablesToUpdate.insert(itr, { entityId, renderable }); - } else if (renderable) { - _renderablesToUpdate.insert({ entityId, renderable }); - } - } } } @@ -324,6 +306,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene uint64_t updateStart = usecTimestampNow(); for (const auto& entry : _renderablesToUpdate) { const auto& renderable = entry.second; + assert(renderable); // only valid renderables are added to _renderablesToUpdate renderable->updateInScene(scene, transaction); } size_t numRenderables = _renderablesToUpdate.size() + 1; // add one to avoid divide by zero @@ -357,6 +340,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene PROFILE_RANGE_EX(simulation_physics, "SortRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); std::unordered_map::iterator itr = _renderablesToUpdate.begin(); while (itr != _renderablesToUpdate.end()) { + assert(itr->second); // only valid renderables are added to _renderablesToUpdate sortedRenderables.push(SortableRenderer(itr->second)); ++itr; } @@ -377,9 +361,9 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene std::unordered_map::iterator itr; size_t numSorted = sortedRenderables.size(); while (!sortedRenderables.empty() && usecTimestampNow() < expiry) { - const EntityRendererPointer& renderer = sortedRenderables.top().getRenderer(); - renderer->updateInScene(scene, transaction); - _renderablesToUpdate.erase(renderer->getEntity()->getID()); + const EntityRendererPointer& renderable = sortedRenderables.top().getRenderer(); + renderable->updateInScene(scene, transaction); + _renderablesToUpdate.erase(renderable->getEntity()->getID()); sortedRenderables.pop(); } @@ -839,7 +823,8 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { } void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { - // If it's in the pending queue, remove it + // If it's in a pending queue, remove it + _renderablesToUpdate.erase(entityID); _entitiesToAdd.erase(entityID); auto itr = _entitiesInScene.find(entityID); @@ -847,7 +832,7 @@ void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { // Not in the scene, and no longer potentially in the pending queue, we're done return; } - + if (_tree && !_shuttingDown && _entitiesScriptEngine) { _entitiesScriptEngine->unloadEntityScript(entityID, true); } From c5d2f598d75728ad3e80acfa14672f3a28a8ea50 Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Thu, 16 Nov 2017 15:46:49 +0300 Subject: [PATCH 34/42] 8244 Rename captionColorOverride to captionColor --- interface/resources/qml/hifi/tablet/TabletButton.qml | 12 +++++++----- .../resources/qml/hifi/toolbars/ToolbarButton.qml | 6 ++++-- scripts/system/edit.js | 4 ++-- unpublishedScripts/marketplace/shapes/shapes.js | 8 ++++---- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 169c7acec1..8fc31d1cd6 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -5,7 +5,9 @@ import TabletScriptingInterface 1.0 Item { id: tabletButton - property string captionColorOverride: "" + property color defaultCaptionColor: "#ffffff" + property color captionColor: defaultCaptionColor + property var uuid; property string icon: "icons/tablet-icons/edit-i.svg" property string hoverIcon: tabletButton.icon @@ -105,7 +107,7 @@ Item { Text { id: text - color: captionColorOverride !== "" ? captionColorOverride: "#ffffff" + color: captionColor text: tabletButton.text font.bold: true font.pixelSize: 18 @@ -171,7 +173,7 @@ Item { PropertyChanges { target: text - color: captionColorOverride !== "" ? captionColorOverride: "#ffffff" + color: captionColor text: tabletButton.hoverText } @@ -197,7 +199,7 @@ Item { PropertyChanges { target: text - color: captionColorOverride !== "" ? captionColorOverride: "#333333" + color: captionColor !== defaultCaptionColor ? captionColor : "#333333" text: tabletButton.activeText } @@ -228,7 +230,7 @@ Item { PropertyChanges { target: text - color: captionColorOverride !== "" ? captionColorOverride: "#333333" + color: captionColor !== defaultCaptionColor ? captionColor : "#333333" text: tabletButton.activeHoverText } diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 63149ad23b..f27ba6bb24 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -4,7 +4,9 @@ import QtQuick.Controls 1.4 StateImage { id: button - property string captionColorOverride: "" + property color defaultCaptionColor: "#ffffff" + property color captionColor: defaultCaptionColor + property bool buttonEnabled: true property bool isActive: false property bool isEntered: false @@ -98,7 +100,7 @@ StateImage { Text { id: caption - color: captionColorOverride !== "" ? captionColorOverride: (button.isActive ? "#000000" : "#ffffff") + color: button.isActive ? "#000000" : captionColor text: button.isActive ? (button.isEntered ? button.activeHoverText : button.activeText) : (button.isEntered ? button.hoverText : button.text) font.bold: false font.pixelSize: 9 diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 15f1c2f6c1..b313079aac 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -419,7 +419,7 @@ var toolBar = (function () { var createButtonIconRsrc = (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON); tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); activeButton = tablet.addButton({ - captionColorOverride: hasRezPermissions ? "" : "#888888", + captionColor: hasRezPermissions ? "#ffffff" : "#888888", icon: createButtonIconRsrc, activeIcon: "icons/tablet-icons/edit-a.svg", text: "CREATE", @@ -792,7 +792,7 @@ function handleDomainChange() { var hasRezPermissions = (Entities.canRez() || Entities.canRezTmp() || Entities.canRezCertified() || Entities.canRezTmpCertified()); createButton.editProperties({ icon: (hasRezPermissions ? CREATE_ENABLED_ICON : CREATE_DISABLED_ICON), - captionColorOverride: (hasRezPermissions ? "" : "#888888"), + captionColor: (hasRezPermissions ? "#ffffff" : "#888888"), }); } diff --git a/unpublishedScripts/marketplace/shapes/shapes.js b/unpublishedScripts/marketplace/shapes/shapes.js index cd5f119588..bb35d9089e 100644 --- a/unpublishedScripts/marketplace/shapes/shapes.js +++ b/unpublishedScripts/marketplace/shapes/shapes.js @@ -18,7 +18,7 @@ APP_ICON_INACTIVE = Script.resolvePath("./assets/shapes-i.svg"), APP_ICON_ACTIVE = Script.resolvePath("./assets/shapes-a.svg"), APP_ICON_DISABLED = Script.resolvePath("./assets/shapes-d.svg"), - ENABLED_CAPTION_COLOR_OVERRIDE = "", + ENABLED_CAPTION_COLOR_OVERRIDE = "#ffffff", DISABLED_CAPTION_COLOR_OVERRIDE = "#888888", START_DELAY = 2000, // ms @@ -1865,7 +1865,7 @@ } button.editProperties({ icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, - captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, + captionColor: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, isActive: isAppActive }); } @@ -1880,7 +1880,7 @@ } button.editProperties({ icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, - captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, + captionColor: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, isActive: isAppActive }); } @@ -1945,7 +1945,7 @@ hasRezPermissions = Entities.canRez() || Entities.canRezTmp(); button = tablet.addButton({ icon: hasRezPermissions ? APP_ICON_INACTIVE : APP_ICON_DISABLED, - captionColorOverride: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, + captionColor: hasRezPermissions ? ENABLED_CAPTION_COLOR_OVERRIDE : DISABLED_CAPTION_COLOR_OVERRIDE, activeIcon: APP_ICON_ACTIVE, text: APP_NAME, isActive: isAppActive From e438ac54d575583d250790a7cf805e599fe21b2f Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 16 Nov 2017 13:16:23 -0800 Subject: [PATCH 35/42] make adds go over NLPacketList as reliable --- .../src/entities/EntityServer.cpp | 1 + .../octree/OctreeInboundPacketProcessor.cpp | 5 +- libraries/networking/src/PacketSender.cpp | 10 +++ .../src/ReceivedPacketProcessor.cpp | 4 + .../octree/src/OctreeEditPacketSender.cpp | 79 ++++++++++++++----- 5 files changed, 77 insertions(+), 22 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 995a5bad27..5417e3f6fe 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -72,6 +72,7 @@ void EntityServer::aboutToFinish() { } void EntityServer::handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode) { + qDebug() << __FUNCTION__ << "from:" << senderNode->getUUID() << "type:" << message->getType(); if (_octreeInboundPacketProcessor) { _octreeInboundPacketProcessor->queueReceivedPacket(message, senderNode); } diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index bce6e7fe44..efadb5650a 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -76,12 +76,15 @@ void OctreeInboundPacketProcessor::midProcess() { } void OctreeInboundPacketProcessor::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { + + qDebug() << __FUNCTION__ << "from:" << sendingNode->getUUID() << "type:" << message->getType(); + if (_shuttingDown) { qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet"; return; } - bool debugProcessPacket = _myServer->wantsVerboseDebug(); + bool debugProcessPacket = true; // _myServer->wantsVerboseDebug(); if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() payload=%p payloadLength=%lld", diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index 01b78585c2..b7fc802883 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -64,6 +64,8 @@ void PacketSender::queuePacketListForSending(const SharedNodePointer& destinatio _totalPacketsQueued += packetList->getNumPackets(); _totalBytesQueued += packetList->getMessageSize(); + qDebug() << __FUNCTION__ << "to:" << destinationNode->getUUID() << "type:" << packetList->getType() << "_totalPacketsQueued:" << _totalPacketsQueued << "_totalBytesQueued:" << _totalBytesQueued; + lock(); _packets.push_back({ destinationNode, PacketOrPacketList { nullptr, std::move(packetList)} }); unlock(); @@ -287,8 +289,12 @@ bool PacketSender::nonThreadedProcess() { //PacketOrPacketList packetOrList = packetPair.second; bool sendAsPacket = packetPair.second.first.get(); if (sendAsPacket) { + + qDebug() << __FUNCTION__ << "sendUnreliablePacket() to:" << packetPair.first->getUUID() << "type:" << packetPair.second.first->getType(); DependencyManager::get()->sendUnreliablePacket(*packetPair.second.first, *packetPair.first); } else { + + qDebug() << __FUNCTION__ << "sendPacketList() to:" << packetPair.first->getUUID() << "type:" << packetPair.second.second->getType(); DependencyManager::get()->sendPacketList(*packetPair.second.second, *packetPair.first); } @@ -303,6 +309,10 @@ bool PacketSender::nonThreadedProcess() { _totalBytesSent += packetSize; emit packetSent(packetSize); // FIXME should include number of packets? + qDebug() << __FUNCTION__ << "packetsSentThisCall:" << packetsSentThisCall + << "_packetsOverCheckInterval:" << _packetsOverCheckInterval + << "_totalPacketsSent:" << _totalPacketsSent; + _lastSendTime = now; } return isStillRunning(); diff --git a/libraries/networking/src/ReceivedPacketProcessor.cpp b/libraries/networking/src/ReceivedPacketProcessor.cpp index c18d4ed1e8..9ed17ec586 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.cpp +++ b/libraries/networking/src/ReceivedPacketProcessor.cpp @@ -25,6 +25,10 @@ void ReceivedPacketProcessor::terminating() { } void ReceivedPacketProcessor::queueReceivedPacket(QSharedPointer message, SharedNodePointer sendingNode) { + + qDebug() << __FUNCTION__ << "from:" << sendingNode->getUUID() << "type:" << message->getType(); + + lock(); _packets.push_back({ sendingNode, message }); _nodePacketCounts[sendingNode->getUUID()]++; diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 7b612a828c..38a34d75dc 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -119,6 +119,8 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::uniqu // a known nodeID. void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr packetList) { + qDebug() << __FUNCTION__ << "to:" << nodeUUID << "type:" << packetList->getType(); + bool wantDebug = false; DependencyManager::get()->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are getMyNodeType() @@ -268,33 +270,65 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, QByteArray& }); } if (isMyJurisdiction) { - std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID].first; //only a NLPacket for now - if (!bufferedPacket) { - bufferedPacket = initializePacket(type, node->getClockSkewUsec()); - } else { - // If we're switching type, then we send the last one and start over - if ((type != bufferedPacket->getType() && bufferedPacket->getPayloadSize() > 0) || - (editMessage.size() >= bufferedPacket->bytesAvailableForWrite())) { + // for edit messages, we will attempt to combine multiple edit commands where possible, we + // don't do this for add because we send those reliably + if (type == PacketType::EntityAdd) { - // create the new packet and swap it with the packet in _pendingEditPackets - auto packetToRelease = initializePacket(type, node->getClockSkewUsec()); - bufferedPacket.swap(packetToRelease); + auto newPacket = NLPacketList::create(type, QByteArray(), true, true); + auto nodeClockSkew = node->getClockSkewUsec(); - // release the previously buffered packet - releaseQueuedPacket(nodeUUID, std::move(packetToRelease)); + // pack sequence number + quint16 sequence = _outgoingSequenceNumbers[nodeUUID]++; + newPacket->writePrimitive(sequence); + + // pack in timestamp + quint64 now = usecTimestampNow() + nodeClockSkew; + newPacket->writePrimitive(now); + + + // We call this virtual function that allows our specific type of EditPacketSender to + // fixup the buffer for any clock skew + if (nodeClockSkew != 0) { + adjustEditPacketForClockSkew(type, editMessage, nodeClockSkew); } - } - // This is really the first time we know which server/node this particular edit message - // is going to, so we couldn't adjust for clock skew till now. But here's our chance. - // We call this virtual function that allows our specific type of EditPacketSender to - // fixup the buffer for any clock skew - if (node->getClockSkewUsec() != 0) { - adjustEditPacketForClockSkew(type, editMessage, node->getClockSkewUsec()); - } + newPacket->write(editMessage); - bufferedPacket->write(editMessage); + // release the new packet + releaseQueuedPacketList(nodeUUID, std::move(newPacket)); + + } else { + + std::unique_ptr& bufferedPacket = _pendingEditPackets[nodeUUID].first; //only a NLPacket for now + + if (!bufferedPacket) { + bufferedPacket = initializePacket(type, node->getClockSkewUsec()); + } else { + // If we're switching type, then we send the last one and start over + if ((type != bufferedPacket->getType() && bufferedPacket->getPayloadSize() > 0) || + (editMessage.size() >= bufferedPacket->bytesAvailableForWrite())) { + + // create the new packet and swap it with the packet in _pendingEditPackets + auto packetToRelease = initializePacket(type, node->getClockSkewUsec()); + bufferedPacket.swap(packetToRelease); + + // release the previously buffered packet + releaseQueuedPacket(nodeUUID, std::move(packetToRelease)); + } + } + + // This is really the first time we know which server/node this particular edit message + // is going to, so we couldn't adjust for clock skew till now. But here's our chance. + // We call this virtual function that allows our specific type of EditPacketSender to + // fixup the buffer for any clock skew + if (node->getClockSkewUsec() != 0) { + adjustEditPacketForClockSkew(type, editMessage, node->getClockSkewUsec()); + } + + bufferedPacket->write(editMessage); + + } } } }); @@ -346,6 +380,9 @@ void OctreeEditPacketSender::releaseQueuedPacket(const QUuid& nodeID, std::uniqu } void OctreeEditPacketSender::releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr packetList) { + + qDebug() << __FUNCTION__ << "to:" << nodeID << "type:" << packetList->getType(); + _releaseQueuedPacketMutex.lock(); if (packetList->getMessageSize() > 0 && packetList->getType() != PacketType::Unknown) { queuePacketListToNode(nodeID, std::move(packetList)); From a33162d7ffc03457f644055cc5e4a23ee470a34a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Fri, 17 Nov 2017 21:07:43 +1300 Subject: [PATCH 36/42] Fix typo in Base3DOverlay "filled" property --- interface/src/ui/overlays/Base3DOverlay.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index 4412014eb1..f60c72ab2d 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -216,7 +216,7 @@ QVariant Base3DOverlay::getProperty(const QString& property) { if (property == "localRotation" || property == "localOrientation") { return quatToVariant(getLocalOrientation()); } - if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filed") { + if (property == "isSolid" || property == "isFilled" || property == "solid" || property == "filled" || property == "filed") { return _isSolid; } if (property == "isWire" || property == "wire") { From cd0fa989e1803f164679abea3f82d96dc8f22fba Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 17 Nov 2017 08:55:55 -0800 Subject: [PATCH 37/42] debugging --- assignment-client/src/entities/EntityServer.cpp | 14 ++++++++++++-- .../src/octree/OctreeInboundPacketProcessor.cpp | 7 ++++++- .../entities/src/EntityEditPacketSender.cpp | 14 ++++++++++++++ libraries/entities/src/EntityItemProperties.cpp | 9 +++++++++ libraries/networking/src/PacketSender.cpp | 17 ++++++++++++----- libraries/networking/src/ReceivedMessage.cpp | 15 +++++++++++---- libraries/networking/src/ReceivedMessage.h | 10 ++++++++++ .../networking/src/ReceivedPacketProcessor.cpp | 7 ++++++- libraries/octree/src/OctreeEditPacketSender.cpp | 4 ++-- libraries/octree/src/OctreePacketData.cpp | 17 ++++++++++++++++- libraries/octree/src/OctreePacketData.h | 9 +++++++-- 11 files changed, 105 insertions(+), 18 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 5417e3f6fe..43cdb6d665 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -41,7 +41,8 @@ EntityServer::EntityServer(ReceivedMessage& message) : DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, + packetReceiver.registerListenerForTypes({ + //PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, @@ -51,6 +52,9 @@ EntityServer::EntityServer(ReceivedMessage& message) : this, "handleEntityPacket"); + packetReceiver.registerListener(PacketType::EntityAdd, this, "handleEntityPacket"); + + connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); _dynamicDomainVerificationTimer.setSingleShot(true); } @@ -72,7 +76,13 @@ void EntityServer::aboutToFinish() { } void EntityServer::handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode) { - qDebug() << __FUNCTION__ << "from:" << senderNode->getUUID() << "type:" << message->getType(); + qDebug() << __FUNCTION__ << "from:" << senderNode->getUUID() << "type:" << message->getType() + << "getNumPackets:" << message->getNumPackets() + << "getSize:" << message->getSize() + << "isFromPacketList:" << message->isFromPacketList() + << "isComplete:" << message->isComplete() + ; + if (_octreeInboundPacketProcessor) { _octreeInboundPacketProcessor->queueReceivedPacket(message, senderNode); } diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index efadb5650a..d2723e936f 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -77,7 +77,12 @@ void OctreeInboundPacketProcessor::midProcess() { void OctreeInboundPacketProcessor::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { - qDebug() << __FUNCTION__ << "from:" << sendingNode->getUUID() << "type:" << message->getType(); + qDebug() << __FUNCTION__ << "from:" << sendingNode->getUUID() << "type:" << message->getType() + << "getNumPackets:" << message->getNumPackets() + << "getSize:" << message->getSize() + << "isFromPacketList:" << message->isFromPacketList() + << "isComplete:" << message->isComplete() + ; if (_shuttingDown) { qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet"; diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 168b0cd446..42c13119b5 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -81,6 +81,9 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties) { + + qDebug() << __FUNCTION__ << "type:" << type; + if (!_shouldSend) { return; // bail early } @@ -93,6 +96,14 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); + if (type == PacketType::EntityAdd) { + auto MAX_ADD_DATA_SIZE = NLPacket::maxPayloadSize(type) * 10; // a really big packet + bufferOut.resize(MAX_ADD_DATA_SIZE); + } + + qDebug() << __FUNCTION__ << "bufferOut.size():" << bufferOut.size(); + + OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send auto nodeList = DependencyManager::get(); @@ -115,6 +126,9 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, qCDebug(entities) << " id:" << entityItemID; qCDebug(entities) << " properties:" << properties; #endif + + qDebug() << __FUNCTION__ << "about to call queueOctreeEditMessage() --- bufferOut.size():" << bufferOut.size(); + queueOctreeEditMessage(type, bufferOut); if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get()->getPlaceName()); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 108fc14e30..2302765b1a 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1227,6 +1227,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too. OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro + qDebug() << __FUNCTION__ << "OctreePacketData::getBytesAvailable():" << packetData->getBytesAvailable(); + + bool success = true; // assume the best OctreeElement::AppendState appendState = OctreeElement::COMPLETED; // assume the best @@ -1525,9 +1528,15 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy const char* finalizedData = reinterpret_cast(packetData->getFinalizedData()); int finalizedSize = packetData->getFinalizedSize(); + qDebug() << __FUNCTION__ << "packetData->getFinalizedSize():" << packetData->getFinalizedSize(); + qDebug() << __FUNCTION__ << "packetData->getUncompressedSize():" << packetData->getUncompressedSize(); + qDebug() << __FUNCTION__ << "buffer.size():" << buffer.size(); + if (finalizedSize <= buffer.size()) { buffer.replace(0, finalizedSize, finalizedData, finalizedSize); buffer.resize(finalizedSize); + qDebug() << __FUNCTION__ << "replaced with finalized data size:" << buffer.size(); + } else { qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer."; success = false; diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index b7fc802883..2ccd9094ed 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -64,7 +64,8 @@ void PacketSender::queuePacketListForSending(const SharedNodePointer& destinatio _totalPacketsQueued += packetList->getNumPackets(); _totalBytesQueued += packetList->getMessageSize(); - qDebug() << __FUNCTION__ << "to:" << destinationNode->getUUID() << "type:" << packetList->getType() << "_totalPacketsQueued:" << _totalPacketsQueued << "_totalBytesQueued:" << _totalBytesQueued; + qDebug() << __FUNCTION__ << "to:" << destinationNode->getUUID() << "type:" << packetList->getType() << "size:" << packetList->getDataSize() + << "_totalPacketsQueued:" << _totalPacketsQueued << "_totalBytesQueued:" << _totalBytesQueued; lock(); _packets.push_back({ destinationNode, PacketOrPacketList { nullptr, std::move(packetList)} }); @@ -288,18 +289,22 @@ bool PacketSender::nonThreadedProcess() { // send the packet through the NodeList... //PacketOrPacketList packetOrList = packetPair.second; bool sendAsPacket = packetPair.second.first.get(); + size_t packetSize = sendAsPacket ? packetPair.second.first->getDataSize() : packetPair.second.second->getMessageSize(); + size_t packetCount = sendAsPacket ? 1 : packetPair.second.second->getNumPackets(); + if (sendAsPacket) { qDebug() << __FUNCTION__ << "sendUnreliablePacket() to:" << packetPair.first->getUUID() << "type:" << packetPair.second.first->getType(); DependencyManager::get()->sendUnreliablePacket(*packetPair.second.first, *packetPair.first); } else { - qDebug() << __FUNCTION__ << "sendPacketList() to:" << packetPair.first->getUUID() << "type:" << packetPair.second.second->getType(); + qDebug() << __FUNCTION__ << "sendPacketList() to:" << packetPair.first->getUUID() << "type:" << packetPair.second.second->getType() + << "getMessageSize:" << packetPair.second.second->getMessageSize() + << "getDataSize:" << packetPair.second.second->getDataSize(); + DependencyManager::get()->sendPacketList(*packetPair.second.second, *packetPair.first); } - size_t packetSize = sendAsPacket ? packetPair.second.first->getDataSize() : packetPair.second.second->getMessageSize(); - size_t packetCount = sendAsPacket ? 1 : packetPair.second.second->getNumPackets(); packetsSentThisCall += packetCount; _packetsOverCheckInterval += packetCount; @@ -311,7 +316,9 @@ bool PacketSender::nonThreadedProcess() { qDebug() << __FUNCTION__ << "packetsSentThisCall:" << packetsSentThisCall << "_packetsOverCheckInterval:" << _packetsOverCheckInterval - << "_totalPacketsSent:" << _totalPacketsSent; + << "_totalPacketsSent:" << _totalPacketsSent + << "packetSize:" << packetSize + << "_totalBytesSent:" << _totalBytesSent; _lastSendTime = now; } diff --git a/libraries/networking/src/ReceivedMessage.cpp b/libraries/networking/src/ReceivedMessage.cpp index 6ca249fb22..8a3322ce20 100644 --- a/libraries/networking/src/ReceivedMessage.cpp +++ b/libraries/networking/src/ReceivedMessage.cpp @@ -26,8 +26,10 @@ ReceivedMessage::ReceivedMessage(const NLPacketList& packetList) _sourceID(packetList.getSourceID()), _packetType(packetList.getType()), _packetVersion(packetList.getVersion()), - _senderSockAddr(packetList.getSenderSockAddr()) + _senderSockAddr(packetList.getSenderSockAddr()), + _fromPacketList(true) { + qDebug() << __FUNCTION__ << "(const NLPacketList& packetList) _fromPacketList:" << _fromPacketList; } ReceivedMessage::ReceivedMessage(NLPacket& packet) @@ -38,8 +40,12 @@ ReceivedMessage::ReceivedMessage(NLPacket& packet) _packetType(packet.getType()), _packetVersion(packet.getVersion()), _senderSockAddr(packet.getSenderSockAddr()), - _isComplete(packet.getPacketPosition() == NLPacket::ONLY) + _isComplete(packet.getPacketPosition() == NLPacket::ONLY), + _fromPacket(true) { + if (packet.getType() == PacketType::EntityAdd) { + qDebug() << __FUNCTION__ << "(NLPacket& packet) _fromPacketList:" << _fromPacketList << "packet.getType():" << packet.getType(); + } } ReceivedMessage::ReceivedMessage(QByteArray byteArray, PacketType packetType, PacketVersion packetVersion, @@ -51,9 +57,10 @@ ReceivedMessage::ReceivedMessage(QByteArray byteArray, PacketType packetType, Pa _packetType(packetType), _packetVersion(packetVersion), _senderSockAddr(senderSockAddr), - _isComplete(true) + _isComplete(true), + _fromByteArray(true) { - + qDebug() << __FUNCTION__ << "(QByteArray byteArray)... _fromPacketList:" << _fromPacketList; } void ReceivedMessage::setFailed() { diff --git a/libraries/networking/src/ReceivedMessage.h b/libraries/networking/src/ReceivedMessage.h index ae51e7592a..2af4798bc3 100644 --- a/libraries/networking/src/ReceivedMessage.h +++ b/libraries/networking/src/ReceivedMessage.h @@ -79,6 +79,10 @@ public: template qint64 readHeadPrimitive(T* data); + bool isFromPacketList() const { return _fromPacketList; }; + bool isFromPacket() const { return _fromPacket; }; + bool isFromByteArray() const { return _fromByteArray; }; + signals: void progress(qint64 size); void completed(); @@ -100,6 +104,12 @@ private: std::atomic _isComplete { true }; std::atomic _failed { false }; + + std::atomic _fromPacketList { false }; + std::atomic _fromPacket { false }; + std::atomic _fromByteArray { false }; + + }; Q_DECLARE_METATYPE(ReceivedMessage*) diff --git a/libraries/networking/src/ReceivedPacketProcessor.cpp b/libraries/networking/src/ReceivedPacketProcessor.cpp index 9ed17ec586..eda5109279 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.cpp +++ b/libraries/networking/src/ReceivedPacketProcessor.cpp @@ -26,7 +26,12 @@ void ReceivedPacketProcessor::terminating() { void ReceivedPacketProcessor::queueReceivedPacket(QSharedPointer message, SharedNodePointer sendingNode) { - qDebug() << __FUNCTION__ << "from:" << sendingNode->getUUID() << "type:" << message->getType(); + qDebug() << __FUNCTION__ << "from:" << sendingNode->getUUID() << "type:" << message->getType() + << "getNumPackets:" << message->getNumPackets() + << "getSize:" << message->getSize() + << "isFromPacketList:" << message->isFromPacketList() + << "isComplete:" << message->isComplete() + ; lock(); diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 38a34d75dc..7e04fa6630 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -119,7 +119,7 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::uniqu // a known nodeID. void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr packetList) { - qDebug() << __FUNCTION__ << "to:" << nodeUUID << "type:" << packetList->getType(); + qDebug() << __FUNCTION__ << "to:" << nodeUUID << "type:" << packetList->getType() << "size:" << packetList->getDataSize(); bool wantDebug = false; DependencyManager::get()->eachNode([&](const SharedNodePointer& node) { @@ -381,7 +381,7 @@ void OctreeEditPacketSender::releaseQueuedPacket(const QUuid& nodeID, std::uniqu void OctreeEditPacketSender::releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr packetList) { - qDebug() << __FUNCTION__ << "to:" << nodeID << "type:" << packetList->getType(); + qDebug() << __FUNCTION__ << "to:" << nodeID << "type:" << packetList->getType() << "size:" << packetList->getDataSize(); _releaseQueuedPacketMutex.lock(); if (packetList->getMessageSize() > 0 && packetList->getType() != PacketType::Unknown) { diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index b5b4a161ef..5b3e68c0a1 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -35,7 +35,14 @@ OctreePacketData::OctreePacketData(bool enableCompression, int targetSize) { void OctreePacketData::changeSettings(bool enableCompression, unsigned int targetSize) { _enableCompression = enableCompression; - _targetSize = std::min(MAX_OCTREE_UNCOMRESSED_PACKET_SIZE, targetSize); + _targetSize = targetSize; // std::min(MAX_OCTREE_UNCOMRESSED_PACKET_SIZE, targetSize); + + _uncompressedByteArray.resize(_targetSize); + _compressedByteArray.resize(_targetSize); + + _uncompressed = (unsigned char*)_uncompressedByteArray.data(); + _compressed = (unsigned char*)_compressedByteArray.data(); + reset(); } @@ -689,6 +696,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + + // FIXME - this size check is wrong if we allow larger packets if (length * sizeof(glm::vec3) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { result.resize(0); return sizeof(uint16_t); @@ -702,6 +711,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char *dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + + // FIXME - this size check is wrong if we allow larger packets if (length * sizeof(glm::quat) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { result.resize(0); return sizeof(uint16_t); @@ -720,6 +731,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + + // FIXME - this size check is wrong if we allow larger packets if (length * sizeof(float) > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { result.resize(0); return sizeof(uint16_t); @@ -733,6 +746,8 @@ int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QVecto uint16_t length; memcpy(&length, dataBytes, sizeof(uint16_t)); dataBytes += sizeof(length); + + // FIXME - this size check is wrong if we allow larger packets if (length / 8 > MAX_OCTREE_UNCOMRESSED_PACKET_SIZE) { result.resize(0); return sizeof(uint16_t); diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 37c171504b..0c582f4ed6 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -279,7 +279,10 @@ private: unsigned int _targetSize; bool _enableCompression; - unsigned char _uncompressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; + //unsigned char _uncompressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; + + QByteArray _uncompressedByteArray; + unsigned char* _uncompressed { nullptr }; int _bytesInUse; int _bytesAvailable; int _subTreeAt; @@ -288,7 +291,9 @@ private: bool compressContent(); - unsigned char _compressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; + //unsigned char _compressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; + QByteArray _compressedByteArray; + unsigned char* _compressed { nullptr }; int _compressedBytes; int _bytesInUseLastCheck; bool _dirty; From 71a46a33745f828d1d1ebb33e991832e1e230082 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 17 Nov 2017 13:03:04 -0800 Subject: [PATCH 38/42] cleanup --- .../src/entities/EntityServer.cpp | 13 +------------ .../octree/OctreeInboundPacketProcessor.cpp | 10 +--------- .../entities/src/EntityEditPacketSender.cpp | 10 +--------- .../entities/src/EntityItemProperties.cpp | 9 --------- libraries/networking/src/PacketSender.cpp | 19 +------------------ libraries/networking/src/ReceivedMessage.cpp | 14 +++----------- libraries/networking/src/ReceivedMessage.h | 10 ---------- .../src/ReceivedPacketProcessor.cpp | 9 --------- .../octree/src/OctreeEditPacketSender.cpp | 11 +---------- libraries/octree/src/OctreePacketData.cpp | 3 +-- libraries/octree/src/OctreePacketData.h | 3 --- 11 files changed, 9 insertions(+), 102 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 43cdb6d665..995a5bad27 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -41,8 +41,7 @@ EntityServer::EntityServer(ReceivedMessage& message) : DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListenerForTypes({ - //PacketType::EntityAdd, + packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, @@ -52,9 +51,6 @@ EntityServer::EntityServer(ReceivedMessage& message) : this, "handleEntityPacket"); - packetReceiver.registerListener(PacketType::EntityAdd, this, "handleEntityPacket"); - - connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); _dynamicDomainVerificationTimer.setSingleShot(true); } @@ -76,13 +72,6 @@ void EntityServer::aboutToFinish() { } void EntityServer::handleEntityPacket(QSharedPointer message, SharedNodePointer senderNode) { - qDebug() << __FUNCTION__ << "from:" << senderNode->getUUID() << "type:" << message->getType() - << "getNumPackets:" << message->getNumPackets() - << "getSize:" << message->getSize() - << "isFromPacketList:" << message->isFromPacketList() - << "isComplete:" << message->isComplete() - ; - if (_octreeInboundPacketProcessor) { _octreeInboundPacketProcessor->queueReceivedPacket(message, senderNode); } diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index d2723e936f..bce6e7fe44 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -76,20 +76,12 @@ void OctreeInboundPacketProcessor::midProcess() { } void OctreeInboundPacketProcessor::processPacket(QSharedPointer message, SharedNodePointer sendingNode) { - - qDebug() << __FUNCTION__ << "from:" << sendingNode->getUUID() << "type:" << message->getType() - << "getNumPackets:" << message->getNumPackets() - << "getSize:" << message->getSize() - << "isFromPacketList:" << message->isFromPacketList() - << "isComplete:" << message->isComplete() - ; - if (_shuttingDown) { qDebug() << "OctreeInboundPacketProcessor::processPacket() while shutting down... ignoring incoming packet"; return; } - bool debugProcessPacket = true; // _myServer->wantsVerboseDebug(); + bool debugProcessPacket = _myServer->wantsVerboseDebug(); if (debugProcessPacket) { qDebug("OctreeInboundPacketProcessor::processPacket() payload=%p payloadLength=%lld", diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index 42c13119b5..90740948ce 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -81,9 +81,6 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, EntityTreePointer entityTree, EntityItemID entityItemID, const EntityItemProperties& properties) { - - qDebug() << __FUNCTION__ << "type:" << type; - if (!_shouldSend) { return; // bail early } @@ -97,13 +94,10 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); if (type == PacketType::EntityAdd) { - auto MAX_ADD_DATA_SIZE = NLPacket::maxPayloadSize(type) * 10; // a really big packet + auto MAX_ADD_DATA_SIZE = NLPacket::maxPayloadSize(type) * 10; // a really big buffer bufferOut.resize(MAX_ADD_DATA_SIZE); } - qDebug() << __FUNCTION__ << "bufferOut.size():" << bufferOut.size(); - - OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send auto nodeList = DependencyManager::get(); @@ -127,8 +121,6 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, qCDebug(entities) << " properties:" << properties; #endif - qDebug() << __FUNCTION__ << "about to call queueOctreeEditMessage() --- bufferOut.size():" << bufferOut.size(); - queueOctreeEditMessage(type, bufferOut); if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get()->getPlaceName()); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 2302765b1a..108fc14e30 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1227,9 +1227,6 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too. OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro - qDebug() << __FUNCTION__ << "OctreePacketData::getBytesAvailable():" << packetData->getBytesAvailable(); - - bool success = true; // assume the best OctreeElement::AppendState appendState = OctreeElement::COMPLETED; // assume the best @@ -1528,15 +1525,9 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy const char* finalizedData = reinterpret_cast(packetData->getFinalizedData()); int finalizedSize = packetData->getFinalizedSize(); - qDebug() << __FUNCTION__ << "packetData->getFinalizedSize():" << packetData->getFinalizedSize(); - qDebug() << __FUNCTION__ << "packetData->getUncompressedSize():" << packetData->getUncompressedSize(); - qDebug() << __FUNCTION__ << "buffer.size():" << buffer.size(); - if (finalizedSize <= buffer.size()) { buffer.replace(0, finalizedSize, finalizedData, finalizedSize); buffer.resize(finalizedSize); - qDebug() << __FUNCTION__ << "replaced with finalized data size:" << buffer.size(); - } else { qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer."; success = false; diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index 2ccd9094ed..657a122cbe 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -64,9 +64,6 @@ void PacketSender::queuePacketListForSending(const SharedNodePointer& destinatio _totalPacketsQueued += packetList->getNumPackets(); _totalBytesQueued += packetList->getMessageSize(); - qDebug() << __FUNCTION__ << "to:" << destinationNode->getUUID() << "type:" << packetList->getType() << "size:" << packetList->getDataSize() - << "_totalPacketsQueued:" << _totalPacketsQueued << "_totalBytesQueued:" << _totalBytesQueued; - lock(); _packets.push_back({ destinationNode, PacketOrPacketList { nullptr, std::move(packetList)} }); unlock(); @@ -293,16 +290,9 @@ bool PacketSender::nonThreadedProcess() { size_t packetCount = sendAsPacket ? 1 : packetPair.second.second->getNumPackets(); if (sendAsPacket) { - - qDebug() << __FUNCTION__ << "sendUnreliablePacket() to:" << packetPair.first->getUUID() << "type:" << packetPair.second.first->getType(); DependencyManager::get()->sendUnreliablePacket(*packetPair.second.first, *packetPair.first); } else { - - qDebug() << __FUNCTION__ << "sendPacketList() to:" << packetPair.first->getUUID() << "type:" << packetPair.second.second->getType() - << "getMessageSize:" << packetPair.second.second->getMessageSize() - << "getDataSize:" << packetPair.second.second->getDataSize(); - - DependencyManager::get()->sendPacketList(*packetPair.second.second, *packetPair.first); + DependencyManager::get()->sendPacketList(std::move(packetPair.second.second), *packetPair.first); } @@ -313,13 +303,6 @@ bool PacketSender::nonThreadedProcess() { _totalBytesSent += packetSize; emit packetSent(packetSize); // FIXME should include number of packets? - - qDebug() << __FUNCTION__ << "packetsSentThisCall:" << packetsSentThisCall - << "_packetsOverCheckInterval:" << _packetsOverCheckInterval - << "_totalPacketsSent:" << _totalPacketsSent - << "packetSize:" << packetSize - << "_totalBytesSent:" << _totalBytesSent; - _lastSendTime = now; } return isStillRunning(); diff --git a/libraries/networking/src/ReceivedMessage.cpp b/libraries/networking/src/ReceivedMessage.cpp index 8a3322ce20..00b16908ce 100644 --- a/libraries/networking/src/ReceivedMessage.cpp +++ b/libraries/networking/src/ReceivedMessage.cpp @@ -26,10 +26,8 @@ ReceivedMessage::ReceivedMessage(const NLPacketList& packetList) _sourceID(packetList.getSourceID()), _packetType(packetList.getType()), _packetVersion(packetList.getVersion()), - _senderSockAddr(packetList.getSenderSockAddr()), - _fromPacketList(true) + _senderSockAddr(packetList.getSenderSockAddr()) { - qDebug() << __FUNCTION__ << "(const NLPacketList& packetList) _fromPacketList:" << _fromPacketList; } ReceivedMessage::ReceivedMessage(NLPacket& packet) @@ -40,12 +38,8 @@ ReceivedMessage::ReceivedMessage(NLPacket& packet) _packetType(packet.getType()), _packetVersion(packet.getVersion()), _senderSockAddr(packet.getSenderSockAddr()), - _isComplete(packet.getPacketPosition() == NLPacket::ONLY), - _fromPacket(true) + _isComplete(packet.getPacketPosition() == NLPacket::ONLY) { - if (packet.getType() == PacketType::EntityAdd) { - qDebug() << __FUNCTION__ << "(NLPacket& packet) _fromPacketList:" << _fromPacketList << "packet.getType():" << packet.getType(); - } } ReceivedMessage::ReceivedMessage(QByteArray byteArray, PacketType packetType, PacketVersion packetVersion, @@ -57,10 +51,8 @@ ReceivedMessage::ReceivedMessage(QByteArray byteArray, PacketType packetType, Pa _packetType(packetType), _packetVersion(packetVersion), _senderSockAddr(senderSockAddr), - _isComplete(true), - _fromByteArray(true) + _isComplete(true) { - qDebug() << __FUNCTION__ << "(QByteArray byteArray)... _fromPacketList:" << _fromPacketList; } void ReceivedMessage::setFailed() { diff --git a/libraries/networking/src/ReceivedMessage.h b/libraries/networking/src/ReceivedMessage.h index 2af4798bc3..ae51e7592a 100644 --- a/libraries/networking/src/ReceivedMessage.h +++ b/libraries/networking/src/ReceivedMessage.h @@ -79,10 +79,6 @@ public: template qint64 readHeadPrimitive(T* data); - bool isFromPacketList() const { return _fromPacketList; }; - bool isFromPacket() const { return _fromPacket; }; - bool isFromByteArray() const { return _fromByteArray; }; - signals: void progress(qint64 size); void completed(); @@ -104,12 +100,6 @@ private: std::atomic _isComplete { true }; std::atomic _failed { false }; - - std::atomic _fromPacketList { false }; - std::atomic _fromPacket { false }; - std::atomic _fromByteArray { false }; - - }; Q_DECLARE_METATYPE(ReceivedMessage*) diff --git a/libraries/networking/src/ReceivedPacketProcessor.cpp b/libraries/networking/src/ReceivedPacketProcessor.cpp index eda5109279..c18d4ed1e8 100644 --- a/libraries/networking/src/ReceivedPacketProcessor.cpp +++ b/libraries/networking/src/ReceivedPacketProcessor.cpp @@ -25,15 +25,6 @@ void ReceivedPacketProcessor::terminating() { } void ReceivedPacketProcessor::queueReceivedPacket(QSharedPointer message, SharedNodePointer sendingNode) { - - qDebug() << __FUNCTION__ << "from:" << sendingNode->getUUID() << "type:" << message->getType() - << "getNumPackets:" << message->getNumPackets() - << "getSize:" << message->getSize() - << "isFromPacketList:" << message->isFromPacketList() - << "isComplete:" << message->isComplete() - ; - - lock(); _packets.push_back({ sendingNode, message }); _nodePacketCounts[sendingNode->getUUID()]++; diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 7e04fa6630..a880b44074 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -118,9 +118,6 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::uniqu // This method is called when the edit packet layer has determined that it has a fully formed packet destined for // a known nodeID. void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr packetList) { - - qDebug() << __FUNCTION__ << "to:" << nodeUUID << "type:" << packetList->getType() << "size:" << packetList->getDataSize(); - bool wantDebug = false; DependencyManager::get()->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are getMyNodeType() @@ -129,10 +126,7 @@ void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::u && node->getActiveSocket()) { // NOTE: unlike packets, the packet lists don't get rewritten sequence numbers. - - // add packet to history -- we don't keep track of sent PacketLists - //_sentPacketHistories[nodeUUID].packetSent(sequence, *packet); - + // or do history for resend queuePacketListForSending(node, std::move(packetList)); } }); @@ -380,9 +374,6 @@ void OctreeEditPacketSender::releaseQueuedPacket(const QUuid& nodeID, std::uniqu } void OctreeEditPacketSender::releaseQueuedPacketList(const QUuid& nodeID, std::unique_ptr packetList) { - - qDebug() << __FUNCTION__ << "to:" << nodeID << "type:" << packetList->getType() << "size:" << packetList->getDataSize(); - _releaseQueuedPacketMutex.lock(); if (packetList->getMessageSize() > 0 && packetList->getType() != PacketType::Unknown) { queuePacketListToNode(nodeID, std::move(packetList)); diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 5b3e68c0a1..a2aad33058 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -35,8 +35,7 @@ OctreePacketData::OctreePacketData(bool enableCompression, int targetSize) { void OctreePacketData::changeSettings(bool enableCompression, unsigned int targetSize) { _enableCompression = enableCompression; - _targetSize = targetSize; // std::min(MAX_OCTREE_UNCOMRESSED_PACKET_SIZE, targetSize); - + _targetSize = targetSize; _uncompressedByteArray.resize(_targetSize); _compressedByteArray.resize(_targetSize); diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 0c582f4ed6..09eb134124 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -279,8 +279,6 @@ private: unsigned int _targetSize; bool _enableCompression; - //unsigned char _uncompressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; - QByteArray _uncompressedByteArray; unsigned char* _uncompressed { nullptr }; int _bytesInUse; @@ -291,7 +289,6 @@ private: bool compressContent(); - //unsigned char _compressed[MAX_OCTREE_UNCOMRESSED_PACKET_SIZE]; QByteArray _compressedByteArray; unsigned char* _compressed { nullptr }; int _compressedBytes; From ab9432c1cda5882ad1d0dd746895303a76d795dd Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 17 Nov 2017 16:02:53 -0800 Subject: [PATCH 39/42] load webpage if tablet root is not initialized --- .../ui/src/ui/TabletScriptingInterface.cpp | 24 ++++++++++++++++--- .../ui/src/ui/TabletScriptingInterface.h | 3 ++- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index b9da230715..2fba6c6160 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -282,6 +282,7 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { _desktopWindow = nullptr; } } + qDebug() << "-------> current path " << _currentPathLoaded; } static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { @@ -328,7 +329,8 @@ void TabletProxy::initialScreen(const QVariant& url) { pushOntoStack(url); } else { _initialScreen = true; - _initialPath = url; + _initialPath.first = url; + _initialPath.second = State::QML; } } @@ -416,10 +418,18 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) { }); if (_initialScreen) { - if (!_showRunningScripts) { - pushOntoStack(_initialPath); + if (!_showRunningScripts && _initialPath.second == State::QML) { + pushOntoStack(_initialPath.first); + } else if (_initialPath.second == State::Web) { + QVariant webUrl = _initialPath.first; + QVariant scriptUrl = _initialWebPathParams.first; + gotoWebScreen(webUrl.toString(), scriptUrl.toString(), _initialWebPathParams.second); } _initialScreen = false; + _initialPath.first = ""; + _initialPath.second = State::Uninitialized; + _initialWebPathParams.first = ""; + _initialWebPathParams.second = false; } if (_showRunningScripts) { @@ -685,6 +695,14 @@ void TabletProxy::gotoWebScreen(const QString& url, const QString& injectedJavaS QMetaObject::invokeMethod(root, "setResizable", Q_ARG(const QVariant&, QVariant(false))); } QMetaObject::invokeMethod(root, "loadWebUrl", Q_ARG(const QVariant&, QVariant(url)), Q_ARG(const QVariant&, QVariant(injectedJavaScriptUrl))); + } else { + // tablet is not initialized yet, save information and load when + // the tablet root is set + _initialPath.first = url; + _initialPath.second = State::Web; + _initialWebPathParams.first = injectedJavaScriptUrl; + _initialWebPathParams.second = loadOtherBase; + _initialScreen = true; } _state = State::Web; emit screenChanged(QVariant("Web"), QVariant(url)); diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index bd195fdd20..ad6a7c8001 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -248,7 +248,6 @@ protected: void removeButtonsFromToolbar(); bool _initialScreen { false }; - QVariant _initialPath { "" }; QVariant _currentPathLoaded { "" }; QString _name; std::vector> _tabletButtonProxies; @@ -260,6 +259,8 @@ protected: enum class State { Uninitialized, Home, Web, Menu, QML }; State _state { State::Uninitialized }; + std::pair _initialPath { "", State::Uninitialized }; + std::pair _initialWebPathParams; bool _landscape { false }; bool _showRunningScripts { false }; }; From 427f6768597cf437a8133a2f17669a0fea6f0f3b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 17 Nov 2017 16:06:34 -0800 Subject: [PATCH 40/42] minimize diff --- libraries/ui/src/ui/TabletScriptingInterface.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 2fba6c6160..dd26dd7be7 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -282,7 +282,6 @@ void TabletProxy::setToolbarMode(bool toolbarMode) { _desktopWindow = nullptr; } } - qDebug() << "-------> current path " << _currentPathLoaded; } static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) { From 66454e72887ea608708fd3afac1401aa9ad0845c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 17 Nov 2017 16:33:43 -0800 Subject: [PATCH 41/42] fix unix warnings --- libraries/networking/src/PacketSender.cpp | 2 +- libraries/octree/src/OctreeEditPacketSender.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/networking/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp index 657a122cbe..02c4815f1f 100644 --- a/libraries/networking/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -191,7 +191,7 @@ bool PacketSender::nonThreadedProcess() { float averagePacketsPerCall = 0; // might be less than 1, if our caller calls us more frequently than the target PPS size_t packetsSentThisCall = 0; - int packetsToSendThisCall = 0; + size_t packetsToSendThisCall = 0; // Since we're in non-threaded mode, we need to determine how many packets to send per call to process // based on how often we get called... We do this by keeping a running average of our call times, and we determine diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index a880b44074..9cb383df41 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -118,7 +118,6 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, std::uniqu // This method is called when the edit packet layer has determined that it has a fully formed packet destined for // a known nodeID. void OctreeEditPacketSender::queuePacketListToNode(const QUuid& nodeUUID, std::unique_ptr packetList) { - bool wantDebug = false; DependencyManager::get()->eachNode([&](const SharedNodePointer& node) { // only send to the NodeTypes that are getMyNodeType() if (node->getType() == getMyNodeType() From c4aa93710817fbc7a7179b96e79393b9c2f8dfdd Mon Sep 17 00:00:00 2001 From: Alexander Ivash Date: Sat, 18 Nov 2017 11:16:40 +0300 Subject: [PATCH 42/42] adjust to coding standards --- interface/src/Application.cpp | 4 ++-- interface/src/Menu.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e35407038b..335a70a76c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7082,7 +7082,7 @@ DisplayPluginPointer Application::getActiveDisplayPlugin() const { return _displayPlugin; } -static const char* exclusionGroupKey = "exclusionGroup"; +static const char* EXCLUSION_GROUP_KEY = "exclusionGroup"; static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool active = false) { auto menu = Menu::getInstance(); @@ -7119,7 +7119,7 @@ static void addDisplayPluginToMenu(DisplayPluginPointer displayPlugin, bool acti action->setChecked(active); displayPluginGroup->addAction(action); - action->setProperty(exclusionGroupKey, QVariant::fromValue(displayPluginGroup)); + action->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(displayPluginGroup)); Q_ASSERT(menu->menuItemExists(MenuOption::OutputMenu, name)); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 9df22ab08e..9ec5cc6034 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -56,7 +56,7 @@ Menu* Menu::getInstance() { return dynamic_cast(qApp->getWindow()->menuBar()); } -const char* exclusionGroupKey = "exclusionGroup"; +const char* EXCLUSION_GROUP_KEY = "exclusionGroup"; Menu::Menu() { auto dialogsManager = DependencyManager::get(); @@ -228,21 +228,21 @@ Menu::Menu() { viewMenu, MenuOption::FirstPerson, Qt::CTRL | Qt::Key_F, true, qApp, SLOT(cameraMenuChanged()))); - firstPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup)); // View > Third Person auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::ThirdPerson, Qt::CTRL | Qt::Key_G, false, qApp, SLOT(cameraMenuChanged()))); - thirdPersonAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup)); // View > Mirror auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash( viewMenu, MenuOption::FullscreenMirror, Qt::CTRL | Qt::Key_H, false, qApp, SLOT(cameraMenuChanged()))); - viewMirrorAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup)); // View > Independent [advanced] auto viewIndependentAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, @@ -250,7 +250,7 @@ Menu::Menu() { false, qApp, SLOT(cameraMenuChanged()), UNSPECIFIED_POSITION, "Advanced")); - viewIndependentAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + viewIndependentAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup)); // View > Entity Camera [advanced] auto viewEntityCameraAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(viewMenu, @@ -258,7 +258,7 @@ Menu::Menu() { false, qApp, SLOT(cameraMenuChanged()), UNSPECIFIED_POSITION, "Advanced")); - viewEntityCameraAction->setProperty(exclusionGroupKey, QVariant::fromValue(cameraModeGroup)); + viewEntityCameraAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup)); viewMenu->addSeparator();