From ab4f110ea4e68aea09eb6b4da01d281c0a941ec9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 22 Mar 2018 11:07:06 -0700 Subject: [PATCH 01/65] add single quote tests to JS baking tests --- tests/baking/src/JSBakerTest.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/baking/src/JSBakerTest.cpp b/tests/baking/src/JSBakerTest.cpp index 082ffb047f..e39ccc6b9a 100644 --- a/tests/baking/src/JSBakerTest.cpp +++ b/tests/baking/src/JSBakerTest.cpp @@ -66,12 +66,14 @@ void JSBakerTest::setTestCases() { _testCases.emplace_back("'abcd1234$%^&[](){}'\na", "'abcd1234$%^&[](){}'\na"); _testCases.emplace_back("\"abcd1234$%^&[](){}\"\na", "\"abcd1234$%^&[](){}\"\na"); _testCases.emplace_back("`abcd1234$%^&[](){}`\na", "`abcd1234$%^&[](){}`a"); + _testCases.emplace_back("\' \';", "\' \';"); + _testCases.emplace_back("\'//single line comment\nvar b=2;\';", "\'//single line comment\nvar b=2;\';"); // Edge Cases //No semicolon to terminate an expression, instead a new line used for termination _testCases.emplace_back("var x=5\nvar y=6;", "var x=5\nvar y=6;"); - + //a + ++b is minified as a+ ++b. _testCases.emplace_back("a + ++b", "a + ++b"); From f27e8868eb730b021c675294ec3ea20619d5f7ce Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 28 Mar 2018 12:25:12 +1300 Subject: [PATCH 02/65] Add HMD JSDoc --- .../src/scripting/HMDScriptingInterface.h | 257 +++++++++++++++++- .../AbstractHMDScriptingInterface.h | 21 ++ tools/jsdoc/plugins/hifi.js | 5 +- 3 files changed, 278 insertions(+), 5 deletions(-) diff --git a/interface/src/scripting/HMDScriptingInterface.h b/interface/src/scripting/HMDScriptingInterface.h index ef8ea95704..1acafab361 100644 --- a/interface/src/scripting/HMDScriptingInterface.h +++ b/interface/src/scripting/HMDScriptingInterface.h @@ -24,6 +24,36 @@ class QScriptEngine; #include +/**jsdoc + * The HMD API provides access to the HMD used in VR display mode. + * + * @namespace HMD + * @property {Vec3} position - The position of the HMD if currently in VR display mode, otherwise + * {@link Vec3(0)|Vec3.ZERO}. Read-only. + * @property {Quat} orientation - The orientation of the HMD if currently in VR display mode, otherwise + * {@link Quat(0)|Quat.IDENTITY}.Read-only. + * @property {boolean} active - true if the display mode is HMD, otherwise false. Read-only. + * @property {boolean} mounted - true if currently in VR display mode and the HMD is being worn, otherwise + * false. Read-only. + * + * @property {number} playerHeight - The real-world height of the user. Read-only. Currently always returns a + * value of 1.755. + * @property {number} eyeHeight - The real-world height of the user's eyes. Read-only. Currently always returns a + * value of 1.655. + * @property {number} ipd - The inter-pupillary distance (distance between eyes) of the user, used for rendering. Defaults to + * the human average of 0.064 unless set by the HMD. Read-only. + * @property {number} ipdScale=1.0 - A scale factor applied to the ipd property value. + * + * @property {boolean} showTablet - true if the tablet is being displayed, false otherwise. + * Read-only. + * @property {boolean} tabletContextualMode - true if the tablet has been opened in contextual mode, otherwise + * false. In contextual mode, the tablet has been opened at a specific world position and orientation rather + * than at a position and orientation relative to the user. Read-only. + * @property {Uuid} tabletID - The UUID of the tablet body model overlay. + * @property {Uuid} tabletScreenID - The UUID of the tablet's screen overlay. + * @property {Uuid} homeButtonID - The UUID of the tablet's "home" button overlay. + * @property {Uuid} homeButtonHighlightID - The UUID of the tablet's "home" button highlight overlay. + */ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency { Q_OBJECT Q_PROPERTY(glm::vec3 position READ getPosition) @@ -37,26 +67,200 @@ class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Depen Q_PROPERTY(QUuid tabletScreenID READ getCurrentTabletScreenID WRITE setCurrentTabletScreenID) public: + + /**jsdoc + * Calculate the intersection of a ray with the HUD overlay. + * @function HMD.calculateRayUICollisionPoint + * @param {Vec3} position - The origin of the ray. + * @param {Vec3} direction - The direction of the ray. + * @returns {Vec3} The point of intersection with the HUD overlay if it intersects, otherwise {@link Vec3(0)|Vec3.ZERO}. + */ Q_INVOKABLE glm::vec3 calculateRayUICollisionPoint(const glm::vec3& position, const glm::vec3& direction) const; + + /**jsdoc + * Get the 2D HUD overlay coordinates of a 3D point on the HUD overlay. + * 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay. + * @function HMD.overlayFromWorldPoint + * @param {Vec3} position - The point on the HUD overlay in world coordinates. + * @returns {Vec2} The point on the HUD overlay in HUD coordinates. + * @example Draw a square on the HUD overlay in the direction you're looking. + * var hudIntersection = HMD.calculateRayUICollisionPoint(MyAvatar.getHeadPosition(), + * Quat.getForward(MyAvatar.headOrientation)); + * var hudPoint = HMD.overlayFromWorldPoint(hudIntersection); + * + * var DIMENSIONS = { x: 50, y: 50 }; + * var square = Overlays.addOverlay("rectangle", { + * x: hudPoint.x - DIMENSIONS.x / 2, + * y: hudPoint.y - DIMENSIONS.y / 2, + * width: DIMENSIONS.x, + * height: DIMENSIONS.y, + * color: { red: 255, green: 0, blue: 0 } + * }); + * + * Script.scriptEnding.connect(function () { + * Overlays.deleteOverlay(square); + * }); + */ Q_INVOKABLE glm::vec2 overlayFromWorldPoint(const glm::vec3& position) const; + + /**jsdoc + * Get the 3D world coordinates of a 2D point on the HUD overlay. + * 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay. + * @function HMD.worldPointFromOverlay + * @param {Vec2} coordinates - The point on the HUD overlay in HUD coordinates. + * @returns {Vec3} The point on the HUD overlay in world coordinates. + */ Q_INVOKABLE glm::vec3 worldPointFromOverlay(const glm::vec2& overlay) const; + + /**jsdoc + * Get the 2D point on the HUD overlay represented by given spherical coordinates. + * 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay. + * Spherical coordinates are polar coordinates in radians with { x: 0, y: 0 } being the center of the HUD + * overlay. + * @function HMD.sphericalToOverlay + * @param {Vec2} sphericalPos - The point on the HUD overlay in spherical coordinates. + * @returns {Vec2} The point on the HUD overlay in HUD coordinates. + */ Q_INVOKABLE glm::vec2 sphericalToOverlay(const glm::vec2 & sphericalPos) const; + + /**jsdoc + * Get the spherical coordinates of a 2D point on the HUD overlay. + * 2D HUD overlay coordinates are pixels with the origin at the top left of the overlay. + * Spherical coordinates are polar coordinates in radians with { x: 0, y: 0 } being the center of the HUD + * overlay. + * @function HMD.overlayToSpherical + * @param {Vec2} overlayPos - The point on the HUD overlay in HUD coordinates. + * @returns {Vec2} The point on the HUD overlay in spherical coordinates. + */ Q_INVOKABLE glm::vec2 overlayToSpherical(const glm::vec2 & overlayPos) const; + + /**jsdoc + * Recenter the HMD HUD to the current HMD position and orientation. + * @function HMD.centerUI + */ + Q_INVOKABLE void centerUI(); + + + /**jsdoc + * Get the name of the HMD audio input device. + * @function HMD.preferredAudioInput + * @returns {string} The name of the HMD audio input device if in HMD mode, otherwise an empty string. + */ Q_INVOKABLE QString preferredAudioInput() const; + + /**jsdoc + * Get the name of the HMD audio output device. + * @function HMD.preferredAudioOutput + * @returns {string} The name of the HMD audio output device if in HMD mode, otherwise an empty string. + */ Q_INVOKABLE QString preferredAudioOutput() const; + + /**jsdoc + * Check whether there is an HMD available. + * @function HMD.isHMDAvailable + * @param {string} [name=""] - The name of the HMD to check for, e.g., "Oculus Rift". The name is the same as + * may be displayed in Interface's "Display" menu. If no name is specified then any HMD matches. + * @returns {boolean} true if an HMD of the specified name is available, otherwise + * false. + * @example Report on HMD availability. + * print("Is any HMD available: " + HMD.isHMDAvailable()); + * print("Is an Oculus Rift HMD available: " + HMD.isHMDAvailable("Oculus Rift")); + * print("Is a Vive HMD available: " + HMD.isHMDAvailable("OpenVR (Vive)")); + */ Q_INVOKABLE bool isHMDAvailable(const QString& name = ""); + + /**jsdoc + * Check whether there is an HMD head controller available. + * @function HMD.isHeadControllerAvailable + * @param {string} [name=""] - The name of the HMD head controller to check for, e.g., "Oculus". If no name is + * specified then any HMD head controller matches. + * @returns {boolean} true if an HMD head controller of the specified name is available, + * otherwise false. + * @example Report HMD head controller availability. + * print("Is any HMD head controller available: " + HMD.isHeadControllerAvailable()); + * print("Is an Oculus head controller available: " + HMD.isHeadControllerAvailable("Oculus")); + * print("Is a Vive head controller available: " + HMD.isHeadControllerAvailable("OpenVR")); + */ Q_INVOKABLE bool isHeadControllerAvailable(const QString& name = ""); + + /**jsdoc + * Check whether there are HMD hand controllers available. + * @function HMD.isHandControllerAvailable + * @param {string} [name=""] - The name of the HMD hand controller to check for, e.g., "Oculus". If no name is + * specified then any HMD hand controller matches. + * @returns {boolean} true if an HMD hand controller of the specified name is available, + * otherwise false. + * @example Report HMD hand controller availability. + * print("Are any HMD hand controllers available: " + HMD.isHandControllerAvailable()); + * print("Are Oculus hand controllers available: " + HMD.isHandControllerAvailable("Oculus")); + * print("Are Vive hand controllers available: " + HMD.isHandControllerAvailable("OpenVR")); + */ Q_INVOKABLE bool isHandControllerAvailable(const QString& name = ""); + + /**jsdoc + * Check whether there are specific HMD controllers available. + * @function HMD.isSubdeviceContainingNameAvailable + * @param {string} - The name of the HMD controller to check for, e.g., "OculusTouch". + * @returns {boolean} true if an HMD controller with a name containing the specified name is + * available, otherwise false. + * @example Report if particular Oculus controllers are available. + * print("Is an Oculus Touch controller available: " + HMD.isSubdeviceContainingNameAvailable("Touch")); + * print("Is an Oculus Remote controller available: " + HMD.isSubdeviceContainingNameAvailable("Remote")); + */ Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name); + /**jsdoc + * Signal that models of the HMD hand controllers being used should be displayed. The models are displayed at their actual, + * real-world locations. + * @function HMD.requestShowHandControllers + * @example Show your hand controllers for 10 seconds. + * HMD.requestShowHandControllers(); + * Script.setTimeout(function () { + * HMD.requestHideHandControllers(); + * }, 10000); + */ Q_INVOKABLE void requestShowHandControllers(); + + /**jsdoc + * Signal that it is no longer necessary to display models of the HMD hand controllers being used. If no other scripts + * want the models displayed then they are no longer displayed. + * @function HMD.requestHideHandControllers + */ Q_INVOKABLE void requestHideHandControllers(); + + /**jsdoc + * Check whether any script wants models of the HMD hand controllers displayed. Requests are made and canceled using + * {@link HMD.requestShowHandControllers|requestShowHandControllers} and + * {@link HMD.requestHideHandControllers|requestHideHandControllers}. + * @function HMD.shouldShowHandControllers + * @returns {boolean} true if any script is requesting that HMD hand controller models be displayed. + */ Q_INVOKABLE bool shouldShowHandControllers() const; + + /**jsdoc + * Causes the borders and decorations in HUD windows to be enlarged when the laser intersects them in HMD mode. By default, + * borders and decorations are not enlarged. + * @function HMD.activateHMDHandMouse + */ Q_INVOKABLE void activateHMDHandMouse(); + + /**jsdoc + * Causes the border and decorations in HUD windows to no longer be enlarged when the laser intersects them in HMD mode. By + * default, borders and decorations are not enlarged. + * @function HMD.deactivateHMDHandMouse + */ Q_INVOKABLE void deactivateHMDHandMouse(); + + /**jsdoc + * Suppress the activation of the HMD-provided keyboard, if any. Successful calls should be balanced with a call to + * {@link HMD.unspressKeyboard|unspressKeyboard} within a reasonable amount of time. + * @function HMD.suppressKeyboard + * @returns {boolean} true if the current HMD provides a keyboard and it was successfully suppressed (e.g., it + * isn't being displayed), otherwise false. + */ /// Suppress the activation of any on-screen keyboard so that a script operation will /// not be interrupted by a keyboard popup /// Returns false if there is already an active keyboard displayed. @@ -65,21 +269,68 @@ public: /// call to unsuppressKeyboard() within a reasonable amount of time Q_INVOKABLE bool suppressKeyboard(); + /**jsdoc + * Unsuppress the activation of the HMD-provided keyboard, if any. + * @function HMD.unsuppressKeyboard + */ /// Enable the keyboard following a suppressKeyboard call Q_INVOKABLE void unsuppressKeyboard(); + /**jsdoc + * Check whether the HMD-provided keyboard, if any, is visible. + * @function HMD.isKeyboardVisible + * @returns {boolean} true if the current HMD provides a keyboard and it is visible, otherwise + * false. + */ /// Query the display plugin to determine the current VR keyboard visibility Q_INVOKABLE bool isKeyboardVisible(); - // rotate the overlay UI sphere so that it is centered about the the current HMD position and orientation - Q_INVOKABLE void centerUI(); - + /**jsdoc + * Closes the tablet if it is open. + * @function HMD.closeTablet + */ Q_INVOKABLE void closeTablet(); + /**jsdoc + * Opens the tablet if the tablet is used in the current display mode and it isn't already showing, and sets the tablet to + * contextual mode if requested. In contextual mode, the page displayed on the tablet is wholly controlled by script (i.e., + * the user cannot navigate to another). + * @function HMD.openTablet + * @param {boolean} [contextualMode=false] - If true then the tablet is opened at a specific position and + * orientation already set by the script, otherwise it opens at a position and orientation relative to the user. For + * contextual mode, set the world or local position and orientation of the HMD.tabletID overlay. + */ Q_INVOKABLE void openTablet(bool contextualMode = false); signals: + /**jsdoc + * Triggered when a request to show or hide models of the HMD hand controllers is made using + * {@link HMD.requestShowHandControllers|requestShowHandControllers} or + * {@link HMD.requestHideHandControllers|requestHideHandControllers}. + * @function HMD.shouldShowHandControllersChanged + * @returns {Signal} + * @example Report when showing of hand controllers changes. + * function onShouldShowHandControllersChanged() { + * print("Should show hand controllers: " + HMD.shouldShowHandControllers()); + * } + * HMD.shouldShowHandControllersChanged.connect(onShouldShowHandControllersChanged); + * + * HMD.requestShowHandControllers(); + * Script.setTimeout(function () { + * HMD.requestHideHandControllers(); + * }, 10000); + */ bool shouldShowHandControllersChanged(); + + /**jsdoc + * Triggered when the HMD.mounted property value changes. + * @function HMD.mountedChanged + * @returns {Signal} + * @example Report when there's a change in the HMD being worn. + * HMD.mountedChanged.connect(function () { + * print("Mounted changed. HMD is mounted: " + HMD.mounted); + * }); + */ void mountedChanged(); public: diff --git a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h index f260fa959f..36e1952d3c 100644 --- a/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h +++ b/libraries/display-plugins/src/display-plugins/AbstractHMDScriptingInterface.h @@ -12,6 +12,7 @@ #include +// These properties have JSDoc documentation in HMDScriptingInterface.h. class AbstractHMDScriptingInterface : public QObject { Q_OBJECT Q_PROPERTY(bool active READ isHMDMode) @@ -30,7 +31,27 @@ public: bool isHMDMode() const; signals: + /**jsdoc + * Triggered when the HMD.ipdScale property value changes. + * @function HMD.IPDScaleChanged + * @returns {Signal} + */ void IPDScaleChanged(); + + /**jsdoc + * Triggered when Interface's display mode changes and when the user puts on or takes off their HMD. + * @function HMD.displayModeChanged + * @param {boolean} isHMDMode - true if the display mode is HMD, otherwise false. This is the + * same value as provided by HMD.active. + * @returns {Signal} + * @example Report when the display mode changes. + * HMD.displayModeChanged.connect(function (isHMDMode) { + * print("Display mode changed"); + * print("isHMD = " + isHMD); + * print("HMD.active = " + HMD.active); + * print("HMD.mounted = " + HMD.mounted); + * }); + */ void displayModeChanged(bool isHMDMode); private: diff --git a/tools/jsdoc/plugins/hifi.js b/tools/jsdoc/plugins/hifi.js index 1f73f14b2b..e4da94ccd5 100644 --- a/tools/jsdoc/plugins/hifi.js +++ b/tools/jsdoc/plugins/hifi.js @@ -24,11 +24,12 @@ exports.handlers = { '../../libraries/animation/src', '../../libraries/avatars/src', '../../libraries/controllers/src/controllers/', - '../../libraries/graphics-scripting/src/graphics-scripting/', + '../../libraries/display-plugins/src/display-plugins/', '../../libraries/entities/src', + '../../libraries/graphics-scripting/src/graphics-scripting/', '../../libraries/model-networking/src/model-networking/', - '../../libraries/octree/src', '../../libraries/networking/src', + '../../libraries/octree/src', '../../libraries/physics/src', '../../libraries/pointers/src', '../../libraries/script-engine/src', From b35ee5ce8aece5472e7b8a650192ff6b6ffa667a Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 28 Mar 2018 12:25:49 +1300 Subject: [PATCH 03/65] Fix JSDoc links to Vec3 constants --- libraries/entities/src/EntityItemProperties.cpp | 16 ++++++++-------- .../entities/src/EntityScriptingInterface.h | 8 ++++---- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f9a96d2293..90d6d942fd 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -481,13 +481,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Vec3} position=0,0,0 - The position of the entity. * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. * @property {Vec3} registrationPoint=0.5,0.5,0.5 - The point in the entity that is set to the entity's position and is rotated - * about, {@link Vec3|Vec3.ZERO} – {@link Vec3|Vec3.ONE}. A value of {@link Vec3|Vec3.ZERO} is the entity's - * minimum x, y, z corner; a value of {@link Vec3|Vec3.ONE} is the entity's maximum x, y, z corner. + * about, {@link Vec3(0)|Vec3.ZERO} – {@link Vec3(0)|Vec3.ONE}. A value of {@link Vec3(0)|Vec3.ZERO} is the entity's + * minimum x, y, z corner; a value of {@link Vec3(0)|Vec3.ONE} is the entity's maximum x, y, z corner. * * @property {Vec3} naturalPosition=0,0,0 - The center of the entity's unscaled mesh model if it has one, otherwise - * {@link Vec3|Vec3.ZERO}. Read-only. + * {@link Vec3(0)|Vec3.ZERO}. Read-only. * @property {Vec3} naturalDimensions - The dimensions of the entity's unscaled mesh model if it has one, otherwise - * {@link Vec3|Vec3.ONE}. Read-only. + * {@link Vec3(0)|Vec3.ONE}. Read-only. * * @property {Vec3} velocity=0,0,0 - The linear velocity of the entity in m/s with respect to world coordinates. * @property {number} damping=0.39347 - How much to slow down the linear velocity of an entity over time, 0.0 @@ -504,13 +504,13 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {Vec3} gravity=0,0,0 - The acceleration due to gravity in m/s2 that the entity should move with, in * world coordinates. Set to { x: 0, y: -9.8, z: 0 } to simulate Earth's gravity. Gravity is applied to an * entity's motion only if its dynamic property is true. If changing an entity's - * gravity from {@link Vec3|Vec3.ZERO}, you need to give it a small velocity in order to kick off - * physics simulation. + * gravity from {@link Vec3(0)|Vec3.ZERO}, you need to give it a small velocity in order to kick + * off physics simulation. * The gravity value is applied in addition to the acceleration value. * @property {Vec3} acceleration=0,0,0 - A general acceleration in m/s2 that the entity should move with, in world * coordinates. The acceleration is applied to an entity's motion only if its dynamic property is - * true. If changing an entity's acceleration from {@link Vec3|Vec3.ZERO}, you need to give it a - * small velocity in order to kick off physics simulation. + * true. If changing an entity's acceleration from {@link Vec3(0)|Vec3.ZERO}, you need to give it + * a small velocity in order to kick off physics simulation. * The acceleration value is applied in addition to the gravity value. * @property {number} restitution=0.5 - The "bounciness" of an entity when it collides, 0.0 – * 0.99. The higher the value, the more bouncy. diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 9613a7a310..633f427342 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -699,7 +699,7 @@ public slots: * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. * @returns {Vec3} The world coordinates of the voxelCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. * @example Create a PolyVox cube with the 0,0,0 voxel replaced by a sphere. * // Cube PolyVox with 0,0,0 voxel missing. * var polyVox = Entities.addEntity({ @@ -734,7 +734,7 @@ public slots: * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. * @param {Vec3} worldCoords - The world coordinates. May be outside the entity's bounding box. * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. The value may be fractional. + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. The value may be fractional. */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 worldCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 worldCoords); @@ -746,7 +746,7 @@ public slots: * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. * @param {Vec3} voxelCoords - The voxel coordinates. May be fractional and outside the entity's bounding box. * @returns {Vec3} The local coordinates of the voxelCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. * @example Get the world dimensions of a voxel in a PolyVox entity. * var polyVox = Entities.addEntity({ * type: "PolyVox", @@ -768,7 +768,7 @@ public slots: * @param {Uuid} entityID - The ID of the {@link Entities.EntityType|PolyVox} entity. * @param {Vec3} localCoords - The local coordinates. May be outside the entity's bounding box. * @returns {Vec3} The voxel coordinates of the worldCoords if the entityID is a - * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3|Vec3.ZERO}. The value may be fractional. + * {@link Entities.EntityType|PolyVox} entity, otherwise {@link Vec3(0)|Vec3.ZERO}. The value may be fractional. */ // FIXME move to a renderable entity interface Q_INVOKABLE glm::vec3 localCoordsToVoxelCoords(const QUuid& entityID, glm::vec3 localCoords); From 1bcb9738f0a5ee4e7ed50c6bb5581c2cde50f414 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Wed, 28 Mar 2018 12:26:08 +1300 Subject: [PATCH 04/65] Miscellaneous fixes noticed in passing --- interface/resources/qml/windows/Decoration.qml | 2 +- libraries/entities/src/EntityItemProperties.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/windows/Decoration.qml b/interface/resources/qml/windows/Decoration.qml index 843ae25596..f8fd9f4e6c 100644 --- a/interface/resources/qml/windows/Decoration.qml +++ b/interface/resources/qml/windows/Decoration.qml @@ -1,5 +1,5 @@ // -// DefaultFrame.qml +// Decoration.qml // // Created by Bradley Austin Davis on 12 Jan 2016 // Copyright 2016 High Fidelity, Inc. diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 90d6d942fd..8602f60e3d 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -476,7 +476,8 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { * @property {boolean} visible=true - Whether or not the entity is rendered. If true then the entity is rendered. * @property {boolean} canCastShadows=true - Whether or not the entity casts shadows. Currently applicable only to * {@link Entities.EntityType|Model} and {@link Entities.EntityType|Shape} entities. Shadows are cast if inside a - * {@link Entities.EntityType|Zone} entity with castShadows enabled in its {@link Entities.EntityProperties-Zone|keyLight} property. + * {@link Entities.EntityType|Zone} entity with castShadows enabled in its + * {@link Entities.EntityProperties-Zone|keyLight} property. * * @property {Vec3} position=0,0,0 - The position of the entity. * @property {Quat} rotation=0,0,0,1 - The orientation of the entity with respect to world coordinates. From d1cb4aab89a0220e82a76f301d16ebc8b555e6d5 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 2 Apr 2018 17:55:23 -0700 Subject: [PATCH 05/65] Make BakedAssetType a scoped enum --- assignment-client/src/assets/AssetServer.cpp | 26 ++++++++++---------- assignment-client/src/assets/AssetServer.h | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 1eb43a45a5..b2fb74f77d 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -61,13 +61,13 @@ static const ScriptBakeVersion CURRENT_SCRIPT_BAKE_VERSION = (ScriptBakeVersion) BakedAssetType assetTypeForExtension(const QString& extension) { auto extensionLower = extension.toLower(); if (BAKEABLE_MODEL_EXTENSIONS.contains(extensionLower)) { - return Model; + return BakedAssetType::Model; } else if (BAKEABLE_TEXTURE_EXTENSIONS.contains(extensionLower.toLocal8Bit())) { - return Texture; + return BakedAssetType::Texture; } else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extensionLower)) { - return Script; + return BakedAssetType::Script; } - return Undefined; + return BakedAssetType::Undefined; } BakedAssetType assetTypeForFilename(const QString& filename) { @@ -82,11 +82,11 @@ BakedAssetType assetTypeForFilename(const QString& filename) { QString bakedFilenameForAssetType(BakedAssetType type) { switch (type) { - case Model: + case BakedAssetType::Model: return BAKED_MODEL_SIMPLE_NAME; - case Texture: + case BakedAssetType::Texture: return BAKED_TEXTURE_SIMPLE_NAME; - case Script: + case BakedAssetType::Script: return BAKED_SCRIPT_SIMPLE_NAME; default: return ""; @@ -95,11 +95,11 @@ QString bakedFilenameForAssetType(BakedAssetType type) { BakeVersion currentBakeVersionForAssetType(BakedAssetType type) { switch (type) { - case Model: + case BakedAssetType::Model: return (BakeVersion)CURRENT_MODEL_BAKE_VERSION; - case Texture: + case BakedAssetType::Texture: return (BakeVersion)CURRENT_TEXTURE_BAKE_VERSION; - case Script: + case BakedAssetType::Script: return (BakeVersion)CURRENT_SCRIPT_BAKE_VERSION; default: return 0; @@ -222,7 +222,7 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU BakedAssetType type = assetTypeForFilename(path); - if (type == Undefined) { + if (type == BakedAssetType::Undefined) { return false; } @@ -241,7 +241,7 @@ bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetU AssetMeta meta; std::tie(loaded, meta) = readMetaFile(assetHash); - if (type == Texture && !loaded) { + if (type == BakedAssetType::Texture && !loaded) { return false; } @@ -1546,7 +1546,7 @@ bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool auto it = _fileMappings.find(path); if (it != _fileMappings.end()) { auto type = assetTypeForFilename(path); - if (type == Undefined) { + if (type == BakedAssetType::Undefined) { continue; } QString bakedFilename = bakedFilenameForAssetType(type); diff --git a/assignment-client/src/assets/AssetServer.h b/assignment-client/src/assets/AssetServer.h index a55a15e6fc..fb88df0171 100644 --- a/assignment-client/src/assets/AssetServer.h +++ b/assignment-client/src/assets/AssetServer.h @@ -27,7 +27,7 @@ using BakeVersion = int; static const BakeVersion INITIAL_BAKE_VERSION = 0; static const BakeVersion NEEDS_BAKING_BAKE_VERSION = -1; -enum BakedAssetType : int { +enum class BakedAssetType : int { Model = 0, Texture, Script, From 895023ca4b5e114ca12e3d3946aeea2f82282bb9 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 5 Mar 2018 17:43:14 -0800 Subject: [PATCH 06/65] Fix unprotected tree traversals --- .../src/entities/EntityServer.cpp | 45 ++++++++++++------- .../src/octree/OctreeSendThread.cpp | 4 +- libraries/entities/src/EntityTree.cpp | 41 +++++++++++------ .../entities/src/SimpleEntitySimulation.cpp | 9 +++- 4 files changed, 65 insertions(+), 34 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index d2bcbf2886..e3aca52ee5 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -363,7 +363,9 @@ void EntityServer::nodeAdded(SharedNodePointer node) { void EntityServer::nodeKilled(SharedNodePointer node) { EntityTreePointer tree = std::static_pointer_cast(_tree); - tree->deleteDescendantsOfAvatar(node->getUUID()); + tree->withWriteLock([&] { + tree->deleteDescendantsOfAvatar(node->getUUID()); + }); tree->forgetAvatarID(node->getUUID()); OctreeServer::nodeKilled(node); } @@ -451,8 +453,6 @@ void EntityServer::domainSettingsRequestFailed() { void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << "Starting Dynamic Domain Verification..."; - QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); - EntityTreePointer tree = std::static_pointer_cast(_tree); QHash localMap(tree->getEntityCertificateIDMap()); @@ -460,15 +460,19 @@ void EntityServer::startDynamicDomainVerification() { qCDebug(entities) << localMap.size() << "entities in _entityCertificateIDMap"; while (i.hasNext()) { i.next(); + const auto& certificateID = i.key(); + const auto& entityID = i.value(); - EntityItemPointer entity = tree->findEntityByEntityItemID(i.value()); + EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); if (entity) { if (!entity->getProperties().verifyStaticCertificateProperties()) { - qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed" + qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << entityID << "failed" << "static certificate verification."; // Delete the entity if it doesn't pass static certificate verification - tree->deleteEntity(i.value(), true); + tree->withWriteLock([&] { + tree->deleteEntity(entityID, true); + }); } else { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest; @@ -477,39 +481,46 @@ void EntityServer::startDynamicDomainVerification() { QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL(); requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location"); QJsonObject request; - request["certificate_id"] = i.key(); + request["certificate_id"] = certificateID; networkRequest.setUrl(requestURL); - QNetworkReply* networkReply = NULL; - networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + + connect(networkReply, &QNetworkReply::finished, this, [this, entityID, networkReply] { + EntityTreePointer tree = std::static_pointer_cast(_tree); - connect(networkReply, &QNetworkReply::finished, [=]() { QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); jsonObject = jsonObject["data"].toObject(); if (networkReply->error() == QNetworkReply::NoError) { + QString thisDomainID = DependencyManager::get()->getDomainID().remove(QRegExp("\\{|\\}")); if (jsonObject["domain_id"].toString() != thisDomainID) { + EntityItemPointer entity = tree->findEntityByEntityItemID(entityID); if (entity->getAge() > (_MAXIMUM_DYNAMIC_DOMAIN_VERIFICATION_TIMER_MS/MSECS_PER_SECOND)) { qCDebug(entities) << "Entity's cert's domain ID" << jsonObject["domain_id"].toString() - << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << i.value(); - tree->deleteEntity(i.value(), true); + << "doesn't match the current Domain ID" << thisDomainID << "; deleting entity" << entityID; + tree->withWriteLock([&] { + tree->deleteEntity(entityID, true); + }); } else { - qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << i.value(); + qCDebug(entities) << "Entity failed dynamic domain verification, but was created too recently to necessitate deletion:" << entityID; } } else { - qCDebug(entities) << "Entity passed dynamic domain verification:" << i.value(); + qCDebug(entities) << "Entity passed dynamic domain verification:" << entityID; } } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << i.value() + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityID << "More info:" << jsonObject; - tree->deleteEntity(i.value(), true); + tree->withWriteLock([&] { + tree->deleteEntity(entityID, true); + }); } networkReply->deleteLater(); }); } } else { - qCWarning(entities) << "During DDV, an entity with ID" << i.value() << "was NOT found in the Entity Tree!"; + qCWarning(entities) << "During DDV, an entity with ID" << entityID << "was NOT found in the Entity Tree!"; } } diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index d5b9da7353..715e83f403 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -400,7 +400,9 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode* if (shouldTraverseAndSend(nodeData)) { quint64 start = usecTimestampNow(); - traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); + _myServer->getOctree()->withReadLock([&]{ + traverseTreeAndSendContents(node, nodeData, viewFrustumChanged, isFullScene); + }); // Here's where we can/should allow the server to send other data... // send the environment packet diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 2cf66911a4..03258cd52d 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1151,7 +1151,9 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) }); connect(_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { qCDebug(entities) << "Ownership challenge timed out, deleting entity" << entityItemID; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); if (_challengeOwnershipTimeoutTimer) { _challengeOwnershipTimeoutTimer->stop(); _challengeOwnershipTimeoutTimer->deleteLater(); @@ -1259,7 +1261,9 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri if (text == "") { qCDebug(entities) << "CRITICAL ERROR: Couldn't compute nonce. Deleting entity..."; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } else { qCDebug(entities) << "Challenging ownership of Cert ID" << certID; // 2. Send the nonce to the rezzing avatar's node @@ -1322,8 +1326,7 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt request["certificate_id"] = certID; networkRequest.setUrl(requestURL); - QNetworkReply* networkReply = NULL; - networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); connect(networkReply, &QNetworkReply::finished, [=]() { QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); @@ -1332,14 +1335,20 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt if (networkReply->error() == QNetworkReply::NoError) { if (!jsonObject["invalid_reason"].toString().isEmpty()) { qCDebug(entities) << "invalid_reason not empty, deleting entity" << entityItemID; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { qCDebug(entities) << "'transfer_status' is 'failed', deleting entity" << entityItemID; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { if (isRetryingValidation) { qCDebug(entities) << "'transfer_status' is 'pending' after retry, deleting entity" << entityItemID; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } else { if (thread() != QThread::currentThread()) { QMetaObject::invokeMethod(this, "startPendingTransferStatusTimer", @@ -1362,7 +1371,9 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID << "More info:" << jsonObject; - deleteEntity(entityItemID, true); + withWriteLock([&] { + deleteEntity(entityItemID, true); + }); } networkReply->deleteLater(); @@ -1796,9 +1807,9 @@ void EntityTree::addToNeedsParentFixupList(EntityItemPointer entity) { void EntityTree::update(bool simulate) { PROFILE_RANGE(simulation_physics, "UpdateTree"); - fixupNeedsParentFixups(); - if (simulate && _simulation) { - withWriteLock([&] { + withWriteLock([&] { + fixupNeedsParentFixups(); + if (simulate && _simulation) { _simulation->updateEntities(); { PROFILE_RANGE(simulation_physics, "Deletes"); @@ -1816,8 +1827,8 @@ void EntityTree::update(bool simulate) { deleteEntities(idsToDelete, true); } } - }); - } + } + }); } quint64 EntityTree::getAdjustedConsiderSince(quint64 sinceTime) { @@ -2286,7 +2297,9 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer QScriptEngine scriptEngine; RecurseOctreeToMapOperator theOperator(entityDescription, element, &scriptEngine, skipDefaultValues, skipThoseWithBadParents, _myAvatar); - recurseTreeWithOperator(&theOperator); + withReadLock([&] { + recurseTreeWithOperator(&theOperator); + }); return true; } diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 3203c2968c..4c01269ed5 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -42,8 +42,13 @@ void SimpleEntitySimulation::clearOwnership(const QUuid& ownerID) { // remove ownership and dirty all the tree elements that contain the it entity->clearSimulationOwnership(); entity->markAsChangedOnServer(); - DirtyOctreeElementOperator op(entity->getElement()); - getEntityTree()->recurseTreeWithOperator(&op); + if (auto element = entity->getElement()) { + auto tree = getEntityTree(); + tree->withReadLock([&] { + DirtyOctreeElementOperator op(element); + tree->recurseTreeWithOperator(&op); + }); + } } else { ++itemItr; } From 9b6306601af9b9b93e73c9ec705767b48a1f288b Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 9 Apr 2018 13:21:03 -0700 Subject: [PATCH 07/65] Merge Clement's PR12700 diffs for early trace start --- interface/src/main.cpp | 13 ++++++++++++- libraries/gpu/src/gpu/Context.cpp | 1 + libraries/gpu/src/gpu/Shader.cpp | 1 + libraries/qml/src/qml/OffscreenSurface.cpp | 10 +++++++++- libraries/qml/src/qml/impl/SharedObject.cpp | 6 +++++- libraries/render-utils/src/BloomEffect.cpp | 1 + libraries/render-utils/src/HighlightEffect.cpp | 1 + libraries/render-utils/src/RenderDeferredTask.cpp | 1 + libraries/render-utils/src/RenderForwardTask.cpp | 1 + libraries/render-utils/src/RenderShadowTask.cpp | 1 + libraries/render-utils/src/RenderViewTask.cpp | 1 + libraries/render-utils/src/UpdateSceneTask.cpp | 1 + libraries/render-utils/src/ZoneRenderer.cpp | 1 + .../render/src/render/RenderFetchCullSortTask.cpp | 1 + libraries/render/src/render/ShapePipeline.cpp | 9 ++++++--- libraries/shared/src/Profile.cpp | 1 + libraries/shared/src/Profile.h | 1 + libraries/ui/src/ui/OffscreenQmlSurface.cpp | 9 +++++++++ libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp | 2 ++ 19 files changed, 56 insertions(+), 6 deletions(-) diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 51ec4b1327..8d98766bfc 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -31,6 +31,8 @@ #include "UserActivityLogger.h" #include "MainWindow.h" +#include "Profile.h" + #ifdef Q_OS_WIN extern "C" { typedef int(__stdcall * CHECKMINSPECPROC) (); @@ -39,7 +41,10 @@ extern "C" { int main(int argc, const char* argv[]) { setupHifiApplication(BuildInfo::INTERFACE_NAME); - + auto tracer = DependencyManager::set(); + tracer->startTracing(); + PROFILE_SYNC_BEGIN(startup, "main startup", ""); + #ifdef Q_OS_LINUX QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); #endif @@ -235,7 +240,10 @@ int main(int argc, const char* argv[]) { argvExtended.push_back("--ignore-gpu-blacklist"); int argcExtended = (int)argvExtended.size(); + PROFILE_SYNC_END(startup, "main startup", ""); + PROFILE_SYNC_BEGIN(startup, "app full ctor", ""); Application app(argcExtended, const_cast(argvExtended.data()), startupTime, runningMarkerExisted); + PROFILE_SYNC_END(startup, "app full ctor", ""); #if 0 // If we failed the OpenGLVersion check, log it. @@ -273,6 +281,9 @@ int main(int argc, const char* argv[]) { qCDebug(interfaceapp, "Created QT Application."); exitCode = app.exec(); server.close(); + + tracer->stopTracing(); + tracer->serialize(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/Traces/trace-startup.json.gz"); } Application::shutdownPlugins(); diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index e1b68c88ca..dc2273ecb6 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -129,6 +129,7 @@ void Context::executeFrame(const FramePointer& frame) const { } bool Context::makeProgram(Shader& shader, const Shader::BindingSet& bindings, const Shader::CompilationHandler& handler) { + PROFILE_RANGE(app, "makeProgram"); // If we're running in another DLL context, we need to fetch the program callback out of the application // FIXME find a way to do this without reliance on Qt app properties if (!_makeProgramCallback) { diff --git a/libraries/gpu/src/gpu/Shader.cpp b/libraries/gpu/src/gpu/Shader.cpp index aa7898569b..b539a84925 100755 --- a/libraries/gpu/src/gpu/Shader.cpp +++ b/libraries/gpu/src/gpu/Shader.cpp @@ -75,6 +75,7 @@ Shader::Pointer Shader::createGeometry(const Source& source) { } ShaderPointer Shader::createOrReuseProgramShader(Type type, const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader) { + PROFILE_RANGE(app, "createOrReuseProgramShader"); ProgramMapKey key(0); if (vertexShader && vertexShader->getType() == VERTEX) { diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index 9c1bb79355..2f1b3910c6 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -27,6 +27,8 @@ #include "impl/SharedObject.h" #include "impl/TextureCache.h" +#include "Profile.h" + using namespace hifi::qml; using namespace hifi::qml::impl; @@ -284,6 +286,7 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, const QmlContextObjectCallback& callback) { + PROFILE_RANGE(app, "loadInternal"); if (QThread::currentThread() != thread()) { qFatal("Called load on a non-surface thread"); } @@ -304,7 +307,11 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource, } auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext); - auto qmlComponent = new QQmlComponent(getSurfaceContext()->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); + QQmlComponent* qmlComponent; + { + PROFILE_RANGE(app, "new QQmlComponent"); + qmlComponent = new QQmlComponent(getSurfaceContext()->engine(), finalQmlSource, QQmlComponent::PreferSynchronous); + } if (qmlComponent->isLoading()) { connect(qmlComponent, &QQmlComponent::statusChanged, this, [=](QQmlComponent::Status) { finishQmlLoad(qmlComponent, targetContext, parent, callback); }); @@ -318,6 +325,7 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent, QQmlContext* qmlContext, QQuickItem* parent, const QmlContextObjectCallback& callback) { + PROFILE_RANGE(app, "finishQmlLoad"); disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0); if (qmlComponent->isError()) { for (const auto& error : qmlComponent->errors()) { diff --git a/libraries/qml/src/qml/impl/SharedObject.cpp b/libraries/qml/src/qml/impl/SharedObject.cpp index d593169d94..b2057117f6 100644 --- a/libraries/qml/src/qml/impl/SharedObject.cpp +++ b/libraries/qml/src/qml/impl/SharedObject.cpp @@ -105,7 +105,10 @@ void SharedObject::create(OffscreenSurface* surface) { // Create a QML engine. auto qmlEngine = acquireEngine(surface); - _qmlContext = new QQmlContext(qmlEngine->rootContext(), qmlEngine); + { + PROFILE_RANGE(startup, "new QQmlContext"); + _qmlContext = new QQmlContext(qmlEngine->rootContext(), qmlEngine); + } surface->onRootContextCreated(_qmlContext); emit surface->rootContextCreated(_qmlContext); @@ -175,6 +178,7 @@ static size_t globalEngineRefCount{ 0 }; #endif QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) { + PROFILE_RANGE(startup, "acquireEngine"); Q_ASSERT(QThread::currentThread() == qApp->thread()); QQmlEngine* result = nullptr; diff --git a/libraries/render-utils/src/BloomEffect.cpp b/libraries/render-utils/src/BloomEffect.cpp index ddd63f012f..75ddb5f8e9 100644 --- a/libraries/render-utils/src/BloomEffect.cpp +++ b/libraries/render-utils/src/BloomEffect.cpp @@ -326,6 +326,7 @@ Bloom::Bloom() { } void Bloom::configure(const Config& config) { + PROFILE_RANGE(startup, "Bloom::build"); std::string blurName{ "BloomBlurN" }; for (auto i = 0; i < BLOOM_BLUR_LEVEL_COUNT; i++) { diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index d151da766b..5876b40d2b 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -476,6 +476,7 @@ void DrawHighlightTask::configure(const Config& config) { } void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, render::Varying& outputs) { + PROFILE_RANGE(startup, "Bloom::build"); const auto items = inputs.getN(0).get(); const auto sceneFrameBuffer = inputs.getN(1); const auto primaryFramebuffer = inputs.getN(2); diff --git a/libraries/render-utils/src/RenderDeferredTask.cpp b/libraries/render-utils/src/RenderDeferredTask.cpp index 2377f5131f..6d44e7f182 100644 --- a/libraries/render-utils/src/RenderDeferredTask.cpp +++ b/libraries/render-utils/src/RenderDeferredTask.cpp @@ -76,6 +76,7 @@ const render::Varying RenderDeferredTask::addSelectItemJobs(JobModel& task, cons } void RenderDeferredTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { + PROFILE_RANGE(startup, "RenderDeferredTask::build"); const auto& items = input.get(); auto fadeEffect = DependencyManager::get(); diff --git a/libraries/render-utils/src/RenderForwardTask.cpp b/libraries/render-utils/src/RenderForwardTask.cpp index 63370109e0..e7603d73d9 100755 --- a/libraries/render-utils/src/RenderForwardTask.cpp +++ b/libraries/render-utils/src/RenderForwardTask.cpp @@ -40,6 +40,7 @@ extern void initForwardPipelines(ShapePlumber& plumber, extern void initOverlay3DPipelines(render::ShapePlumber& plumber, bool depthTest = false); void RenderForwardTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { + PROFILE_RANGE(startup, "RenderForwardTask::build"); auto items = input.get(); auto fadeEffect = DependencyManager::get(); diff --git a/libraries/render-utils/src/RenderShadowTask.cpp b/libraries/render-utils/src/RenderShadowTask.cpp index 69c5b3c689..84fd46f429 100644 --- a/libraries/render-utils/src/RenderShadowTask.cpp +++ b/libraries/render-utils/src/RenderShadowTask.cpp @@ -215,6 +215,7 @@ void RenderShadowMap::run(const render::RenderContextPointer& renderContext, con void RenderShadowTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cameraCullFunctor, uint8_t tagBits, uint8_t tagMask) { ::CullFunctor shadowCullFunctor = [this](const RenderArgs* args, const AABox& bounds) { + PROFILE_RANGE(startup, "RenderShadowTask::build"); return _cullFunctor(args, bounds); }; diff --git a/libraries/render-utils/src/RenderViewTask.cpp b/libraries/render-utils/src/RenderViewTask.cpp index 122fc16e61..aed2972243 100644 --- a/libraries/render-utils/src/RenderViewTask.cpp +++ b/libraries/render-utils/src/RenderViewTask.cpp @@ -17,6 +17,7 @@ void RenderViewTask::build(JobModel& task, const render::Varying& input, render::Varying& output, render::CullFunctor cullFunctor, bool isDeferred, uint8_t tagBits, uint8_t tagMask) { // auto items = input.get(); + PROFILE_RANGE(startup, "RenderViewTask::build"); // Warning : the cull functor passed to the shadow pass should only be testing for LOD culling. If frustum culling // is performed, then casters not in the view frustum will be removed, which is not what we wish. task.addJob("RenderShadowTask", cullFunctor, tagBits, tagMask); diff --git a/libraries/render-utils/src/UpdateSceneTask.cpp b/libraries/render-utils/src/UpdateSceneTask.cpp index e05f28ef0d..2b4e626fed 100644 --- a/libraries/render-utils/src/UpdateSceneTask.cpp +++ b/libraries/render-utils/src/UpdateSceneTask.cpp @@ -19,6 +19,7 @@ #include "DeferredLightingEffect.h" void UpdateSceneTask::build(JobModel& task, const render::Varying& input, render::Varying& output) { + PROFILE_RANGE(startup, "UpdateSceneTask::build"); task.addJob("LightStageSetup"); task.addJob("BackgroundStageSetup"); task.addJob("HazeStageSetup"); diff --git a/libraries/render-utils/src/ZoneRenderer.cpp b/libraries/render-utils/src/ZoneRenderer.cpp index 51939efd4f..bd03668b11 100644 --- a/libraries/render-utils/src/ZoneRenderer.cpp +++ b/libraries/render-utils/src/ZoneRenderer.cpp @@ -42,6 +42,7 @@ protected: const Selection::Name ZoneRendererTask::ZONES_SELECTION { "RankedZones" }; void ZoneRendererTask::build(JobModel& task, const Varying& input, Varying& ouput) { + PROFILE_RANGE(startup, "ZoneRendererTask::build"); // Filter out the sorted list of zones const auto zoneItems = task.addJob("FilterZones", input, ZONES_SELECTION.c_str()); diff --git a/libraries/render/src/render/RenderFetchCullSortTask.cpp b/libraries/render/src/render/RenderFetchCullSortTask.cpp index 7b9765dca1..629bcf81bf 100644 --- a/libraries/render/src/render/RenderFetchCullSortTask.cpp +++ b/libraries/render/src/render/RenderFetchCullSortTask.cpp @@ -20,6 +20,7 @@ using namespace render; void RenderFetchCullSortTask::build(JobModel& task, const Varying& input, Varying& output, CullFunctor cullFunctor, uint8_t tagBits, uint8_t tagMask) { cullFunctor = cullFunctor ? cullFunctor : [](const RenderArgs*, const AABox&){ return true; }; + PROFILE_RANGE(startup, "RenderFetchCullSortTask::build"); // CPU jobs: // Fetch and cull the items from the scene const ItemFilter filter = ItemFilter::Builder::visibleWorldItems().withoutLayered().withTagBits(tagBits, tagMask); diff --git a/libraries/render/src/render/ShapePipeline.cpp b/libraries/render/src/render/ShapePipeline.cpp index 92e22d86f6..35cc66315b 100644 --- a/libraries/render/src/render/ShapePipeline.cpp +++ b/libraries/render/src/render/ShapePipeline.cpp @@ -134,9 +134,12 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p locations->lightClusterFrustumBufferUnit = -1; } - auto gpuPipeline = gpu::Pipeline::create(program, state); - auto shapePipeline = std::make_shared(gpuPipeline, locations, batchSetter, itemSetter); - addPipelineHelper(filter, key, 0, shapePipeline); + { + PROFILE_RANGE(app, "Pipeline::create"); + auto gpuPipeline = gpu::Pipeline::create(program, state); + auto shapePipeline = std::make_shared(gpuPipeline, locations, batchSetter, itemSetter); + addPipelineHelper(filter, key, 0, shapePipeline); + } } const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Key& key) const { diff --git a/libraries/shared/src/Profile.cpp b/libraries/shared/src/Profile.cpp index 97def2015a..f3cbbf9262 100644 --- a/libraries/shared/src/Profile.cpp +++ b/libraries/shared/src/Profile.cpp @@ -27,6 +27,7 @@ Q_LOGGING_CATEGORY(trace_simulation_animation, "trace.simulation.animation") Q_LOGGING_CATEGORY(trace_simulation_animation_detail, "trace.simulation.animation.detail") Q_LOGGING_CATEGORY(trace_simulation_physics, "trace.simulation.physics") Q_LOGGING_CATEGORY(trace_simulation_physics_detail, "trace.simulation.physics.detail") +Q_LOGGING_CATEGORY(trace_startup, "trace.startup") Q_LOGGING_CATEGORY(trace_workload, "trace.workload") #if defined(NSIGHT_FOUND) diff --git a/libraries/shared/src/Profile.h b/libraries/shared/src/Profile.h index f2a747afa3..e78ce210c9 100644 --- a/libraries/shared/src/Profile.h +++ b/libraries/shared/src/Profile.h @@ -32,6 +32,7 @@ Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation) Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation_detail) Q_DECLARE_LOGGING_CATEGORY(trace_simulation_physics) Q_DECLARE_LOGGING_CATEGORY(trace_simulation_physics_detail) +Q_DECLARE_LOGGING_CATEGORY(trace_startup) Q_DECLARE_LOGGING_CATEGORY(trace_workload) class Duration { diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 12e9b8b87c..cfff0405c0 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -256,6 +256,15 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { #if !defined(Q_OS_ANDROID) rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); + { + PROFILE_RANGE(startup, "FileTypeProfile"); + rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); + } + { + PROFILE_RANGE(startup, "HFWebEngineProfile"); + rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); + + } #endif rootContext->setContextProperty("Paths", DependencyManager::get().data()); rootContext->setContextProperty("Tablet", DependencyManager::get().data()); diff --git a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp index 7efa36624b..51fe11fdc7 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurfaceCache.cpp @@ -12,6 +12,7 @@ #include #include "OffscreenQmlSurface.h" +#include "Profile.h" OffscreenQmlSurfaceCache::OffscreenQmlSurfaceCache() { } @@ -38,6 +39,7 @@ void OffscreenQmlSurfaceCache::reserve(const QString& rootSource, int count) { } void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedPointer& surface) { + PROFILE_RANGE(app, "buildSurface"); surface->pause(); _cache[rootSource].push_back(surface); } From 513a4875f0081fc7b4eafa8e82428fe3513ae851 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Mon, 9 Apr 2018 15:45:58 -0700 Subject: [PATCH 08/65] Move --traceFile option to start of main() --- interface/src/Application.cpp | 2 ++ interface/src/main.cpp | 15 ++++++++++++--- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 343074f61c..7d5a608a68 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -959,6 +959,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning())); setProperty(hifi::properties::CRASHED, _previousSessionCrashed); +#if 0 { const QString TEST_SCRIPT = "--testScript"; const QString TRACE_FILE = "--traceFile"; @@ -976,6 +977,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo } } } +#endif // make sure the debug draw singleton is initialized on the main thread. DebugDraw::getInstance().removeMarker(""); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 8d98766bfc..677edb0bf3 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -42,9 +42,18 @@ extern "C" { int main(int argc, const char* argv[]) { setupHifiApplication(BuildInfo::INTERFACE_NAME); auto tracer = DependencyManager::set(); - tracer->startTracing(); + const char * traceFile = ""; + { + for (int a = 1; a < argc; ++a) { + if (strcmp(argv[a], "--traceFile") == 0 && argc > a + 1) { + traceFile = argv[a + 1]; + tracer->startTracing(); + break; + } + } + } PROFILE_SYNC_BEGIN(startup, "main startup", ""); - + #ifdef Q_OS_LINUX QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar); #endif @@ -283,7 +292,7 @@ int main(int argc, const char* argv[]) { server.close(); tracer->stopTracing(); - tracer->serialize(QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/Traces/trace-startup.json.gz"); + tracer->serialize(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/" + traceFile); } Application::shutdownPlugins(); From cf3648409d5c419b20384e3dc297ab7322e23fde Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 10 Apr 2018 12:19:42 -0700 Subject: [PATCH 09/65] Allow duration for tracing, other fixes --- interface/src/Application.cpp | 7 ------- interface/src/main.cpp | 36 ++++++++++++++++++++++++++--------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 7d5a608a68..a0fe9ef13a 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -959,10 +959,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning())); setProperty(hifi::properties::CRASHED, _previousSessionCrashed); -#if 0 { const QString TEST_SCRIPT = "--testScript"; - const QString TRACE_FILE = "--traceFile"; const QStringList args = arguments(); for (int i = 0; i < args.size() - 1; ++i) { if (args.at(i) == TEST_SCRIPT) { @@ -970,14 +968,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo if (QFileInfo(testScriptPath).exists()) { setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath)); } - } else if (args.at(i) == TRACE_FILE) { - QString traceFilePath = args.at(i + 1); - setProperty(hifi::properties::TRACING, traceFilePath); - DependencyManager::get()->startTracing(); } } } -#endif // make sure the debug draw singleton is initialized on the main thread. DebugDraw::getInstance().removeMarker(""); diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 677edb0bf3..5f2afa9155 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -41,17 +41,25 @@ extern "C" { int main(int argc, const char* argv[]) { setupHifiApplication(BuildInfo::INTERFACE_NAME); + + // Early check for --traceFile argument auto tracer = DependencyManager::set(); - const char * traceFile = ""; - { - for (int a = 1; a < argc; ++a) { - if (strcmp(argv[a], "--traceFile") == 0 && argc > a + 1) { - traceFile = argv[a + 1]; - tracer->startTracing(); - break; + const char * traceFile = nullptr; + const QString traceFileFlag("--traceFile"); + float traceDuration = 0.0f; + for (int a = 1; a < argc; ++a) { + if (traceFileFlag == argv[a] && argc > a + 1) { + traceFile = argv[a + 1]; + if (argc > a + 2) { + traceDuration = atof(argv[a + 2]); } + break; } } + if (traceFile != nullptr) { + tracer->startTracing(); + } + PROFILE_SYNC_BEGIN(startup, "main startup", ""); #ifdef Q_OS_LINUX @@ -253,6 +261,14 @@ int main(int argc, const char* argv[]) { PROFILE_SYNC_BEGIN(startup, "app full ctor", ""); Application app(argcExtended, const_cast(argvExtended.data()), startupTime, runningMarkerExisted); PROFILE_SYNC_END(startup, "app full ctor", ""); + + + QTimer exitTimer; + if (traceDuration > 0.0) { + exitTimer.setSingleShot(true); + QObject::connect(&exitTimer, &QTimer::timeout, &app, &Application::quit); + exitTimer.start(int(1000 * traceDuration)); + } #if 0 // If we failed the OpenGLVersion check, log it. @@ -291,8 +307,10 @@ int main(int argc, const char* argv[]) { exitCode = app.exec(); server.close(); - tracer->stopTracing(); - tracer->serialize(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/" + traceFile); + if (traceFile != nullptr) { + tracer->stopTracing(); + tracer->serialize(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/" + traceFile); + } } Application::shutdownPlugins(); From ca5720c0158a8a3b261b32b53f7ccff9af908263 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Tue, 10 Apr 2018 13:52:46 -0700 Subject: [PATCH 10/65] Take out superfluous DependencyManager::set() --- interface/src/Application.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index a0fe9ef13a..11427fb1e6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -773,7 +773,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { steamClient->init(); } - DependencyManager::set(); PROFILE_SET_THREAD_NAME("Main Thread"); #if defined(Q_OS_WIN) From f81836c8300ee89146f07b4757d392f3f79bcc3a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 9 Apr 2018 18:28:59 -0700 Subject: [PATCH 11/65] support specification of named landing-points in serverless-domain json files --- interface/src/Application.cpp | 3 +++ libraries/entities/src/EntityTree.cpp | 12 ++++++++++++ libraries/entities/src/EntityTree.h | 4 ++++ libraries/networking/src/AddressManager.cpp | 12 ++++++++++-- libraries/networking/src/DomainHandler.cpp | 15 +++++++++++---- libraries/networking/src/DomainHandler.h | 6 +++++- libraries/networking/src/NodeList.cpp | 20 ++++++++++++++++---- 7 files changed, 61 insertions(+), 11 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 18ba881573..9a97e9a42f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3146,6 +3146,9 @@ void Application::loadServerlessDomain(QUrl domainURL) { tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0); } + std::map namedPaths = tmpTree->getNamedPaths(); + nodeList->getDomainHandler().setIsConnected(true, namedPaths); + _fullSceneReceivedCounter++; } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 2cf66911a4..d5c7b9c2d6 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -2303,6 +2303,18 @@ bool EntityTree::readFromMap(QVariantMap& map) { _persistDataVersion = map["DataVersion"].toInt(); } + _namedPaths.clear(); + if (map.contains("Paths")) { + QVariantMap namedPathsMap = map["Paths"].toMap(); + for(QVariantMap::const_iterator iter = namedPathsMap.begin(); iter != namedPathsMap.end(); ++iter) { + QString namedPathName = iter.key(); + QString namedPathViewPoint = iter.value().toString(); + _namedPaths[namedPathName] = namedPathViewPoint; + } + } else { + _namedPaths["/"] = "/"; + } + // map will have a top-level list keyed as "Entities". This will be extracted // and iterated over. Each member of this list is converted to a QVariantMap, then // to a QScriptValue, and then to EntityItemProperties. These properties are used diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 5f69714432..791c030fc8 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -301,6 +301,8 @@ public: static bool addMaterialToOverlay(const QUuid& overlayID, graphics::MaterialLayer material, const std::string& parentMaterialName); static bool removeMaterialFromOverlay(const QUuid& overlayID, graphics::MaterialPointer material, const std::string& parentMaterialName); + std::map getNamedPaths() const { return _namedPaths; } + signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); @@ -417,6 +419,8 @@ private: static std::function _removeMaterialFromOverlayOperator; bool _serverlessDomain { false }; + + std::map _namedPaths; }; #endif // hifi_EntityTree_h diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 3c24cc796c..56b148a43c 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -29,7 +29,7 @@ #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" -const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json"; +const QString DEFAULT_HIFI_ADDRESS = "file:///~/serverless/tutorial.json?location=/"; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; @@ -312,7 +312,15 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl, LookupTrigger trigger) { _shareablePlaceName.clear(); setDomainInfo(lookupUrl, trigger); emit lookupResultsFinished(); - handlePath(DOMAIN_SPAWNING_POINT, LookupTrigger::Internal, false); + + QString path = DOMAIN_SPAWNING_POINT; + QUrlQuery queryArgs(lookupUrl); + const QString LOCATION_QUERY_KEY = "location"; + if (queryArgs.hasQueryItem(LOCATION_QUERY_KEY)) { + path = queryArgs.queryItemValue(LOCATION_QUERY_KEY); + } + + handlePath(path, LookupTrigger::Internal, false); return true; } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index c20d6d73be..fe3b0abcb7 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -173,9 +173,7 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { QString previousHost = _domainURL.host(); _domainURL = domainURL; - if (domainURL.scheme() != URL_SCHEME_HIFI) { - setIsConnected(true); - } else if (previousHost != domainURL.host()) { + if (previousHost != domainURL.host()) { qCDebug(networking) << "Updated domain hostname to" << domainURL.host(); if (!domainURL.host().isEmpty()) { @@ -250,6 +248,14 @@ void DomainHandler::activateICEPublicSocket() { emit completedSocketDiscovery(); } +QString DomainHandler::getViewPointFromNamedPath(QString namedPath) { + auto lookup = _namedPaths.find(namedPath); + if (lookup != _namedPaths.end()) { + return lookup->second; + } + return DOMAIN_SPAWNING_POINT; +} + void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { for (int i = 0; i < hostInfo.addresses().size(); i++) { if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { @@ -279,7 +285,8 @@ void DomainHandler::completedIceServerHostnameLookup() { emit iceSocketAndIDReceived(); } -void DomainHandler::setIsConnected(bool isConnected) { +void DomainHandler::setIsConnected(bool isConnected, std::map namedPaths) { + _namedPaths = namedPaths; if (_isConnected != isConnected) { _isConnected = isConnected; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index fbc60e2492..760b2f8235 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -73,9 +73,11 @@ public: void activateICEPublicSocket(); bool isConnected() const { return _isConnected; } - void setIsConnected(bool isConnected); + void setIsConnected(bool isConnected, std::map namedPaths = std::map()); bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } + QString getViewPointFromNamedPath(QString namedPath); + bool hasSettings() const { return !_settingsObject.isEmpty(); } void requestDomainSettings(); const QJsonObject& getSettingsObject() const { return _settingsObject; } @@ -200,6 +202,8 @@ private: int _checkInPacketsSinceLastReply { 0 }; QTimer _apiRefreshTimer; + + std::map _namedPaths; }; const QString DOMAIN_SPAWNING_POINT { "/0, -10, 0" }; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index cb0d2e4cd5..3c2b4cd336 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -413,7 +413,14 @@ void NodeList::sendDomainServerCheckIn() { } void NodeList::handleDSPathQuery(const QString& newPath) { - if (_domainHandler.isSocketKnown()) { + if (_domainHandler.isServerless()) { + if (_domainHandler.isConnected()) { + auto viewpoint = _domainHandler.getViewPointFromNamedPath(newPath); + DependencyManager::get()->goToViewpointForPath(viewpoint, newPath); + } else { + _domainHandler.setPendingPath(newPath); + } + } else if (_domainHandler.isSocketKnown()) { // if we have a DS socket we assume it will get this packet and send if off right away sendDSPathQuery(newPath); } else { @@ -427,10 +434,15 @@ void NodeList::sendPendingDSPathQuery() { QString pendingPath = _domainHandler.getPendingPath(); if (!pendingPath.isEmpty()) { - qCDebug(networking) << "Attempting to send pending query to DS for path" << pendingPath; - // this is a slot triggered if we just established a network link with a DS and want to send a path query - sendDSPathQuery(_domainHandler.getPendingPath()); + if (_domainHandler.isServerless()) { + auto viewpoint = _domainHandler.getViewPointFromNamedPath(pendingPath); + DependencyManager::get()->goToViewpointForPath(viewpoint, pendingPath); + } else { + qCDebug(networking) << "Attempting to send pending query to DS for path" << pendingPath; + // this is a slot triggered if we just established a network link with a DS and want to send a path query + sendDSPathQuery(_domainHandler.getPendingPath()); + } // clear whatever the pending path was _domainHandler.clearPendingPath(); From ee3e8093e18bd06f1564e594bd6b882aeea228d4 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 10 Apr 2018 10:19:35 -0700 Subject: [PATCH 12/65] update tutorial content to include default landing-point --- cmake/externals/serverless-content/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/externals/serverless-content/CMakeLists.txt b/cmake/externals/serverless-content/CMakeLists.txt index 4d0773f5f5..6235205aad 100644 --- a/cmake/externals/serverless-content/CMakeLists.txt +++ b/cmake/externals/serverless-content/CMakeLists.txt @@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC66-v2.zip - URL_MD5 d76bdb3e2bf7ae5d20115bd97b0c44a8 + URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC67.zip + URL_MD5 3fc4b7332be771d71b43b6d688de9aa7 CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From a9b9a1cb105016be5160f7b634c7dfd9f0a75901 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 10 Apr 2018 15:54:19 -0700 Subject: [PATCH 13/65] Commerce: Gifts --- .../fonts/hifi-glyphs.eot | Bin 31678 -> 33642 bytes .../fonts/hifi-glyphs.svg | 11 +- .../fonts/hifi-glyphs.ttf | Bin 31500 -> 33464 bytes .../hifi-glyphs-1.31/fonts/hifi-glyphs.woff | Bin 0 -> 21496 bytes .../icons-reference.html | 90 +- .../styles.css | 33 +- interface/resources/fonts/hifi-glyphs.ttf | Bin 32544 -> 33464 bytes .../fonts/hifi-glyphs/fonts/hifi-glyphs.woff | Bin 20032 -> 0 bytes .../qml/hifi/commerce/checkout/Checkout.qml | 57 +- .../hifi/commerce/common/CommerceLightbox.qml | 28 +- .../sendAsset}/ConnectionItem.qml | 6 +- .../sendAsset}/RecipientDisplay.qml | 4 +- .../sendAsset/SendAsset.qml} | 519 ++++++----- .../sendAsset}/images/connection.svg | 0 .../sendAsset}/images/nearby.svg | 0 .../sendAsset}/images/p2p-nearby-selected.svg | 0 .../images/p2p-nearby-unselected.svg | 0 .../images/send-money-effect-sm.jpg | Bin .../hifi/commerce/purchases/PurchasedItem.qml | 781 ++++++++-------- .../qml/hifi/commerce/purchases/Purchases.qml | 204 +++-- .../hifi/commerce/wallet/PassphraseModal.qml | 4 +- .../qml/hifi/commerce/wallet/Wallet.qml | 12 +- .../qml/hifi/commerce/wallet/WalletChoice.qml | 17 +- .../qml/hifi/commerce/wallet/WalletSetup.qml | 4 +- .../qml/styles-uit/HifiConstants.qml | 5 + interface/src/Application.cpp | 2 +- interface/src/commerce/Ledger.cpp | 26 +- interface/src/commerce/Ledger.h | 16 +- interface/src/commerce/QmlCommerce.cpp | 16 +- interface/src/commerce/QmlCommerce.h | 8 +- .../entities/src/EntityScriptingInterface.cpp | 2 +- scripts/system/commerce/wallet.js | 196 ++-- scripts/system/libraries/connectionUtils.js | 94 ++ scripts/system/marketplaces/marketplaces.js | 836 +++++++++++++++--- 34 files changed, 1995 insertions(+), 976 deletions(-) rename interface/resources/fonts/{hifi-glyphs => hifi-glyphs-1.31}/fonts/hifi-glyphs.eot (82%) rename interface/resources/fonts/{hifi-glyphs => hifi-glyphs-1.31}/fonts/hifi-glyphs.svg (92%) rename interface/resources/fonts/{hifi-glyphs => hifi-glyphs-1.31}/fonts/hifi-glyphs.ttf (82%) create mode 100644 interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.woff rename interface/resources/fonts/{hifi-glyphs => hifi-glyphs-1.31}/icons-reference.html (93%) rename interface/resources/fonts/{hifi-glyphs => hifi-glyphs-1.31}/styles.css (94%) delete mode 100644 interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.woff rename interface/resources/qml/hifi/commerce/{wallet/sendMoney => common/sendAsset}/ConnectionItem.qml (97%) rename interface/resources/qml/hifi/commerce/{wallet/sendMoney => common/sendAsset}/RecipientDisplay.qml (97%) rename interface/resources/qml/hifi/commerce/{wallet/sendMoney/SendMoney.qml => common/sendAsset/SendAsset.qml} (77%) rename interface/resources/qml/hifi/commerce/{wallet/sendMoney => common/sendAsset}/images/connection.svg (100%) rename interface/resources/qml/hifi/commerce/{wallet/sendMoney => common/sendAsset}/images/nearby.svg (100%) rename interface/resources/qml/hifi/commerce/{wallet/sendMoney => common/sendAsset}/images/p2p-nearby-selected.svg (100%) rename interface/resources/qml/hifi/commerce/{wallet/sendMoney => common/sendAsset}/images/p2p-nearby-unselected.svg (100%) rename interface/resources/qml/hifi/commerce/{wallet/sendMoney => common/sendAsset}/images/send-money-effect-sm.jpg (100%) create mode 100644 scripts/system/libraries/connectionUtils.js diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.eot similarity index 82% rename from interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.eot rename to interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.eot index d3591e6499c6afaf63e4a2c0f144f4722c2f8d9f..1be5435ced839789c55bbeb6584a2ae2d04400fd 100644 GIT binary patch delta 3467 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 QIOC1+r~f&WfB6IQ2jFE`i~s-t delta 1504 zcmYjRYfO`86n@_Kd%sJ+ek}#suca*o3barzr56eaPDBLc?hO$U?c^p>ZIn6W*3CKG zyd-K(w(JKn%R-jS7$cJyV+=9=Fw2se#Vw1vgqZyxY8*dYwq#3K+OTBrc~8!B&Uv4c z^Zt2W&eHpT65;+NpiEvCLTsh$f$ehb>DZ}=`VTO%3c%i2zqJ8(ApjGP0c3aV^!hvQ zwMGHb4WR2#BpiLO@Fy=op8#}lD10KiwgwKEcmzNi8a*|5XHSO+2;IQwo#BCSzrO8u zA%N!qLc_xYVOB<#0b&G@Hyjy@jc2{F7yxn;AkNWahr*pzkzs(Z0f;>k9*^P`=>te* zVxgnq$iS-&i(SBp1ZN?7?8I3AmfLn<>>(f!5Fp{U2VE-%QfmHymLhR}{_L*$UtD_z z(EMaw0*x;bFS!_BCHqNHyqz`@Yy2wxJ(hUPt-C!r733heB(j5%`own={N(tV<)XE< zB+6OFI!f*V0ZRZjpc*yUh)tM8EgDgWE!cquG+`T>QHBl_p#`lN#1OV(Gv4V$1u7B3 zF0`Q?yJ1Hrve1ugai15aK!sr zzRv^|W>}B`D{Sb(K6GO*_MjL0(T4*#h%ny8Aq-#`hj9d>@p~*IcLGzG#tdTk03YHs zKEfG%jB}X9S$u*|aRKM!67TNYP+hZe(`0R9-Ig5>kfKRah6;>^vAL#7aUca--!7*MCC5^0AbovxSYFfI{q?#=m zR$JG;?!EEL{A~OhFT~A4UaVJhPfnBj`N{Gt`X! zjO!V1tvijETeof2cEvVldt_U+JM5$OCHrcoGxK=N5pw*NRhRWTyEEr#&a!hN zw>Ix)eoy{Wm*nbl>)oa9dG{^%efP@(N5NvjUxm$u-+B~J)bqCJTJcEn=f#gp3QE2% zZ77{6oiBai9rby9i@t}xAN^(iUjL1N9HIz=HttfqVlDIy*stR5LNb;i$+o1_L>46=;Q8?V&^#7?UVRDXY_oQp}*}QZ?eVUX&zJucS&MIZinp z157WAae#OMdX>v36v4aM5>WQN(f<+hNq;xmk}Z` zC0kT@N%=~Y6rI6fkt9LZ>Le*uk!3L<3_+AHoU#19cR@R;C1LQ{uFRYjgh=@Kk%5IPGnK3{DH|YbYoJMW(F3p~ bi;0}W1rRV`U1<{fhVM6i`9mW9U)BBv{ diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.svg b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.svg similarity index 92% rename from interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.svg rename to interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.svg index 43c4932879..c68cb63a0d 100644 --- a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.svg +++ b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.svg @@ -25,7 +25,6 @@ - @@ -145,4 +144,14 @@ + + + + + + + + + + diff --git a/interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.ttf similarity index 82% rename from interface/resources/fonts/hifi-glyphs/fonts/hifi-glyphs.ttf rename to interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.ttf index 8907cf7858dd304cdad024bfa1ff4ca5cd94caf9..edc447c13245b74845bcce8687c7432f37872c39 100644 GIT binary patch delta 3470 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 Fe*ppxP9FdO delta 1526 zcmYLJdrXs86#w1(-S5$_{YruMYiUb?0xc9M^g#i^7a|~npdtby+R00x+5swptZwtM z`ACdivS)}{7P4f<7@5Qnvl!zaW?3?0wqXXjN&=z1xRJ$LWjbU z{^x5JI)L#+&3yFm_(b2Pn|5I09w3n9a0Ia3?RdH`rRGm)DH8J2hl}cKOBdD4WG%TE zUq|+mqWEW|fmq{D$RDvpnp@rWWFL_I;F6vljMOCtgoM`(-&!hKSxL6eS;DG^BaL4ffgJZlPN*Y38UVP8mqKVQ4m7OPfr4k=~uYnEuiz8IKwlOkUHlX--wusCw7j zZvMm)vdmf@WT+W^8CNr2S(~gA)~nVRwocm_+hyCV?Y?c5BSZh*+IT@LMb5}Av`4<;TYoy<5`*_nyR+uQ-$%2BfO4M z#xcg^o;!@EgiyjsqRffBrkaU~8jgxgV=$0|R)JQi)gDS@fia14l(Jeylwt;*lByAB z^r9q*dR?j{k}=9Dlou$caMZ|i3MU$rRKiG#MwSWX6qz#?$eck+5qYL^0%e3Uk!mE7 z5<-}y;VIeH!w3HMmWToP zPy%0kWZJ_5z7k*j^Y0V(f3{mx{{zrIB&PrX diff --git a/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.woff b/interface/resources/fonts/hifi-glyphs-1.31/fonts/hifi-glyphs.woff new file mode 100644 index 0000000000000000000000000000000000000000..f26000caf42cd88ba2e2ba3ac079299b43515ce3 GIT binary patch literal 21496 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

HiFi Glyphs

-

This font was created for use inHigh Fidelity

+

This font was created for use in High Fidelity

CSS mapping

  • @@ -87,10 +87,6 @@
  • -
  • -
    - -
  • @@ -567,6 +563,46 @@
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +

Character mapping

    @@ -642,10 +678,6 @@
    -
  • -
    - -
  • @@ -1122,6 +1154,46 @@
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +
  • +
  • +
    + +