diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 8a7d03ba75..4006673cad 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -863,15 +863,6 @@ "help": "The path to the directory assets are stored in.
If this path is relative, it will be relative to the application data directory.
If you change this path you will need to manually copy any existing assets from the previous directory.", "default": "", "advanced": true - }, - { - "name": "max_bandwidth", - "type": "double", - "label": "Max Bandwidth Per User", - "help": "The maximum upstream bandwidth each user can use (in Mb/s).", - "placeholder": "10.0", - "default": "", - "advanced": true } ] }, diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 9c564f6518..b43376c374 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -41,8 +41,10 @@ endif () if (ANDROID) set(PLATFORM_QT_COMPONENTS AndroidExtras) + set(PLATFORM_QT_LIBRARIES Qt5::AndroidExtras) else () set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets) + set(PLATFORM_QT_LIBRARIES Qt5::WebEngine Qt5::WebEngineWidgets) endif () find_package( @@ -244,7 +246,8 @@ target_link_libraries( ${TARGET_NAME} Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg - Qt5::WebChannel Qt5::WebEngine + Qt5::WebChannel Qt5::WebEngine + ${PLATFORM_QT_LIBRARIES} ) if (UNIX) diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index f2af980010..7c54a6a626 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -279,6 +279,7 @@ Window { verticalCenter: backgroundImage.verticalCenter; horizontalCenter: scroll.horizontalCenter; } + z: 100 } HifiControls.Keyboard { diff --git a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml index fc7eac3d00..f1b36ff6a7 100644 --- a/interface/resources/qml/LoginDialog/CompleteProfileBody.qml +++ b/interface/resources/qml/LoginDialog/CompleteProfileBody.qml @@ -29,8 +29,9 @@ Item { readonly property int maxHeight: 720 function resize() { - var targetWidth = Math.max(titleWidth, additionalTextContainer.contentWidth) - var targetHeight = 4 * hifi.dimensions.contentSpacing.y + buttons.height + additionalTextContainer.height + var targetWidth = Math.max(titleWidth, Math.max(additionalTextContainer.contentWidth, + termsContainer.contentWidth)) + var targetHeight = 5 * hifi.dimensions.contentSpacing.y + buttons.height + additionalTextContainer.height + termsContainer.height root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)) root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) @@ -43,7 +44,7 @@ Item { top: parent.top horizontalCenter: parent.horizontalCenter margins: 0 - topMargin: 3 * hifi.dimensions.contentSpacing.y + topMargin: 2 * hifi.dimensions.contentSpacing.y } spacing: hifi.dimensions.contentSpacing.x onHeightChanged: d.resize(); onWidthChanged: d.resize(); @@ -91,6 +92,25 @@ Item { } } + InfoItem { + id: termsContainer + anchors { + top: additionalTextContainer.bottom + left: parent.left + margins: 0 + topMargin: 2 * hifi.dimensions.contentSpacing.y + } + + text: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") + wrapMode: Text.WordWrap + color: hifi.colors.baseGrayHighlight + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + + onLinkActivated: loginDialog.openUrl(link) + } + Component.onCompleted: { root.title = qsTr("Complete Your Profile") root.iconText = "<" diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index e1d7a639da..fbcb3d5e37 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -42,8 +42,14 @@ Item { function resize() { var targetWidth = Math.max(titleWidth, form.contentWidth); - var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height - + 4 * hifi.dimensions.contentSpacing.y + form.height + hifi.dimensions.contentSpacing.y + buttons.height; + var targetHeight = hifi.dimensions.contentSpacing.y + mainTextContainer.height + + 4 * hifi.dimensions.contentSpacing.y + form.height + + hifi.dimensions.contentSpacing.y + buttons.height; + + if (additionalInformation.visible) { + targetWidth = Math.max(targetWidth, additionalInformation.width); + targetHeight += hifi.dimensions.contentSpacing.y + additionalInformation.height + } root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) @@ -136,6 +142,25 @@ Item { } + InfoItem { + id: additionalInformation + anchors { + top: form.bottom + left: parent.left + margins: 0 + topMargin: hifi.dimensions.contentSpacing.y + } + + visible: loginDialog.isSteamRunning() + + text: qsTr("Your steam account informations will not be exposed to other users.") + wrapMode: Text.WordWrap + color: hifi.colors.baseGrayHighlight + lineHeight: 1 + lineHeightMode: Text.ProportionalHeight + horizontalAlignment: Text.AlignHCenter + } + // Override ScrollingWindow's keyboard that would be at very bottom of dialog. Keyboard { raised: keyboardEnabled && keyboardRaised diff --git a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml index b949c660d6..eafe9e4b47 100644 --- a/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml +++ b/interface/resources/qml/LoginDialog/UsernameCollisionBody.qml @@ -27,6 +27,13 @@ Item { loginDialog.createAccountFromStream(textField.text) } + + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + onKeyboardRaisedChanged: d.resize(); + QtObject { id: d readonly property int minWidth: 480 @@ -35,15 +42,16 @@ Item { readonly property int maxHeight: 720 function resize() { - var targetWidth = Math.max(titleWidth, Math.max(mainTextContainer.contentWidth, - termsContainer.contentWidth)) + var targetWidth = Math.max(titleWidth, mainTextContainer.contentWidth) var targetHeight = mainTextContainer.height + - 2 * hifi.dimensions.contentSpacing.y + textField.height + - 5 * hifi.dimensions.contentSpacing.y + termsContainer.height + - 1 * hifi.dimensions.contentSpacing.y + buttons.height + hifi.dimensions.contentSpacing.y + textField.height + + hifi.dimensions.contentSpacing.y + buttons.height root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)) root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) + + (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y) + + height = root.height } } @@ -71,39 +79,32 @@ Item { top: mainTextContainer.bottom left: parent.left margins: 0 - topMargin: 2 * hifi.dimensions.contentSpacing.y + topMargin: hifi.dimensions.contentSpacing.y } width: 250 placeholderText: "Choose your own" } - InfoItem { - id: termsContainer + // Override ScrollingWindow's keyboard that would be at very bottom of dialog. + Keyboard { + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode anchors { - top: textField.bottom left: parent.left - margins: 0 - topMargin: 3 * hifi.dimensions.contentSpacing.y + right: parent.right + bottom: buttons.top + bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 } - - text: qsTr("By creating this user profile, you agree to High Fidelity's Terms of Service") - wrapMode: Text.WordWrap - color: hifi.colors.baseGrayHighlight - lineHeight: 1 - lineHeightMode: Text.ProportionalHeight - horizontalAlignment: Text.AlignHCenter - - onLinkActivated: loginDialog.openUrl(link) } Row { id: buttons anchors { - top: termsContainer.bottom + bottom: parent.bottom right: parent.right margins: 0 - topMargin: 1 * hifi.dimensions.contentSpacing.y + topMargin: hifi.dimensions.contentSpacing.y } spacing: hifi.dimensions.contentSpacing.x onHeightChanged: d.resize(); onWidthChanged: d.resize(); @@ -130,8 +131,10 @@ Item { Component.onCompleted: { root.title = qsTr("Complete Your Profile") root.iconText = "<" + keyboardEnabled = HMD.active; d.resize(); } + Connections { target: loginDialog onHandleCreateCompleted: { diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml index a5b724f113..763e6530fb 100644 --- a/interface/resources/qml/controls-uit/BaseWebView.qml +++ b/interface/resources/qml/controls-uit/BaseWebView.qml @@ -10,10 +10,10 @@ import QtQuick 2.5 import QtWebEngine 1.2 +import HFWebEngineProfile 1.0 WebEngineView { id: root - property var newUrl; profile: desktop.browserProfile @@ -25,28 +25,6 @@ WebEngineView { }); } - // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 - Timer { - id: urlReplacementTimer - running: false - repeat: false - interval: 50 - onTriggered: url = newUrl; - } - - onUrlChanged: { - var originalUrl = url.toString(); - newUrl = urlHandler.fixupUrl(originalUrl).toString(); - if (newUrl !== originalUrl) { - root.stop(); - if (urlReplacementTimer.running) { - console.warn("Replacement timer already running"); - return; - } - urlReplacementTimer.start(); - } - } - onLoadingChanged: { // Required to support clicking on "hifi://" links if (WebEngineView.LoadStartedStatus == loadRequest.status) { diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 1056a3b75e..abaf11a8e2 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -2,6 +2,7 @@ import QtQuick 2.5 import QtWebEngine 1.1 import QtWebChannel 1.0 import "../controls-uit" as HiFiControls +import HFWebEngineProfile 1.0 Item { property alias url: root.url @@ -31,6 +32,11 @@ Item { width: parent.width height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height + profile: HFWebEngineProfile { + id: webviewProfile + storageName: "qmlWebEngine" + } + // creates a global EventBridge object. WebEngineScript { id: createGlobalEventBridge @@ -62,28 +68,6 @@ Item { root.profile.httpUserAgent = "Mozilla/5.0 Chrome (HighFidelityInterface)"; } - // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 - Timer { - id: urlReplacementTimer - running: false - repeat: false - interval: 50 - onTriggered: url = root.newUrl; - } - - onUrlChanged: { - var originalUrl = url.toString(); - root.newUrl = urlHandler.fixupUrl(originalUrl).toString(); - if (root.newUrl !== originalUrl) { - root.stop(); - if (urlReplacementTimer.running) { - console.warn("Replacement timer already running"); - return; - } - urlReplacementTimer.start(); - } - } - onFeaturePermissionRequested: { grantFeaturePermission(securityOrigin, feature, true); } diff --git a/interface/resources/qml/desktop/Desktop.qml b/interface/resources/qml/desktop/Desktop.qml index ea2f048c1f..b207087be0 100644 --- a/interface/resources/qml/desktop/Desktop.qml +++ b/interface/resources/qml/desktop/Desktop.qml @@ -33,7 +33,7 @@ FocusScope { } } - + onHeightChanged: d.handleSizeChanged(); onWidthChanged: d.handleSizeChanged(); diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 7e523bbb70..b4fe773364 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -2,6 +2,7 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 import QtWebEngine 1.1; import Qt.labs.settings 1.0 +import HFWebEngineProfile 1.0 import "../desktop" as OriginalDesktop import ".." @@ -20,17 +21,14 @@ OriginalDesktop.Desktop { onEntered: ApplicationCompositor.reticleOverDesktop = true onExited: ApplicationCompositor.reticleOverDesktop = false acceptedButtons: Qt.NoButton - - } // The tool window, one instance property alias toolWindow: toolWindow ToolWindow { id: toolWindow } - property var browserProfile: WebEngineProfile { + property var browserProfile: HFWebEngineProfile { id: webviewProfile - httpUserAgent: "Chrome/48.0 (HighFidelityInterface)" storageName: "qmlWebEngine" } @@ -127,5 +125,3 @@ OriginalDesktop.Desktop { return result; } } - - diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 4351ffd230..beefc9c67b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -34,6 +34,8 @@ #include #include +#include + #include #include @@ -126,6 +128,7 @@ #include "InterfaceLogging.h" #include "LODManager.h" #include "ModelPackager.h" +#include "networking/HFWebEngineProfile.h" #include "scripting/AccountScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/AudioDeviceScriptingInterface.h" @@ -1698,6 +1701,7 @@ void Application::initializeUi() { UpdateDialog::registerType(); qmlRegisterType("Hifi", 1, 0, "Preference"); + qmlRegisterType("HFWebEngineProfile", 1, 0, "HFWebEngineProfile"); auto offscreenUi = DependencyManager::get(); offscreenUi->create(_glWidget->qglContext()); diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 717a0eeac7..94632d5e45 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -603,14 +603,14 @@ void Avatar::fixupModelsInScene() { _skeletonModel->removeFromScene(scene, pendingChanges); _skeletonModel->addToScene(scene, pendingChanges); } - for (auto& attachmentModel : _attachmentModels) { + for (auto attachmentModel : _attachmentModels) { if (attachmentModel->isRenderable() && attachmentModel->needsFixupInScene()) { attachmentModel->removeFromScene(scene, pendingChanges); attachmentModel->addToScene(scene, pendingChanges); } } - for (auto& attachmentModelToRemove : _attachmentsToRemove) { + for (auto attachmentModelToRemove : _attachmentsToRemove) { attachmentModelToRemove->removeFromScene(scene, pendingChanges); } _attachmentsToDelete.insert(_attachmentsToDelete.end(), _attachmentsToRemove.begin(), _attachmentsToRemove.end()); diff --git a/interface/src/networking/HFWebEngineProfile.cpp b/interface/src/networking/HFWebEngineProfile.cpp new file mode 100644 index 0000000000..6b377fa900 --- /dev/null +++ b/interface/src/networking/HFWebEngineProfile.cpp @@ -0,0 +1,27 @@ +// +// HFWebEngineProfile.cpp +// interface/src/networking +// +// Created by Stephen Birarda on 2016-10-17. +// Copyright 2016 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 "HFWebEngineProfile.h" + +#include "HFWebEngineRequestInterceptor.h" + +static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; + +HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : + QQuickWebEngineProfile(parent) +{ + static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; + setHttpUserAgent(WEB_ENGINE_USER_AGENT); + + // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user + auto requestInterceptor = new HFWebEngineRequestInterceptor(this); + setRequestInterceptor(requestInterceptor); +} diff --git a/interface/src/networking/HFWebEngineProfile.h b/interface/src/networking/HFWebEngineProfile.h new file mode 100644 index 0000000000..5c7655479e --- /dev/null +++ b/interface/src/networking/HFWebEngineProfile.h @@ -0,0 +1,25 @@ +// +// HFWebEngineProfile.h +// interface/src/networking +// +// Created by Stephen Birarda on 2016-10-17. +// Copyright 2016 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_HFWebEngineProfile_h +#define hifi_HFWebEngineProfile_h + +#include + +class HFWebEngineProfile : public QQuickWebEngineProfile { +public: + HFWebEngineProfile(QObject* parent = Q_NULLPTR); +}; + + +#endif // hifi_HFWebEngineProfile_h diff --git a/interface/src/networking/HFWebEngineRequestInterceptor.cpp b/interface/src/networking/HFWebEngineRequestInterceptor.cpp new file mode 100644 index 0000000000..9c3f0b232e --- /dev/null +++ b/interface/src/networking/HFWebEngineRequestInterceptor.cpp @@ -0,0 +1,40 @@ +// +// HFWebEngineRequestInterceptor.cpp +// interface/src/networking +// +// Created by Stephen Birarda on 2016-10-14. +// Copyright 2016 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 "HFWebEngineRequestInterceptor.h" + +#include + +#include + +bool isAuthableHighFidelityURL(const QUrl& url) { + static const QStringList HF_HOSTS = { + "highfidelity.com", "highfidelity.io", + "metaverse.highfidelity.com", "metaverse.highfidelity.io" + }; + + return url.scheme() == "https" && HF_HOSTS.contains(url.host()); +} + +void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { + // check if this is a request to a highfidelity URL + if (isAuthableHighFidelityURL(info.requestUrl())) { + // if we have an access token, add it to the right HTTP header for authorization + auto accountManager = DependencyManager::get(); + + if (accountManager->hasValidAccessToken()) { + static const QString OAUTH_AUTHORIZATION_HEADER = "Authorization"; + + QString bearerTokenString = "Bearer " + accountManager->getAccountInfo().getAccessToken().token; + info.setHttpHeader(OAUTH_AUTHORIZATION_HEADER.toLocal8Bit(), bearerTokenString.toLocal8Bit()); + } + } +} diff --git a/interface/src/networking/HFWebEngineRequestInterceptor.h b/interface/src/networking/HFWebEngineRequestInterceptor.h new file mode 100644 index 0000000000..a4c308426c --- /dev/null +++ b/interface/src/networking/HFWebEngineRequestInterceptor.h @@ -0,0 +1,26 @@ +// +// HFWebEngineRequestInterceptor.h +// interface/src/networking +// +// Created by Stephen Birarda on 2016-10-14. +// Copyright 2016 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_HFWebEngineRequestInterceptor_h +#define hifi_HFWebEngineRequestInterceptor_h + +#include + +class HFWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor { +public: + HFWebEngineRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {}; + + virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; +}; + +#endif // hifi_HFWebEngineRequestInterceptor_h diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index d16968f370..8bf510bb2a 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -40,19 +40,6 @@ #include "TextureRecycler.h" #include "Context.h" -QString fixupHifiUrl(const QString& urlString) { - static const QString ACCESS_TOKEN_PARAMETER = "access_token"; - static const QString ALLOWED_HOST = "metaverse.highfidelity.com"; - QUrl url(urlString); - QUrlQuery query(url); - if (url.host() == ALLOWED_HOST && query.allQueryItemValues(ACCESS_TOKEN_PARAMETER).empty()) { - auto accountManager = DependencyManager::get(); - query.addQueryItem(ACCESS_TOKEN_PARAMETER, accountManager->getAccountInfo().getAccessToken().token); - url.setQuery(query.query()); - return url.toString(); - } - return urlString; -} class UrlHandler : public QObject { Q_OBJECT @@ -66,11 +53,6 @@ public: static auto handler = dynamic_cast(qApp); return handler->acceptURL(url); } - - // FIXME hack for authentication, remove when we migrate to Qt 5.6 - Q_INVOKABLE QString fixupUrl(const QString& originalUrl) { - return fixupHifiUrl(originalUrl); - } }; // Time between receiving a request to render the offscreen UI actually triggering diff --git a/libraries/networking/src/udt/BasePacket.h b/libraries/networking/src/udt/BasePacket.h index 33b8020d3c..d9b624b595 100644 --- a/libraries/networking/src/udt/BasePacket.h +++ b/libraries/networking/src/udt/BasePacket.h @@ -18,6 +18,8 @@ #include +#include + #include "../HifiSockAddr.h" #include "Constants.h" @@ -80,6 +82,9 @@ public: qint64 writeString(const QString& string); QString readString(); + + void setReceiveTime(p_high_resolution_clock::time_point receiveTime) { _receiveTime = receiveTime; } + p_high_resolution_clock::time_point getReceiveTime() const { return _receiveTime; } template qint64 peekPrimitive(T* data); template qint64 readPrimitive(T* data); @@ -108,6 +113,8 @@ protected: qint64 _payloadSize = 0; // How much of the payload is actually used HifiSockAddr _senderSockAddr; // sender address for packet (only used on receiving end) + + p_high_resolution_clock::time_point _receiveTime; // captures the time the packet received (only used on receiving end) }; template qint64 BasePacket::peekPrimitive(T* data) { diff --git a/libraries/networking/src/udt/CongestionControl.cpp b/libraries/networking/src/udt/CongestionControl.cpp index 5826bfa11c..7ade4f004f 100644 --- a/libraries/networking/src/udt/CongestionControl.cpp +++ b/libraries/networking/src/udt/CongestionControl.cpp @@ -45,11 +45,11 @@ DefaultCC::DefaultCC() : { _mss = udt::MAX_PACKET_SIZE_WITH_UDP_HEADER; - _congestionWindowSize = 16.0; + _congestionWindowSize = 16; setPacketSendPeriod(1.0); } -void DefaultCC::onACK(SequenceNumber ackNum) { +bool DefaultCC::onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) { double increase = 0; // Note from UDT original code: @@ -61,7 +61,7 @@ void DefaultCC::onACK(SequenceNumber ackNum) { // we will only adjust once per sync interval so check that it has been at least that long now auto now = p_high_resolution_clock::now(); if (duration_cast(now - _lastRCTime).count() < synInterval()) { - return; + return false; } // our last rate increase time is now @@ -93,13 +93,13 @@ void DefaultCC::onACK(SequenceNumber ackNum) { // during slow start we perform no rate increases if (_slowStart) { - return; + return false; } // if loss has happened since the last rate increase we do not perform another increase if (_loss) { _loss = false; - return; + return false; } double capacitySpeedDelta = (_bandwidth - USECS_PER_SECOND / _packetSendPeriod); @@ -132,6 +132,8 @@ void DefaultCC::onACK(SequenceNumber ackNum) { } setPacketSendPeriod((_packetSendPeriod * synInterval()) / (_packetSendPeriod * increase + synInterval())); + + return false; } void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) { @@ -218,7 +220,7 @@ void DefaultCC::stopSlowStart() { // If no receiving rate is observed, we have to compute the sending // rate according to the current window size, and decrease it // using the method below. - setPacketSendPeriod(_congestionWindowSize / (_rtt + synInterval())); + setPacketSendPeriod(double(_congestionWindowSize) / (_rtt + synInterval())); } } diff --git a/libraries/networking/src/udt/CongestionControl.h b/libraries/networking/src/udt/CongestionControl.h index 3ab69efe52..3a93134a5e 100644 --- a/libraries/networking/src/udt/CongestionControl.h +++ b/libraries/networking/src/udt/CongestionControl.h @@ -40,9 +40,18 @@ public: void setMaxBandwidth(int maxBandwidth); virtual void init() {} - virtual void onACK(SequenceNumber ackNum) {} + + // return value specifies if connection should perform a fast re-transmit of ACK + 1 (used in TCP style congestion control) + virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) { return false; } + virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) {} virtual void onTimeout() {} + + virtual bool shouldNAK() { return true; } + virtual bool shouldACK2() { return true; } + virtual bool shouldProbe() { return true; } + + virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) {} protected: void setAckInterval(int ackInterval) { _ackInterval = ackInterval; } void setRTO(int rto) { _userDefinedRTO = true; _rto = rto; } @@ -57,11 +66,11 @@ protected: void setPacketSendPeriod(double newSendPeriod); // call this internally to ensure send period doesn't go past max bandwidth double _packetSendPeriod { 1.0 }; // Packet sending period, in microseconds - double _congestionWindowSize { 16.0 }; // Congestion window size, in packets + int _congestionWindowSize { 16 }; // Congestion window size, in packets int _bandwidth { 0 }; // estimated bandwidth, packets per second std::atomic _maxBandwidth { -1 }; // Maximum desired bandwidth, bits per second - double _maxCongestionWindowSize { 0.0 }; // maximum cwnd size, in packets + int _maxCongestionWindowSize { 0 }; // maximum cwnd size, in packets int _mss { 0 }; // Maximum Packet Size, including all packet headers SequenceNumber _sendCurrSeqNum; // current maximum seq num sent out @@ -102,7 +111,7 @@ public: DefaultCC(); public: - virtual void onACK(SequenceNumber ackNum) override; + virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) override; virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) override; virtual void onTimeout() override; diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index 53cacaeeb4..a190fe3ddc 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -118,12 +118,14 @@ SendQueue& Connection::getSendQueue() { QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive); QObject::connect(_sendQueue.get(), &SendQueue::timeout, this, &Connection::queueTimeout); QObject::connect(_sendQueue.get(), &SendQueue::shortCircuitLoss, this, &Connection::queueShortCircuitLoss); + // set defaults on the send queue from our congestion control object and estimatedTimeout() _sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod); _sendQueue->setSyncInterval(_synInterval); _sendQueue->setEstimatedTimeout(estimatedTimeout()); _sendQueue->setFlowWindowSize(std::min(_flowWindowSize, (int) _congestionControl->_congestionWindowSize)); + _sendQueue->setProbePacketEnabled(_congestionControl->shouldProbe()); // give the randomized sequence number to the congestion control object _congestionControl->setInitialSendSequenceNumber(_sendQueue->getCurrentSequenceNumber()); @@ -150,13 +152,13 @@ void Connection::queueInactive() { } void Connection::queueTimeout() { - updateCongestionControlAndSendQueue([this]{ + updateCongestionControlAndSendQueue([this] { _congestionControl->onTimeout(); }); } void Connection::queueShortCircuitLoss(quint32 sequenceNumber) { - updateCongestionControlAndSendQueue([this, sequenceNumber]{ + updateCongestionControlAndSendQueue([this, sequenceNumber] { _congestionControl->onLoss(SequenceNumber { sequenceNumber }, SequenceNumber { sequenceNumber }); }); } @@ -223,9 +225,11 @@ void Connection::sync() { // reset the number of light ACKs or non SYN ACKs during this sync interval _lightACKsDuringSYN = 1; _acksDuringSYN = 1; - - // we send out a periodic ACK every rate control interval - sendACK(); + + if (_congestionControl->_ackInterval > 1) { + // we send out a periodic ACK every rate control interval + sendACK(); + } if (_lossList.getLength() > 0) { // check if we need to re-transmit a loss list @@ -260,12 +264,17 @@ void Connection::sync() { } } -void Connection::recordSentPackets(int dataSize, int payloadSize) { - _stats.recordSentPackets(payloadSize, dataSize); +void Connection::recordSentPackets(int wireSize, int payloadSize, + SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { + _stats.recordSentPackets(payloadSize, wireSize); + + _congestionControl->onPacketSent(wireSize, seqNum, timePoint); } -void Connection::recordRetransmission() { +void Connection::recordRetransmission(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { _stats.record(ConnectionStats::Stats::Retransmission); + + _congestionControl->onPacketSent(wireSize, seqNum, timePoint); } void Connection::sendACK(bool wasCausedBySyncTimeout) { @@ -308,8 +317,8 @@ void Connection::sendACK(bool wasCausedBySyncTimeout) { // pack the available buffer size, in packets // in our implementation we have no hard limit on receive buffer size, send the default value - _ackPacket->writePrimitive((int32_t) udt::CONNECTION_RECEIVE_BUFFER_SIZE_PACKETS); - + _ackPacket->writePrimitive((int32_t) udt::MAX_PACKETS_IN_FLIGHT); + if (wasCausedBySyncTimeout) { // grab the up to date packet receive speed and estimated bandwidth int32_t packetReceiveSpeed = _receiveWindow.getPacketReceiveSpeed(); @@ -475,23 +484,24 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in } else { _lossList.append(_lastReceivedSequenceNumber + 1, sequenceNumber - 1); } - - // Send a NAK packet - sendNAK(sequenceNumber); - - // figure out when we should send the next loss report, if we haven't heard anything back - _nakInterval = estimatedTimeout(); - - int receivedPacketsPerSecond = _receiveWindow.getPacketReceiveSpeed(); - if (receivedPacketsPerSecond > 0) { - // the NAK interval is at least the _minNAKInterval - // but might be the time required for all lost packets to be retransmitted - _nakInterval += (int) (_lossList.getLength() * (USECS_PER_SECOND / receivedPacketsPerSecond)); - } - - // the NAK interval is at least the _minNAKInterval but might be the value calculated above, if that is larger - _nakInterval = std::max(_nakInterval, _minNAKInterval); + if (_congestionControl->shouldNAK()) { + // Send a NAK packet + sendNAK(sequenceNumber); + + // figure out when we should send the next loss report, if we haven't heard anything back + _nakInterval = estimatedTimeout(); + + int receivedPacketsPerSecond = _receiveWindow.getPacketReceiveSpeed(); + if (receivedPacketsPerSecond > 0) { + // the NAK interval is at least the _minNAKInterval + // but might be the time required for all lost packets to be retransmitted + _nakInterval += (int) (_lossList.getLength() * (USECS_PER_SECOND / receivedPacketsPerSecond)); + } + + // the NAK interval is at least the _minNAKInterval but might be the value calculated above, if that is larger + _nakInterval = std::max(_nakInterval, _minNAKInterval); + } } bool wasDuplicate = false; @@ -508,7 +518,10 @@ bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, in ++_packetsSinceACK; // check if we need to send an ACK, according to CC params - if (_congestionControl->_ackInterval > 0 && _packetsSinceACK >= _congestionControl->_ackInterval * _acksDuringSYN) { + if (_congestionControl->_ackInterval == 1) { + // using a congestion control that ACKs every packet (like TCP Vegas) + sendACK(true); + } else if (_congestionControl->_ackInterval > 0 && _packetsSinceACK >= _congestionControl->_ackInterval * _acksDuringSYN) { _acksDuringSYN++; sendACK(false); } else if (_congestionControl->_lightACKInterval > 0 @@ -598,7 +611,8 @@ void Connection::processACK(ControlPacketPointer controlPacket) { microseconds sinceLastACK2 = duration_cast(currentTime - lastACK2SendTime); - if (sinceLastACK2.count() >= _synInterval || currentACKSubSequenceNumber == _lastSentACK2) { + if (_congestionControl->shouldACK2() + && (sinceLastACK2.count() >= _synInterval || currentACKSubSequenceNumber == _lastSentACK2)) { // Send ACK2 packet sendACK2(currentACKSubSequenceNumber); @@ -678,8 +692,11 @@ void Connection::processACK(ControlPacketPointer controlPacket) { } // give this ACK to the congestion control and update the send queue parameters - updateCongestionControlAndSendQueue([this, ack](){ - _congestionControl->onACK(ack); + updateCongestionControlAndSendQueue([this, ack, &controlPacket] { + if (_congestionControl->onACK(ack, controlPacket->getReceiveTime())) { + // the congestion control has told us it needs a fast re-transmit of ack + 1, add that now + _sendQueue->fastRetransmit(ack + 1); + } }); _stats.record(ConnectionStats::Stats::ProcessedACK); @@ -764,7 +781,7 @@ void Connection::processNAK(ControlPacketPointer controlPacket) { getSendQueue().nak(start, end); // give the loss to the congestion control object and update the send queue parameters - updateCongestionControlAndSendQueue([this, start, end](){ + updateCongestionControlAndSendQueue([this, start, end] { _congestionControl->onLoss(start, end); }); diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index a18a23f160..f94550426e 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -87,8 +87,8 @@ signals: void receiverHandshakeRequestComplete(const HifiSockAddr& sockAddr); private slots: - void recordSentPackets(int payload, int total); - void recordRetransmission(); + void recordSentPackets(int wireSize, int payloadSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); + void recordRetransmission(int wireSize, SequenceNumber sequenceNumber, p_high_resolution_clock::time_point timePoint); void queueInactive(); void queueTimeout(); void queueShortCircuitLoss(quint32 sequenceNumber); diff --git a/libraries/networking/src/udt/Constants.h b/libraries/networking/src/udt/Constants.h index 3186571f9b..243fa4edda 100644 --- a/libraries/networking/src/udt/Constants.h +++ b/libraries/networking/src/udt/Constants.h @@ -17,6 +17,7 @@ #include "SequenceNumber.h" namespace udt { + static const int UDP_IPV4_HEADER_SIZE = 28; static const int MAX_PACKET_SIZE_WITH_UDP_HEADER = 1492; static const int MAX_PACKET_SIZE = MAX_PACKET_SIZE_WITH_UDP_HEADER - UDP_IPV4_HEADER_SIZE; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index ec4e724c1b..7e788ce02b 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -58,8 +58,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::AssetGetInfo: case PacketType::AssetGet: case PacketType::AssetUpload: - // Removal of extension from Asset requests - return 18; + return static_cast(AssetServerPacketVersion::VegasCongestionControl); case PacketType::NodeIgnoreRequest: return 18; // Introduction of node ignore request (which replaced an unused packet tpye) diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index aa775b9f53..844847e791 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -189,6 +189,10 @@ const PacketVersion VERSION_MODEL_ENTITIES_SUPPORT_SIMPLE_HULLS = 62; const PacketVersion VERSION_WEB_ENTITIES_SUPPORT_DPI = 63; const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64; +enum class AssetServerPacketVersion: PacketVersion { + VegasCongestionControl = 19 +}; + enum class AvatarMixerPacketVersion : PacketVersion { TranslationSupport = 17, SoftAttachmentSupport, diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index 65619e2b50..f42e2b77fb 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -180,6 +180,16 @@ void SendQueue::nak(SequenceNumber start, SequenceNumber end) { _emptyCondition.notify_one(); } +void SendQueue::fastRetransmit(udt::SequenceNumber ack) { + { + std::lock_guard nakLocker(_naksLock); + _naks.insert(ack, ack); + } + + // call notify_one on the condition_variable_any in case the send thread is sleeping waiting for losses to re-send + _emptyCondition.notify_one(); +} + void SendQueue::overrideNAKListFromPacket(ControlPacket& packet) { // this is a response from the client, re-set our timeout expiry _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); @@ -242,11 +252,13 @@ bool SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr newPacket, newPacket->writeSequenceNumber(sequenceNumber); // Save packet/payload size before we move it - auto packetSize = newPacket->getDataSize(); + auto packetSize = newPacket->getWireSize(); auto payloadSize = newPacket->getPayloadSize(); auto bytesWritten = sendPacket(*newPacket); + emit packetSent(packetSize, payloadSize, sequenceNumber, p_high_resolution_clock::now()); + { // Insert the packet we have just sent in the sent list QWriteLocker locker(&_sentLock); @@ -256,8 +268,6 @@ bool SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr newPacket, } Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list"); - emit packetSent(packetSize, payloadSize); - if (bytesWritten < 0) { // this is a short-circuit loss - we failed to put this packet on the wire // so immediately add it to the loss list @@ -328,60 +338,66 @@ void SendQueue::run() { return; } - // push the next packet timestamp forwards by the current packet send period - auto nextPacketDelta = (newPacketCount == 2 ? 2 : 1) * _packetSendPeriod; - nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta); + if (_packetSendPeriod > 0) { + // push the next packet timestamp forwards by the current packet send period + auto nextPacketDelta = (newPacketCount == 2 ? 2 : 1) * _packetSendPeriod; + nextPacketTimestamp += std::chrono::microseconds(nextPacketDelta); - // sleep as long as we need for next packet send, if we can - auto now = p_high_resolution_clock::now(); + // sleep as long as we need for next packet send, if we can + auto now = p_high_resolution_clock::now(); - auto timeToSleep = duration_cast(nextPacketTimestamp - now); + auto timeToSleep = duration_cast(nextPacketTimestamp - now); - // we use nextPacketTimestamp so that we don't fall behind, not to force long sleeps - // we'll never allow nextPacketTimestamp to force us to sleep for more than nextPacketDelta - // so cap it to that value - if (timeToSleep > std::chrono::microseconds(nextPacketDelta)) { - // reset the nextPacketTimestamp so that it is correct next time we come around - nextPacketTimestamp = now + std::chrono::microseconds(nextPacketDelta); + // we use nextPacketTimestamp so that we don't fall behind, not to force long sleeps + // we'll never allow nextPacketTimestamp to force us to sleep for more than nextPacketDelta + // so cap it to that value + if (timeToSleep > std::chrono::microseconds(nextPacketDelta)) { + // reset the nextPacketTimestamp so that it is correct next time we come around + nextPacketTimestamp = now + std::chrono::microseconds(nextPacketDelta); - timeToSleep = std::chrono::microseconds(nextPacketDelta); - } + timeToSleep = std::chrono::microseconds(nextPacketDelta); + } - // we're seeing SendQueues sleep for a long period of time here, - // which can lock the NodeList if it's attempting to clear connections - // for now we guard this by capping the time this thread and sleep for + // we're seeing SendQueues sleep for a long period of time here, + // which can lock the NodeList if it's attempting to clear connections + // for now we guard this by capping the time this thread and sleep for - const microseconds MAX_SEND_QUEUE_SLEEP_USECS { 2000000 }; - if (timeToSleep > MAX_SEND_QUEUE_SLEEP_USECS) { - qWarning() << "udt::SendQueue wanted to sleep for" << timeToSleep.count() << "microseconds"; - qWarning() << "Capping sleep to" << MAX_SEND_QUEUE_SLEEP_USECS.count(); - qWarning() << "PSP:" << _packetSendPeriod << "NPD:" << nextPacketDelta + const microseconds MAX_SEND_QUEUE_SLEEP_USECS { 2000000 }; + if (timeToSleep > MAX_SEND_QUEUE_SLEEP_USECS) { + qWarning() << "udt::SendQueue wanted to sleep for" << timeToSleep.count() << "microseconds"; + qWarning() << "Capping sleep to" << MAX_SEND_QUEUE_SLEEP_USECS.count(); + qWarning() << "PSP:" << _packetSendPeriod << "NPD:" << nextPacketDelta << "NPT:" << nextPacketTimestamp.time_since_epoch().count() << "NOW:" << now.time_since_epoch().count(); - // alright, we're in a weird state - // we want to know why this is happening so we can implement a better fix than this guard - // send some details up to the API (if the user allows us) that indicate how we could such a large timeToSleep - static const QString SEND_QUEUE_LONG_SLEEP_ACTION = "sendqueue-sleep"; + // alright, we're in a weird state + // we want to know why this is happening so we can implement a better fix than this guard + // send some details up to the API (if the user allows us) that indicate how we could such a large timeToSleep + static const QString SEND_QUEUE_LONG_SLEEP_ACTION = "sendqueue-sleep"; - // setup a json object with the details we want - QJsonObject longSleepObject; - longSleepObject["timeToSleep"] = qint64(timeToSleep.count()); - longSleepObject["packetSendPeriod"] = _packetSendPeriod.load(); - longSleepObject["nextPacketDelta"] = nextPacketDelta; - longSleepObject["nextPacketTimestamp"] = qint64(nextPacketTimestamp.time_since_epoch().count()); - longSleepObject["then"] = qint64(now.time_since_epoch().count()); + // setup a json object with the details we want + QJsonObject longSleepObject; + longSleepObject["timeToSleep"] = qint64(timeToSleep.count()); + longSleepObject["packetSendPeriod"] = _packetSendPeriod.load(); + longSleepObject["nextPacketDelta"] = nextPacketDelta; + longSleepObject["nextPacketTimestamp"] = qint64(nextPacketTimestamp.time_since_epoch().count()); + longSleepObject["then"] = qint64(now.time_since_epoch().count()); - // hopefully send this event using the user activity logger - UserActivityLogger::getInstance().logAction(SEND_QUEUE_LONG_SLEEP_ACTION, longSleepObject); - - timeToSleep = MAX_SEND_QUEUE_SLEEP_USECS; + // hopefully send this event using the user activity logger + UserActivityLogger::getInstance().logAction(SEND_QUEUE_LONG_SLEEP_ACTION, longSleepObject); + + timeToSleep = MAX_SEND_QUEUE_SLEEP_USECS; + } + + std::this_thread::sleep_for(timeToSleep); } - - std::this_thread::sleep_for(timeToSleep); } } +void SendQueue::setProbePacketEnabled(bool enabled) { + _shouldSendProbes = enabled; +} + int SendQueue::maybeSendNewPacket() { if (!isFlowWindowFull()) { // we didn't re-send a packet, so time to send a new one @@ -399,7 +415,7 @@ int SendQueue::maybeSendNewPacket() { std::unique_ptr secondPacket; bool shouldSendPairTail = false; - if (((uint32_t) nextNumber & 0xF) == 0) { + if (_shouldSendProbes && ((uint32_t) nextNumber & 0xF) == 0) { // the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets // pull off a second packet if we can before we unlock shouldSendPairTail = true; @@ -492,7 +508,7 @@ bool SendQueue::maybeResendPacket() { sentLocker.unlock(); } - emit packetRetransmitted(); + emit packetRetransmitted(resendPacket.getWireSize(), it->first, p_high_resolution_clock::now()); // Signal that we did resend a packet return true; diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index 21f6141c3c..6e17c5b9c6 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -64,18 +64,21 @@ public: void setEstimatedTimeout(int estimatedTimeout) { _estimatedTimeout = estimatedTimeout; } void setSyncInterval(int syncInterval) { _syncInterval = syncInterval; } + + void setProbePacketEnabled(bool enabled); public slots: void stop(); void ack(SequenceNumber ack); void nak(SequenceNumber start, SequenceNumber end); + void fastRetransmit(SequenceNumber ack); void overrideNAKListFromPacket(ControlPacket& packet); void handshakeACK(SequenceNumber initialSequenceNumber); signals: - void packetSent(int dataSize, int payloadSize); - void packetRetransmitted(); + void packetSent(int wireSize, int payloadSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); + void packetRetransmitted(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint); void queueInactive(); @@ -139,6 +142,9 @@ private: std::condition_variable _handshakeACKCondition; std::condition_variable_any _emptyCondition; + + + std::atomic _shouldSendProbes { true }; }; } diff --git a/libraries/networking/src/udt/SequenceNumber.cpp b/libraries/networking/src/udt/SequenceNumber.cpp index 3e94c35d44..a518beb172 100644 --- a/libraries/networking/src/udt/SequenceNumber.cpp +++ b/libraries/networking/src/udt/SequenceNumber.cpp @@ -11,6 +11,14 @@ #include "SequenceNumber.h" +#include + +using namespace udt; + +Q_DECLARE_METATYPE(SequenceNumber); + +static const int sequenceNumberMetaTypeID = qRegisterMetaType(); + int udt::seqlen(const SequenceNumber& seq1, const SequenceNumber& seq2) { return (seq1._value <= seq2._value) ? (seq2._value - seq1._value + 1) : (seq2._value - seq1._value + SequenceNumber::MAX + 2); diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 03a32a5fe3..16a1bd1639 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -289,6 +289,9 @@ void Socket::messageFailed(Connection* connection, Packet::MessageNumber message void Socket::readPendingDatagrams() { int packetSizeWithHeader = -1; while ((packetSizeWithHeader = _udpSocket.pendingDatagramSize()) != -1) { + // grab a time point we can mark as the receive time of this packet + auto receiveTime = p_high_resolution_clock::now(); + // setup a HifiSockAddr to read into HifiSockAddr senderSockAddr; @@ -311,6 +314,7 @@ void Socket::readPendingDatagrams() { // we have a registered unfiltered handler for this HifiSockAddr - call that and return if (it->second) { auto basePacket = BasePacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + basePacket->setReceiveTime(receiveTime); it->second(std::move(basePacket)); } @@ -323,6 +327,7 @@ void Socket::readPendingDatagrams() { if (isControlPacket) { // setup a control packet from the data we just read auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + controlPacket->setReceiveTime(receiveTime); // move this control packet to the matching connection, if there is one auto connection = findOrCreateConnection(senderSockAddr); @@ -334,6 +339,7 @@ void Socket::readPendingDatagrams() { } else { // setup a Packet from the data we just read auto packet = Packet::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); + packet->setReceiveTime(receiveTime); // call our verification operator to see if this packet is verified if (!_packetFilterOperator || _packetFilterOperator(*packet)) { diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 59c3ec9cde..cced0c53df 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -22,7 +22,7 @@ #include #include "../HifiSockAddr.h" -#include "CongestionControl.h" +#include "TCPVegasCC.h" #include "Connection.h" //#define UDT_CONNECTION_DEBUG @@ -130,8 +130,8 @@ private: QTimer* _synTimer { nullptr }; int _maxBandwidth { -1 }; - - std::unique_ptr _ccFactory { new CongestionControlFactory() }; + + std::unique_ptr _ccFactory { new CongestionControlFactory() }; friend UDTTest; }; diff --git a/libraries/networking/src/udt/TCPVegasCC.cpp b/libraries/networking/src/udt/TCPVegasCC.cpp new file mode 100644 index 0000000000..5738ea8421 --- /dev/null +++ b/libraries/networking/src/udt/TCPVegasCC.cpp @@ -0,0 +1,282 @@ +// +// TCPVegasCC.cpp +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2016-09-20. +// Copyright 2016 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 "TCPVegasCC.h" + +#include +#include + +using namespace udt; +using namespace std::chrono; + +TCPVegasCC::TCPVegasCC() { + _packetSendPeriod = 0.0; + _congestionWindowSize = 2; + + setAckInterval(1); // TCP sends an ACK for every packet received + + // set our minimum RTT variables to the maximum possible value + // we can't do this as a member initializer until our VS has support for constexpr + _currentMinRTT = std::numeric_limits::max(); + _baseRTT = std::numeric_limits::max(); +} + +bool TCPVegasCC::onACK(SequenceNumber ack, p_high_resolution_clock::time_point receiveTime) { + auto it = _sentPacketTimes.find(ack); + + auto previousAck = _lastACK; + _lastACK = ack; + + if (it != _sentPacketTimes.end()) { + + // calculate the RTT (receive time - time ACK sent) + int lastRTT = duration_cast(receiveTime - it->second).count(); + + const int MAX_RTT_SAMPLE_MICROSECONDS = 10000000; + + if (lastRTT < 0) { + Q_ASSERT_X(false, __FUNCTION__, "calculated an RTT that is not > 0"); + return false; + } else if (lastRTT == 0) { + // we do not allow a zero microsecond RTT (as per the UNIX kernel implementation of TCP Vegas) + lastRTT = 1; + } else if (lastRTT > MAX_RTT_SAMPLE_MICROSECONDS) { + // we cap the lastRTT to MAX_RTT_SAMPLE_MICROSECONDS to avoid overflows in window size calculations + lastRTT = MAX_RTT_SAMPLE_MICROSECONDS; + } + + if (_ewmaRTT == -1) { + // first RTT sample - set _ewmaRTT to the value and set the variance to half the value + _ewmaRTT = lastRTT; + _rttVariance = lastRTT / 2; + } else { + // This updates the RTT using exponential weighted moving average + // This is the Jacobson's forumla for RTT estimation + // http://www.mathcs.emory.edu/~cheung/Courses/455/Syllabus/7-transport/Jacobson-88.pdf + + // Estimated RTT = (1 - x)(estimatedRTT) + (x)(sampleRTT) + // (where x = 0.125 via Jacobson) + + // Deviation = (1 - x)(deviation) + x |sampleRTT - estimatedRTT| + // (where x = 0.25 via Jacobson) + + static const int RTT_ESTIMATION_ALPHA = 8; + static const int RTT_ESTIMATION_VARIANCE_ALPHA = 4; + + _ewmaRTT = (_ewmaRTT * (RTT_ESTIMATION_ALPHA - 1) + lastRTT) / RTT_ESTIMATION_ALPHA; + _rttVariance = (_rttVariance * (RTT_ESTIMATION_VARIANCE_ALPHA- 1) + + abs(lastRTT - _ewmaRTT)) / RTT_ESTIMATION_VARIANCE_ALPHA; + } + + // add 1 to the number of ACKs during this RTT + ++_numACKs; + + // keep track of the lowest RTT during connection + _baseRTT = std::min(_baseRTT, lastRTT); + + // find the min RTT during the last RTT + _currentMinRTT = std::min(_currentMinRTT, lastRTT); + + auto sinceLastAdjustment = duration_cast(p_high_resolution_clock::now() - _lastAdjustmentTime).count(); + if (sinceLastAdjustment >= _ewmaRTT) { + performCongestionAvoidance(ack); + } + + // remove this sent packet time from the hash + _sentPacketTimes.erase(it); + } + + ++_numACKSinceFastRetransmit; + + // perform the fast re-transmit check if this is a duplicate ACK or if this is the first or second ACK + // after a previous fast re-transmit + if (ack == previousAck || _numACKSinceFastRetransmit < 3) { + // we may need to re-send ackNum + 1 if it has been more than our estimated timeout since it was sent + + auto it = _sentPacketTimes.find(ack + 1); + if (it != _sentPacketTimes.end()) { + auto estimatedTimeout = _ewmaRTT + _rttVariance * 4; + + auto now = p_high_resolution_clock::now(); + auto sinceSend = duration_cast(now - it->second).count(); + + if (sinceSend >= estimatedTimeout) { + // break out of slow start, we've decided this is loss + _slowStart = false; + + // reset the fast re-transmit counter + _numACKSinceFastRetransmit = 0; + + // return true so the caller knows we needed a fast re-transmit + return true; + } + } + + // if this is the 3rd duplicate ACK, we fallback to Reno's fast re-transmit + static const int RENO_FAST_RETRANSMIT_DUPLICATE_COUNT = 3; + + ++_duplicateACKCount; + + if (ack == previousAck && _duplicateACKCount == RENO_FAST_RETRANSMIT_DUPLICATE_COUNT) { + // break out of slow start, we just hit loss + _slowStart = false; + + // reset our fast re-transmit counters + _numACKSinceFastRetransmit = 0; + _duplicateACKCount = 0; + + // return true so the caller knows we needed a fast re-transmit + return true; + } + } else { + _duplicateACKCount = 0; + } + + // ACK processed, no fast re-transmit required + return false; +} + +void TCPVegasCC::performCongestionAvoidance(udt::SequenceNumber ack) { + static int VEGAS_ALPHA_SEGMENTS = 4; + static int VEGAS_BETA_SEGMENTS = 6; + static int VEGAS_GAMMA_SEGMENTS = 1; + + // http://pages.cs.wisc.edu/~akella/CS740/S08/740-Papers/BOP94.pdf + // Use the Vegas algorithm to see if we should + // increase or decrease the congestion window size, and by how much + + // Grab the minimum RTT seen during the last RTT (since the last performed congestion avoidance) + + // Taking the min avoids the effects of delayed ACKs + // (though congestion may be noticed a bit later) + int rtt = _currentMinRTT; + + int64_t windowSizeDiff = (int64_t) _congestionWindowSize * (rtt - _baseRTT) / _baseRTT; + + if (_numACKs <= 2) { + performRenoCongestionAvoidance(ack); + } else { + if (_slowStart) { + if (windowSizeDiff > VEGAS_GAMMA_SEGMENTS) { + // we're going too fast - this breaks us out of slow start and we switch to linear increase/decrease + _slowStart = false; + + int expectedWindowSize = _congestionWindowSize * _baseRTT / rtt; + _baseRTT = std::numeric_limits::max(); + + // drop the congestion window size to the expected size, if smaller + _congestionWindowSize = std::min(_congestionWindowSize, expectedWindowSize + 1); + + } else if (++_slowStartOddAdjust & 1) { + // we're in slow start and not going too fast + // this means that once every second RTT we perform exponential congestion window growth + _congestionWindowSize *= 2; + } + } else { + // this is the normal linear increase/decrease of the Vegas algorithm + // to figure out where the congestion window should be + if (windowSizeDiff > VEGAS_BETA_SEGMENTS) { + // the old congestion window was too fast (difference > beta) + // so reduce it to slow down + --_congestionWindowSize; + + } else if (windowSizeDiff < VEGAS_ALPHA_SEGMENTS) { + // there aren't enough packets on the wire, add more to the congestion window + ++_congestionWindowSize; + } else { + // sending rate seems good, no congestion window adjustment + } + } + } + + // we never allow the congestion window to be smaller than two packets + static int VEGAS_CW_MIN_PACKETS = 2; + if (_congestionWindowSize < VEGAS_CW_MIN_PACKETS) { + _congestionWindowSize = VEGAS_CW_MIN_PACKETS; + } else if (_congestionWindowSize > udt::MAX_PACKETS_IN_FLIGHT) { + _congestionWindowSize = udt::MAX_PACKETS_IN_FLIGHT; + } + + // mark this as the last adjustment time + _lastAdjustmentTime = p_high_resolution_clock::now(); + + // reset our state for the next RTT + _currentMinRTT = std::numeric_limits::max(); + + // reset our count of collected RTT samples + _numACKs = 0; +} + +bool TCPVegasCC::isCongestionWindowLimited() { + if (_slowStart) { + return true; + } else { + return seqlen(_sendCurrSeqNum, _lastACK) < _congestionWindowSize; + } +} + +void TCPVegasCC::performRenoCongestionAvoidance(SequenceNumber ack) { + if (!isCongestionWindowLimited()) { + return; + } + + int numAcked = _numACKs; + + if (_slowStart) { + // while in slow start we grow the congestion window by the number of ACKed packets + // allowing it to grow as high as the slow start threshold + int congestionWindow = _congestionWindowSize + numAcked; + + if (congestionWindow > udt::MAX_PACKETS_IN_FLIGHT) { + // we're done with slow start, set the congestion window to the slow start threshold + _congestionWindowSize = udt::MAX_PACKETS_IN_FLIGHT; + + // figure out how many left over ACKs we should apply using the regular reno congestion avoidance + numAcked = congestionWindow - udt::MAX_PACKETS_IN_FLIGHT; + } else { + _congestionWindowSize = congestionWindow; + numAcked = 0; + } + } + + // grab the size of the window prior to reno additive increase + int preAIWindowSize = _congestionWindowSize; + + if (numAcked > 0) { + // Once we are out of slow start, we use additive increase to grow the window slowly. + // We grow the congestion window by a single packet everytime the entire congestion window is sent. + + // If credits accumulated at a higher preAIWindowSize, apply them gently now. + if (_ackAICount >= preAIWindowSize) { + _ackAICount = 0; + ++_congestionWindowSize; + } + + // increase the window size by (1 / window size) for every ACK received + _ackAICount += numAcked; + if (_ackAICount >= preAIWindowSize) { + // when _ackAICount % preAIWindowSize == 0 then _ackAICount is 0 + // when _ackAICount % preAIWindowSize != 0 then _ackAICount is _ackAICount - (_ackAICount % preAIWindowSize) + + int delta = _ackAICount / preAIWindowSize; + + _ackAICount -= delta * preAIWindowSize; + _congestionWindowSize += delta; + } + } +} + +void TCPVegasCC::onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) { + if (_sentPacketTimes.find(seqNum) == _sentPacketTimes.end()) { + _sentPacketTimes[seqNum] = timePoint; + } +} + diff --git a/libraries/networking/src/udt/TCPVegasCC.h b/libraries/networking/src/udt/TCPVegasCC.h new file mode 100644 index 0000000000..862ea36d8f --- /dev/null +++ b/libraries/networking/src/udt/TCPVegasCC.h @@ -0,0 +1,77 @@ +// +// TCPVegasCC.h +// libraries/networking/src/udt +// +// Created by Stephen Birarda on 2016-09-20. +// Copyright 2016 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_TCPVegasCC_h +#define hifi_TCPVegasCC_h + +#include + +#include "CongestionControl.h" +#include "Constants.h" + +namespace udt { + + +class TCPVegasCC : public CongestionControl { +public: + TCPVegasCC(); + + virtual bool onACK(SequenceNumber ackNum, p_high_resolution_clock::time_point receiveTime) override; + virtual void onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) override {}; + virtual void onTimeout() override {}; + + virtual bool shouldNAK() override { return false; } + virtual bool shouldACK2() override { return false; } + virtual bool shouldProbe() override { return false; } + + virtual void onPacketSent(int wireSize, SequenceNumber seqNum, p_high_resolution_clock::time_point timePoint) override; + +protected: + virtual void performCongestionAvoidance(SequenceNumber ack); + virtual void setInitialSendSequenceNumber(SequenceNumber seqNum) override { _lastACK = seqNum - 1; } +private: + bool isCongestionWindowLimited(); + void performRenoCongestionAvoidance(SequenceNumber ack); + + using PacketTimeList = std::map; + PacketTimeList _sentPacketTimes; // Map of sequence numbers to sent time + + p_high_resolution_clock::time_point _lastAdjustmentTime; // Time of last congestion control adjustment + + bool _slowStart { true }; // Marker for slow start phase + + SequenceNumber _lastACK; // Sequence number of last packet that was ACKed + + int _numACKSinceFastRetransmit { 3 }; // Number of ACKs received since fast re-transmit, default avoids immediate re-transmit + + int _currentMinRTT; // Current min RTT during last RTT (since last congestion avoidance check), in microseconds + int _baseRTT; // Lowest RTT during connection, in microseconds + int _ewmaRTT { -1 }; // Exponential weighted moving average RTT + int _rttVariance { 0 }; // Variance in collected RTT values + + int _numACKs { 0 }; // Number of ACKs received during the last RTT (since last performed congestion avoidance) + + int _ackAICount { 0 }; // Counter for number of ACKs received for Reno additive increase + int _duplicateACKCount { 0 }; // Counter for duplicate ACKs received + + int _slowStartOddAdjust { 0 }; // Marker for every window adjustment every other RTT in slow-start + +}; + +} + + + + + +#endif // hifi_TCPVegasCC_h diff --git a/libraries/script-engine/src/BatchLoader.cpp b/libraries/script-engine/src/BatchLoader.cpp index 692c0ecd7a..605d7e95bd 100644 --- a/libraries/script-engine/src/BatchLoader.cpp +++ b/libraries/script-engine/src/BatchLoader.cpp @@ -38,6 +38,12 @@ void BatchLoader::start() { _started = true; + if (_urls.size() == 0) { + _finished = true; + emit finished(_data); + return; + } + for (const auto& rawURL : _urls) { QUrl url = expandScriptUrl(normalizeScriptURL(rawURL)); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 7a4265829b..a9e1defabe 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1199,6 +1199,11 @@ void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callbac } } + // If there are no URLs left to download, don't bother attempting to download anything and return early + if (urls.size() == 0) { + return; + } + BatchLoader* loader = new BatchLoader(urls); EntityItemID capturedEntityIdentifier = currentEntityIdentifier; QUrl capturedSandboxURL = currentSandboxURL; diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 818521ecc5..4e528ec52c 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -143,13 +143,13 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) { auto accountManager = DependencyManager::get(); - if (accountManager->hasValidAccessToken()) { - QUrlQuery urlQuery(_url.query()); - urlQuery.addQueryItem("access_token", accountManager->getAccountInfo().getAccessToken().token); - _url.setQuery(urlQuery); + if (_url.scheme() == "https" && accountManager->hasValidAccessToken()) { + static const QString HTTP_AUTHORIZATION_HEADER = "Authorization"; + QString bearerString = "Bearer " + accountManager->getAccountInfo().getAccessToken().token; + _request.setRawHeader(HTTP_AUTHORIZATION_HEADER.toLocal8Bit(), bearerString.toLocal8Bit()); } - } + if (!username.isEmpty()) { _url.setUserName(username); } diff --git a/libraries/shared/src/PortableHighResolutionClock.h b/libraries/shared/src/PortableHighResolutionClock.h index ec1b068e66..383c6cdf3e 100644 --- a/libraries/shared/src/PortableHighResolutionClock.h +++ b/libraries/shared/src/PortableHighResolutionClock.h @@ -21,6 +21,8 @@ #include +#include + #if defined(_MSC_VER) && _MSC_VER < 1900 #define WIN32_LEAN_AND_MEAN @@ -47,5 +49,8 @@ using p_high_resolution_clock = std::chrono::high_resolution_clock; #endif +Q_DECLARE_METATYPE(p_high_resolution_clock::time_point); + +static const int timePointMetaTypeID = qRegisterMetaType(); #endif // hifi_PortableHighResolutionClock_h diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 84d0aa0489..2130e84220 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -86,13 +86,10 @@ QString QmlWebWindowClass::getURL() const { return result.toString(); } -// HACK find a good place to declare and store this -extern QString fixupHifiUrl(const QString& urlString); - void QmlWebWindowClass::setURL(const QString& urlString) { DependencyManager::get()->executeOnUiThread([=] { if (!_qmlWindow.isNull()) { - _qmlWindow->setProperty(URL_PROPERTY, fixupHifiUrl(urlString)); + _qmlWindow->setProperty(URL_PROPERTY, urlString); } }); } diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index e32c6d5a04..731afb3acb 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -19,7 +19,7 @@ class QmlWebWindowClass : public QmlWindowClass { public: static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - public slots: +public slots: QString getURL() const; void setURL(const QString& url); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index e2a982b308..bc7c77a286 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -2091,12 +2091,12 @@ function MyController(hand) { var TEAR_AWAY_DISTANCE = 0.1; var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props); if (dist > TEAR_AWAY_DISTANCE) { - this.autoUnequipCounter += 1; + this.autoUnequipCounter += deltaTime; } else { this.autoUnequipCounter = 0; } - if (this.autoUnequipCounter > 1) { + if (this.autoUnequipCounter > 0.25) { // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand." + props.parentID + ", dist = " + dist); diff --git a/tests/render-perf/src/main.cpp b/tests/render-perf/src/main.cpp index 8abb9f54e5..3df36c0edf 100644 --- a/tests/render-perf/src/main.cpp +++ b/tests/render-perf/src/main.cpp @@ -18,10 +18,12 @@ #include #include #include +#include #include #include #include + #include #include #include diff --git a/tools/udt-test/src/UDTTest.cpp b/tools/udt-test/src/UDTTest.cpp index af94d2294b..6161dbfdbc 100644 --- a/tools/udt-test/src/UDTTest.cpp +++ b/tools/udt-test/src/UDTTest.cpp @@ -25,7 +25,7 @@ const QCommandLineOption TARGET_OPTION { "IP:PORT or HOSTNAME:PORT" }; const QCommandLineOption PACKET_SIZE { - "packet-size", "size for sent packets in bytes (defaults to " + QString(MAX_PACKET_SIZE) + ")", "bytes", + "packet-size", "size for sent packets in bytes (defaults to " + QString::number(udt::MAX_PACKET_SIZE) + ")", "bytes", QString(udt::MAX_PACKET_SIZE) }; const QCommandLineOption MIN_PACKET_SIZE {