From bb1fe8f4390273f945a2d3417e5ce4093ab37baf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 May 2015 16:57:25 -0700 Subject: [PATCH 01/46] cleanup and stability for grabHockey.js script --- examples/example/games/grabHockey.js | 180 +++++++++++++-------------- 1 file changed, 85 insertions(+), 95 deletions(-) diff --git a/examples/example/games/grabHockey.js b/examples/example/games/grabHockey.js index 7e379294ec..5773dfa849 100644 --- a/examples/example/games/grabHockey.js +++ b/examples/example/games/grabHockey.js @@ -10,6 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +var adebug = 0; // these are hand-measured bounds of the AirHockey table var fieldMaxOffset = { x: 0.475, @@ -40,13 +41,14 @@ var entityProps; var moveUpDown = false; var CLOSE_ENOUGH = 0.001; var FULL_STRENGTH = 1.0; -var SPRING_RATE = 1.5; +var SPRING_TIMESCALE = 0.05; var DAMPING_RATE = 0.80; var ANGULAR_DAMPING_RATE = 0.40; var SCREEN_TO_METERS = 0.001; var currentPosition, currentVelocity, cameraEntityDistance, currentRotation; var grabHeight; var velocityTowardTarget, desiredVelocity, addedVelocity, newVelocity, dPosition, camYaw, distanceToTarget, targetPosition; +var grabOffset; var originalGravity = { x: 0, y: 0, @@ -94,6 +96,20 @@ function nearLinePoint(targetPosition) { return Vec3.sum(handPosition, along); } +function xzPickRayIntersetion(pointOnPlane, mouseX, mouseY) { + var relativePosition = Vec3.subtract(pointOnPlane, Camera.getPosition()); + var pickRay = Camera.computePickRay(mouseX, mouseY); + if (Math.abs(pickRay.direction.y) > 0.001) { + var length = relativePosition.y / pickRay.direction.y; + var pickInersection = Vec3.multiply(pickRay.direction, length); + pickInersection = Vec3.sum(Camera.getPosition(), pickInersection); + return pickInersection; + } + // point and line are more-or-less co-planar: compute closest approach of pickRay and pointOnPlane + var length = Vec3.dot(relativePosition, pickRay.direction); + var pickInersection = Vec3.multiply(pickRay.direction, length); + return pickInersection; +} function mousePressEvent(event) { if (!event.isLeftButton) { @@ -103,19 +119,23 @@ function mousePressEvent(event) { prevMouse.y = event.y; var pickRay = Camera.computePickRay(event.x, event.y); - var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking - if (!intersection.intersects) { + var pickResults = Entities.findRayIntersection(pickRay, true); // accurate picking + if (!pickResults.intersects) { return; } - if (intersection.properties.collisionsWillMove) { - grabbedEntity = intersection.entityID; + if (pickResults.properties.collisionsWillMove) { + grabbedEntity = pickResults.entityID; var props = Entities.getEntityProperties(grabbedEntity) isGrabbing = true; originalGravity = props.gravity; - targetPosition = props.position; + var objectPosition = props.position; currentPosition = props.position; currentVelocity = props.velocity; - updateDropLine(targetPosition); + updateDropLine(objectPosition); + + var pointOnPlane = xzPickRayIntersetion(objectPosition, event.x, event.y); + grabOffset = Vec3.subtract(pointOnPlane, objectPosition); + targetPosition = objectPosition; // remember the height of the object when first grabbed // we'll try to maintain this height during the rest of this grab @@ -223,53 +243,48 @@ function mouseMoveEvent(event) { axisAngle = Quat.axis(dQ); angularVelocity = Vec3.multiply((theta / dT), axisAngle); } else { - var relativePosition = Vec3.subtract(currentPosition, Camera.getPosition()); - if (relativePosition.y < 0) { - // grabee is below camera, so movement is valid - // compute intersectionPoint where mouse ray hits grabee's current x-z plane - var pickRay = Camera.computePickRay(event.x, event.y); - var mousePosition = pickRay.direction; - var length = relativePosition.y / mousePosition.y; - mousePosition = Vec3.multiply(mousePosition, length); - mousePosition = Vec3.sum(Camera.getPosition(), mousePosition); + var pointOnPlane = xzPickRayIntersetion(currentPosition, event.x, event.y); + pointOnPlane = Vec3.subtract(pointOnPlane, grabOffset); - // translate mousePosition into local-frame - mousePosition = Vec3.subtract(mousePosition, tablePosition); + // translate pointOnPlane into local-frame + pointOnPlane = Vec3.subtract(pointOnPlane, tablePosition); - // clamp local mousePosition to table field - if (mousePosition.x > fieldMaxOffset.x) { - mousePosition.x = fieldMaxOffset.x; - } else if (mousePosition.x < fieldMinOffset.x) { - mousePosition.x = fieldMinOffset.x; - } - if (mousePosition.z > fieldMaxOffset.z) { - mousePosition.z = fieldMaxOffset.z; - } else if (mousePosition.z < fieldMinOffset.z) { - mousePosition.z = fieldMinOffset.z; - } - - // clamp to rotated square (for cut corners) - var rotation = Quat.angleAxis(45, { x:0, y:1, z:0 }); - mousePosition = Vec3.multiplyQbyV(rotation, mousePosition); - if (mousePosition.x > halfCornerBoxWidth) { - mousePosition.x = halfCornerBoxWidth; - } else if (mousePosition.x < -halfCornerBoxWidth) { - mousePosition.x = -halfCornerBoxWidth; - } - if (mousePosition.z > halfCornerBoxWidth) { - mousePosition.z = halfCornerBoxWidth; - } else if (mousePosition.z < -halfCornerBoxWidth) { - mousePosition.z = -halfCornerBoxWidth; - } - // rotate back into local frame - rotation.y = -rotation.y; - mousePosition = Vec3.multiplyQbyV(rotation, mousePosition); - // translate into world-frame - mousePosition = Vec3.sum(tablePosition, mousePosition); - - mousePosition.y = grabHeight; - targetPosition = mousePosition; + // clamp local pointOnPlane to table field + if (pointOnPlane.x > fieldMaxOffset.x) { + pointOnPlane.x = fieldMaxOffset.x; + } else if (pointOnPlane.x < fieldMinOffset.x) { + pointOnPlane.x = fieldMinOffset.x; } + if (pointOnPlane.z > fieldMaxOffset.z) { + pointOnPlane.z = fieldMaxOffset.z; + } else if (pointOnPlane.z < fieldMinOffset.z) { + pointOnPlane.z = fieldMinOffset.z; + } + + // clamp to rotated square (for cut corners) + var rotation = Quat.angleAxis(45, { x:0, y:1, z:0 }); + pointOnPlane = Vec3.multiplyQbyV(rotation, pointOnPlane); + if (pointOnPlane.x > halfCornerBoxWidth) { + pointOnPlane.x = halfCornerBoxWidth; + } else if (pointOnPlane.x < -halfCornerBoxWidth) { + pointOnPlane.x = -halfCornerBoxWidth; + } + if (pointOnPlane.z > halfCornerBoxWidth) { + pointOnPlane.z = halfCornerBoxWidth; + } else if (pointOnPlane.z < -halfCornerBoxWidth) { + pointOnPlane.z = -halfCornerBoxWidth; + } + // rotate back into local frame + rotation.y = -rotation.y; + pointOnPlane = Vec3.multiplyQbyV(rotation, pointOnPlane); + + // translate into world-frame + pointOnPlane = Vec3.sum(tablePosition, pointOnPlane); + + // clamp to gragHeight + pointOnPlane.y = grabHeight; + + targetPosition = pointOnPlane; } } prevMouse.x = event.x; @@ -297,57 +312,32 @@ function keyPressEvent(event) { function update(deltaTime) { dT = deltaTime; if (isGrabbing) { - entityProps = Entities.getEntityProperties(grabbedEntity); currentPosition = entityProps.position; - currentVelocity = entityProps.velocity; - currentRotation = entityProps.rotation; - - var dPosition = Vec3.subtract(targetPosition, currentPosition); - - distanceToTarget = Vec3.length(dPosition); - if (distanceToTarget > CLOSE_ENOUGH) { - // compute current velocity in the direction we want to move - velocityTowardTarget = Vec3.dot(currentVelocity, Vec3.normalize(dPosition)); - velocityTowardTarget = Vec3.multiply(Vec3.normalize(dPosition), velocityTowardTarget); - // compute the speed we would like to be going toward the target position - - desiredVelocity = Vec3.multiply(dPosition, (1.0 / deltaTime) * SPRING_RATE); - // compute how much we want to add to the existing velocity - addedVelocity = Vec3.subtract(desiredVelocity, velocityTowardTarget); - // If target is too far, roll off the force as inverse square of distance - if (distanceToTarget / cameraEntityDistance > FULL_STRENGTH) { - addedVelocity = Vec3.multiply(addedVelocity, Math.pow(FULL_STRENGTH / distanceToTarget, 2.0)); - } - newVelocity = Vec3.sum(currentVelocity, addedVelocity); - // Add Damping - newVelocity = Vec3.subtract(newVelocity, Vec3.multiply(newVelocity, DAMPING_RATE)); - // Update entity - } else { - newVelocity = { - x: 0, - y: 0, - z: 0 - }; - } if (shouldRotate) { angularVelocity = Vec3.subtract(angularVelocity, Vec3.multiply(angularVelocity, ANGULAR_DAMPING_RATE)); + Entities.editEntity(grabbedEntity, { angularVelocity: angularVelocity, }); } else { - angularVelocity = entityProps.angularVelocity; + var dPosition = Vec3.subtract(targetPosition, currentPosition); + var delta = Vec3.length(dPosition); + if (delta > CLOSE_ENOUGH) { + var MAX_POSITION_DELTA = 0.50; + if (delta > MAX_POSITION_DELTA) { + dPosition = Vec3.multiply(dPosition, MAX_POSITION_DELTA / delta); + } + // desired speed is proportional to displacement by the inverse of timescale + // (for critically damped motion) + newVelocity = Vec3.multiply(dPosition, 1.0 / SPRING_TIMESCALE); + } else { + newVelocity = { + x: 0, + y: 0, + z: 0 + }; + } + Entities.editEntity(grabbedEntity, { velocity: newVelocity, }); } - // enforce that grabee's rotation is only about y-axis while being grabbed - currentRotation.x = 0; - currentRotation.z = 0; - currentRotation.y = Math.sqrt(1.0 - currentRotation.w * currentRotation.w); - // Hrm... slamming the currentRotation doesn't seem to work - - Entities.editEntity(grabbedEntity, { - position: currentPosition, - rotation: currentRotation, - velocity: newVelocity, - angularVelocity: angularVelocity - }); updateDropLine(targetPosition); } } From b60907570a5db887bf94a5876399dcfd5c3503d6 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 May 2015 22:32:20 -0700 Subject: [PATCH 02/46] safe to use grabHockey.js to grab misc objects --- examples/example/games/grabHockey.js | 149 +++++++++++++++++++-------- 1 file changed, 104 insertions(+), 45 deletions(-) diff --git a/examples/example/games/grabHockey.js b/examples/example/games/grabHockey.js index 5773dfa849..153016e639 100644 --- a/examples/example/games/grabHockey.js +++ b/examples/example/games/grabHockey.js @@ -10,7 +10,6 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var adebug = 0; // these are hand-measured bounds of the AirHockey table var fieldMaxOffset = { x: 0.475, @@ -30,12 +29,14 @@ var tablePosition = { z: 0 } var isGrabbing = false; +var isGrabbingPaddle = false; var grabbedEntity = null; var prevMouse = {}; var deltaMouse = { z: 0 } +var MAX_GRAB_DISTANCE = 100; var TABLE_SEARCH_RANGE = 10; var entityProps; var moveUpDown = false; @@ -47,6 +48,9 @@ var ANGULAR_DAMPING_RATE = 0.40; var SCREEN_TO_METERS = 0.001; var currentPosition, currentVelocity, cameraEntityDistance, currentRotation; var grabHeight; +var initialVerticalGrabPosition; +var MAX_VERTICAL_ANGLE = Math.PI / 3; +var MIN_VERTICAL_ANGLE = - MAX_VERTICAL_ANGLE; var velocityTowardTarget, desiredVelocity, addedVelocity, newVelocity, dPosition, camYaw, distanceToTarget, targetPosition; var grabOffset; var originalGravity = { @@ -97,20 +101,50 @@ function nearLinePoint(targetPosition) { } function xzPickRayIntersetion(pointOnPlane, mouseX, mouseY) { - var relativePosition = Vec3.subtract(pointOnPlane, Camera.getPosition()); + var relativePoint = Vec3.subtract(pointOnPlane, Camera.getPosition()); var pickRay = Camera.computePickRay(mouseX, mouseY); if (Math.abs(pickRay.direction.y) > 0.001) { - var length = relativePosition.y / pickRay.direction.y; - var pickInersection = Vec3.multiply(pickRay.direction, length); + var distance = relativePoint.y / pickRay.direction.y; + var pickInersection = Vec3.multiply(pickRay.direction, distance); pickInersection = Vec3.sum(Camera.getPosition(), pickInersection); return pickInersection; } // point and line are more-or-less co-planar: compute closest approach of pickRay and pointOnPlane - var length = Vec3.dot(relativePosition, pickRay.direction); - var pickInersection = Vec3.multiply(pickRay.direction, length); + var distance = Vec3.dot(relativePoint, pickRay.direction); + var pickInersection = Vec3.multiply(pickRay.direction, distance); return pickInersection; } +function forwardPickRayIntersection(pointOnPlane, mouseX, mouseY) { + var relativePoint = Vec3.subtract(pointOnPlane, Camera.getPosition()); + var pickRay = Camera.computePickRay(mouseX, mouseY); + var planeNormal = Quat.getFront(Camera.getOrientation()); + var distance = Vec3.dot(planeNormal, relativePoint); + var rayDistance = Vec3.dot(planeNormal, pickRay.direction); + var pickIntersection = Vec3.multiply(pickRay.direction, distance / rayDistance); + pickIntersection = Vec3.sum(pickIntersection, Camera.getPosition()) + return pickIntersection; +} + +/* +function yCylinderPickRayIntersection(grabRadius, mouseX, mouseY) { + var pickRay = Camera.computePickRay(mouseX, mouseY); + var angle = Math.asin(pickRay.direction.y); + if (angle > MAX_VERTICAL_ANGLE) { + angle = MAX_VERTICAL_ANGLE; + } else if (angle < MIN_VERTICAL_ANGLE) { + angle = MIN_VERTICAL_ANGLE; + } + var horizontalNormal = pickRay.direction; + horizontalNormal.y = 0; + horizontalNormal = Vec3.normalize(horizontalNormal); + var pickIntersection = Vec3.multiply(horizontalNormal, grabRadius); + pickIntersection.y = grabRadius * Math.tan(angle); + pickIntersection = Vec3.sum(pickIntersection, Camera.getPosition()) + return pickIntersection; +} +*/ + function mousePressEvent(event) { if (!event.isLeftButton) { return; @@ -126,10 +160,17 @@ function mousePressEvent(event) { if (pickResults.properties.collisionsWillMove) { grabbedEntity = pickResults.entityID; var props = Entities.getEntityProperties(grabbedEntity) - isGrabbing = true; originalGravity = props.gravity; var objectPosition = props.position; currentPosition = props.position; + if (Vec3.distance(currentPosition, Camera.getPosition()) > MAX_GRAB_DISTANCE) { + // don't allow grabs of things far away + return; + } + + isGrabbing = true; + isGrabbingPaddle = (props.name == "air-hockey-paddle-23j4h1jh82jsjfw91jf232n2k"); + currentVelocity = props.velocity; updateDropLine(objectPosition); @@ -140,6 +181,7 @@ function mousePressEvent(event) { // remember the height of the object when first grabbed // we'll try to maintain this height during the rest of this grab grabHeight = currentPosition.y; + initialVerticalGrabPosition = currentPosition; Entities.editEntity(grabbedEntity, { gravity: { @@ -243,48 +285,65 @@ function mouseMoveEvent(event) { axisAngle = Quat.axis(dQ); angularVelocity = Vec3.multiply((theta / dT), axisAngle); } else { - var pointOnPlane = xzPickRayIntersetion(currentPosition, event.x, event.y); - pointOnPlane = Vec3.subtract(pointOnPlane, grabOffset); + if (moveUpDown) { + targetPosition = forwardPickRayIntersection(currentPosition, event.x, event.y); + grabHeight = targetPosition.y; + } else { + var pointOnPlane = xzPickRayIntersetion(currentPosition, event.x, event.y); + pointOnPlane = Vec3.subtract(pointOnPlane, grabOffset); - // translate pointOnPlane into local-frame - pointOnPlane = Vec3.subtract(pointOnPlane, tablePosition); + if (isGrabbingPaddle) { + // translate pointOnPlane into local-frame + pointOnPlane = Vec3.subtract(pointOnPlane, tablePosition); - // clamp local pointOnPlane to table field - if (pointOnPlane.x > fieldMaxOffset.x) { - pointOnPlane.x = fieldMaxOffset.x; - } else if (pointOnPlane.x < fieldMinOffset.x) { - pointOnPlane.x = fieldMinOffset.x; - } - if (pointOnPlane.z > fieldMaxOffset.z) { - pointOnPlane.z = fieldMaxOffset.z; - } else if (pointOnPlane.z < fieldMinOffset.z) { - pointOnPlane.z = fieldMinOffset.z; + // clamp local pointOnPlane to table field + if (pointOnPlane.x > fieldMaxOffset.x) { + pointOnPlane.x = fieldMaxOffset.x; + } else if (pointOnPlane.x < fieldMinOffset.x) { + pointOnPlane.x = fieldMinOffset.x; + } + if (pointOnPlane.z > fieldMaxOffset.z) { + pointOnPlane.z = fieldMaxOffset.z; + } else if (pointOnPlane.z < fieldMinOffset.z) { + pointOnPlane.z = fieldMinOffset.z; + } + + // clamp to rotated square (for cut corners) + var rotation = Quat.angleAxis(45, { x:0, y:1, z:0 }); + pointOnPlane = Vec3.multiplyQbyV(rotation, pointOnPlane); + if (pointOnPlane.x > halfCornerBoxWidth) { + pointOnPlane.x = halfCornerBoxWidth; + } else if (pointOnPlane.x < -halfCornerBoxWidth) { + pointOnPlane.x = -halfCornerBoxWidth; + } + if (pointOnPlane.z > halfCornerBoxWidth) { + pointOnPlane.z = halfCornerBoxWidth; + } else if (pointOnPlane.z < -halfCornerBoxWidth) { + pointOnPlane.z = -halfCornerBoxWidth; + } + // rotate back into local frame + rotation.y = -rotation.y; + pointOnPlane = Vec3.multiplyQbyV(rotation, pointOnPlane); + + // translate into world-frame + pointOnPlane = Vec3.sum(tablePosition, pointOnPlane); + } else { + // we're grabbing a non-paddle object + + // clamp distance + var relativePosition = Vec3.subtract(pointOnPlane, Camera.getPosition()); + var length = Vec3.length(relativePosition); + if (length > MAX_GRAB_DISTANCE) { + relativePosition = Vec3.multiply(relativePosition, MAX_GRAB_DISTANCE / length); + pointOnPlane = Vec3.sum(relativePosition, Camera.getPosition()); + } + } + // clamp to grabHeight + pointOnPlane.y = grabHeight; + targetPosition = pointOnPlane; + initialVerticalGrabPosition = targetPosition; } - // clamp to rotated square (for cut corners) - var rotation = Quat.angleAxis(45, { x:0, y:1, z:0 }); - pointOnPlane = Vec3.multiplyQbyV(rotation, pointOnPlane); - if (pointOnPlane.x > halfCornerBoxWidth) { - pointOnPlane.x = halfCornerBoxWidth; - } else if (pointOnPlane.x < -halfCornerBoxWidth) { - pointOnPlane.x = -halfCornerBoxWidth; - } - if (pointOnPlane.z > halfCornerBoxWidth) { - pointOnPlane.z = halfCornerBoxWidth; - } else if (pointOnPlane.z < -halfCornerBoxWidth) { - pointOnPlane.z = -halfCornerBoxWidth; - } - // rotate back into local frame - rotation.y = -rotation.y; - pointOnPlane = Vec3.multiplyQbyV(rotation, pointOnPlane); - - // translate into world-frame - pointOnPlane = Vec3.sum(tablePosition, pointOnPlane); - - // clamp to gragHeight - pointOnPlane.y = grabHeight; - - targetPosition = pointOnPlane; } } prevMouse.x = event.x; From fbd1b7e8b0d8c8c981a0c56a681025ef51604b7d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 May 2015 22:49:23 -0700 Subject: [PATCH 03/46] grabHockey.js can also rotate objects again --- examples/example/games/grabHockey.js | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/examples/example/games/grabHockey.js b/examples/example/games/grabHockey.js index 153016e639..57333e0d1e 100644 --- a/examples/example/games/grabHockey.js +++ b/examples/example/games/grabHockey.js @@ -31,7 +31,7 @@ var tablePosition = { var isGrabbing = false; var isGrabbingPaddle = false; var grabbedEntity = null; -var prevMouse = {}; +var prevMouse = {x: 0, y: 0}; var deltaMouse = { z: 0 } @@ -43,6 +43,7 @@ var moveUpDown = false; var CLOSE_ENOUGH = 0.001; var FULL_STRENGTH = 1.0; var SPRING_TIMESCALE = 0.05; +var ANGULAR_SPRING_TIMESCALE = 0.03; var DAMPING_RATE = 0.80; var ANGULAR_DAMPING_RATE = 0.40; var SCREEN_TO_METERS = 0.001; @@ -59,7 +60,7 @@ var originalGravity = { z: 0 }; var shouldRotate = false; -var dQ, theta, axisAngle, dT; +var dQ, dT; var angularVelocity = { x: 0, y: 0, @@ -266,24 +267,20 @@ function mouseMoveEvent(event) { } if (shouldRotate) { + targetPosition = currentPosition; deltaMouse.x = event.x - prevMouse.x; - if (!moveUpDown) { - deltaMouse.z = event.y - prevMouse.y; - deltaMouse.y = 0; - } else { - deltaMouse.y = (event.y - prevMouse.y) * -1; - deltaMouse.z = 0; - } + deltaMouse.z = event.y - prevMouse.y; + deltaMouse.y = 0; + var transformedDeltaMouse = { x: deltaMouse.z, y: deltaMouse.x, z: deltaMouse.y }; - transformedDeltaMouse = Vec3.multiplyQbyV(Quat.fromPitchYawRollDegrees(0, camYaw, 0), transformedDeltaMouse); dQ = Quat.fromVec3Degrees(transformedDeltaMouse); - theta = 2 * Math.acos(dQ.w); - axisAngle = Quat.axis(dQ); - angularVelocity = Vec3.multiply((theta / dT), axisAngle); + var theta = 2 * Math.acos(dQ.w); + var axis = Quat.axis(dQ); + angularVelocity = Vec3.multiply((theta / ANGULAR_SPRING_TIMESCALE), axis); } else { if (moveUpDown) { targetPosition = forwardPickRayIntersection(currentPosition, event.x, event.y); From d486899aadd2558458a82a431bd9b420098812e3 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 May 2015 22:54:00 -0700 Subject: [PATCH 04/46] remove unused experimental grab placement --- examples/example/games/grabHockey.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/examples/example/games/grabHockey.js b/examples/example/games/grabHockey.js index 57333e0d1e..afce60c358 100644 --- a/examples/example/games/grabHockey.js +++ b/examples/example/games/grabHockey.js @@ -127,25 +127,6 @@ function forwardPickRayIntersection(pointOnPlane, mouseX, mouseY) { return pickIntersection; } -/* -function yCylinderPickRayIntersection(grabRadius, mouseX, mouseY) { - var pickRay = Camera.computePickRay(mouseX, mouseY); - var angle = Math.asin(pickRay.direction.y); - if (angle > MAX_VERTICAL_ANGLE) { - angle = MAX_VERTICAL_ANGLE; - } else if (angle < MIN_VERTICAL_ANGLE) { - angle = MIN_VERTICAL_ANGLE; - } - var horizontalNormal = pickRay.direction; - horizontalNormal.y = 0; - horizontalNormal = Vec3.normalize(horizontalNormal); - var pickIntersection = Vec3.multiply(horizontalNormal, grabRadius); - pickIntersection.y = grabRadius * Math.tan(angle); - pickIntersection = Vec3.sum(pickIntersection, Camera.getPosition()) - return pickIntersection; -} -*/ - function mousePressEvent(event) { if (!event.isLeftButton) { return; From 139ebca258e6cb9a05792b6fd12c0fea67370727 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 18 May 2015 23:09:08 -0700 Subject: [PATCH 05/46] less bad behavior when dragging across horizon --- examples/example/games/grabHockey.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/example/games/grabHockey.js b/examples/example/games/grabHockey.js index afce60c358..fc21caee1d 100644 --- a/examples/example/games/grabHockey.js +++ b/examples/example/games/grabHockey.js @@ -106,6 +106,9 @@ function xzPickRayIntersetion(pointOnPlane, mouseX, mouseY) { var pickRay = Camera.computePickRay(mouseX, mouseY); if (Math.abs(pickRay.direction.y) > 0.001) { var distance = relativePoint.y / pickRay.direction.y; + if (distance < 0.001) { + return pointOnPlane; + } var pickInersection = Vec3.multiply(pickRay.direction, distance); pickInersection = Vec3.sum(Camera.getPosition(), pickInersection); return pickInersection; @@ -238,7 +241,6 @@ function mouseReleaseEvent() { } } -// new mouseMoveEvent function mouseMoveEvent(event) { if (isGrabbing) { // see if something added/restored gravity @@ -321,7 +323,6 @@ function mouseMoveEvent(event) { targetPosition = pointOnPlane; initialVerticalGrabPosition = targetPosition; } - } } prevMouse.x = event.x; From 69f1cd80e6eea8e0b974f3bb931a8646e2293ef3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 22 May 2015 16:29:01 -0700 Subject: [PATCH 06/46] immediately send ICE heartbeat once data present --- interface/src/DatagramProcessor.cpp | 1 + libraries/audio-client/src/AudioClient.cpp | 241 ++++++++++---------- libraries/networking/src/AddressManager.cpp | 4 + libraries/networking/src/DomainHandler.cpp | 21 ++ libraries/networking/src/DomainHandler.h | 3 + libraries/networking/src/NodeList.cpp | 15 ++ libraries/networking/src/NodeList.h | 3 +- 7 files changed, 167 insertions(+), 121 deletions(-) diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index f691527186..51f21738f3 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -68,6 +68,7 @@ void DatagramProcessor::processDatagrams() { Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); } else { + // qDebug() << "Received audio data packet at" << usecTimestampNow(); QMetaObject::invokeMethod(DependencyManager::get().data(), "addReceivedAudioToStream", Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 10e2bc3bbf..1fcd53dc38 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -116,7 +116,7 @@ AudioClient::AudioClient() : { // clear the array of locally injected samples memset(_localProceduralSamples, 0, AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL); - + connect(&_receivedAudioStream, &MixedProcessedAudioStream::processSamples, this, &AudioClient::processReceivedSamples, Qt::DirectConnection); @@ -127,7 +127,7 @@ AudioClient::AudioClient() : QTimer* updateTimer = new QTimer(this); connect(updateTimer, &QTimer::timeout, this, &AudioClient::checkDevices); updateTimer->start(DEVICE_CHECK_INTERVAL_MSECS); - + // create GVerb filter _gverb = createGverbFilter(); configureGverbFilter(_gverb); @@ -135,7 +135,7 @@ AudioClient::AudioClient() : AudioClient::~AudioClient() { stop(); - + if (_gverb) { gverb_free(_gverb); } @@ -148,7 +148,7 @@ void AudioClient::reset() { _toneSource.reset(); _sourceGain.reset(); _inputGain.reset(); - + gverb_flush(_gverb); } @@ -186,7 +186,7 @@ int numDestinationSamplesRequired(const QAudioFormat& sourceFormat, const QAudio int numSourceSamples) { float ratio = (float) destinationFormat.channelCount() / sourceFormat.channelCount(); ratio *= (float) destinationFormat.sampleRate() / sourceFormat.sampleRate(); - + return (numSourceSamples * ratio) + 0.5f; } @@ -287,7 +287,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { } qCDebug(audioclient) << "DEBUG [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]"; - + return getNamedAudioDeviceForMode(mode, deviceName); #endif @@ -302,7 +302,7 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, if (!audioDevice.isFormatSupported(desiredAudioFormat)) { qCDebug(audioclient) << "The desired format for audio I/O is" << desiredAudioFormat; qCDebug(audioclient, "The desired audio format is not supported by this device"); - + if (desiredAudioFormat.channelCount() == 1) { adjustedAudioFormat = desiredAudioFormat; adjustedAudioFormat.setChannelCount(2); @@ -313,17 +313,17 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, adjustedAudioFormat.setChannelCount(1); } } - + const int FORTY_FOUR = 44100; - + adjustedAudioFormat = desiredAudioFormat; - + #ifdef Q_OS_ANDROID adjustedAudioFormat.setSampleRate(FORTY_FOUR); #else - + const int HALF_FORTY_FOUR = FORTY_FOUR / 2; - + if (audioDevice.supportedSampleRates().contains(AudioConstants::SAMPLE_RATE * 2)) { // use 48, which is a sample downsample, upsample adjustedAudioFormat.setSampleRate(AudioConstants::SAMPLE_RATE * 2); @@ -335,7 +335,7 @@ bool adjustedFormatForAudioDevice(const QAudioDeviceInfo& audioDevice, adjustedAudioFormat.setSampleRate(FORTY_FOUR); } #endif - + if (adjustedAudioFormat != desiredAudioFormat) { // return the nearest in case it needs 2 channels adjustedAudioFormat = audioDevice.nearestFormat(adjustedAudioFormat); @@ -357,18 +357,18 @@ bool sampleChannelConversion(const int16_t* sourceSamples, int16_t* destinationS for (uint i = 0; i < numSourceSamples; i += 2) { destinationSamples[i / 2] = (sourceSamples[i] / 2) + (sourceSamples[i + 1] / 2); } - + return true; } else if (sourceAudioFormat.channelCount() == 1 && destinationAudioFormat.channelCount() == 2) { - + // loop through the mono input audio and repeat each sample twice for (uint i = 0; i < numSourceSamples; ++i) { destinationSamples[i * 2] = destinationSamples[(i * 2) + 1] = sourceSamples[i]; } - + return true; } - + return false; } @@ -376,7 +376,7 @@ soxr_error_t possibleResampling(soxr_t resampler, const int16_t* sourceSamples, int16_t* destinationSamples, unsigned int numSourceSamples, unsigned int numDestinationSamples, const QAudioFormat& sourceAudioFormat, const QAudioFormat& destinationAudioFormat) { - + if (numSourceSamples > 0) { if (!resampler) { if (!sampleChannelConversion(sourceSamples, destinationSamples, numSourceSamples, @@ -384,41 +384,41 @@ soxr_error_t possibleResampling(soxr_t resampler, // no conversion, we can copy the samples directly across memcpy(destinationSamples, sourceSamples, numSourceSamples * sizeof(int16_t)); } - + return 0; } else { soxr_error_t resampleError = 0; - + if (sourceAudioFormat.channelCount() != destinationAudioFormat.channelCount()) { float channelCountRatio = (float) destinationAudioFormat.channelCount() / sourceAudioFormat.channelCount(); - + int numChannelCoversionSamples = (int) (numSourceSamples * channelCountRatio); int16_t* channelConversionSamples = new int16_t[numChannelCoversionSamples]; - + sampleChannelConversion(sourceSamples, channelConversionSamples, numSourceSamples, sourceAudioFormat, destinationAudioFormat); - + resampleError = soxr_process(resampler, channelConversionSamples, numChannelCoversionSamples, NULL, destinationSamples, numDestinationSamples, NULL); - + delete[] channelConversionSamples; } else { - + unsigned int numAdjustedSourceSamples = numSourceSamples; unsigned int numAdjustedDestinationSamples = numDestinationSamples; - + if (sourceAudioFormat.channelCount() == 2 && destinationAudioFormat.channelCount() == 2) { numAdjustedSourceSamples /= 2; numAdjustedDestinationSamples /= 2; } - + resampleError = soxr_process(resampler, sourceSamples, numAdjustedSourceSamples, NULL, destinationSamples, numAdjustedDestinationSamples, NULL); } - + return resampleError; } } else { @@ -429,30 +429,30 @@ soxr_error_t possibleResampling(soxr_t resampler, soxr_t soxrResamplerFromInputFormatToOutputFormat(const QAudioFormat& sourceAudioFormat, const QAudioFormat& destinationAudioFormat) { soxr_error_t soxrError; - + // setup soxr_io_spec_t for input and output soxr_io_spec_t inputToNetworkSpec = soxr_io_spec(soxrDataTypeFromQAudioFormat(sourceAudioFormat), soxrDataTypeFromQAudioFormat(destinationAudioFormat)); - + // setup soxr_quality_spec_t for quality options soxr_quality_spec_t qualitySpec = soxr_quality_spec(SOXR_MQ, 0); - + int channelCount = (sourceAudioFormat.channelCount() == 2 && destinationAudioFormat.channelCount() == 2) ? 2 : 1; - + soxr_t newResampler = soxr_create(sourceAudioFormat.sampleRate(), destinationAudioFormat.sampleRate(), channelCount, &soxrError, &inputToNetworkSpec, &qualitySpec, 0); - + if (soxrError) { qCDebug(audioclient) << "There was an error setting up the soxr resampler -" << "soxr error code was " << soxrError; - + soxr_delete(newResampler); - + return NULL; } - + return newResampler; } @@ -476,7 +476,7 @@ void AudioClient::start() { QAudioDeviceInfo outputDeviceInfo = defaultAudioDeviceForMode(QAudio::AudioOutput); qCDebug(audioclient) << "The default audio output device is" << outputDeviceInfo.deviceName(); bool outputFormatSupported = switchOutputToAudioDevice(outputDeviceInfo); - + if (!inputFormatSupported) { qCDebug(audioclient) << "Unable to set up audio input because of a problem with input format."; qCDebug(audioclient) << "The closest format available is" << inputDeviceInfo.nearestFormat(_desiredInputFormat); @@ -503,11 +503,11 @@ void AudioClient::stop() { _sourceGain.finalize(); _noiseSource.finalize(); _toneSource.finalize(); - + // "switch" to invalid devices in order to shut down the state switchInputToAudioDevice(QAudioDeviceInfo()); switchOutputToAudioDevice(QAudioDeviceInfo()); - + if (_loopbackResampler) { soxr_delete(_loopbackResampler); _loopbackResampler = NULL; @@ -543,7 +543,7 @@ ty_gverb* AudioClient::createGverbFilter() { _reverbOptions->getReverbTime(), _reverbOptions->getDamping(), _reverbOptions->getSpread(), _reverbOptions->getInputBandwidth(), _reverbOptions->getEarlyLevel(), _reverbOptions->getTailLevel()); - + return filter; } @@ -560,7 +560,7 @@ void AudioClient::configureGverbFilter(ty_gverb* filter) { void AudioClient::updateGverbOptions() { bool reverbChanged = false; if (_receivedAudioStream.hasReverb()) { - + if (_zoneReverbOptions.getReverbTime() != _receivedAudioStream.getRevebTime()) { _zoneReverbOptions.setReverbTime(_receivedAudioStream.getRevebTime()); reverbChanged = true; @@ -569,7 +569,7 @@ void AudioClient::updateGverbOptions() { _zoneReverbOptions.setWetLevel(_receivedAudioStream.getWetLevel()); // Not part of actual filter config, no need to set reverbChanged to true } - + if (_reverbOptions != &_zoneReverbOptions) { _reverbOptions = &_zoneReverbOptions; reverbChanged = true; @@ -578,7 +578,7 @@ void AudioClient::updateGverbOptions() { _reverbOptions = &_scriptReverbOptions; reverbChanged = true; } - + if (reverbChanged) { gverb_free(_gverb); _gverb = createGverbFilter(); @@ -588,7 +588,7 @@ void AudioClient::updateGverbOptions() { void AudioClient::setReverb(bool reverb) { _reverb = reverb; - + if (!_reverb) { gverb_flush(_gverb); } @@ -620,7 +620,7 @@ void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reve QAudioFormat& audioFormat, bool noEcho) { float wetFraction = DB_CO(_reverbOptions->getWetLevel()); float dryFraction = 1.0f - wetFraction; - + float lValue,rValue; for (int sample = 0; sample < numSamples; sample += audioFormat.channelCount()) { // Run GVerb @@ -634,7 +634,7 @@ void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reve int lResult = glm::clamp((int)(samplesData[j] * dryFraction + lValue * wetFraction), AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); samplesData[j] = (int16_t)lResult; - + if (noEcho) { reverbAlone[j] = (int16_t)lValue * wetFraction; } @@ -643,7 +643,7 @@ void AudioClient::addReverb(ty_gverb* gverb, int16_t* samplesData, int16_t* reve int rResult = glm::clamp((int)(samplesData[j] * dryFraction + rValue * wetFraction), AudioConstants::MIN_SAMPLE_VALUE, AudioConstants::MAX_SAMPLE_VALUE); samplesData[j] = (int16_t)rResult; - + if (noEcho) { reverbAlone[j] = (int16_t)rValue * wetFraction; } @@ -660,53 +660,53 @@ void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) { if (_muted || !_audioOutput || (!_shouldEchoLocally && !hasReverb)) { return; } - + // if this person wants local loopback add that to the locally injected audio // if there is reverb apply it to local audio and substract the origin samples - + if (!_loopbackOutputDevice && _loopbackAudioOutput) { // we didn't have the loopback output device going so set that up now _loopbackOutputDevice = _loopbackAudioOutput->start(); - + if (!_loopbackOutputDevice) { return; } } - + // do we need to setup a resampler? if (_inputFormat.sampleRate() != _outputFormat.sampleRate() && !_loopbackResampler) { qCDebug(audioclient) << "Attemping to create a resampler for input format to output format for audio loopback."; _loopbackResampler = soxrResamplerFromInputFormatToOutputFormat(_inputFormat, _outputFormat); - + if (!_loopbackResampler) { return; } } - + static QByteArray reverbAlone; // Intermediary for local reverb with no echo static QByteArray loopBackByteArray; - + int numInputSamples = inputByteArray.size() / sizeof(int16_t); int numLoopbackSamples = numDestinationSamplesRequired(_inputFormat, _outputFormat, numInputSamples); - + reverbAlone.resize(numInputSamples * sizeof(int16_t)); loopBackByteArray.resize(numLoopbackSamples * sizeof(int16_t)); - + int16_t* inputSamples = reinterpret_cast(inputByteArray.data()); int16_t* reverbAloneSamples = reinterpret_cast(reverbAlone.data()); int16_t* loopbackSamples = reinterpret_cast(loopBackByteArray.data()); - + if (hasReverb) { updateGverbOptions(); addReverb(_gverb, inputSamples, reverbAloneSamples, numInputSamples, _inputFormat, !_shouldEchoLocally); } - + possibleResampling(_loopbackResampler, (_shouldEchoLocally) ? inputSamples : reverbAloneSamples, loopbackSamples, numInputSamples, numLoopbackSamples, _inputFormat, _outputFormat); - + _loopbackOutputDevice->write(loopBackByteArray); } @@ -726,7 +726,7 @@ void AudioClient::handleAudioInput() { int inputSamplesRequired = (int)((float)AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio); QByteArray inputByteArray = _inputDevice->readAll(); - + // Add audio source injection if enabled if (!_muted && _audioSourceInjectEnabled) { int16_t* inputFrameData = (int16_t*)inputByteArray.data(); @@ -745,11 +745,11 @@ void AudioClient::handleAudioInput() { _sourceGain.render(_inputFrameBuffer); // post gain _inputFrameBuffer.copyFrames(1, inputFrameCount, inputFrameData, true /*copy out*/); } - + handleLocalEchoAndReverb(inputByteArray); _inputRingBuffer.writeData(inputByteArray.data(), inputByteArray.size()); - + float audioInputMsecsRead = inputByteArray.size() / (float)(_inputFormat.bytesForDuration(USECS_PER_MSEC)); _stats.updateInputMsecsRead(audioInputMsecsRead); @@ -763,50 +763,50 @@ void AudioClient::handleAudioInput() { : AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL; if (!_muted) { - + // zero out the monoAudioSamples array and the locally injected audio memset(networkAudioSamples, 0, numNetworkBytes); - + // Increment the time since the last clip if (_timeSinceLastClip >= 0.0f) { _timeSinceLastClip += (float) numNetworkSamples / (float) AudioConstants::SAMPLE_RATE; } - + int16_t* inputAudioSamples = new int16_t[inputSamplesRequired]; _inputRingBuffer.readSamples(inputAudioSamples, inputSamplesRequired); - + possibleResampling(_inputToNetworkResampler, inputAudioSamples, networkAudioSamples, inputSamplesRequired, numNetworkSamples, _inputFormat, _desiredInputFormat); - + delete[] inputAudioSamples; - + // only impose the noise gate and perform tone injection if we are sending mono audio if (!_isStereoInput && !_audioSourceInjectEnabled && _isNoiseGateEnabled) { _inputGate.gateSamples(networkAudioSamples, numNetworkSamples); - + // if we performed the noise gate we can get values from it instead of enumerating the samples again _lastInputLoudness = _inputGate.getLastLoudness(); - + if (_inputGate.clippedInLastFrame()) { _timeSinceLastClip = 0.0f; } } else { float loudness = 0.0f; - + for (int i = 0; i < numNetworkSamples; i++) { int thisSample = std::abs(networkAudioSamples[i]); loudness += (float) thisSample; - + if (thisSample > (AudioConstants::MAX_SAMPLE_VALUE * AudioNoiseGate::CLIPPING_THRESHOLD)) { _timeSinceLastClip = 0.0f; } } - + _lastInputLoudness = fabs(loudness / numNetworkSamples); } - + emit inputReceived(QByteArray(reinterpret_cast(networkAudioSamples), AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL * sizeof(AudioConstants::AudioSample))); @@ -814,13 +814,13 @@ void AudioClient::handleAudioInput() { // our input loudness is 0, since we're muted _lastInputLoudness = 0; _timeSinceLastClip = 0.0f; - + _inputRingBuffer.shiftReadPosition(inputSamplesRequired); } auto nodeList = DependencyManager::get(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - + if (audioMixer && audioMixer->getActiveSocket()) { glm::vec3 headPosition = _positionGetter(); glm::quat headOrientation = _orientationGetter(); @@ -852,7 +852,7 @@ void AudioClient::handleAudioInput() { // memcpy the three float positions memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); currentPacketPtr += (sizeof(headPosition)); - + // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); @@ -864,7 +864,7 @@ void AudioClient::handleAudioInput() { // memcpy the three float positions memcpy(currentPacketPtr, &headPosition, sizeof(headPosition)); currentPacketPtr += (sizeof(headPosition)); - + // memcpy our orientation memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); @@ -875,6 +875,8 @@ void AudioClient::handleAudioInput() { _stats.sentPacket(); + // qDebug() << "Sending audio packet at" << usecTimestampNow(); + int packetBytes = currentPacketPtr - audioDataPacket; nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer); _outgoingAvatarAudioSequenceNumber++; @@ -890,7 +892,7 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t)); const int16_t* receivedSamples = reinterpret_cast(inputBuffer.data()); - + // copy the packet from the RB to the output possibleResampling(_networkToOutputResampler, receivedSamples, reinterpret_cast(outputBuffer.data()), @@ -900,20 +902,20 @@ void AudioClient::processReceivedSamples(const QByteArray& inputBuffer, QByteArr void AudioClient::sendMuteEnvironmentPacket() { auto nodeList = DependencyManager::get(); - + QByteArray mutePacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeMuteEnvironment); int headerSize = mutePacket.size(); - + const float MUTE_RADIUS = 50; - + glm::vec3 currentSourcePosition = _positionGetter(); mutePacket.resize(mutePacket.size() + sizeof(glm::vec3) + sizeof(float)); memcpy(mutePacket.data() + headerSize, ¤tSourcePosition, sizeof(glm::vec3)); memcpy(mutePacket.data() + headerSize + sizeof(glm::vec3), &MUTE_RADIUS, sizeof(float)); - + // grab our audio mixer from the NodeList, if it exists SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - + if (audioMixer) { // send off this mute packet nodeList->writeDatagram(mutePacket, audioMixer); @@ -922,6 +924,7 @@ void AudioClient::sendMuteEnvironmentPacket() { void AudioClient::addReceivedAudioToStream(const QByteArray& audioByteArray) { if (_audioOutput) { + // qDebug() << "Adding received audio to stream at" << usecTimestampNow(); // Audio output must exist and be correctly set up if we're going to process received audio _receivedAudioStream.parseData(audioByteArray); } @@ -930,11 +933,11 @@ void AudioClient::addReceivedAudioToStream(const QByteArray& audioByteArray) { void AudioClient::parseAudioEnvironmentData(const QByteArray &packet) { int numBytesPacketHeader = numBytesForPacketHeader(packet); const char* dataAt = packet.constData() + numBytesPacketHeader; - + char bitset; memcpy(&bitset, dataAt, sizeof(char)); dataAt += sizeof(char); - + bool hasReverb = oneAtBit(bitset, HAS_REVERB_BIT);; if (hasReverb) { float reverbTime, wetLevel; @@ -956,13 +959,13 @@ void AudioClient::toggleMute() { void AudioClient::setIsStereoInput(bool isStereoInput) { if (isStereoInput != _isStereoInput) { _isStereoInput = isStereoInput; - + if (_isStereoInput) { _desiredInputFormat.setChannelCount(2); } else { _desiredInputFormat.setChannelCount(1); } - + // change in channel count for desired input format, restart the input device switchInputToAudioDevice(_inputAudioDeviceName); } @@ -976,7 +979,7 @@ void AudioClient::selectAudioSourcePinkNoise() { _noiseSourceEnabled = true; _toneSourceEnabled = false; } - + void AudioClient::selectAudioSourceSine440() { _toneSourceEnabled = true; _noiseSourceEnabled = false; @@ -986,24 +989,24 @@ bool AudioClient::outputLocalInjector(bool isStereo, AudioInjector* injector) { if (injector->getLocalBuffer()) { QAudioFormat localFormat = _desiredOutputFormat; localFormat.setChannelCount(isStereo ? 2 : 1); - + QAudioOutput* localOutput = new QAudioOutput(getNamedAudioDeviceForMode(QAudio::AudioOutput, _outputAudioDeviceName), localFormat, injector->getLocalBuffer()); - + // move the localOutput to the same thread as the local injector buffer localOutput->moveToThread(injector->getLocalBuffer()->thread()); - + // have it be stopped when that local buffer is about to close connect(injector->getLocalBuffer(), &AudioInjectorLocalBuffer::bufferEmpty, localOutput, &QAudioOutput::stop); connect(injector->getLocalBuffer(), &QIODevice::aboutToClose, localOutput, &QAudioOutput::stop); - + qCDebug(audioclient) << "Starting QAudioOutput for local injector" << localOutput; - + localOutput->start(injector->getLocalBuffer()); return localOutput->state() == QAudio::ActiveState; } - + return false; } @@ -1015,7 +1018,7 @@ void AudioClient::outputFormatChanged() { bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { bool supportedFormat = false; - + // cleanup any previously initialized device if (_audioInput) { // The call to stop() causes _inputDevice to be destructed. @@ -1030,7 +1033,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn _inputAudioDeviceName = ""; } - + if (_inputToNetworkResampler) { // if we were using an input to network resampler, delete it here soxr_delete(_inputToNetworkResampler); @@ -1040,36 +1043,36 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn if (!inputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio input device " << inputDeviceInfo.deviceName() << "is available."; _inputAudioDeviceName = inputDeviceInfo.deviceName().trimmed(); - + if (adjustedFormatForAudioDevice(inputDeviceInfo, _desiredInputFormat, _inputFormat)) { qCDebug(audioclient) << "The format to be used for audio input is" << _inputFormat; - + // we've got the best we can get for input // if required, setup a soxr resampler for this input to our desired network format if (_inputFormat != _desiredInputFormat && _inputFormat.sampleRate() != _desiredInputFormat.sampleRate()) { qCDebug(audioclient) << "Attemping to create a soxr resampler for input format to network format."; _inputToNetworkResampler = soxrResamplerFromInputFormatToOutputFormat(_inputFormat, _desiredInputFormat); - + if (!_inputToNetworkResampler) { return false; } } else { qCDebug(audioclient) << "No resampling required for audio input to match desired network format."; } - + // if the user wants stereo but this device can't provide then bail - if (!_isStereoInput || _inputFormat.channelCount() == 2) { + if (!_isStereoInput || _inputFormat.channelCount() == 2) { _audioInput = new QAudioInput(inputDeviceInfo, _inputFormat, this); _numInputCallbackBytes = calculateNumberOfInputCallbackBytes(_inputFormat); _audioInput->setBufferSize(_numInputCallbackBytes); - + // how do we want to handle input working, but output not working? int numFrameSamples = calculateNumberOfFrameSamples(_numInputCallbackBytes); _inputRingBuffer.resizeForFrameSize(numFrameSamples); - + _inputDevice = _audioInput->start(); - + if (_inputDevice) { connect(_inputDevice, SIGNAL(readyRead()), this, SLOT(handleAudioInput())); supportedFormat = true; @@ -1079,7 +1082,7 @@ bool AudioClient::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceIn } } } - + return supportedFormat; } @@ -1117,7 +1120,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice // cleanup any previously initialized device if (_audioOutput) { _audioOutput->stop(); - + delete _audioOutput; _audioOutput = NULL; @@ -1125,13 +1128,13 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice delete _loopbackAudioOutput; _loopbackAudioOutput = NULL; } - + if (_networkToOutputResampler) { // if we were using an input to network resampler, delete it here soxr_delete(_networkToOutputResampler); _networkToOutputResampler = NULL; } - + if (_loopbackResampler) { // if we were using an input to output resample, delete it here soxr_delete(_loopbackResampler); @@ -1141,24 +1144,24 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice if (!outputDeviceInfo.isNull()) { qCDebug(audioclient) << "The audio output device " << outputDeviceInfo.deviceName() << "is available."; _outputAudioDeviceName = outputDeviceInfo.deviceName().trimmed(); - + if (adjustedFormatForAudioDevice(outputDeviceInfo, _desiredOutputFormat, _outputFormat)) { qCDebug(audioclient) << "The format to be used for audio output is" << _outputFormat; - + // we've got the best we can get for input // if required, setup a soxr resampler for this input to our desired network format if (_desiredOutputFormat != _outputFormat && _desiredOutputFormat.sampleRate() != _outputFormat.sampleRate()) { qCDebug(audioclient) << "Attemping to create a resampler for network format to output format."; _networkToOutputResampler = soxrResamplerFromInputFormatToOutputFormat(_desiredOutputFormat, _outputFormat); - + if (!_networkToOutputResampler) { return false; } } else { qCDebug(audioclient) << "No resampling required for network output to match actual output format."; } - + outputFormatChanged(); // setup our general output device for audio-mixer audio @@ -1168,20 +1171,20 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice connect(_audioOutput, &QAudioOutput::notify, this, &AudioClient::outputNotify); qCDebug(audioclient) << "Output Buffer capacity in frames: " << _audioOutput->bufferSize() / sizeof(int16_t) / (float)_outputFrameSize; - + _audioOutputIODevice.start(); _audioOutput->start(&_audioOutputIODevice); // setup a loopback audio output device _loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); - + _timeSinceLastReceived.start(); supportedFormat = true; } } - + return supportedFormat; } @@ -1202,7 +1205,7 @@ void AudioClient::setOutputBufferSize(int numFrames) { // The following constant is operating system dependent due to differences in // the way input audio is handled. The audio input buffer size is inversely -// proportional to the accelerator ratio. +// proportional to the accelerator ratio. #ifdef Q_OS_WIN const float AudioClient::CALLBACK_ACCELERATOR_RATIO = 0.1f; @@ -1226,7 +1229,7 @@ int AudioClient::calculateNumberOfInputCallbackBytes(const QAudioFormat& format) } float AudioClient::calculateDeviceToNetworkInputRatio() const { - float inputToNetworkInputRatio = (int)((_numInputCallbackBytes + float inputToNetworkInputRatio = (int)((_numInputCallbackBytes * CALLBACK_ACCELERATOR_RATIO / AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL) + 0.5f); @@ -1257,7 +1260,7 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) { int samplesRequested = maxSize / sizeof(int16_t); int samplesPopped; int bytesWritten; - + if ((samplesPopped = _receivedAudioStream.popSamples(samplesRequested, false)) > 0) { AudioRingBuffer::ConstIterator lastPopOutput = _receivedAudioStream.getLastPopOutput(); lastPopOutput.readSamples((int16_t*)data, samplesPopped); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 48d9655e43..84db573dfb 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -106,6 +106,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); + qDebug() << "Lookup of HF URL at" << usecTimestampNow(); + // there are 4 possible lookup strings // 1. global place name (name of domain or place) - example: sanfrancisco @@ -163,6 +165,8 @@ void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); + qDebug() << "Go to address from API response at" << usecTimestampNow(); + goToAddressFromObject(dataObject.toVariantMap(), requestReply); emit lookupResultsFinished(); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index f7d460d7dd..16672565d4 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -135,10 +135,21 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, replaceableSockAddr->~HifiSockAddr(); replaceableSockAddr = new (replaceableSockAddr) HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); + if (_iceServerSockAddr.getAddress().isNull()) { + // connect to lookup completed for ice-server socket so we can request a heartbeat once hostname is looked up + connect(&_iceServerSockAddr, &HifiSockAddr::lookupCompleted, this, &DomainHandler::completedIceServerHostnameLookup); + } else { + completedIceServerHostnameLookup(); + } + + // refresh our ICE client UUID to something new _iceClientID = QUuid::createUuid(); qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname; + + qDebug() << "ICE server info set at" << usecTimestampNow(); + } } @@ -172,6 +183,14 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { qCDebug(networking, "Failed domain server lookup"); } +void DomainHandler::completedIceServerHostnameLookup() { + qDebug() << "ICE server socket is at" << _iceServerSockAddr; + qDebug() << "ICE server socket lookup completed at" << usecTimestampNow(); + // emit our signal so we can send a heartbeat to ice-server immediately + emit iceSocketAndIDReceived(); + +} + void DomainHandler::setIsConnected(bool isConnected) { if (_isConnected != isConnected) { _isConnected = isConnected; @@ -270,6 +289,8 @@ void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { NetworkPeer packetPeer; iceResponseStream >> packetPeer; + qDebug() << "Recieved response for network peer from ICE server at" << usecTimestampNow(); + if (packetPeer.getUUID() != _iceDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; } else { diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 80a211405b..794a7793bb 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -85,7 +85,9 @@ public slots: private slots: void completedHostnameLookup(const QHostInfo& hostInfo); + void completedIceServerHostnameLookup(); void settingsRequestFinished(); + signals: void hostnameChanged(const QString& hostname); @@ -96,6 +98,7 @@ signals: void connectedToDomain(const QString& hostname); void disconnectedFromDomain(); + void iceSocketAndIDReceived(); void requestICEConnectionAttempt(); void settingsReceived(const QJsonObject& domainSettingsObject); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index b39503f709..462e256d15 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -64,6 +64,9 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // clear our NodeList when the domain changes connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, this, &NodeList::reset); + // send an ICE heartbeat as soon as we get ice server information + connect(&_domainHandler, &DomainHandler::iceSocketAndIDReceived, this, &NodeList::handleICEConnectionToDomainServer); + // handle ICE signal from DS so connection is attempted immediately connect(&_domainHandler, &DomainHandler::requestICEConnectionAttempt, this, &NodeList::handleICEConnectionToDomainServer); @@ -160,6 +163,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr if (!_domainHandler.getSockAddr().isNull()) { // only process a list from domain-server if we're talking to a domain // TODO: how do we make sure this is actually the domain we want the list from (DTLS probably) + qDebug() << "Processing domain server list at" << usecTimestampNow(); processDomainServerList(packet); } break; @@ -198,6 +202,8 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr if (sendingNode) { sendingNode->setLastHeardMicrostamp(usecTimestampNow()); + qDebug() << "Activating socket for node" << sendingNode->getUUID() << "at" << usecTimestampNow(); + // activate the appropriate socket for this node, if not yet updated activateSocketFromNodeCommunication(packet, sendingNode); @@ -216,6 +222,8 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr case PacketTypeUnverifiedPingReply: { qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr; + qDebug() << "Received reply from domain server at" << usecTimestampNow(); + // for now we're unsafely assuming this came back from the domain if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) { qCDebug(networking) << "Connecting to domain using local socket"; @@ -379,6 +387,8 @@ void NodeList::sendDomainServerCheckIn() { } } + qDebug() << "Sending domain server check in at" << usecTimestampNow(); + if (!isUsingDTLS) { writeUnverifiedDatagram(domainServerPacket, _domainHandler.getSockAddr()); } @@ -504,6 +514,8 @@ void NodeList::handleICEConnectionToDomainServer() { _domainHandler.getICEPeer().resetConnectionAttemps(); + qDebug() << "Sending heartbeat to ice server at" << usecTimestampNow(); + LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), _domainHandler.getICEDomainID()); @@ -511,6 +523,8 @@ void NodeList::handleICEConnectionToDomainServer() { qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); + qDebug() << "Sending ping packet to domain server at" << usecTimestampNow(); + // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID()); writeUnverifiedDatagram(localPingPacket, _domainHandler.getICEPeer().getLocalSocket()); @@ -596,6 +610,7 @@ void NodeList::sendAssignment(Assignment& assignment) { } void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { + qDebug() << "Sending ping punch to node" << node->getUUID() << "at" << usecTimestampNow(); // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local); diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 39b1e3e2d2..aba7661663 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -76,6 +76,7 @@ signals: void limitOfSilentDomainCheckInsReached(); private slots: void sendPendingDSPathQuery(); + void handleICEConnectionToDomainServer(); private: NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); @@ -85,8 +86,6 @@ private: void sendSTUNRequest(); bool processSTUNResponse(const QByteArray& packet); - void handleICEConnectionToDomainServer(); - void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); From 27717db9d11df12d6223263e06529adf54ef3706 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 22 May 2015 16:38:26 -0700 Subject: [PATCH 07/46] fire off DS check immediately once DS info is known --- libraries/networking/src/NodeList.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 462e256d15..b44bc44e9f 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -234,6 +234,9 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr } else { qCDebug(networking) << "Reply does not match either local or public socket for domain. Will not connect."; } + + // immediately send a domain-server check in now that we have channel to talk to domain-server on + sendDomainServerCheckIn(); } case PacketTypeStunResponse: { // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch From f28ade10753e29e99a85d6f263ae293db57fd3e2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 26 May 2015 10:37:44 -0700 Subject: [PATCH 08/46] log connection times for eventual stats display --- interface/src/DatagramProcessor.cpp | 1 - libraries/audio-client/src/AudioClient.cpp | 1 - libraries/networking/src/AddressManager.cpp | 9 ++++-- libraries/networking/src/DomainHandler.cpp | 25 ++++++++++++--- libraries/networking/src/NodeList.cpp | 34 +++++++++++++++++---- libraries/networking/src/NodeList.h | 25 +++++++++++++++ 6 files changed, 79 insertions(+), 16 deletions(-) diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 51f21738f3..f691527186 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -68,7 +68,6 @@ void DatagramProcessor::processDatagrams() { Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); } else { - // qDebug() << "Received audio data packet at" << usecTimestampNow(); QMetaObject::invokeMethod(DependencyManager::get().data(), "addReceivedAudioToStream", Qt::QueuedConnection, Q_ARG(QByteArray, incomingPacket)); diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 1fcd53dc38..7c93bb0252 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -924,7 +924,6 @@ void AudioClient::sendMuteEnvironmentPacket() { void AudioClient::addReceivedAudioToStream(const QByteArray& audioByteArray) { if (_audioOutput) { - // qDebug() << "Adding received audio to stream at" << usecTimestampNow(); // Audio output must exist and be correctly set up if we're going to process received audio _receivedAudioStream.parseData(audioByteArray); } diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 84db573dfb..34d43a894e 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -106,7 +106,8 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); - qDebug() << "Lookup of HF URL at" << usecTimestampNow(); + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::LookupAddress, + usecTimestampNow()); // there are 4 possible lookup strings @@ -165,8 +166,6 @@ void AddressManager::handleAPIResponse(QNetworkReply& requestReply) { QJsonObject responseObject = QJsonDocument::fromJson(requestReply.readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); - qDebug() << "Go to address from API response at" << usecTimestampNow(); - goToAddressFromObject(dataObject.toVariantMap(), requestReply); emit lookupResultsFinished(); @@ -206,6 +205,8 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString DOMAIN_NETWORK_PORT_KEY = "network_port"; const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address"; + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::HandleAddress); + if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) { QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); @@ -436,6 +437,8 @@ void AddressManager::setDomainInfo(const QString& hostname, quint16 port) { qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port; + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::HandleAddress); + emit possibleDomainChangeRequired(hostname, port); } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 16672565d4..a5d53e8c7a 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -84,6 +84,10 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos _sockAddr = sockAddr; } + if (!_sockAddr.isNull()) { + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); + } + // some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification _hostname = hostname; } @@ -111,6 +115,8 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainHostname); + UserActivityLogger::getInstance().changedDomain(_hostname); emit hostnameChanged(_hostname); } @@ -135,6 +141,10 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, replaceableSockAddr->~HifiSockAddr(); replaceableSockAddr = new (replaceableSockAddr) HifiSockAddr(iceServerHostname, ICE_SERVER_DEFAULT_PORT); + auto nodeList = DependencyManager::get(); + + nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::LookupICEHostname); + if (_iceServerSockAddr.getAddress().isNull()) { // connect to lookup completed for ice-server socket so we can request a heartbeat once hostname is looked up connect(&_iceServerSockAddr, &HifiSockAddr::lookupCompleted, this, &DomainHandler::completedIceServerHostnameLookup); @@ -148,18 +158,19 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname; - qDebug() << "ICE server info set at" << usecTimestampNow(); - + nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::SetICEServerInfo); } } void DomainHandler::activateICELocalSocket() { + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getLocalSocket(); _hostname = _sockAddr.getAddress().toString(); emit completedSocketDiscovery(); } void DomainHandler::activateICEPublicSocket() { + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getPublicSocket(); _hostname = _sockAddr.getAddress().toString(); emit completedSocketDiscovery(); @@ -170,6 +181,9 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { _sockAddr.setAddress(hostInfo.addresses()[i]); + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); + + qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(), _sockAddr.getAddress().toString().toLocal8Bit().constData()); @@ -185,10 +199,11 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { void DomainHandler::completedIceServerHostnameLookup() { qDebug() << "ICE server socket is at" << _iceServerSockAddr; - qDebug() << "ICE server socket lookup completed at" << usecTimestampNow(); + + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::HandleICEHostname); + // emit our signal so we can send a heartbeat to ice-server immediately emit iceSocketAndIDReceived(); - } void DomainHandler::setIsConnected(bool isConnected) { @@ -289,7 +304,7 @@ void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { NetworkPeer packetPeer; iceResponseStream >> packetPeer; - qDebug() << "Recieved response for network peer from ICE server at" << usecTimestampNow(); + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::ReceiveDSPeerInformation); if (packetPeer.getUUID() != _iceDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index b44bc44e9f..6d172ed8ea 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -163,7 +164,6 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr if (!_domainHandler.getSockAddr().isNull()) { // only process a list from domain-server if we're talking to a domain // TODO: how do we make sure this is actually the domain we want the list from (DTLS probably) - qDebug() << "Processing domain server list at" << usecTimestampNow(); processDomainServerList(packet); } break; @@ -222,8 +222,6 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr case PacketTypeUnverifiedPingReply: { qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr; - qDebug() << "Received reply from domain server at" << usecTimestampNow(); - // for now we're unsafely assuming this came back from the domain if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) { qCDebug(networking) << "Connecting to domain using local socket"; @@ -271,6 +269,9 @@ void NodeList::reset() { if (_dtlsSocket) { disconnect(_dtlsSocket, 0, this, 0); } + + // reset the connection times + _lastConnectionTimes.clear(); } void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { @@ -390,7 +391,7 @@ void NodeList::sendDomainServerCheckIn() { } } - qDebug() << "Sending domain server check in at" << usecTimestampNow(); + flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstDSCheckIn); if (!isUsingDTLS) { writeUnverifiedDatagram(domainServerPacket, _domainHandler.getSockAddr()); @@ -517,7 +518,7 @@ void NodeList::handleICEConnectionToDomainServer() { _domainHandler.getICEPeer().resetConnectionAttemps(); - qDebug() << "Sending heartbeat to ice server at" << usecTimestampNow(); + flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstICEServerHearbeat); LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), @@ -526,7 +527,7 @@ void NodeList::handleICEConnectionToDomainServer() { qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); - qDebug() << "Sending ping packet to domain server at" << usecTimestampNow(); + flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstPingsToDS); // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID()); @@ -543,6 +544,8 @@ int NodeList::processDomainServerList(const QByteArray& packet) { // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::ReceiveFirstDSList); + // if this was the first domain-server list from this domain, we've now connected if (!_domainHandler.isConnected()) { _domainHandler.setUUID(uuidFromPacketHeader(packet)); @@ -655,3 +658,22 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con sendingNode->activateSymmetricSocket(); } } + +void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep) { + QMetaObject::invokeMethod(this, "flagTimeForConnectionStep", + Q_ARG(NodeList::ConnectionStep::Value, connectionStep), + Q_ARG(quint64, usecTimestampNow())); +} + +void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep, quint64 timestamp) { + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "flagTimeForConnectionStep", + Q_ARG(NodeList::ConnectionStep::Value, connectionStep), + Q_ARG(quint64, timestamp)); + } else { + // we only add a timestamp on the first call for each NodeList::ConnectionStep + if (!_lastConnectionTimes.contains(connectionStep)) { + _lastConnectionTimes[connectionStep] = timestamp; + } + } +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index aba7661663..c2b460ecc2 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -45,6 +45,25 @@ class NodeList : public LimitedNodeList { SINGLETON_DEPENDENCY public: + + class ConnectionStep { + public: + enum Value { + LookupAddress, + HandleAddress, + SetICEServerInfo, + LookupICEHostname, + HandleICEHostname, + SendFirstICEServerHearbeat, + ReceiveDSPeerInformation, + SendFirstPingsToDS, + SetDomainHostname, + SetDomainSocket, + SendFirstDSCheckIn, + ReceiveFirstDSList + }; + }; + NodeType_t getOwnerType() const { return _ownerType; } void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } @@ -63,6 +82,8 @@ public: int processDomainServerList(const QByteArray& packet); + void flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep); + void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); @@ -95,6 +116,8 @@ private: void sendDSPathQuery(const QString& newPath); + void flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep, qint64 timestamp); + NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; @@ -103,6 +126,8 @@ private: bool _hasCompletedInitialSTUNFailure; unsigned int _stunRequestsSinceSuccess; + QMap _lastConnectionTimes; + friend class Application; }; From f9251471da08e7a2213bc28b52a05a04abe85dec Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 26 May 2015 10:44:45 -0700 Subject: [PATCH 09/46] add audio connection to final part of connection steps --- libraries/audio-client/src/AudioClient.cpp | 4 +++- libraries/networking/src/AddressManager.cpp | 3 +-- libraries/networking/src/NodeList.cpp | 8 ++++++++ libraries/networking/src/NodeList.h | 8 ++++++-- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 7c93bb0252..2bf25a3575 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -875,7 +875,7 @@ void AudioClient::handleAudioInput() { _stats.sentPacket(); - // qDebug() << "Sending audio packet at" << usecTimestampNow(); + nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstAudioPacket); int packetBytes = currentPacketPtr - audioDataPacket; nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer); @@ -923,6 +923,8 @@ void AudioClient::sendMuteEnvironmentPacket() { } void AudioClient::addReceivedAudioToStream(const QByteArray& audioByteArray) { + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::ReceiveFirstAudioPacket); + if (_audioOutput) { // Audio output must exist and be correctly set up if we're going to process received audio _receivedAudioStream.parseData(audioByteArray); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 34d43a894e..63bcf4c4bc 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -106,8 +106,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::LookupAddress, - usecTimestampNow()); + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::LookupAddress); // there are 4 possible lookup strings diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 6d172ed8ea..31e7a88e66 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -636,6 +636,10 @@ void NodeList::pingInactiveNodes() { if (!node->getActiveSocket()) { // we don't have an active link to this node, ping it to set that up pingPunchForInactiveNode(node); + + if (node->getType() == NodeType::AudioMixer) { + flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstAudioPing); + } } }); } @@ -657,6 +661,10 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { sendingNode->activateSymmetricSocket(); } + + if (sendingNode->getType() == NodeType::AudioMixer) { + flagTimeForConnectionStep(NodeList::ConnectionStep::SetAudioMixerSocket); + } } void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep) { diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index c2b460ecc2..2ceee95f94 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -60,7 +60,11 @@ public: SetDomainHostname, SetDomainSocket, SendFirstDSCheckIn, - ReceiveFirstDSList + ReceiveFirstDSList, + SendFirstAudioPing, + SetAudioMixerSocket, + SendFirstAudioPacket, + ReceiveFirstAudioPacket }; }; @@ -116,7 +120,7 @@ private: void sendDSPathQuery(const QString& newPath); - void flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep, qint64 timestamp); + void flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep, quint64 timestamp); NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; From 626f22191c3d2d7a3b05c217858408733318507b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 26 May 2015 14:17:02 -0700 Subject: [PATCH 10/46] add a QAbstractTableModel for domain connection steps --- .../src/ui/DomainConnectionTableModel.cpp | 82 +++++++++++++++++++ interface/src/ui/DomainConnectionTableModel.h | 41 ++++++++++ libraries/networking/src/DomainHandler.cpp | 6 +- libraries/networking/src/NodeList.cpp | 27 +++--- libraries/networking/src/NodeList.h | 46 +++++------ 5 files changed, 157 insertions(+), 45 deletions(-) create mode 100644 interface/src/ui/DomainConnectionTableModel.cpp create mode 100644 interface/src/ui/DomainConnectionTableModel.h diff --git a/interface/src/ui/DomainConnectionTableModel.cpp b/interface/src/ui/DomainConnectionTableModel.cpp new file mode 100644 index 0000000000..0f27b56434 --- /dev/null +++ b/interface/src/ui/DomainConnectionTableModel.cpp @@ -0,0 +1,82 @@ +// +// DomainConnectionTableModel.cpp +// interface/src/ui +// +// Created by Stephen Birarda on 05/26/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include +#include + +#include "DomainConnectionTableModel.h" + +DomainConnectionTableModel::DomainConnectionTableModel(QObject* parent) : + QAbstractTableModel(parent) +{ + // ask the NodeList for the current values for connection times + QMap times = DependencyManager::get()->getLastConnectionTimes(); + + // setup our data with the returned values + + quint64 totalTime = 0; + quint64 firstStepTime = times[NodeList::ConnectionStep::LookupAddress] / USECS_PER_MSEC; + quint64 lastStepTime = firstStepTime; + + const QMetaObject &nodeListMeta = NodeList::staticMetaObject; + QMetaEnum stepEnum = nodeListMeta.enumerator(nodeListMeta.indexOfEnumerator("ConnectionStep")); + + for (int i = 0; i < stepEnum.keyCount(); i++) { + NodeList::ConnectionStep step = static_cast(i); + + if (times.contains(step)) { + // When did this step occur, how long since the last step, how long since the start? + _timestamps[_numRows] = times[step] / USECS_PER_MSEC; + _deltas[_numRows] = (_timestamps[_numRows] - lastStepTime); + _totals[_numRows] = _timestamps[_numRows] - firstStepTime; + + // increment the total time by this delta to keep track + totalTime += _deltas[_numRows]; + + lastStepTime = _timestamps[_numRows]; + + // increment our counted number of rows + ++_numRows; + } + } +} + +QVariant DomainConnectionTableModel::headerData(int section, Qt::Orientation orientation, int role) const { + switch(section) { + case 0: + return QVariant("Name"); + case 1: + return QVariant("Timestamp (ms)"); + case 2: + return QVariant("Delta (ms)"); + case 3: + return QVariant("Total Elapsed (ms)"); + default: + return QVariant(); + } +} + +QVariant DomainConnectionTableModel::data(const QModelIndex& index, int role) const { + switch(index.column()) { + case 0: + return _names[index.row()]; + case 1: + return QVariant(_timestamps[index.row()]); + case 2: + return QVariant(_deltas[index.row()]); + case 3: + return QVariant(_totals[index.row()]); + default: + return QVariant(); + } +} diff --git a/interface/src/ui/DomainConnectionTableModel.h b/interface/src/ui/DomainConnectionTableModel.h new file mode 100644 index 0000000000..9c2001dc00 --- /dev/null +++ b/interface/src/ui/DomainConnectionTableModel.h @@ -0,0 +1,41 @@ +// +// DomainConnectionTableModel.h +// interface/src/ui +// +// Created by Stephen Birarda on 05/26/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DomainConnectionTableModel_h +#define hifi_DomainConnectionTableModel_h + +#pragma once + +#include + +class DomainConnectionTableModel: public QAbstractTableModel { + Q_OBJECT +public: + DomainConnectionTableModel(QObject* parent = 0); + + const int NUM_COLUMNS = 4; // name, time, delta, since start + + int rowCount(const QModelIndex& parent = QModelIndex()) const { return _numRows; } + int columnCount(const QModelIndex& parent = QModelIndex()) const { return NUM_COLUMNS; } + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; +private: + int _numRows = 0; + + QVariantList _names; + QList _timestamps; + QList _deltas; + QList _totals; +}; + + +#endif // hifi_DomainConnectionTableModel_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index a5d53e8c7a..622eec6fd5 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -143,7 +143,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, auto nodeList = DependencyManager::get(); - nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::LookupICEHostname); + nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::SetICEServerHostname); if (_iceServerSockAddr.getAddress().isNull()) { // connect to lookup completed for ice-server socket so we can request a heartbeat once hostname is looked up @@ -157,8 +157,6 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, _iceClientID = QUuid::createUuid(); qCDebug(networking) << "ICE required to connect to domain via ice server at" << iceServerHostname; - - nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::SetICEServerInfo); } } @@ -200,7 +198,7 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { void DomainHandler::completedIceServerHostnameLookup() { qDebug() << "ICE server socket is at" << _iceServerSockAddr; - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::HandleICEHostname); + DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetICEServerSocket); // emit our signal so we can send a heartbeat to ice-server immediately emit iceSocketAndIDReceived(); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 31e7a88e66..4bfedfd555 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -76,6 +77,8 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // clear our NodeList when logout is requested connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + + qRegisterMetaType("NodeList::ConnectionStep"); } qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& destination) { @@ -202,8 +205,6 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr if (sendingNode) { sendingNode->setLastHeardMicrostamp(usecTimestampNow()); - qDebug() << "Activating socket for node" << sendingNode->getUUID() << "at" << usecTimestampNow(); - // activate the appropriate socket for this node, if not yet updated activateSocketFromNodeCommunication(packet, sendingNode); @@ -616,8 +617,6 @@ void NodeList::sendAssignment(Assignment& assignment) { } void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { - qDebug() << "Sending ping punch to node" << node->getUUID() << "at" << usecTimestampNow(); - // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local); writeDatagram(localPingPacket, node, node->getLocalSocket()); @@ -667,21 +666,15 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con } } -void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep) { +void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep) { QMetaObject::invokeMethod(this, "flagTimeForConnectionStep", - Q_ARG(NodeList::ConnectionStep::Value, connectionStep), - Q_ARG(quint64, usecTimestampNow())); + Q_ARG(NodeList::ConnectionStep, connectionStep), + Q_ARG(quint64, usecTimestampNow())); } -void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep, quint64 timestamp) { - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "flagTimeForConnectionStep", - Q_ARG(NodeList::ConnectionStep::Value, connectionStep), - Q_ARG(quint64, timestamp)); - } else { - // we only add a timestamp on the first call for each NodeList::ConnectionStep - if (!_lastConnectionTimes.contains(connectionStep)) { - _lastConnectionTimes[connectionStep] = timestamp; - } +void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep, quint64 timestamp) { + // we only add a timestamp on the first call for each NodeList::ConnectionStep + if (!_lastConnectionTimes.contains(connectionStep)) { + _lastConnectionTimes[connectionStep] = timestamp; } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 2ceee95f94..42356570a9 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -46,28 +46,26 @@ class NodeList : public LimitedNodeList { public: - class ConnectionStep { - public: - enum Value { - LookupAddress, - HandleAddress, - SetICEServerInfo, - LookupICEHostname, - HandleICEHostname, - SendFirstICEServerHearbeat, - ReceiveDSPeerInformation, - SendFirstPingsToDS, - SetDomainHostname, - SetDomainSocket, - SendFirstDSCheckIn, - ReceiveFirstDSList, - SendFirstAudioPing, - SetAudioMixerSocket, - SendFirstAudioPacket, - ReceiveFirstAudioPacket - }; + enum ConnectionStep { + LookupAddress, + HandleAddress, + SetICEServerHostname, + SetICEServerSocket, + SendFirstICEServerHearbeat, + ReceiveDSPeerInformation, + SendFirstPingsToDS, + SetDomainHostname, + SetDomainSocket, + SendFirstDSCheckIn, + ReceiveFirstDSList, + SendFirstAudioPing, + SetAudioMixerSocket, + SendFirstAudioPacket, + ReceiveFirstAudioPacket }; + Q_ENUMS(ConnectionStep); + NodeType_t getOwnerType() const { return _ownerType; } void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } @@ -86,7 +84,8 @@ public: int processDomainServerList(const QByteArray& packet); - void flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep); + const QMap getLastConnectionTimes() const; + void flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep); void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); @@ -102,6 +101,7 @@ signals: private slots: void sendPendingDSPathQuery(); void handleICEConnectionToDomainServer(); + void flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep, quint64 timestamp); private: NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); @@ -120,8 +120,6 @@ private: void sendDSPathQuery(const QString& newPath); - void flagTimeForConnectionStep(NodeList::ConnectionStep::Value connectionStep, quint64 timestamp); - NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; @@ -130,7 +128,7 @@ private: bool _hasCompletedInitialSTUNFailure; unsigned int _stunRequestsSinceSuccess; - QMap _lastConnectionTimes; + QMap _lastConnectionTimes; friend class Application; }; From d1489c50fec226658a2a3495829d5ae0cd025c9f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 26 May 2015 14:18:58 -0700 Subject: [PATCH 11/46] add connection times getter to NodeList --- libraries/networking/src/NodeList.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 42356570a9..8d40e252e5 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -84,7 +84,7 @@ public: int processDomainServerList(const QByteArray& packet); - const QMap getLastConnectionTimes() const; + const QMap getLastConnectionTimes() const { return _lastConnectionTimes; } void flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep); void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } From 61029fd0b160c07eb3b99499721a322d63e97dba Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 26 May 2015 15:22:25 -0700 Subject: [PATCH 12/46] add ability to display domain connection times --- interface/src/Menu.cpp | 76 +++++++++-------- interface/src/Menu.h | 25 +++--- interface/src/ui/DialogsManager.cpp | 22 +++-- interface/src/ui/DialogsManager.h | 17 ++-- interface/src/ui/DomainConnectionDialog.cpp | 74 +++++++++++++++++ interface/src/ui/DomainConnectionDialog.h | 25 ++++++ .../src/ui/DomainConnectionTableModel.cpp | 82 ------------------- interface/src/ui/DomainConnectionTableModel.h | 41 ---------- 8 files changed, 178 insertions(+), 184 deletions(-) create mode 100644 interface/src/ui/DomainConnectionDialog.cpp create mode 100644 interface/src/ui/DomainConnectionDialog.h delete mode 100644 interface/src/ui/DomainConnectionTableModel.cpp delete mode 100644 interface/src/ui/DomainConnectionTableModel.h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index ddd581fe53..6242318170 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -71,14 +71,14 @@ Menu::Menu() { { addActionToQMenuAndActionHash(fileMenu, MenuOption::Login); - + // connect to the appropriate signal of the AccountManager so that we can change the Login/Logout menu item connect(&accountManager, &AccountManager::profileChanged, dialogsManager.data(), &DialogsManager::toggleLoginDialog); connect(&accountManager, &AccountManager::logoutComplete, dialogsManager.data(), &DialogsManager::toggleLoginDialog); } - + addDisabledActionAndSeparator(fileMenu, "Scripts"); addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, qApp, SLOT(loadDialog())); @@ -92,7 +92,7 @@ Menu::Menu() { addDisabledActionAndSeparator(fileMenu, "Location"); qApp->getBookmarks()->setupMenus(this, fileMenu); - + addActionToQMenuAndActionHash(fileMenu, MenuOption::AddressBar, Qt::CTRL | Qt::Key_L, @@ -148,8 +148,8 @@ Menu::Menu() { SLOT(setEnabled(bool))); connect(speechRecognizer.data(), SIGNAL(enabledUpdated(bool)), speechRecognizerAction, SLOT(setChecked(bool))); #endif - - addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, + + addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, 0, // QML Qt::Key_Backslash, dialogsManager.data(), SLOT(showIRCLink())); addActionToQMenuAndActionHash(toolsMenu, MenuOption::AddRemoveFriends, 0, @@ -175,7 +175,7 @@ Menu::Menu() { discoverabilityManager.data(), SLOT(setVisibility())); visibilityGroup->addAction(visibleToNoOne); - connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, + connect(discoverabilityManager.data(), &DiscoverabilityManager::discoverabilityModeChanged, discoverabilityManager.data(), &DiscoverabilityManager::visibilityChanged); } @@ -196,7 +196,7 @@ Menu::Menu() { 0, // QML Qt::Key_Apostrophe, qApp, SLOT(resetSensors())); - + addActionToQMenuAndActionHash(toolsMenu, MenuOption::PackageModel, 0, qApp, SLOT(packageModel())); @@ -245,17 +245,17 @@ Menu::Menu() { qApp, SLOT(setFullscreen(bool))); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, - 0, // QML Qt::Key_P, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, + 0, // QML Qt::Key_P, true, qApp, SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, - 0, //QML Qt::SHIFT | Qt::Key_H, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, + 0, //QML Qt::SHIFT | Qt::Key_H, true); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, - 0, // QML Qt::Key_H, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, + 0, // QML Qt::Key_H, false, qApp, SLOT(cameraMenuChanged())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::HMDTools, #ifdef Q_OS_MAC Qt::META | Qt::Key_H, #else @@ -285,8 +285,8 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::TurnWithHead, 0, false); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats); - addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, - Qt::CTRL | Qt::SHIFT | Qt::Key_L, + addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, + Qt::CTRL | Qt::SHIFT | Qt::Key_L, qApp, SLOT(toggleLogDialog())); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, dialogsManager.data(), SLOT(bandwidthDetails())); @@ -297,8 +297,8 @@ Menu::Menu() { MenuWrapper* developerMenu = addMenu("Developer"); MenuWrapper* renderOptionsMenu = developerMenu->addMenu("Render"); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, - 0, // QML Qt::SHIFT | Qt::Key_A, + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Atmosphere, + 0, // QML Qt::SHIFT | Qt::Key_A, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::AmbientOcclusion); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::DontFadeOnOctreeServerChanges); @@ -317,7 +317,7 @@ Menu::Menu() { ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight7, 0, false)); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight8, 0, false)); ambientLightGroup->addAction(addCheckableActionToQMenuAndActionHash(ambientLightMenu, MenuOption::RenderAmbientLight9, 0, false)); - + MenuWrapper* shadowMenu = renderOptionsMenu->addMenu("Shadows"); QActionGroup* shadowGroup = new QActionGroup(shadowMenu); shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true)); @@ -351,14 +351,14 @@ Menu::Menu() { resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionThird, 0, false)); resolutionGroup->addAction(addCheckableActionToQMenuAndActionHash(resolutionMenu, MenuOption::RenderResolutionQuarter, 0, false)); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Stars, 0, // QML Qt::Key_Asterisk, true); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true, + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::EnableGlowEffect, 0, true, DependencyManager::get().data(), SLOT(toggleGlowEffect(bool))); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Wireframe, Qt::ALT | Qt::Key_W, false); - addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, + addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, 0, // QML Qt::SHIFT | Qt::Key_L, dialogsManager.data(), SLOT(lodTools())); @@ -384,7 +384,7 @@ Menu::Menu() { faceTrackerGroup->addAction(faceshiftFaceTracker); #endif #ifdef HAVE_DDE - QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera, + QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera, 0, true, qApp, SLOT(setActiveFaceTracker())); faceTrackerGroup->addAction(ddeFaceTracker); @@ -404,13 +404,13 @@ Menu::Menu() { #endif #if defined(HAVE_FACESHIFT) || defined(HAVE_DDE) faceTrackingMenu->addSeparator(); - addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking, + addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking, Qt::CTRL | Qt::SHIFT | Qt::Key_F, true, // DDE face tracking is on by default qApp, SLOT(toggleFaceTrackerMute())); addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, true); #endif - - auto avatarManager = DependencyManager::get(); + + auto avatarManager = DependencyManager::get(); addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false, avatarManager.data(), SLOT(setShouldShowReceiveStats(bool))); @@ -426,7 +426,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHands, 0, true); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false); addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false); - + MenuWrapper* sixenseOptionsMenu = handOptionsMenu->addMenu("Sixense"); #ifdef __APPLE__ addCheckableActionToQMenuAndActionHash(sixenseOptionsMenu, @@ -472,7 +472,11 @@ Menu::Menu() { addActionToQMenuAndActionHash(networkMenu, MenuOption::DiskCacheEditor, 0, dialogsManager.data(), SLOT(toggleDiskCacheEditor())); + addActionToQMenuAndActionHash(networkMenu, MenuOption::ShowDSConnectTable, 0, + dialogsManager.data(), SLOT(showDomainConnectionDialog())); + MenuWrapper* timingMenu = developerMenu->addMenu("Timing and Stats"); + MenuWrapper* perfTimerMenu = timingMenu->addMenu("Performance Timer"); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayDebugTimingDetails, 0, false); addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::OnlyDisplayTopTen, 0, true); @@ -510,7 +514,7 @@ Menu::Menu() { 0, audioIO.data(), SLOT(sendMuteEnvironmentPacket())); - + auto scope = DependencyManager::get(); MenuWrapper* audioScopeMenu = audioDebugMenu->addMenu("Audio Scope"); @@ -548,7 +552,7 @@ Menu::Menu() { audioScopeFramesGroup->addAction(twentyFrames); audioScopeFramesGroup->addAction(fiftyFrames); } - + auto statsRenderer = DependencyManager::get(); addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioStats, Qt::CTRL | Qt::SHIFT | Qt::Key_A, @@ -767,7 +771,7 @@ QAction* Menu::getActionFromName(const QString& menuName, MenuWrapper* menu) { } else { menuActions = actions(); } - + foreach (QAction* menuAction, menuActions) { QString actionText = menuAction->text(); if (menuName == menuAction->text()) { @@ -868,14 +872,14 @@ MenuWrapper* Menu::addMenu(const QString& menuName) { } addTo = menu; } - + QMenuBar::repaint(); return menu; } void Menu::removeMenu(const QString& menuName) { QAction* action = getMenuAction(menuName); - + // only proceed if the menu actually exists if (action) { QString finalMenuPart; @@ -885,14 +889,14 @@ void Menu::removeMenu(const QString& menuName) { } else { QMenuBar::removeAction(action); } - + QMenuBar::repaint(); } } bool Menu::menuExists(const QString& menuName) { QAction* action = getMenuAction(menuName); - + // only proceed if the menu actually exists if (action) { return true; @@ -937,7 +941,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { if (!properties.shortcutKeySequence.isEmpty()) { shortcut = new QShortcut(properties.shortcutKeySequence, this); } - + // check for positioning requests int requestedPosition = properties.position; if (requestedPosition == UNSPECIFIED_POSITION && !properties.beforeItem.isEmpty()) { @@ -951,7 +955,7 @@ void Menu::addMenuItem(const MenuItemProperties& properties) { requestedPosition = afterPosition + 1; } } - + QAction* menuItemAction = NULL; if (properties.isSeparator) { addDisabledActionAndSeparator(menuObj, properties.menuItemName, requestedPosition); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 12ec312a56..6107744abc 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -58,15 +58,15 @@ class Menu : public QMenuBar { Q_OBJECT public: static Menu* getInstance(); - + void loadSettings(); void saveSettings(); - + MenuWrapper* getMenu(const QString& menuName); void triggerOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption); - + QAction* addActionToQMenuAndActionHash(MenuWrapper* destinationMenu, const QString& actionName, const QKeySequence& shortcut = 0, @@ -80,9 +80,9 @@ public: const QKeySequence& shortcut = 0, QAction::MenuRole role = QAction::NoRole, int menuItemLocation = UNSPECIFIED_POSITION); - + void removeAction(MenuWrapper* menu, const QString& actionName); - + public slots: MenuWrapper* addMenu(const QString& menuName); void removeMenu(const QString& menuName); @@ -94,21 +94,21 @@ public slots: bool menuItemExists(const QString& menuName, const QString& menuitem); bool isOptionChecked(const QString& menuOption) const; void setIsOptionChecked(const QString& menuOption, bool isChecked); - + private: static Menu* _instance; Menu(); - + typedef void(*settingsAction)(Settings&, QAction&); static void loadAction(Settings& settings, QAction& action); static void saveAction(Settings& settings, QAction& action); void scanMenuBar(settingsAction modifySetting); void scanMenu(QMenu& menu, settingsAction modifySetting, Settings& settings); - + /// helper method to have separators with labels that are also compatible with OS X void addDisabledActionAndSeparator(MenuWrapper* destinationMenu, const QString& actionName, int menuItemLocation = UNSPECIFIED_POSITION); - + QAction* addCheckableActionToQMenuAndActionHash(MenuWrapper* destinationMenu, const QString& actionName, const QKeySequence& shortcut = 0, @@ -116,15 +116,15 @@ private: const QObject* receiver = NULL, const char* member = NULL, int menuItemLocation = UNSPECIFIED_POSITION); - + QAction* getActionFromName(const QString& menuName, MenuWrapper* menu); MenuWrapper* getSubMenuFromName(const QString& menuName, MenuWrapper* menu); MenuWrapper* getMenuParent(const QString& menuName, QString& finalMenuPart); - + QAction* getMenuAction(const QString& menuName); int findPositionOfMenuItem(MenuWrapper* menu, const QString& searchMenuItem); int positionBeforeSeparatorIfNeeded(MenuWrapper* menu, int requestedPosition); - + QHash _actionHash; }; @@ -262,6 +262,7 @@ namespace MenuOption { const QString RunTimingTests = "Run Timing Tests"; const QString ScriptEditor = "Script Editor..."; const QString ScriptedMotorControl = "Enable Scripted Motor Control"; + const QString ShowDSConnectTable = "Show Domain Connection Timing"; const QString ShowBordersEntityNodes = "Show Entity Nodes"; const QString ShowIKConstraints = "Show IK Constraints"; const QString SimpleShadows = "Simple"; diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 56dd69eeeb..ca7a13eb07 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -24,6 +24,7 @@ #include "BandwidthDialog.h" #include "CachesSizeDialog.h" #include "DiskCacheEditor.h" +#include "DomainConnectionDialog.h" #include "HMDToolsDialog.h" #include "LodToolsDialog.h" #include "LoginDialog.h" @@ -52,7 +53,7 @@ void DialogsManager::showLoginDialog() { void DialogsManager::octreeStatsDetails() { if (!_octreeStatsDialog) { _octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), qApp->getOcteeSceneStats()); - + if (_hmdToolsDialog) { _hmdToolsDialog->watchWindow(_octreeStatsDialog->windowHandle()); } @@ -65,7 +66,7 @@ void DialogsManager::octreeStatsDetails() { void DialogsManager::cachesSizeDialog() { if (!_cachesSizeDialog) { maybeCreateDialog(_cachesSizeDialog); - + connect(_cachesSizeDialog, SIGNAL(closed()), _cachesSizeDialog, SLOT(deleteLater())); _cachesSizeDialog->show(); } @@ -112,11 +113,11 @@ void DialogsManager::bandwidthDetails() { if (! _bandwidthDialog) { _bandwidthDialog = new BandwidthDialog(qApp->getWindow()); connect(_bandwidthDialog, SIGNAL(closed()), _bandwidthDialog, SLOT(deleteLater())); - + if (_hmdToolsDialog) { _hmdToolsDialog->watchWindow(_bandwidthDialog->windowHandle()); } - + _bandwidthDialog->show(); } _bandwidthDialog->raise(); @@ -125,7 +126,7 @@ void DialogsManager::bandwidthDetails() { void DialogsManager::lodTools() { if (!_lodToolsDialog) { maybeCreateDialog(_lodToolsDialog); - + connect(_lodToolsDialog, SIGNAL(closed()), _lodToolsDialog, SLOT(deleteLater())); _lodToolsDialog->show(); } @@ -172,7 +173,16 @@ void DialogsManager::showIRCLink() { _ircInfoBox->setAttribute(Qt::WA_DeleteOnClose); _ircInfoBox->show(); } - + _ircInfoBox->raise(); } +void DialogsManager::showDomainConnectionDialog() { + if (!_domainConnectionDialog) { + // if the dialog already exists we delete it so the connection data is refreshed + maybeCreateDialog(_domainConnectionDialog); + + _domainConnectionDialog->show(); + _domainConnectionDialog->raise(); + } +} diff --git a/interface/src/ui/DialogsManager.h b/interface/src/ui/DialogsManager.h index d5d9c33a9a..fc2dad072b 100644 --- a/interface/src/ui/DialogsManager.h +++ b/interface/src/ui/DialogsManager.h @@ -34,18 +34,19 @@ class PreferencesDialog; class ScriptEditorWindow; class QMessageBox; class AvatarAppearanceDialog; +class DomainConnectionDialog; class DialogsManager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY - + public: QPointer getBandwidthDialog() const { return _bandwidthDialog; } QPointer getHMDToolsDialog() const { return _hmdToolsDialog; } QPointer getLodToolsDialog() const { return _lodToolsDialog; } QPointer getOctreeStatsDialog() const { return _octreeStatsDialog; } QPointer getPreferencesDialog() const { return _preferencesDialog; } - + public slots: void toggleAddressBar(); void toggleDiskCacheEditor(); @@ -62,14 +63,15 @@ public slots: void showScriptEditor(); void showIRCLink(); void changeAvatarAppearance(); + void showDomainConnectionDialog(); private slots: void toggleToolWindow(); void hmdToolsClosed(); - + private: DialogsManager() {} - + template void maybeCreateDialog(QPointer& member) { if (!member) { @@ -77,13 +79,13 @@ private: Q_CHECK_PTR(parent); member = new T(parent); Q_CHECK_PTR(member); - + if (_hmdToolsDialog && member->windowHandle()) { _hmdToolsDialog->watchWindow(member->windowHandle()); } } } - + QPointer _addressBarDialog; QPointer _animationsDialog; QPointer _attachmentsDialog; @@ -98,6 +100,7 @@ private: QPointer _preferencesDialog; QPointer _scriptEditor; QPointer _avatarAppearanceDialog; + QPointer _domainConnectionDialog; }; -#endif // hifi_DialogsManager_h \ No newline at end of file +#endif // hifi_DialogsManager_h diff --git a/interface/src/ui/DomainConnectionDialog.cpp b/interface/src/ui/DomainConnectionDialog.cpp new file mode 100644 index 0000000000..98c0401296 --- /dev/null +++ b/interface/src/ui/DomainConnectionDialog.cpp @@ -0,0 +1,74 @@ +// +// DomainConnectionDialog.cpp +// interface/src/ui +// +// Created by Stephen Birarda on 05/26/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include +#include + +#include "DomainConnectionDialog.h" + +DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : + QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint) +{ + setWindowTitle("Domain Connection Timing"); + setAttribute(Qt::WA_DeleteOnClose); + + // setup a QTableWidget so we can populate it with our values + QTableWidget* timeTable = new QTableWidget; + + const QStringList TABLE_HEADERS = QStringList() << "Name" << "Timestamp (ms)" << "Delta (ms)" << "Time elapsed (ms)"; + + timeTable->setHorizontalHeaderLabels(TABLE_HEADERS); + timeTable->setColumnCount(TABLE_HEADERS.size()); + + // ask the NodeList for the current values for connection times + QMap times = DependencyManager::get()->getLastConnectionTimes(); + + timeTable->setRowCount(times.size()); + + // setup our data with the values from the NodeList + quint64 firstStepTime = times[NodeList::ConnectionStep::LookupAddress] / USECS_PER_MSEC; + quint64 lastStepTime = firstStepTime; + + int thisRow = 0; + + const QMetaObject &nodeListMeta = NodeList::staticMetaObject; + QMetaEnum stepEnum = nodeListMeta.enumerator(nodeListMeta.indexOfEnumerator("ConnectionStep")); + + for (int i = 0; i < stepEnum.keyCount(); i++) { + NodeList::ConnectionStep step = static_cast(i); + + if (times.contains(step)) { + // When did this step occur, how long since the last step, how long since the start? + quint64 stepTime = times[step] / USECS_PER_MSEC; + quint64 delta = (stepTime - lastStepTime); + quint64 elapsed = stepTime - firstStepTime; + + lastStepTime = stepTime; + + // setup the columns for this row in the table + timeTable->setItem(thisRow, 0, new QTableWidgetItem(stepEnum.valueToKey(i))); + timeTable->setItem(thisRow, 1, new QTableWidgetItem(QString::number(stepTime))); + timeTable->setItem(thisRow, 2, new QTableWidgetItem(QString::number(delta))); + timeTable->setItem(thisRow, 3, new QTableWidgetItem(QString::number(elapsed))); + + ++thisRow; + } + } + + QHBoxLayout* hBoxLayout = new QHBoxLayout; + hBoxLayout->addWidget(timeTable); + + setLayout(hBoxLayout); +} diff --git a/interface/src/ui/DomainConnectionDialog.h b/interface/src/ui/DomainConnectionDialog.h new file mode 100644 index 0000000000..0f49a1ee68 --- /dev/null +++ b/interface/src/ui/DomainConnectionDialog.h @@ -0,0 +1,25 @@ +// +// DomainConnectionDialog.h +// interface/src/ui +// +// Created by Stephen Birarda on 05/26/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_DomainConnectionDialog_h +#define hifi_DomainConnectionDialog_h + +#pragma once + +#include + +class DomainConnectionDialog : public QDialog { + Q_OBJECT +public: + DomainConnectionDialog(QWidget* parent); +}; + +#endif // hifi_DomainConnectionDialog_h diff --git a/interface/src/ui/DomainConnectionTableModel.cpp b/interface/src/ui/DomainConnectionTableModel.cpp deleted file mode 100644 index 0f27b56434..0000000000 --- a/interface/src/ui/DomainConnectionTableModel.cpp +++ /dev/null @@ -1,82 +0,0 @@ -// -// DomainConnectionTableModel.cpp -// interface/src/ui -// -// Created by Stephen Birarda on 05/26/15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include - -#include -#include - -#include "DomainConnectionTableModel.h" - -DomainConnectionTableModel::DomainConnectionTableModel(QObject* parent) : - QAbstractTableModel(parent) -{ - // ask the NodeList for the current values for connection times - QMap times = DependencyManager::get()->getLastConnectionTimes(); - - // setup our data with the returned values - - quint64 totalTime = 0; - quint64 firstStepTime = times[NodeList::ConnectionStep::LookupAddress] / USECS_PER_MSEC; - quint64 lastStepTime = firstStepTime; - - const QMetaObject &nodeListMeta = NodeList::staticMetaObject; - QMetaEnum stepEnum = nodeListMeta.enumerator(nodeListMeta.indexOfEnumerator("ConnectionStep")); - - for (int i = 0; i < stepEnum.keyCount(); i++) { - NodeList::ConnectionStep step = static_cast(i); - - if (times.contains(step)) { - // When did this step occur, how long since the last step, how long since the start? - _timestamps[_numRows] = times[step] / USECS_PER_MSEC; - _deltas[_numRows] = (_timestamps[_numRows] - lastStepTime); - _totals[_numRows] = _timestamps[_numRows] - firstStepTime; - - // increment the total time by this delta to keep track - totalTime += _deltas[_numRows]; - - lastStepTime = _timestamps[_numRows]; - - // increment our counted number of rows - ++_numRows; - } - } -} - -QVariant DomainConnectionTableModel::headerData(int section, Qt::Orientation orientation, int role) const { - switch(section) { - case 0: - return QVariant("Name"); - case 1: - return QVariant("Timestamp (ms)"); - case 2: - return QVariant("Delta (ms)"); - case 3: - return QVariant("Total Elapsed (ms)"); - default: - return QVariant(); - } -} - -QVariant DomainConnectionTableModel::data(const QModelIndex& index, int role) const { - switch(index.column()) { - case 0: - return _names[index.row()]; - case 1: - return QVariant(_timestamps[index.row()]); - case 2: - return QVariant(_deltas[index.row()]); - case 3: - return QVariant(_totals[index.row()]); - default: - return QVariant(); - } -} diff --git a/interface/src/ui/DomainConnectionTableModel.h b/interface/src/ui/DomainConnectionTableModel.h deleted file mode 100644 index 9c2001dc00..0000000000 --- a/interface/src/ui/DomainConnectionTableModel.h +++ /dev/null @@ -1,41 +0,0 @@ -// -// DomainConnectionTableModel.h -// interface/src/ui -// -// Created by Stephen Birarda on 05/26/15. -// Copyright 2015 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#ifndef hifi_DomainConnectionTableModel_h -#define hifi_DomainConnectionTableModel_h - -#pragma once - -#include - -class DomainConnectionTableModel: public QAbstractTableModel { - Q_OBJECT -public: - DomainConnectionTableModel(QObject* parent = 0); - - const int NUM_COLUMNS = 4; // name, time, delta, since start - - int rowCount(const QModelIndex& parent = QModelIndex()) const { return _numRows; } - int columnCount(const QModelIndex& parent = QModelIndex()) const { return NUM_COLUMNS; } - - QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; -private: - int _numRows = 0; - - QVariantList _names; - QList _timestamps; - QList _deltas; - QList _totals; -}; - - -#endif // hifi_DomainConnectionTableModel_h From 5b5518077d67987f95e650660f5f08a0a8f42861 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 26 May 2015 15:51:00 -0700 Subject: [PATCH 13/46] fix headers and stats for domain connection timing --- interface/src/ui/DomainConnectionDialog.cpp | 27 ++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/interface/src/ui/DomainConnectionDialog.cpp b/interface/src/ui/DomainConnectionDialog.cpp index 98c0401296..3695fdff4d 100644 --- a/interface/src/ui/DomainConnectionDialog.cpp +++ b/interface/src/ui/DomainConnectionDialog.cpp @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -29,7 +30,6 @@ DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : const QStringList TABLE_HEADERS = QStringList() << "Name" << "Timestamp (ms)" << "Delta (ms)" << "Time elapsed (ms)"; - timeTable->setHorizontalHeaderLabels(TABLE_HEADERS); timeTable->setColumnCount(TABLE_HEADERS.size()); // ask the NodeList for the current values for connection times @@ -37,6 +37,8 @@ DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : timeTable->setRowCount(times.size()); + timeTable->setHorizontalHeaderLabels(TABLE_HEADERS); + // setup our data with the values from the NodeList quint64 firstStepTime = times[NodeList::ConnectionStep::LookupAddress] / USECS_PER_MSEC; quint64 lastStepTime = firstStepTime; @@ -67,8 +69,31 @@ DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : } } + timeTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + timeTable->resizeColumnsToContents(); + + const int TABLE_HEADER_GAP = 4; + + // figure out the size of the table so we can size the dialog + int tableWidth = timeTable->verticalHeader()->width() + TABLE_HEADER_GAP; + for (int i = 0; i < timeTable->columnCount(); i++) { + tableWidth += timeTable->columnWidth(i); + } + + int tableHeight = timeTable->horizontalHeader()->height() + TABLE_HEADER_GAP; + for (int i = 0; i < timeTable->rowCount(); i++) { + tableHeight += timeTable->rowHeight(i); + } + + timeTable->setMaximumSize(QSize(tableWidth, tableHeight)); + timeTable->setMinimumSize(timeTable->maximumSize()); + QHBoxLayout* hBoxLayout = new QHBoxLayout; hBoxLayout->addWidget(timeTable); + hBoxLayout->setSizeConstraint(QLayout::SetMinimumSize); + + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setLayout(hBoxLayout); + adjustSize(); } From 08affbfd0db6f9036cf35d4d3e596f6941dcd799 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 26 May 2015 16:14:12 -0700 Subject: [PATCH 14/46] fix table size calculations --- interface/src/ui/DomainConnectionDialog.cpp | 35 +++++++++------------ 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/interface/src/ui/DomainConnectionDialog.cpp b/interface/src/ui/DomainConnectionDialog.cpp index 3695fdff4d..0e82b2ed93 100644 --- a/interface/src/ui/DomainConnectionDialog.cpp +++ b/interface/src/ui/DomainConnectionDialog.cpp @@ -69,30 +69,25 @@ DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : } } - timeTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - timeTable->resizeColumnsToContents(); - - const int TABLE_HEADER_GAP = 4; - - // figure out the size of the table so we can size the dialog - int tableWidth = timeTable->verticalHeader()->width() + TABLE_HEADER_GAP; - for (int i = 0; i < timeTable->columnCount(); i++) { - tableWidth += timeTable->columnWidth(i); - } - - int tableHeight = timeTable->horizontalHeader()->height() + TABLE_HEADER_GAP; - for (int i = 0; i < timeTable->rowCount(); i++) { - tableHeight += timeTable->rowHeight(i); - } - - timeTable->setMaximumSize(QSize(tableWidth, tableHeight)); - timeTable->setMinimumSize(timeTable->maximumSize()); - QHBoxLayout* hBoxLayout = new QHBoxLayout; hBoxLayout->addWidget(timeTable); hBoxLayout->setSizeConstraint(QLayout::SetMinimumSize); - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + timeTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + timeTable->resizeColumnsToContents(); + + // figure out the size of the table so we can size the dialog + int tableWidth = timeTable->verticalHeader()->sizeHint().width(); + for (int i = 0; i < timeTable->columnCount(); i++) { + tableWidth += timeTable->columnWidth(i); + } + + int tableHeight = timeTable->horizontalHeader()->sizeHint().height(); + for (int i = 0; i < timeTable->rowCount(); i++) { + tableHeight += timeTable->rowHeight(i); + } + + timeTable->setMinimumSize(tableWidth, tableHeight); setLayout(hBoxLayout); adjustSize(); From 9cb6ccaa46f591e5fc59753e4eb226039083cf2a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 26 May 2015 16:49:18 -0700 Subject: [PATCH 15/46] thread safe timing of domain connection --- interface/src/ui/DomainConnectionDialog.cpp | 4 +++- libraries/networking/src/NodeList.cpp | 10 +++++++--- libraries/networking/src/NodeList.h | 5 ++++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/interface/src/ui/DomainConnectionDialog.cpp b/interface/src/ui/DomainConnectionDialog.cpp index 0e82b2ed93..f5fdc530d5 100644 --- a/interface/src/ui/DomainConnectionDialog.cpp +++ b/interface/src/ui/DomainConnectionDialog.cpp @@ -69,11 +69,12 @@ DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : } } + // setup a horizontal box layout QHBoxLayout* hBoxLayout = new QHBoxLayout; hBoxLayout->addWidget(timeTable); hBoxLayout->setSizeConstraint(QLayout::SetMinimumSize); - timeTable->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); + // resize the table columns timeTable->resizeColumnsToContents(); // figure out the size of the table so we can size the dialog @@ -87,6 +88,7 @@ DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : tableHeight += timeTable->rowHeight(i); } + // set the minimum size of the table to whatever we got timeTable->setMinimumSize(tableWidth, tableHeight); setLayout(hBoxLayout); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 4bfedfd555..f9189e47d7 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -270,9 +270,6 @@ void NodeList::reset() { if (_dtlsSocket) { disconnect(_dtlsSocket, 0, this, 0); } - - // reset the connection times - _lastConnectionTimes.clear(); } void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { @@ -673,6 +670,13 @@ void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep } void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep, quint64 timestamp) { + QWriteLocker writeLock(&_connectionTimeLock); + + if (connectionStep == NodeList::ConnectionStep::LookupAddress) { + // we clear the current times if the user just fired off a lookup + _lastConnectionTimes.clear(); + } + // we only add a timestamp on the first call for each NodeList::ConnectionStep if (!_lastConnectionTimes.contains(connectionStep)) { _lastConnectionTimes[connectionStep] = timestamp; diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 8d40e252e5..acdfe535f1 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -84,8 +84,10 @@ public: int processDomainServerList(const QByteArray& packet); - const QMap getLastConnectionTimes() const { return _lastConnectionTimes; } + const QMap getLastConnectionTimes() const + { QReadLocker readLock(&_connectionTimeLock); return _lastConnectionTimes; } void flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep); + void resetConnectionTimes() { QWriteLocker writeLock(&_connectionTimeLock); _lastConnectionTimes.clear(); } void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); @@ -128,6 +130,7 @@ private: bool _hasCompletedInitialSTUNFailure; unsigned int _stunRequestsSinceSuccess; + mutable QReadWriteLock _connectionTimeLock { }; QMap _lastConnectionTimes; friend class Application; From 4b6b34836db8aaf4b25bb3889f88732a57a05765 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 27 May 2015 10:30:45 -0700 Subject: [PATCH 16/46] repair anomalies in timing table --- libraries/networking/src/NodeList.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index f9189e47d7..113ec4f155 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -677,8 +677,21 @@ void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep _lastConnectionTimes.clear(); } - // we only add a timestamp on the first call for each NodeList::ConnectionStep if (!_lastConnectionTimes.contains(connectionStep)) { + // if there is no time for existing step add a timestamp on the first call for each NodeList::ConnectionStep _lastConnectionTimes[connectionStep] = timestamp; + } else { + // if the existing time for this step is before the nearest sibling before then replace it + // this handles the case where audio comes in after an address lookup after the previous times have been cleared + quint64 currentTime = _lastConnectionTimes[connectionStep]; + + // go down the sibling steps and check if the registered time is actually before the sibling + for (int i = ((int) connectionStep) - 1; i >= 0; i--) { + NodeList::ConnectionStep thisStep = static_cast(i); + if (_lastConnectionTimes.contains(thisStep) && _lastConnectionTimes[thisStep] >= currentTime) { + _lastConnectionTimes[connectionStep] = timestamp; + break; + } + } } } From c7c542ef4c5dd147f45635225e13c67aa7a7b34b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 27 May 2015 14:24:15 -0700 Subject: [PATCH 17/46] decouple STUN from DS check in --- domain-server/src/DomainServer.cpp | 38 +++------- domain-server/src/DomainServer.h | 1 - libraries/networking/src/DomainHandler.cpp | 1 - libraries/networking/src/LimitedNodeList.cpp | 77 +++++++++++++++++++- libraries/networking/src/LimitedNodeList.h | 28 ++++--- libraries/networking/src/NodeList.cpp | 73 +++---------------- libraries/networking/src/NodeList.h | 6 +- 7 files changed, 116 insertions(+), 108 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 6bf5c62710..2c784abe54 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -354,37 +354,23 @@ bool DomainServer::optionallySetupAssignmentPayment() { void DomainServer::setupAutomaticNetworking() { auto nodeList = DependencyManager::get(); - const int STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS = 10 * 1000; - const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; - - // setup our timer to check our IP via stun every X seconds - QTimer* dynamicIPTimer = new QTimer(this); - connect(dynamicIPTimer, &QTimer::timeout, this, &DomainServer::requestCurrentPublicSocketViaSTUN); - _automaticNetworkingSetting = _settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString(); if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) { - dynamicIPTimer->start(STUN_REFLEXIVE_KEEPALIVE_INTERVAL_MSECS); - - // setup a timer to heartbeat with the ice-server every so often - QTimer* iceHeartbeatTimer = new QTimer(this); - connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates); - iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); - // call our sendHeartbeatToIceServer immediately anytime a local or public socket changes connect(nodeList.data(), &LimitedNodeList::localSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendHeartbeatToIceServer); - // attempt to update our public socket now, this will send a heartbeat once we get public socket - requestCurrentPublicSocketViaSTUN(); - - // in case the STUN lookup is still happening we should re-request a public socket once we get that address - connect(&nodeList->getSTUNSockAddr(), &HifiSockAddr::lookupCompleted, - this, &DomainServer::requestCurrentPublicSocketViaSTUN); + // we need this DS to know what our public IP is - start trying to figure that out now + nodeList->startSTUNPublicSocketUpdate(); + // setup a timer to heartbeat with the ice-server every so often + QTimer* iceHeartbeatTimer = new QTimer(this); + connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates); + iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); } if (!didSetupAccountManagerWithAccessToken()) { @@ -404,14 +390,12 @@ void DomainServer::setupAutomaticNetworking() { << uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString(); if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) { - dynamicIPTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); - - // send public socket changes to the data server so nodes can find us at our new IP + // send any public socket changes to the data server so nodes can find us at our new IP connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::performIPAddressUpdate); - // attempt to update our sockets now - requestCurrentPublicSocketViaSTUN(); + // have the LNL enable public socket updating via STUN + nodeList->startSTUNPublicSocketUpdate(); } else { // send our heartbeat to data server so it knows what our network settings are sendHeartbeatToDataServer(); @@ -1216,10 +1200,6 @@ void DomainServer::transactionJSONCallback(const QJsonObject& data) { } } -void DomainServer::requestCurrentPublicSocketViaSTUN() { - DependencyManager::get()->sendSTUNRequest(); -} - QJsonObject jsonForDomainSocketUpdate(const HifiSockAddr& socket) { const QString SOCKET_NETWORK_ADDRESS_KEY = "network_address"; const QString SOCKET_PORT_KEY = "port"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 9e9d3f13f3..22e3efa378 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -62,7 +62,6 @@ private slots: void setupPendingAssignmentCredits(); void sendPendingTransactionsToServer(); - void requestCurrentPublicSocketViaSTUN(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); void performICEUpdates(); void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 622eec6fd5..9dab4152d9 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -181,7 +181,6 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); - qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(), _sockAddr.getAddress().toString().toLocal8Bit().constData()); diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 34e0576e81..bd412be414 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -596,6 +596,22 @@ const int NUM_BYTES_STUN_HEADER = 20; void LimitedNodeList::sendSTUNRequest() { + static quint64 lastTimeStamp = usecTimestampNow(); + lastTimeStamp = usecTimestampNow(); + + const int NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL = 10; + + if (!_hasCompletedInitialSTUN) { + qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; + + if (_numInitialSTUNRequests > NUM_INITIAL_STUN_REQUESTS_BEFORE_FAIL) { + // we're still trying to do our initial STUN we're over the fail threshold + stopInitialSTUNUpdate(false); + } + + ++_numInitialSTUNRequests; + } + unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER]; int packetIndex = 0; @@ -652,8 +668,6 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { const int NUM_BYTES_FAMILY_ALIGN = 1; const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8; - - int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; uint8_t addressFamily = 0; @@ -679,6 +693,11 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { QHostAddress newPublicAddress = QHostAddress(stunAddress); if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { + if (!_hasCompletedInitialSTUN) { + // if we're here we have definitely completed our initial STUN sequence + stopInitialSTUNUpdate(true); + } + _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); qCDebug(networking, "New public socket received from STUN server is %s:%hu", @@ -707,6 +726,60 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { return false; } +void LimitedNodeList::startSTUNPublicSocketUpdate() { + assert(!_initialSTUNTimer); + + if (!_initialSTUNTimer) { + // if we don't know the STUN IP yet we need to have ourselves be called once it is known + if (_stunSockAddr.getAddress().isNull()) { + connect(&_stunSockAddr, &HifiSockAddr::lookupCompleted, this, &LimitedNodeList::startSTUNPublicSocketUpdate); + } else { + // setup our initial STUN timer here so we can quickly find out our public IP address + _initialSTUNTimer = new QTimer(this); + + connect(_initialSTUNTimer.data(), &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); + + const int STUN_INITIAL_UPDATE_INTERVAL_MSECS = 250; + _initialSTUNTimer->start(STUN_INITIAL_UPDATE_INTERVAL_MSECS); + } + } +} + +void LimitedNodeList::stopInitialSTUNUpdate(bool success) { + _hasCompletedInitialSTUN = true; + + if (!success) { + // if we're here this was the last failed STUN request + // use our DS as our stun server + qCDebug(networking, "Failed to lookup public address via STUN server at %s:%hu.", + STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); + qCDebug(networking) << "LimitedNodeList public socket will be set with local port and null QHostAddress."; + + // reset the public address and port to a null address + _publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort()); + + // we have changed the publicSockAddr, so emit our signal + emit publicSockAddrChanged(_publicSockAddr); + } + + assert(_initialSTUNTimer); + + // stop our initial fast timer + if (_initialSTUNTimer) { + _initialSTUNTimer->stop(); + _initialSTUNTimer->deleteLater(); + } + + // We now setup a timer here to fire every so often to check that our IP address has not changed. + // Or, if we failed - if will check if we can eventually get a public socket + const int STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS = 30 * 1000; + + QTimer* stunOccasionalTimer = new QTimer(this); + connect(stunOccasionalTimer, &QTimer::timeout, this, &LimitedNodeList::sendSTUNRequest); + + stunOccasionalTimer->start(STUN_IP_ADDRESS_CHECK_INTERVAL_MSECS); +} + void LimitedNodeList::updateLocalSockAddr() { HifiSockAddr newSockAddr(getLocalAddress(), _nodeSocket.localPort()); if (newSockAddr != _localSockAddr) { diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 3401cd7ca8..faaeac3c57 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -21,13 +21,14 @@ #include // not on windows, not needed for mac or windows #endif -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include #include @@ -130,6 +131,8 @@ public: const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez); + bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } + const HifiSockAddr& getLocalSockAddr() const { return _localSockAddr; } const HifiSockAddr& getSTUNSockAddr() const { return _stunSockAddr; } @@ -149,7 +152,6 @@ public: const QUuid& packetHeaderID = QUuid()); QByteArray constructPingReplyPacket(const QByteArray& pingPacket, const QUuid& packetHeaderID = QUuid()); - virtual void sendSTUNRequest(); virtual bool processSTUNResponse(const QByteArray& packet); void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr, @@ -210,6 +212,9 @@ public slots: void updateLocalSockAddr(); + void startSTUNPublicSocketUpdate(); + virtual void sendSTUNRequest(); + void killNodeWithUUID(const QUuid& nodeUUID); signals: void uuidChanged(const QUuid& ownerUUID, const QUuid& oldUUID); @@ -240,6 +245,8 @@ protected: void handleNodeKill(const SharedNodePointer& node); + void stopInitialSTUNUpdate(bool success); + QUuid _sessionUUID; NodeHash _nodeHash; QReadWriteLock _nodeMutex; @@ -259,6 +266,10 @@ protected: std::unordered_map _packetSequenceNumbers; + QPointer _initialSTUNTimer; + int _numInitialSTUNRequests = 0; + bool _hasCompletedInitialSTUN = false; + template void eachNodeHashIterator(IteratorLambda functor) { QWriteLocker writeLock(&_nodeMutex); @@ -268,7 +279,6 @@ protected: functor(it); } } - }; #endif // hifi_LimitedNodeList_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 113ec4f155..20d9604c59 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -36,9 +36,7 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned _nodeTypesOfInterest(), _domainHandler(this), _numNoReplyDomainCheckIns(0), - _assignmentServerSocket(), - _hasCompletedInitialSTUNFailure(false), - _stunRequestsSinceSuccess(0) + _assignmentServerSocket() { static bool firstCall = true; if (firstCall) { @@ -63,6 +61,12 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // fire off any pending DS path query when we get socket information connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendPendingDSPathQuery); + // send a domain server check in immediately once the DS socket is known + connect(&_domainHandler, &DomainHandler::completedSocketDiscovery, this, &NodeList::sendDomainServerCheckIn); + + // send a domain server check in immediately if there is a public socket change + connect(this, &LimitedNodeList::publicSockAddrChanged, this, &NodeList::sendDomainServerCheckIn); + // clear our NodeList when the domain changes connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, this, &NodeList::reset); @@ -79,6 +83,9 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); qRegisterMetaType("NodeList::ConnectionStep"); + + // we definitely want STUN to update our public socket, so call the LNL to kick that off + startSTUNPublicSocketUpdate(); } qint64 NodeList::sendStats(const QJsonObject& statsObject, const HifiSockAddr& destination) { @@ -233,9 +240,6 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr } else { qCDebug(networking) << "Reply does not match either local or public socket for domain. Will not connect."; } - - // immediately send a domain-server check in now that we have channel to talk to domain-server on - sendDomainServerCheckIn(); } case PacketTypeStunResponse: { // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch @@ -280,57 +284,13 @@ void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) _nodeTypesOfInterest.unite(setOfNodeTypes); } - -const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5; - -void NodeList::sendSTUNRequest() { - - if (!_hasCompletedInitialSTUNFailure) { - qCDebug(networking) << "Sending intial stun request to" << STUN_SERVER_HOSTNAME; - } - - LimitedNodeList::sendSTUNRequest(); - - _stunRequestsSinceSuccess++; - - if (_stunRequestsSinceSuccess >= NUM_STUN_REQUESTS_BEFORE_FALLBACK) { - if (!_hasCompletedInitialSTUNFailure) { - // if we're here this was the last failed STUN request - // use our DS as our stun server - qCDebug(networking, "Failed to lookup public address via STUN server at %s:%hu. Using DS for STUN.", - STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - - _hasCompletedInitialSTUNFailure = true; - } - - // reset the public address and port - // use 0 so the DS knows to act as out STUN server - _publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort()); - } -} - -bool NodeList::processSTUNResponse(const QByteArray& packet) { - if (LimitedNodeList::processSTUNResponse(packet)) { - // reset the number of failed STUN requests since last success - _stunRequestsSinceSuccess = 0; - - _hasCompletedInitialSTUNFailure = true; - - return true; - } else { - return false; - } -} - void NodeList::sendDomainServerCheckIn() { - if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) { + if (_publicSockAddr.isNull()) { // we don't know our public socket and we need to send it to the domain server - // send a STUN request to figure it out - sendSTUNRequest(); + qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in."; } else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) { handleICEConnectionToDomainServer(); } else if (!_domainHandler.getIP().isNull()) { - bool isUsingDTLS = false; PacketType domainPacketType = !_domainHandler.isConnected() @@ -375,7 +335,6 @@ void NodeList::sendDomainServerCheckIn() { // pack our data to send to the domain-server packetStream << _ownerType << _publicSockAddr << _localSockAddr << _nodeTypesOfInterest.toList(); - // if this is a connect request, and we can present a username signature, send it along if (!_domainHandler.isConnected()) { DataServerAccountInfo& accountInfo = AccountManager::getInstance().getAccountInfo(); @@ -395,14 +354,6 @@ void NodeList::sendDomainServerCheckIn() { writeUnverifiedDatagram(domainServerPacket, _domainHandler.getSockAddr()); } - const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; - static unsigned int numDomainCheckins = 0; - - // send a STUN request every Nth domain server check in so we update our public socket, if required - if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { - sendSTUNRequest(); - } - if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS // so emit our signal that says that diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index acdfe535f1..d52dd6e101 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -56,6 +56,7 @@ public: SendFirstPingsToDS, SetDomainHostname, SetDomainSocket, + ForcedSTUNRequest, SendFirstDSCheckIn, ReceiveFirstDSList, SendFirstAudioPing, @@ -110,9 +111,6 @@ private: NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton - void sendSTUNRequest(); - bool processSTUNResponse(const QByteArray& packet); - void processDomainServerAuthRequest(const QByteArray& packet); void requestAuthForDomainServer(); void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); @@ -127,8 +125,6 @@ private: DomainHandler _domainHandler; int _numNoReplyDomainCheckIns; HifiSockAddr _assignmentServerSocket; - bool _hasCompletedInitialSTUNFailure; - unsigned int _stunRequestsSinceSuccess; mutable QReadWriteLock _connectionTimeLock { }; QMap _lastConnectionTimes; From 734798a365c7081aca44f034eb4c07b3811e8231 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 27 May 2015 15:50:31 -0700 Subject: [PATCH 18/46] fix move of connection steps to LNL --- interface/src/ui/DomainConnectionDialog.cpp | 34 +++++----- libraries/audio-client/src/AudioClient.cpp | 4 +- libraries/networking/src/LimitedNodeList.cpp | 55 ++++++++++++++-- libraries/networking/src/LimitedNodeList.h | 37 ++++++++++- libraries/networking/src/NodeList.cpp | 66 +++++--------------- libraries/networking/src/NodeList.h | 31 --------- 6 files changed, 122 insertions(+), 105 deletions(-) diff --git a/interface/src/ui/DomainConnectionDialog.cpp b/interface/src/ui/DomainConnectionDialog.cpp index f5fdc530d5..c0471dc5e1 100644 --- a/interface/src/ui/DomainConnectionDialog.cpp +++ b/interface/src/ui/DomainConnectionDialog.cpp @@ -28,19 +28,21 @@ DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : // setup a QTableWidget so we can populate it with our values QTableWidget* timeTable = new QTableWidget; + timeTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + const QStringList TABLE_HEADERS = QStringList() << "Name" << "Timestamp (ms)" << "Delta (ms)" << "Time elapsed (ms)"; timeTable->setColumnCount(TABLE_HEADERS.size()); // ask the NodeList for the current values for connection times - QMap times = DependencyManager::get()->getLastConnectionTimes(); + QMap times = DependencyManager::get()->getLastConnectionTimes(); timeTable->setRowCount(times.size()); timeTable->setHorizontalHeaderLabels(TABLE_HEADERS); // setup our data with the values from the NodeList - quint64 firstStepTime = times[NodeList::ConnectionStep::LookupAddress] / USECS_PER_MSEC; + quint64 firstStepTime = times.firstKey() / USECS_PER_MSEC; quint64 lastStepTime = firstStepTime; int thisRow = 0; @@ -48,25 +50,23 @@ DomainConnectionDialog::DomainConnectionDialog(QWidget* parent) : const QMetaObject &nodeListMeta = NodeList::staticMetaObject; QMetaEnum stepEnum = nodeListMeta.enumerator(nodeListMeta.indexOfEnumerator("ConnectionStep")); - for (int i = 0; i < stepEnum.keyCount(); i++) { - NodeList::ConnectionStep step = static_cast(i); + foreach(quint64 timestamp, times.keys()) { + // When did this step occur, how long since the last step, how long since the start? + quint64 stepTime = timestamp / USECS_PER_MSEC; + quint64 delta = (stepTime - lastStepTime); + quint64 elapsed = stepTime - firstStepTime; - if (times.contains(step)) { - // When did this step occur, how long since the last step, how long since the start? - quint64 stepTime = times[step] / USECS_PER_MSEC; - quint64 delta = (stepTime - lastStepTime); - quint64 elapsed = stepTime - firstStepTime; + lastStepTime = stepTime; - lastStepTime = stepTime; + // setup the columns for this row in the table + int stepIndex = (int) times.value(timestamp); - // setup the columns for this row in the table - timeTable->setItem(thisRow, 0, new QTableWidgetItem(stepEnum.valueToKey(i))); - timeTable->setItem(thisRow, 1, new QTableWidgetItem(QString::number(stepTime))); - timeTable->setItem(thisRow, 2, new QTableWidgetItem(QString::number(delta))); - timeTable->setItem(thisRow, 3, new QTableWidgetItem(QString::number(elapsed))); + timeTable->setItem(thisRow, 0, new QTableWidgetItem(stepEnum.valueToKey(stepIndex))); + timeTable->setItem(thisRow, 1, new QTableWidgetItem(QString::number(stepTime))); + timeTable->setItem(thisRow, 2, new QTableWidgetItem(QString::number(delta))); + timeTable->setItem(thisRow, 3, new QTableWidgetItem(QString::number(elapsed))); - ++thisRow; - } + ++thisRow; } // setup a horizontal box layout diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 2bf25a3575..5c123b3bd0 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -875,7 +875,7 @@ void AudioClient::handleAudioInput() { _stats.sentPacket(); - nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstAudioPacket); + nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::SendAudioPacket); int packetBytes = currentPacketPtr - audioDataPacket; nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer); @@ -923,7 +923,7 @@ void AudioClient::sendMuteEnvironmentPacket() { } void AudioClient::addReceivedAudioToStream(const QByteArray& audioByteArray) { - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::ReceiveFirstAudioPacket); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveFirstAudioPacket); if (_audioOutput) { // Audio output must exist and be correctly set up if we're going to process received audio diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index bd412be414..76bdc5d089 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -61,6 +61,8 @@ LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short firstCall = false; } + qRegisterMetaType("ConnectionStep"); + _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); qCDebug(networking) << "NodeList socket is listening on" << _nodeSocket.localPort(); @@ -636,6 +638,8 @@ void LimitedNodeList::sendSTUNRequest() { QUuid randomUUID = QUuid::createUuid(); memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); + flagTimeForConnectionStep(ConnectionStep::SendSTUNRequest); + _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), _stunSockAddr.getAddress(), _stunSockAddr.getPort()); } @@ -693,18 +697,20 @@ bool LimitedNodeList::processSTUNResponse(const QByteArray& packet) { QHostAddress newPublicAddress = QHostAddress(stunAddress); if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { - if (!_hasCompletedInitialSTUN) { - // if we're here we have definitely completed our initial STUN sequence - stopInitialSTUNUpdate(true); - } - _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); qCDebug(networking, "New public socket received from STUN server is %s:%hu", _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), _publicSockAddr.getPort()); + if (!_hasCompletedInitialSTUN) { + // if we're here we have definitely completed our initial STUN sequence + stopInitialSTUNUpdate(true); + } + emit publicSockAddrChanged(_publicSockAddr); + + flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN); } return true; @@ -741,6 +747,9 @@ void LimitedNodeList::startSTUNPublicSocketUpdate() { const int STUN_INITIAL_UPDATE_INTERVAL_MSECS = 250; _initialSTUNTimer->start(STUN_INITIAL_UPDATE_INTERVAL_MSECS); + + // send an initial STUN request right away + sendSTUNRequest(); } } } @@ -760,6 +769,8 @@ void LimitedNodeList::stopInitialSTUNUpdate(bool success) { // we have changed the publicSockAddr, so emit our signal emit publicSockAddrChanged(_publicSockAddr); + + flagTimeForConnectionStep(ConnectionStep::SetPublicSocketFromSTUN); } assert(_initialSTUNTimer); @@ -847,3 +858,37 @@ bool LimitedNodeList::getLocalServerPortFromSharedMemory(const QString key, quin return true; } } + +void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep) { + QMetaObject::invokeMethod(this, "flagTimeForConnectionStep", + Q_ARG(ConnectionStep, connectionStep), + Q_ARG(quint64, usecTimestampNow())); +} + +void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp) { + if (!_areConnectionTimesComplete) { + QWriteLocker writeLock(&_connectionTimeLock); + + if (connectionStep == ConnectionStep::LookupAddress) { + // we clear the current times if the user just fired off a lookup + _lastConnectionTimes.clear(); + _areConnectionTimesComplete = false; + } + + // anything > than sending the first DS check should not come before the DS check in, so we drop those + // this handles the case where you lookup an address and get packets in the existing domain before changing domains + if (connectionStep > LimitedNodeList::ConnectionStep::SendDSCheckIn + && (_lastConnectionTimes.key(ConnectionStep::SendDSCheckIn) == 0 + || timestamp <= _lastConnectionTimes.key(ConnectionStep::SendDSCheckIn))) { + return; + } + + // if there is no time for existing step add a timestamp on the first call for each ConnectionStep + _lastConnectionTimes[timestamp] = connectionStep; + + // if this is a received audio packet we consider our connection times complete + if (connectionStep == ConnectionStep::ReceiveFirstAudioPacket) { + _areConnectionTimesComplete = true; + } + } +} diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index faaeac3c57..990508e89d 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -80,8 +80,30 @@ namespace PingType { class LimitedNodeList : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY - public: + + enum ConnectionStep { + LookupAddress = 1, + HandleAddress, + SendSTUNRequest, + SetPublicSocketFromSTUN, + SetICEServerHostname, + SetICEServerSocket, + SendICEServerHearbeat, + ReceiveDSPeerInformation, + SendPingsToDS, + SetDomainHostname, + SetDomainSocket, + SendDSCheckIn, + ReceiveDSList, + SendAudioPing, + SetAudioMixerSocket, + SendAudioPacket, + ReceiveFirstAudioPacket + }; + + Q_ENUMS(ConnectionStep); + const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); @@ -204,6 +226,11 @@ public: void putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort); bool getLocalServerPortFromSharedMemory(const QString key, quint16& localPort); + const QMap getLastConnectionTimes() const + { QReadLocker readLock(&_connectionTimeLock); return _lastConnectionTimes; } + void flagTimeForConnectionStep(ConnectionStep connectionStep); + + public slots: void reset(); void eraseAllNodes(); @@ -269,6 +296,12 @@ protected: QPointer _initialSTUNTimer; int _numInitialSTUNRequests = 0; bool _hasCompletedInitialSTUN = false; + quint64 _firstSTUNTime = 0; + quint64 _publicSocketUpdateTime = 0; + + mutable QReadWriteLock _connectionTimeLock { }; + QMap _lastConnectionTimes; + bool _areConnectionTimesComplete = false; template void eachNodeHashIterator(IteratorLambda functor) { @@ -279,6 +312,8 @@ protected: functor(it); } } +private slots: + void flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp); }; #endif // hifi_LimitedNodeList_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 20d9604c59..b7870bcdea 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -82,8 +82,6 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // clear our NodeList when logout is requested connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); - qRegisterMetaType("NodeList::ConnectionStep"); - // we definitely want STUN to update our public socket, so call the LNL to kick that off startSTUNPublicSocketUpdate(); } @@ -230,15 +228,18 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr case PacketTypeUnverifiedPingReply: { qCDebug(networking) << "Received reply from domain-server on" << senderSockAddr; - // for now we're unsafely assuming this came back from the domain - if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) { - qCDebug(networking) << "Connecting to domain using local socket"; - _domainHandler.activateICELocalSocket(); - } else if (senderSockAddr == _domainHandler.getICEPeer().getPublicSocket()) { - qCDebug(networking) << "Conecting to domain using public socket"; - _domainHandler.activateICEPublicSocket(); - } else { - qCDebug(networking) << "Reply does not match either local or public socket for domain. Will not connect."; + if (_domainHandler.getIP().isNull()) { + // for now we're unsafely assuming this came back from the domain + if (senderSockAddr == _domainHandler.getICEPeer().getLocalSocket()) { + qCDebug(networking) << "Connecting to domain using local socket"; + _domainHandler.activateICELocalSocket(); + } else if (senderSockAddr == _domainHandler.getICEPeer().getPublicSocket()) { + qCDebug(networking) << "Conecting to domain using public socket"; + _domainHandler.activateICEPublicSocket(); + } else { + qCDebug(networking) << "Reply does not match either local or public socket for domain. Will not connect."; + } + } } case PacketTypeStunResponse: { @@ -348,7 +349,7 @@ void NodeList::sendDomainServerCheckIn() { } } - flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstDSCheckIn); + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn); if (!isUsingDTLS) { writeUnverifiedDatagram(domainServerPacket, _domainHandler.getSockAddr()); @@ -467,7 +468,7 @@ void NodeList::handleICEConnectionToDomainServer() { _domainHandler.getICEPeer().resetConnectionAttemps(); - flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstICEServerHearbeat); + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendICEServerHearbeat); LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), @@ -476,7 +477,7 @@ void NodeList::handleICEConnectionToDomainServer() { qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); - flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstPingsToDS); + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendPingsToDS); // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local, false, _domainHandler.getICEClientID()); @@ -493,7 +494,7 @@ int NodeList::processDomainServerList(const QByteArray& packet) { // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::ReceiveFirstDSList); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSList); // if this was the first domain-server list from this domain, we've now connected if (!_domainHandler.isConnected()) { @@ -585,7 +586,7 @@ void NodeList::pingInactiveNodes() { pingPunchForInactiveNode(node); if (node->getType() == NodeType::AudioMixer) { - flagTimeForConnectionStep(NodeList::ConnectionStep::SendFirstAudioPing); + flagTimeForConnectionStep(NodeList::ConnectionStep::SendAudioPing); } } }); @@ -613,36 +614,3 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con flagTimeForConnectionStep(NodeList::ConnectionStep::SetAudioMixerSocket); } } - -void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep) { - QMetaObject::invokeMethod(this, "flagTimeForConnectionStep", - Q_ARG(NodeList::ConnectionStep, connectionStep), - Q_ARG(quint64, usecTimestampNow())); -} - -void NodeList::flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep, quint64 timestamp) { - QWriteLocker writeLock(&_connectionTimeLock); - - if (connectionStep == NodeList::ConnectionStep::LookupAddress) { - // we clear the current times if the user just fired off a lookup - _lastConnectionTimes.clear(); - } - - if (!_lastConnectionTimes.contains(connectionStep)) { - // if there is no time for existing step add a timestamp on the first call for each NodeList::ConnectionStep - _lastConnectionTimes[connectionStep] = timestamp; - } else { - // if the existing time for this step is before the nearest sibling before then replace it - // this handles the case where audio comes in after an address lookup after the previous times have been cleared - quint64 currentTime = _lastConnectionTimes[connectionStep]; - - // go down the sibling steps and check if the registered time is actually before the sibling - for (int i = ((int) connectionStep) - 1; i >= 0; i--) { - NodeList::ConnectionStep thisStep = static_cast(i); - if (_lastConnectionTimes.contains(thisStep) && _lastConnectionTimes[thisStep] >= currentTime) { - _lastConnectionTimes[connectionStep] = timestamp; - break; - } - } - } -} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index d52dd6e101..67810180f4 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -45,28 +45,6 @@ class NodeList : public LimitedNodeList { SINGLETON_DEPENDENCY public: - - enum ConnectionStep { - LookupAddress, - HandleAddress, - SetICEServerHostname, - SetICEServerSocket, - SendFirstICEServerHearbeat, - ReceiveDSPeerInformation, - SendFirstPingsToDS, - SetDomainHostname, - SetDomainSocket, - ForcedSTUNRequest, - SendFirstDSCheckIn, - ReceiveFirstDSList, - SendFirstAudioPing, - SetAudioMixerSocket, - SendFirstAudioPacket, - ReceiveFirstAudioPacket - }; - - Q_ENUMS(ConnectionStep); - NodeType_t getOwnerType() const { return _ownerType; } void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } @@ -85,11 +63,6 @@ public: int processDomainServerList(const QByteArray& packet); - const QMap getLastConnectionTimes() const - { QReadLocker readLock(&_connectionTimeLock); return _lastConnectionTimes; } - void flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep); - void resetConnectionTimes() { QWriteLocker writeLock(&_connectionTimeLock); _lastConnectionTimes.clear(); } - void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); @@ -104,7 +77,6 @@ signals: private slots: void sendPendingDSPathQuery(); void handleICEConnectionToDomainServer(); - void flagTimeForConnectionStep(NodeList::ConnectionStep connectionStep, quint64 timestamp); private: NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); @@ -126,9 +98,6 @@ private: int _numNoReplyDomainCheckIns; HifiSockAddr _assignmentServerSocket; - mutable QReadWriteLock _connectionTimeLock { }; - QMap _lastConnectionTimes; - friend class Application; }; From e6e2c4b95d47b318c89761d4b0081242dc9c07ad Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 27 May 2015 15:54:39 -0700 Subject: [PATCH 19/46] look for connection steps in LimitedNodeList --- libraries/audio-client/src/AudioClient.cpp | 2 +- libraries/networking/src/AddressManager.cpp | 6 +++--- libraries/networking/src/DomainHandler.cpp | 16 ++++++++-------- libraries/networking/src/NodeList.cpp | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 5c123b3bd0..41c8cb9537 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -875,7 +875,7 @@ void AudioClient::handleAudioInput() { _stats.sentPacket(); - nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::SendAudioPacket); + nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPacket); int packetBytes = currentPacketPtr - audioDataPacket; nodeList->writeDatagram(audioDataPacket, packetBytes, audioMixer); diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 63bcf4c4bc..3c6b7bd3f5 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -106,7 +106,7 @@ bool AddressManager::handleUrl(const QUrl& lookupUrl) { qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::LookupAddress); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::LookupAddress); // there are 4 possible lookup strings @@ -204,7 +204,7 @@ void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const const QString DOMAIN_NETWORK_PORT_KEY = "network_port"; const QString DOMAIN_ICE_SERVER_ADDRESS_KEY = "ice_server_address"; - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::HandleAddress); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); if (domainObject.contains(DOMAIN_NETWORK_ADDRESS_KEY)) { QString domainHostname = domainObject[DOMAIN_NETWORK_ADDRESS_KEY].toString(); @@ -436,7 +436,7 @@ void AddressManager::setDomainInfo(const QString& hostname, quint16 port) { qCDebug(networking) << "Possible domain change required to connect to domain at" << hostname << "on" << port; - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::HandleAddress); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::HandleAddress); emit possibleDomainChangeRequired(hostname, port); } diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 9dab4152d9..593f0b1597 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -85,7 +85,7 @@ void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hos } if (!_sockAddr.isNull()) { - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); } // some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification @@ -115,7 +115,7 @@ void DomainHandler::setHostnameAndPort(const QString& hostname, quint16 port) { qCDebug(networking, "Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainHostname); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainHostname); UserActivityLogger::getInstance().changedDomain(_hostname); emit hostnameChanged(_hostname); @@ -143,7 +143,7 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, auto nodeList = DependencyManager::get(); - nodeList->flagTimeForConnectionStep(NodeList::ConnectionStep::SetICEServerHostname); + nodeList->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerHostname); if (_iceServerSockAddr.getAddress().isNull()) { // connect to lookup completed for ice-server socket so we can request a heartbeat once hostname is looked up @@ -161,14 +161,14 @@ void DomainHandler::setIceServerHostnameAndID(const QString& iceServerHostname, } void DomainHandler::activateICELocalSocket() { - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getLocalSocket(); _hostname = _sockAddr.getAddress().toString(); emit completedSocketDiscovery(); } void DomainHandler::activateICEPublicSocket() { - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); _sockAddr = _icePeer.getPublicSocket(); _hostname = _sockAddr.getAddress().toString(); emit completedSocketDiscovery(); @@ -179,7 +179,7 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { _sockAddr.setAddress(hostInfo.addresses()[i]); - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetDomainSocket); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetDomainSocket); qCDebug(networking, "DS at %s is at %s", _hostname.toLocal8Bit().constData(), _sockAddr.getAddress().toString().toLocal8Bit().constData()); @@ -197,7 +197,7 @@ void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { void DomainHandler::completedIceServerHostnameLookup() { qDebug() << "ICE server socket is at" << _iceServerSockAddr; - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::SetICEServerSocket); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetICEServerSocket); // emit our signal so we can send a heartbeat to ice-server immediately emit iceSocketAndIDReceived(); @@ -301,7 +301,7 @@ void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { NetworkPeer packetPeer; iceResponseStream >> packetPeer; - DependencyManager::get()->flagTimeForConnectionStep(NodeList::ConnectionStep::ReceiveDSPeerInformation); + DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation); if (packetPeer.getUUID() != _iceDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index b7870bcdea..d6e394cc7b 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -586,7 +586,7 @@ void NodeList::pingInactiveNodes() { pingPunchForInactiveNode(node); if (node->getType() == NodeType::AudioMixer) { - flagTimeForConnectionStep(NodeList::ConnectionStep::SendAudioPing); + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPing); } } }); @@ -611,6 +611,6 @@ void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, con } if (sendingNode->getType() == NodeType::AudioMixer) { - flagTimeForConnectionStep(NodeList::ConnectionStep::SetAudioMixerSocket); + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SetAudioMixerSocket); } } From 757d02a600e0828b50f6c0f26d2e7d31b0bdb69b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 27 May 2015 16:00:56 -0700 Subject: [PATCH 20/46] fix reset on address lookup --- libraries/networking/src/LimitedNodeList.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 76bdc5d089..8133f1417c 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -866,14 +866,18 @@ void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep) { } void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp) { - if (!_areConnectionTimesComplete) { + + if (connectionStep == ConnectionStep::LookupAddress) { + QWriteLocker writeLock(&_connectionTimeLock); + + // we clear the current times if the user just fired off a lookup + _lastConnectionTimes.clear(); + _areConnectionTimesComplete = false; + + _lastConnectionTimes[timestamp] = connectionStep; + } else if (!_areConnectionTimesComplete) { QWriteLocker writeLock(&_connectionTimeLock); - if (connectionStep == ConnectionStep::LookupAddress) { - // we clear the current times if the user just fired off a lookup - _lastConnectionTimes.clear(); - _areConnectionTimesComplete = false; - } // anything > than sending the first DS check should not come before the DS check in, so we drop those // this handles the case where you lookup an address and get packets in the existing domain before changing domains From 0cb67cce2cd998ff13885cdc6691a6dfa10e171f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 27 May 2015 18:22:23 -0700 Subject: [PATCH 21/46] add ping timer tied to NetworkPeer --- libraries/networking/src/LimitedNodeList.cpp | 7 ++- libraries/networking/src/LimitedNodeList.h | 3 +- libraries/networking/src/NetworkPeer.cpp | 32 +++++++++--- libraries/networking/src/NetworkPeer.h | 41 ++++++++++------ libraries/networking/src/Node.cpp | 37 ++++++++------ libraries/networking/src/Node.h | 23 +++++---- libraries/networking/src/NodeList.cpp | 49 ++++++++++++------- libraries/networking/src/NodeList.h | 4 +- libraries/networking/src/ThreadedAssignment.h | 1 - 9 files changed, 126 insertions(+), 71 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 8133f1417c..8c18c42f50 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -472,7 +472,8 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) { SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool canAdjustLocks, bool canRez) { + bool canAdjustLocks, bool canRez, + const QUuid& connectionSecret) { NodeHash::const_iterator it = _nodeHash.find(uuid); if (it != _nodeHash.end()) { @@ -482,11 +483,13 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t matchingNode->setLocalSocket(localSocket); matchingNode->setCanAdjustLocks(canAdjustLocks); matchingNode->setCanRez(canRez); + matchingNode->setConnectionSecret(connectionSecret); return matchingNode; } else { // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez); + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez, connectionSecret); + SharedNodePointer newNodePointer(newNode); _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 990508e89d..8d99c28d5c 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -151,7 +151,8 @@ public: SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool canAdjustLocks, bool canRez); + bool canAdjustLocks, bool canRez, + const QUuid& connectionSecret = QUuid()); bool hasCompletedInitialSTUN() const { return _hasCompletedInitialSTUN; } diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index de1b8f66ba..69099c5201 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -25,7 +25,7 @@ NetworkPeer::NetworkPeer() : _lastHeardMicrostamp(usecTimestampNow()), _connectionAttempts(0) { - + } NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : @@ -36,14 +36,14 @@ NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, co _lastHeardMicrostamp(usecTimestampNow()), _connectionAttempts(0) { - + } NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) : QObject() { _uuid = otherPeer._uuid; _publicSocket = otherPeer._publicSocket; _localSocket = otherPeer._localSocket; - + _wakeTimestamp = otherPeer._wakeTimestamp; _lastHeardMicrostamp = otherPeer._lastHeardMicrostamp; _connectionAttempts = otherPeer._connectionAttempts; @@ -57,7 +57,7 @@ NetworkPeer& NetworkPeer::operator=(const NetworkPeer& otherPeer) { void NetworkPeer::swap(NetworkPeer& otherPeer) { using std::swap; - + swap(_uuid, otherPeer._uuid); swap(_publicSocket, otherPeer._publicSocket); swap(_localSocket, otherPeer._localSocket); @@ -71,15 +71,33 @@ QByteArray NetworkPeer::toByteArray() const { QDataStream peerStream(&peerByteArray, QIODevice::Append); peerStream << *this; - + return peerByteArray; } +void NetworkPeer::startPingTimer() { + if (!_pingTimer) { + _pingTimer = new QTimer(this); + + connect(_pingTimer, &QTimer::timeout, this, &NetworkPeer::pingTimerTimeout); + + _pingTimer->start(UDP_PUNCH_PING_INTERVAL_MS); + } +} + +void NetworkPeer::stopPingTimer() { + if (_pingTimer) { + _pingTimer->stop(); + _pingTimer->deleteLater(); + _pingTimer = NULL; + } +} + QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer) { out << peer._uuid; out << peer._publicSocket; out << peer._localSocket; - + return out; } @@ -87,7 +105,7 @@ QDataStream& operator>>(QDataStream& in, NetworkPeer& peer) { in >> peer._uuid; in >> peer._publicSocket; in >> peer._localSocket; - + return in; } diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index e44fac2dcc..2a060408c1 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -12,8 +12,9 @@ #ifndef hifi_NetworkPeer_h #define hifi_NetworkPeer_h -#include -#include +#include +#include +#include #include "HifiSockAddr.h" @@ -22,35 +23,38 @@ const int ICE_SERVER_DEFAULT_PORT = 7337; const int ICE_HEARBEAT_INTERVAL_MSECS = 2 * 1000; const int MAX_ICE_CONNECTION_ATTEMPTS = 5; +const int UDP_PUNCH_PING_INTERVAL_MS = 25; + class NetworkPeer : public QObject { + Q_OBJECT public: NetworkPeer(); NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); - + // privatize copy and assignment operator to disallow peer copying NetworkPeer(const NetworkPeer &otherPeer); NetworkPeer& operator=(const NetworkPeer& otherPeer); - + bool isNull() const { return _uuid.isNull(); } - + const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } - + void reset(); - + const HifiSockAddr& getPublicSocket() const { return _publicSocket; } virtual void setPublicSocket(const HifiSockAddr& publicSocket) { _publicSocket = publicSocket; } const HifiSockAddr& getLocalSocket() const { return _localSocket; } virtual void setLocalSocket(const HifiSockAddr& localSocket) { _localSocket = localSocket; } - + quint64 getWakeTimestamp() const { return _wakeTimestamp; } void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; } - + quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } - + QByteArray toByteArray() const; - + int getConnectionAttempts() const { return _connectionAttempts; } void incrementConnectionAttempts() { ++_connectionAttempts; } void resetConnectionAttemps() { _connectionAttempts = 0; } @@ -60,18 +64,25 @@ public: float getOutboundBandwidth(); // in kbps float getInboundBandwidth(); // in kbps - + + void startPingTimer(); + void stopPingTimer(); + friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer); friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer); +signals: + void pingTimerTimeout(); protected: QUuid _uuid; - + HifiSockAddr _publicSocket; HifiSockAddr _localSocket; - + quint64 _wakeTimestamp; quint64 _lastHeardMicrostamp; - + + QTimer* _pingTimer = NULL; + int _connectionAttempts; private: void swap(NetworkPeer& otherPeer); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index ddc0571db4..f651553ff6 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -42,12 +42,12 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { } Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, - const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez) : + const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez, const QUuid& connectionSecret) : NetworkPeer(uuid, publicSocket, localSocket), _type(type), _activeSocket(NULL), _symmetricSocket(), - _connectionSecret(), + _connectionSecret(connectionSecret), _linkedData(NULL), _isAlive(true), _pingMs(-1), // "Uninitialized" @@ -57,7 +57,7 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, _canAdjustLocks(canAdjustLocks), _canRez(canRez) { - + } Node::~Node() { @@ -75,11 +75,11 @@ void Node::setPublicSocket(const HifiSockAddr& publicSocket) { // if the active socket was the public socket then reset it to NULL _activeSocket = NULL; } - + if (!_publicSocket.isNull()) { qCDebug(networking) << "Public socket change for node" << *this; } - + _publicSocket = publicSocket; } } @@ -90,11 +90,11 @@ void Node::setLocalSocket(const HifiSockAddr& localSocket) { // if the active socket was the local socket then reset it to NULL _activeSocket = NULL; } - + if (!_localSocket.isNull()) { qCDebug(networking) << "Local socket change for node" << *this; } - + _localSocket = localSocket; } } @@ -105,32 +105,39 @@ void Node::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { // if the active socket was the symmetric socket then reset it to NULL _activeSocket = NULL; } - + if (!_symmetricSocket.isNull()) { qCDebug(networking) << "Symmetric socket change for node" << *this; } - + _symmetricSocket = symmetricSocket; } } +void Node::setActiveSocket(HifiSockAddr* discoveredSocket) { + _activeSocket = discoveredSocket; + + // we have an active socket, stop our ping timer + stopPingTimer(); +} + void Node::activateLocalSocket() { qCDebug(networking) << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_localSocket; + setActiveSocket(&_localSocket); } void Node::activatePublicSocket() { qCDebug(networking) << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_publicSocket; + setActiveSocket(&_publicSocket); } void Node::activateSymmetricSocket() { qCDebug(networking) << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - _activeSocket = &_symmetricSocket; + setActiveSocket(&_symmetricSocket); } PacketSequenceNumber Node::getLastSequenceNumberForPacketType(PacketType packetType) const { - auto typeMatch = _lastSequenceNumbers.find(packetType); + auto typeMatch = _lastSequenceNumbers.find(packetType); if (typeMatch != _lastSequenceNumbers.end()) { return typeMatch->second; } else { @@ -145,7 +152,7 @@ QDataStream& operator<<(QDataStream& out, const Node& node) { out << node._localSocket; out << node._canAdjustLocks; out << node._canRez; - + return out; } @@ -156,7 +163,7 @@ QDataStream& operator>>(QDataStream& in, Node& node) { in >> node._localSocket; in >> node._canAdjustLocks; in >> node._canRez; - + return in; } diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 0836a448b2..884e65a626 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -37,16 +37,17 @@ namespace NodeType { const NodeType_t AudioMixer = 'M'; const NodeType_t AvatarMixer = 'W'; const NodeType_t Unassigned = 1; - + void init(); const QString& getNodeTypeName(NodeType_t nodeType); } class Node : public NetworkPeer { Q_OBJECT -public: +public: Node(const QUuid& uuid, NodeType_t type, - const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez); + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, + bool canAdjustLocks, bool canRez, const QUuid& connectionSecret = QUuid()); ~Node(); bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; } @@ -54,7 +55,7 @@ public: char getType() const { return _type; } void setType(char type) { _type = type; } - + const QUuid& getConnectionSecret() const { return _connectionSecret; } void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } @@ -70,12 +71,12 @@ public: int getClockSkewUsec() const { return _clockSkewUsec; } void updateClockSkewUsec(int clockSkewSample); QMutex& getMutex() { return _mutex; } - + virtual void setPublicSocket(const HifiSockAddr& publicSocket); virtual void setLocalSocket(const HifiSockAddr& localSocket); const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; } virtual void setSymmetricSocket(const HifiSockAddr& symmetricSocket); - + const HifiSockAddr* getActiveSocket() const { return _activeSocket; } void setCanAdjustLocks(bool canAdjustLocks) { _canAdjustLocks = canAdjustLocks; } @@ -83,7 +84,7 @@ public: void setCanRez(bool canRez) { _canRez = canRez; } bool getCanRez() { return _canRez; } - + void activatePublicSocket(); void activateLocalSocket(); void activateSymmetricSocket(); @@ -91,7 +92,7 @@ public: void setLastSequenceNumberForPacketType(PacketSequenceNumber sequenceNumber, PacketType packetType) { _lastSequenceNumbers[packetType] = sequenceNumber; } PacketSequenceNumber getLastSequenceNumberForPacketType(PacketType packetType) const; - + friend QDataStream& operator<<(QDataStream& out, const Node& node); friend QDataStream& operator>>(QDataStream& in, Node& node); @@ -100,11 +101,13 @@ private: Node(const Node &otherNode); Node& operator=(Node otherNode); + void setActiveSocket(HifiSockAddr* discoveredSocket); + NodeType_t _type; - + HifiSockAddr* _activeSocket; HifiSockAddr _symmetricSocket; - + QUuid _connectionSecret; NodeData* _linkedData; bool _isAlive; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index d6e394cc7b..67876ccc73 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -82,6 +82,9 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // clear our NodeList when logout is requested connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + // anytime we get a new node we will want to attempt to punch to it + connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); + // we definitely want STUN to update our public socket, so call the LNL to kick that off startSTUNPublicSocketUpdate(); } @@ -521,7 +524,7 @@ int NodeList::processDomainServerList(const QByteArray& packet) { setThisNodeCanRez(thisNodeCanRez); // pull each node in the packet - while(packetStream.device()->pos() < packet.size()) { + while (packetStream.device()->pos() < packet.size()) { // setup variables to read into from QDataStream qint8 nodeType; QUuid nodeUUID, connectionUUID; @@ -537,16 +540,12 @@ int NodeList::processDomainServerList(const QByteArray& packet) { nodePublicSocket.setAddress(_domainHandler.getIP()); } - SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, - nodeLocalSocket, canAdjustLocks, canRez); - packetStream >> connectionUUID; - node->setConnectionSecret(connectionUUID); - } - // ping inactive nodes in conjunction with receipt of list from domain-server - // this makes it happen every second and also pings any newly added nodes - pingInactiveNodes(); + SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, + nodeLocalSocket, canAdjustLocks, canRez, + connectionUUID); + } return readNodes; } @@ -566,6 +565,10 @@ void NodeList::sendAssignment(Assignment& assignment) { } void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { + if (node->getType() == NodeType::AudioMixer) { + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPing); + } + // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local); writeDatagram(localPingPacket, node, node->getLocalSocket()); @@ -577,19 +580,27 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric); writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket()); } + + node->incrementConnectionAttempts(); } -void NodeList::pingInactiveNodes() { - eachNode([this](const SharedNodePointer& node){ - if (!node->getActiveSocket()) { - // we don't have an active link to this node, ping it to set that up - pingPunchForInactiveNode(node); +void NodeList::startNodeHolePunch(const SharedNodePointer& node) { + // connect to the correct signal on this node so we know when to ping it + connect(node.data(), &Node::pingTimerTimeout, this, &NodeList::handleNodePingTimeout); - if (node->getType() == NodeType::AudioMixer) { - flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPing); - } - } - }); + // start the ping timer for this node + node->startPingTimer(); + + // ping this node immediately + pingPunchForInactiveNode(node); +} + +void NodeList::handleNodePingTimeout() { + Node* senderNode = qobject_cast(sender()); + + if (senderNode) { + pingPunchForInactiveNode(nodeWithUUID(senderNode->getUUID())); + } } void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index 67810180f4..e699f99aad 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -70,13 +70,15 @@ public: public slots: void reset(); void sendDomainServerCheckIn(); - void pingInactiveNodes(); void handleDSPathQuery(const QString& newPath); signals: void limitOfSilentDomainCheckInsReached(); private slots: void sendPendingDSPathQuery(); void handleICEConnectionToDomainServer(); + + void startNodeHolePunch(const SharedNodePointer& node); + void handleNodePingTimeout(); private: NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index 676d1a04ce..e2e0aa0fed 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -46,7 +46,6 @@ protected: private slots: void checkInWithDomainServerOrExit(); - }; typedef QSharedPointer SharedAssignmentPointer; From 667df66963d2609dc0ad1b7b49a99db0bc5131e0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 27 May 2015 18:25:38 -0700 Subject: [PATCH 22/46] add packet type for DS node add --- libraries/networking/src/PacketHeaders.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 8b952e6eb3..56045be1c3 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -49,7 +49,7 @@ enum PacketType { PacketTypeDataServerConfirm, // 20 PacketTypeDomainServerPathQuery, PacketTypeDomainServerPathResponse, - UNUSED_3, + PacketTypeDomainServerAddedNode, UNUSED_4, UNUSED_5, // 25 PacketTypeOctreeStats, From 5c75863af43e1f511bc668dd8fb6afc3144419ea Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 10:45:03 -0700 Subject: [PATCH 23/46] immediately send add packet from DS for new node --- domain-server/src/DomainServer.cpp | 87 +++++++++++++++++++----- domain-server/src/DomainServer.h | 3 + libraries/networking/src/PacketHeaders.h | 17 ++--- 3 files changed, 81 insertions(+), 26 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 2c784abe54..8eaf1a29f3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -654,6 +654,19 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr, canAdjustLocks, canRez); + + // So that we can send messages to this node at will - we need to activate the correct socket on this node now + if (senderSockAddr == publicSockAddr) { + newNode->activatePublicSocket(); + } else if (senderSockAddr == localSockAddr) { + newNode->activateLocalSocket(); + } else { + // set the Node's symmetric socket to the sender socket + newNode->setSymmetricSocket(senderSockAddr); + // activate that symmetric socket + newNode->activateSymmetricSocket(); + } + // when the newNode is created the linked data is also created // if this was a static assignment set the UUID, set the sendingSockAddr DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); @@ -683,6 +696,9 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock // reply back to the user with a PacketTypeDomainList sendDomainListToNode(newNode, senderSockAddr, nodeInterestList.toSet()); + + // send out this node to our other connected nodes + broadcastNewNode(newNode); } } @@ -699,9 +715,9 @@ unsigned int DomainServer::countConnectedUsers() { } -bool DomainServer::verifyUsersKey (const QString& username, - const QByteArray& usernameSignature, - QString& reasonReturn) { +bool DomainServer::verifyUsersKey(const QString& username, + const QByteArray& usernameSignature, + QString& reasonReturn) { // it's possible this user can be allowed to connect, but we need to check their username signature QByteArray publicKeyArray = _userPublicKeys.value(username); @@ -954,21 +970,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif nodeDataStream << *otherNode.data(); // pack the secret that these two nodes will use to communicate with each other - QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID()); - if (secretUUID.isNull()) { - // generate a new secret UUID these two nodes can use - secretUUID = QUuid::createUuid(); - - // set that on the current Node's sessionSecretHash - nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID); - - // set it on the other Node's sessionSecretHash - reinterpret_cast(otherNode->getLinkedData()) - ->getSessionSecretHash().insert(node->getUUID(), secretUUID); - - } - - nodeDataStream << secretUUID; + nodeDataStream << connectionSecretForNodes(node, otherNode); if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) { // we need to break here and start a new packet @@ -992,6 +994,55 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif limitedNodeList->writeUnverifiedDatagram(broadcastPacket, node, senderSockAddr); } +QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) { + DomainServerNodeData* nodeAData = dynamic_cast(nodeA->getLinkedData()); + DomainServerNodeData* nodeBData = dynamic_cast(nodeB->getLinkedData()); + + if (nodeAData && nodeBData) { + QUuid& secretUUID = nodeAData->getSessionSecretHash()[nodeB->getUUID()]; + + if (secretUUID.isNull()) { + // generate a new secret UUID these two nodes can use + secretUUID = QUuid::createUuid(); + + // set it on the other Node's sessionSecretHash + reinterpret_cast(nodeBData)->getSessionSecretHash().insert(nodeA->getUUID(), secretUUID); + } + + return secretUUID; + } + + return QUuid(); +} + +void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { + + auto limitedNodeList = DependencyManager::get(); + + // setup the add packet for this new node + QByteArray addNodePacket = limitedNodeList->byteArrayWithPopulatedHeader(PacketTypeDomainServerAddedNode); + QDataStream addNodeStream(&addNodePacket, QIODevice::Append); + + addNodeStream << *addedNode.data(); + + int connectionSecretIndex = addNodePacket.size(); + + limitedNodeList->eachMatchingNode( + [&](const SharedNodePointer& node)->bool { + return (node->getLinkedData() && node->getActiveSocket() && node != addedNode); + }, + [&](const SharedNodePointer& node) { + QByteArray rfcConnectionSecret = connectionSecretForNodes(node, addedNode).toRfc4122(); + + // replace the bytes at the end of the packet for the connection secret between these nodes + addNodePacket.replace(connectionSecretIndex, NUM_BYTES_RFC4122_UUID, rfcConnectionSecret); + + // send off this packet to the node + limitedNodeList->writeUnverifiedDatagram(addNodePacket, node); + } + ); +} + void DomainServer::readAvailableDatagrams() { auto limitedNodeList = DependencyManager::get(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 22e3efa378..c70c9cec13 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -101,6 +101,9 @@ private: void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, const NodeSet& nodeInterestList); + QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB); + void broadcastNewNode(const SharedNodePointer& node); + void parseAssignmentConfigs(QSet& excludedTypes); void addStaticAssignmentToAssignmentHash(Assignment* newAssignment); void createStaticAssignmentsForType(Assignment::Type type, const QVariantList& configList); diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index 56045be1c3..a702df7798 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -91,14 +91,15 @@ const PacketSequenceNumber DEFAULT_SEQUENCE_NUMBER = 0; typedef std::map PacketTypeSequenceMap; const QSet NON_VERIFIED_PACKETS = QSet() -<< PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest -<< PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainConnectionDenied -<< PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse -<< PacketTypeNodeJsonStats << PacketTypeEntityQuery -<< PacketTypeOctreeDataNack << PacketTypeEntityEditNack -<< PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse -<< PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode -<< PacketTypeDomainServerPathQuery << PacketTypeDomainServerPathResponse; + << PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest + << PacketTypeDomainList << PacketTypeDomainListRequest << PacketTypeDomainConnectionDenied + << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse + << PacketTypeNodeJsonStats << PacketTypeEntityQuery + << PacketTypeOctreeDataNack << PacketTypeEntityEditNack + << PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse + << PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode + << PacketTypeDomainServerPathQuery << PacketTypeDomainServerPathResponse + << PacketTypeDomainServerAddedNode; const QSet SEQUENCE_NUMBERED_PACKETS = QSet() << PacketTypeAvatarData; From cee058c4ce42ace1c32a9cdf0806dc5e25d86347 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 11:02:45 -0700 Subject: [PATCH 24/46] handle immediate add packets in NodeList --- libraries/networking/src/LimitedNodeList.cpp | 4 ++ libraries/networking/src/LimitedNodeList.h | 1 + libraries/networking/src/NodeList.cpp | 67 +++++++++++++------- libraries/networking/src/NodeList.h | 7 +- 4 files changed, 53 insertions(+), 26 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 8c18c42f50..7102df62e0 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -490,6 +490,10 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t // we didn't have this node, so add them Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez, connectionSecret); + if (nodeType == NodeType::AudioMixer) { + LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); + } + SharedNodePointer newNodePointer(newNode); _nodeHash.insert(UUIDNodePair(newNode->getUUID(), newNodePointer)); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 8d99c28d5c..fdfb1fa834 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -96,6 +96,7 @@ public: SetDomainSocket, SendDSCheckIn, ReceiveDSList, + AddedAudioMixer, SendAudioPing, SetAudioMixerSocket, SendAudioPacket, diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 67876ccc73..7384457458 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -170,12 +170,18 @@ void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& } void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { - switch (packetTypeForPacket(packet)) { - case PacketTypeDomainList: { + PacketType packetType = packetTypeForPacket(packet); + switch (packetType) { + case PacketTypeDomainList: + case PacketTypeDomainServerAddedNode: { if (!_domainHandler.getSockAddr().isNull()) { - // only process a list from domain-server if we're talking to a domain + // only process a packet from domain-server if we're talking to a domain // TODO: how do we make sure this is actually the domain we want the list from (DTLS probably) - processDomainServerList(packet); + if (packetType == PacketTypeDomainList) { + processDomainServerList(packet); + } else if (packetType == PacketTypeDomainServerAddedNode) { + processDomainServerAddedNode(packet); + } } break; } @@ -525,31 +531,44 @@ int NodeList::processDomainServerList(const QByteArray& packet) { // pull each node in the packet while (packetStream.device()->pos() < packet.size()) { - // setup variables to read into from QDataStream - qint8 nodeType; - QUuid nodeUUID, connectionUUID; - HifiSockAddr nodePublicSocket, nodeLocalSocket; - bool canAdjustLocks; - bool canRez; - - packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> canAdjustLocks >> canRez; - - // if the public socket address is 0 then it's reachable at the same IP - // as the domain server - if (nodePublicSocket.getAddress().isNull()) { - nodePublicSocket.setAddress(_domainHandler.getIP()); - } - - packetStream >> connectionUUID; - - SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, - nodeLocalSocket, canAdjustLocks, canRez, - connectionUUID); + parseNodeFromPacketStream(packetStream); } return readNodes; } +void NodeList::processDomainServerAddedNode(const QByteArray& packet) { + // setup a QDataStream, skip the header + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + // use our shared method to pull out the new node + parseNodeFromPacketStream(packetStream); +} + +void NodeList::parseNodeFromPacketStream(QDataStream& packetStream) { + // setup variables to read into from QDataStream + qint8 nodeType; + QUuid nodeUUID, connectionUUID; + HifiSockAddr nodePublicSocket, nodeLocalSocket; + bool canAdjustLocks; + bool canRez; + + packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket >> canAdjustLocks >> canRez; + + // if the public socket address is 0 then it's reachable at the same IP + // as the domain server + if (nodePublicSocket.getAddress().isNull()) { + nodePublicSocket.setAddress(_domainHandler.getIP()); + } + + packetStream >> connectionUUID; + + SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, + nodeLocalSocket, canAdjustLocks, canRez, + connectionUUID); +} + void NodeList::sendAssignment(Assignment& assignment) { PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index e699f99aad..d62715af8b 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -61,8 +61,6 @@ public: void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet); - int processDomainServerList(const QByteArray& packet); - void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); @@ -94,6 +92,11 @@ private: void sendDSPathQuery(const QString& newPath); + int processDomainServerList(const QByteArray& packet); + void processDomainServerAddedNode(const QByteArray& packet); + void parseNodeFromPacketStream(QDataStream& packetStream); + + NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; From 81c23e9ca0188a7817e47012f17dd812a5bb04e2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 11:22:27 -0700 Subject: [PATCH 25/46] output if pinged node cannot be reached --- libraries/networking/src/NetworkPeer.h | 2 +- libraries/networking/src/Node.cpp | 3 +++ libraries/networking/src/NodeList.cpp | 13 ++++++++++--- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 2a060408c1..92f271efa4 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -57,7 +57,7 @@ public: int getConnectionAttempts() const { return _connectionAttempts; } void incrementConnectionAttempts() { ++_connectionAttempts; } - void resetConnectionAttemps() { _connectionAttempts = 0; } + void resetConnectionAttempts() { _connectionAttempts = 0; } void recordBytesSent(int count); void recordBytesReceived(int count); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index f651553ff6..e4144d080c 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -119,6 +119,9 @@ void Node::setActiveSocket(HifiSockAddr* discoveredSocket) { // we have an active socket, stop our ping timer stopPingTimer(); + + // we're now considered connected to this peer - reset the number of connection attemps + resetConnectionAttempts(); } void Node::activateLocalSocket() { diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 7384457458..0838ec18ec 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -475,7 +475,7 @@ void NodeList::handleICEConnectionToDomainServer() { if (_domainHandler.getICEPeer().isNull() || _domainHandler.getICEPeer().getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) { - _domainHandler.getICEPeer().resetConnectionAttemps(); + _domainHandler.getICEPeer().resetConnectionAttempts(); flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendICEServerHearbeat); @@ -588,6 +588,13 @@ void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendAudioPing); } + // every second we're trying to ping this node and we're not getting anywhere - debug that out + const int NUM_DEBUG_CONNECTION_ATTEMPTS = 1000 / (UDP_PUNCH_PING_INTERVAL_MS); + + if (node->getConnectionAttempts() > 0 && node->getConnectionAttempts() % NUM_DEBUG_CONNECTION_ATTEMPTS == 0) { + qCDebug(networking) << "No response to UDP hole punch pings for node" << node->getUUID() << "in last second."; + } + // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = constructPingPacket(PingType::Local); writeDatagram(localPingPacket, node, node->getLocalSocket()); @@ -615,10 +622,10 @@ void NodeList::startNodeHolePunch(const SharedNodePointer& node) { } void NodeList::handleNodePingTimeout() { - Node* senderNode = qobject_cast(sender()); + SharedNodePointer senderNode = nodeWithUUID(qobject_cast(sender())->getUUID()); if (senderNode) { - pingPunchForInactiveNode(nodeWithUUID(senderNode->getUUID())); + pingPunchForInactiveNode(senderNode); } } From 810c766f77f3a605dfe12e1e32333a3a015c32d5 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 28 May 2015 12:50:02 -0700 Subject: [PATCH 26/46] When an entity is added or changed with a collisionSoundURL, cache it so that it is ready to play on the first hit. --- interface/src/Application.cpp | 3 +++ libraries/entities/src/EntityTree.cpp | 12 ++++++++++-- libraries/entities/src/EntityTree.h | 2 ++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ec80f14bce..3f05b0e4d1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2181,6 +2181,9 @@ void Application::init() { // initialize the GlowEffect with our widget bool glow = Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect); DependencyManager::get()->init(glow); + + // Make sure any new sounds are loaded as soon as know about them. + connect(tree, &EntityTree::newCollisionSoundURL, DependencyManager::get().data(), &SoundCache::getSound); } void Application::closeMirrorView() { diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0cee73c584..5eec93e8f5 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -84,6 +84,7 @@ void EntityTree::postAddEntity(EntityItemPointer entity) { _simulation->unlock(); } _isDirty = true; + maybeNotifyNewCollisionSoundURL("", entity->getCollisionSoundURL()); emit addingEntity(entity->getEntityItemID()); } @@ -184,6 +185,7 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI // else client accepts what the server says QString entityScriptBefore = entity->getScript(); + QString collisionSoundURLBefore = entity->getCollisionSoundURL(); uint32_t preFlags = entity->getDirtyFlags(); UpdateEntityOperator theOperator(this, containingElement, entity, properties); recurseTreeWithOperator(&theOperator); @@ -206,8 +208,9 @@ bool EntityTree::updateEntityWithElement(EntityItemPointer entity, const EntityI QString entityScriptAfter = entity->getScript(); if (entityScriptBefore != entityScriptAfter) { emitEntityScriptChanging(entity->getEntityItemID()); // the entity script has changed - } - } + } + maybeNotifyNewCollisionSoundURL(collisionSoundURLBefore, entity->getCollisionSoundURL()); + } // TODO: this final containingElement check should eventually be removed (or wrapped in an #ifdef DEBUG). containingElement = getContainingElement(entity->getEntityItemID()); @@ -266,6 +269,11 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti void EntityTree::emitEntityScriptChanging(const EntityItemID& entityItemID) { emit entityScriptChanging(entityItemID); } +void EntityTree::maybeNotifyNewCollisionSoundURL(const QString& previousCollisionSoundURL, const QString& nextCollisionSoundURL) { + if (!nextCollisionSoundURL.isEmpty() && (nextCollisionSoundURL != previousCollisionSoundURL)) { + emit newCollisionSoundURL(QUrl(nextCollisionSoundURL)); + } +} void EntityTree::setSimulation(EntitySimulation* simulation) { if (simulation) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index b18e989571..0d99c8e82d 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -171,6 +171,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); void entityScriptChanging(const EntityItemID& entityItemID); + void newCollisionSoundURL(const QUrl& url); void clearingEntities(); private: @@ -199,6 +200,7 @@ private: EntitySimulation* _simulation; bool _wantEditLogging = false; + void maybeNotifyNewCollisionSoundURL(const QString& oldCollisionSoundURL, const QString& newCollisionSoundURL); }; #endif // hifi_EntityTree_h From eb6b80133b5299ae8ad174a9e535dea7294f4d90 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 13:17:48 -0700 Subject: [PATCH 27/46] immediately ping the ICE DS every 25ms --- libraries/networking/src/DomainHandler.cpp | 11 +++++-- libraries/networking/src/DomainHandler.h | 2 +- libraries/networking/src/NetworkPeer.cpp | 15 +++++++++- libraries/networking/src/NetworkPeer.h | 11 +++---- libraries/networking/src/NodeList.cpp | 34 ++++++++++++++++++---- libraries/networking/src/NodeList.h | 4 ++- 6 files changed, 60 insertions(+), 17 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index 593f0b1597..e09dcb186b 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -31,12 +31,13 @@ DomainHandler::DomainHandler(QObject* parent) : _iceDomainID(), _iceClientID(), _iceServerSockAddr(), - _icePeer(), + _icePeer(this), _isConnected(false), _settingsObject(), _failedSettingsRequests(0) { - + // if we get a socket that make sure our NetworkPeer ping timer stops + connect(this, &DomainHandler::completedSocketDiscovery, &_icePeer, &NetworkPeer::stopPingTimer); } void DomainHandler::clearConnectionInfo() { @@ -309,6 +310,10 @@ void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { qCDebug(networking) << "Received network peer object for domain -" << packetPeer; _icePeer = packetPeer; - emit requestICEConnectionAttempt(); + // ask the peer object to start its ping timer + _icePeer.startPingTimer(); + + // emit our signal so the NodeList knows to send a ping immediately + emit icePeerSocketsReceived(); } } diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 794a7793bb..0c1698f5ec 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -99,7 +99,7 @@ signals: void disconnectedFromDomain(); void iceSocketAndIDReceived(); - void requestICEConnectionAttempt(); + void icePeerSocketsReceived(); void settingsReceived(const QJsonObject& domainSettingsObject); void settingsReceiveFail(); diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 69099c5201..a6ed7e44fb 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -17,7 +17,8 @@ #include "NetworkPeer.h" #include "BandwidthRecorder.h" -NetworkPeer::NetworkPeer() : +NetworkPeer::NetworkPeer(QObject* parent) : + QObject(parent), _uuid(), _publicSocket(), _localSocket(), @@ -66,6 +67,18 @@ void NetworkPeer::swap(NetworkPeer& otherPeer) { swap(_connectionAttempts, otherPeer._connectionAttempts); } +void NetworkPeer::softReset() { + // a soft reset should clear the sockets and reset the number of connection attempts + _localSocket.clear(); + _publicSocket.clear(); + + // stop our ping timer since we don't have sockets to ping anymore anyways + stopPingTimer(); + + _connectionAttempts = 0; +} + + QByteArray NetworkPeer::toByteArray() const { QByteArray peerByteArray; diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 92f271efa4..53b79c0126 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -28,7 +28,7 @@ const int UDP_PUNCH_PING_INTERVAL_MS = 25; class NetworkPeer : public QObject { Q_OBJECT public: - NetworkPeer(); + NetworkPeer(QObject* parent = 0); NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); // privatize copy and assignment operator to disallow peer copying @@ -36,11 +36,12 @@ public: NetworkPeer& operator=(const NetworkPeer& otherPeer); bool isNull() const { return _uuid.isNull(); } + bool hasSockets() const { return !_localSocket.isNull() && !_publicSocket.isNull(); } const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } - void reset(); + void softReset(); const HifiSockAddr& getPublicSocket() const { return _publicSocket; } virtual void setPublicSocket(const HifiSockAddr& publicSocket) { _publicSocket = publicSocket; } @@ -65,11 +66,11 @@ public: float getOutboundBandwidth(); // in kbps float getInboundBandwidth(); // in kbps - void startPingTimer(); - void stopPingTimer(); - friend QDataStream& operator<<(QDataStream& out, const NetworkPeer& peer); friend QDataStream& operator>>(QDataStream& in, NetworkPeer& peer); +public slots: + void startPingTimer(); + void stopPingTimer(); signals: void pingTimerTimeout(); protected: diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 0838ec18ec..bfd0c64b96 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -73,8 +73,11 @@ NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned // send an ICE heartbeat as soon as we get ice server information connect(&_domainHandler, &DomainHandler::iceSocketAndIDReceived, this, &NodeList::handleICEConnectionToDomainServer); - // handle ICE signal from DS so connection is attempted immediately - connect(&_domainHandler, &DomainHandler::requestICEConnectionAttempt, this, &NodeList::handleICEConnectionToDomainServer); + // handle ping timeout from DomainHandler to establish a connection with auto networked domain-server + connect(&_domainHandler.getICEPeer(), &NetworkPeer::pingTimerTimeout, this, &NodeList::pingPunchForDomainServer); + + // send a ping punch immediately + connect(&_domainHandler, &DomainHandler::icePeerSocketsReceived, this, &NodeList::pingPunchForDomainServer); // clear out NodeList when login is finished connect(&AccountManager::getInstance(), &AccountManager::loginComplete , this, &NodeList::reset); @@ -299,6 +302,7 @@ void NodeList::sendDomainServerCheckIn() { // we don't know our public socket and we need to send it to the domain server qCDebug(networking) << "Waiting for inital public socket from STUN. Will not send domain-server check in."; } else if (_domainHandler.getIP().isNull() && _domainHandler.requiresICE()) { + qCDebug(networking) << "Waiting for ICE discovered domain-server socket. Will not send domain-server check in."; handleICEConnectionToDomainServer(); } else if (!_domainHandler.getIP().isNull()) { bool isUsingDTLS = false; @@ -472,8 +476,9 @@ void NodeList::handleDSPathQueryResponse(const QByteArray& packet) { } void NodeList::handleICEConnectionToDomainServer() { - if (_domainHandler.getICEPeer().isNull() - || _domainHandler.getICEPeer().getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) { + // if we're still waiting to get sockets we want to ping for the domain-server + // then send another heartbeat now + if (!_domainHandler.getICEPeer().hasSockets()) { _domainHandler.getICEPeer().resetConnectionAttempts(); @@ -482,7 +487,24 @@ void NodeList::handleICEConnectionToDomainServer() { LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), _domainHandler.getICEDomainID()); - } else { + } +} + +void NodeList::pingPunchForDomainServer() { + // make sure if we're here that we actually still need to ping the domain-server + if (_domainHandler.getIP().isNull() && _domainHandler.getICEPeer().hasSockets()) { + + // check if we've hit the number of pings we'll send to the DS before we consider it a fail + const int NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET = 2000 / UDP_PUNCH_PING_INTERVAL_MS; + + if (_domainHandler.getICEPeer().getConnectionAttempts() > 0 + && _domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { + // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat + + _domainHandler.getICEPeer().softReset(); + handleICEConnectionToDomainServer(); + } + qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); @@ -624,7 +646,7 @@ void NodeList::startNodeHolePunch(const SharedNodePointer& node) { void NodeList::handleNodePingTimeout() { SharedNodePointer senderNode = nodeWithUUID(qobject_cast(sender())->getUUID()); - if (senderNode) { + if (senderNode && !senderNode->getActiveSocket()) { pingPunchForInactiveNode(senderNode); } } diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index d62715af8b..5e70b45b5f 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -64,7 +64,6 @@ public: void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); - void pingPunchForInactiveNode(const SharedNodePointer& node); public slots: void reset(); void sendDomainServerCheckIn(); @@ -77,6 +76,8 @@ private slots: void startNodeHolePunch(const SharedNodePointer& node); void handleNodePingTimeout(); + + void pingPunchForDomainServer(); private: NodeList() : LimitedNodeList(0, 0) { assert(false); } // Not implemented, needed for DependencyManager templates compile NodeList(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsListenPort = 0); @@ -96,6 +97,7 @@ private: void processDomainServerAddedNode(const QByteArray& packet); void parseNodeFromPacketStream(QDataStream& packetStream); + void pingPunchForInactiveNode(const SharedNodePointer& node); NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; From 0f30ec2cccb13ec7ff3054e98b812c00b129fd96 Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Thu, 28 May 2015 13:48:27 -0700 Subject: [PATCH 28/46] Undo the merge with the broken master checking. What could go wrong? --- CMakeLists.txt | 1 + ...hange.cmake => OSXInstallNameChange.cmake} | 27 +- cmake/externals/polyvox/CMakeLists.txt | 57 ++ cmake/externals/tbb/CMakeLists.txt | 4 +- cmake/modules/FindPolyVox.cmake | 54 ++ examples/blockWorld.js | 201 ++++++ examples/pointer.js | 15 - examples/voxels.js | 43 ++ interface/src/Application.cpp | 2 + interface/src/avatar/Avatar.h | 6 +- interface/src/avatar/AvatarMotionState.cpp | 6 +- interface/src/avatar/AvatarMotionState.h | 6 +- interface/src/ui/ApplicationOverlay.cpp | 75 +-- interface/src/ui/ApplicationOverlay.h | 2 +- libraries/avatars/src/AvatarData.h | 2 +- .../src/EntityTreeRenderer.cpp | 2 + .../src/RenderablePolyVoxEntityItem.cpp | 336 ++++++++++ .../src/RenderablePolyVoxEntityItem.h | 68 +++ libraries/entities/CMakeLists.txt | 6 +- libraries/entities/src/EntityItem.cpp | 572 +++++++++--------- .../entities/src/EntityItemProperties.cpp | 46 +- libraries/entities/src/EntityItemProperties.h | 10 +- .../entities/src/EntityItemPropertiesMacros.h | 22 +- libraries/entities/src/EntityPropertyFlags.h | 4 + .../entities/src/EntityScriptingInterface.cpp | 38 ++ .../entities/src/EntityScriptingInterface.h | 2 + libraries/entities/src/EntityTypes.cpp | 2 + libraries/entities/src/EntityTypes.h | 3 +- libraries/entities/src/PolyVoxEntityItem.cpp | 117 ++++ libraries/entities/src/PolyVoxEntityItem.h | 98 +++ libraries/gpu/src/gpu/Resource.h | 40 +- libraries/model/src/model/Geometry.cpp | 9 + libraries/model/src/model/Geometry.h | 1 + libraries/model/src/model/Skybox.cpp | 2 +- libraries/octree/src/OctreePacketData.cpp | 28 +- libraries/octree/src/OctreePacketData.h | 30 +- libraries/physics/src/EntityMotionState.cpp | 10 +- libraries/physics/src/EntityMotionState.h | 6 +- libraries/physics/src/ObjectMotionState.h | 6 +- .../src/DeferredLightingEffect.cpp | 8 +- libraries/shared/src/ShapeInfo.h | 3 +- 41 files changed, 1526 insertions(+), 444 deletions(-) rename cmake/externals/{tbb/OSXTBBInstallNameChange.cmake => OSXInstallNameChange.cmake} (53%) create mode 100644 cmake/externals/polyvox/CMakeLists.txt create mode 100644 cmake/modules/FindPolyVox.cmake create mode 100644 examples/voxels.js create mode 100644 libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp create mode 100644 libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h create mode 100644 libraries/entities/src/PolyVoxEntityItem.cpp create mode 100644 libraries/entities/src/PolyVoxEntityItem.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e57e33e3b2..1def1867f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,7 @@ option(GET_TBB "Get Threading Building Blocks library automatically as external option(GET_LIBOVR "Get LibOVR library automatically as external project" 1) option(USE_NSIGHT "Attempt to find the nSight libraries" 1) option(GET_VHACD "Get V-HACD library automatically as external project" 1) +option(GET_POLYVOX "Get polyvox library automatically as external project" 1) if (WIN32) option(GET_GLEW "Get GLEW library automatically as external project" 1) diff --git a/cmake/externals/tbb/OSXTBBInstallNameChange.cmake b/cmake/externals/OSXInstallNameChange.cmake similarity index 53% rename from cmake/externals/tbb/OSXTBBInstallNameChange.cmake rename to cmake/externals/OSXInstallNameChange.cmake index 0fa377959b..4922f2f8a5 100644 --- a/cmake/externals/tbb/OSXTBBInstallNameChange.cmake +++ b/cmake/externals/OSXInstallNameChange.cmake @@ -1,6 +1,6 @@ # -# OSXTBBInstallNameChange.cmake -# cmake/externals/tbb +# OSXInstallNameChange.cmake +# cmake/macros # # Copyright 2015 High Fidelity, Inc. # Created by Stephen Birarda on February 20, 2014 @@ -10,36 +10,33 @@ # # first find the so files in the source dir -set(_TBB_LIBRARY_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib) -file(GLOB_RECURSE _TBB_LIBRARIES "${_TBB_LIBRARY_DIR}/*.dylib") +message("INSTALL_NAME_LIBRARY_DIR ${INSTALL_NAME_LIBRARY_DIR}") -# raise an error if we found none -if (NOT _TBB_LIBRARIES) - message(FATAL_ERROR "Did not find any TBB libraries") +file(GLOB_RECURSE _LIBRARIES "${INSTALL_NAME_LIBRARY_DIR}/*.dylib") + +if (NOT _LIBRARIES) + message(FATAL_ERROR "OSXInstallNameChange -- no libraries found: ${INSTALL_NAME_LIBRARY_DIR}") endif () # find the install_name_tool command find_program(INSTALL_NAME_TOOL_COMMAND NAMES install_name_tool DOC "Path to the install_name_tool command") -# find the lipo command -find_program(LIPO_COMMAND NAMES lipo DOC "Path to the lipo command") - # enumerate the libraries -foreach(_TBB_LIBRARY ${_TBB_LIBRARIES}) - get_filename_component(_TBB_LIBRARY_FILENAME ${_TBB_LIBRARY} NAME) +foreach(_LIBRARY ${_LIBRARIES}) + get_filename_component(_LIBRARY_FILENAME ${_LIBRARY} NAME) - set(_INSTALL_NAME_ARGS ${INSTALL_NAME_TOOL_COMMAND} -id ${_TBB_LIBRARY} ${_TBB_LIBRARY_FILENAME}) + set(_INSTALL_NAME_ARGS ${INSTALL_NAME_TOOL_COMMAND} -id ${_LIBRARY} ${_LIBRARY_FILENAME}) message(STATUS "${INSTALL_NAME_COMMAND} ${_INSTALL_NAME_ARGS}") execute_process( COMMAND ${INSTALL_NAME_COMMAND} ${_INSTALL_NAME_ARGS} - WORKING_DIRECTORY ${_TBB_LIBRARY_DIR} + WORKING_DIRECTORY ${INSTALL_NAME_LIBRARY_DIR} ERROR_VARIABLE _INSTALL_NAME_ERROR ) if (_INSTALL_NAME_ERROR) - message(FATAL_ERROR "There was an error changing install name for ${_TBB_LIBRARY_FILENAME} - ${_INSTALL_NAME_ERROR}") + message(FATAL_ERROR "There was an error changing install name for ${_LIBRARY_FILENAME} - ${_INSTALL_NAME_ERROR}") endif () endforeach() diff --git a/cmake/externals/polyvox/CMakeLists.txt b/cmake/externals/polyvox/CMakeLists.txt new file mode 100644 index 0000000000..76302f9b4a --- /dev/null +++ b/cmake/externals/polyvox/CMakeLists.txt @@ -0,0 +1,57 @@ +set(EXTERNAL_NAME polyvox) + +include(ExternalProject) +ExternalProject_Add( + ${EXTERNAL_NAME} + URL http://hifi-public.s3.amazonaws.com/dependencies/polyvox-master-2015-5-27.zip + URL_MD5 e3dd09a24df4db29ba370e3bea753388 + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH= + BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build + LOG_DOWNLOAD 1 + LOG_CONFIGURE 1 + LOG_BUILD 1 +) + + +ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR) + +if (APPLE) + set(INSTALL_NAME_LIBRARY_DIR ${INSTALL_DIR}/lib) + message(STATUS "in polyvox INSTALL_NAME_LIBRARY_DIR ${INSTALL_NAME_LIBRARY_DIR}") + ExternalProject_Add_Step( + ${EXTERNAL_NAME} + change-install-name + COMMENT "Calling install_name_tool on libraries to fix install name for dylib linking" + COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${INSTALL_NAME_LIBRARY_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake + DEPENDEES install + WORKING_DIRECTORY + LOG 1 + ) +endif () + + +string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER) + +if (WIN32) + set(${EXTERNAL_NAME_UPPER}_CORE_INCLUDE_DIRS ${INSTALL_DIR}/PolyVoxCore/include CACHE FILEPATH + "Path to polyvox core include directory") + set(${EXTERNAL_NAME_UPPER}_UTIL_INCLUDE_DIRS ${INSTALL_DIR}/PolyVoxUtil/include CACHE FILEPATH + "Path to polyvox util include directory") +else () + set(${EXTERNAL_NAME_UPPER}_CORE_INCLUDE_DIRS ${INSTALL_DIR}/include/PolyVoxCore CACHE FILEPATH + "Path to polyvox core include directory") + set(${EXTERNAL_NAME_UPPER}_UTIL_INCLUDE_DIRS ${INSTALL_DIR}/include/PolyVoxUtil CACHE FILEPATH + "Path to polyvox util include directory") +endif () + + +if (WIN32) + set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY ${INSTALL_DIR}/PolyVoxCore/lib/PolyVoxCore.lib CACHE FILEPATH "polyvox core library") +# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/PolyVoxUtil/lib/PolyVoxUtil.lib CACHE FILEPATH "polyvox util library") +elseif (APPLE) +set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxCore.dylib CACHE FILEPATH "polyvox core library") +# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxUtil.dylib CACHE FILEPATH "polyvox util library") +else () + set(${EXTERNAL_NAME_UPPER}_CORE_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxCore.so CACHE FILEPATH "polyvox core library") +# set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY ${INSTALL_DIR}/lib/libPolyVoxUtil.so CACHE FILEPATH "polyvox util library") +endif () diff --git a/cmake/externals/tbb/CMakeLists.txt b/cmake/externals/tbb/CMakeLists.txt index b9b2b65010..8f327bd69f 100644 --- a/cmake/externals/tbb/CMakeLists.txt +++ b/cmake/externals/tbb/CMakeLists.txt @@ -66,7 +66,7 @@ if (APPLE) ${EXTERNAL_NAME} change-install-name COMMENT "Calling install_name_tool on TBB libraries to fix install name for dylib linking" - COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_SOURCE_DIR}/OSXTBBInstallNameChange.cmake + COMMAND ${CMAKE_COMMAND} -DINSTALL_NAME_LIBRARY_DIR=${_TBB_LIB_DIR} -P ${EXTERNAL_PROJECT_DIR}/OSXInstallNameChange.cmake DEPENDEES install WORKING_DIRECTORY LOG 1 @@ -115,4 +115,4 @@ endif () if (DEFINED ${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE) set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE "List of tbb include directories") -endif () \ No newline at end of file +endif () diff --git a/cmake/modules/FindPolyVox.cmake b/cmake/modules/FindPolyVox.cmake new file mode 100644 index 0000000000..60a639e87c --- /dev/null +++ b/cmake/modules/FindPolyVox.cmake @@ -0,0 +1,54 @@ +# +# FindPolyvox.cmake +# +# Try to find the libpolyvox resampling library +# +# You can provide a LIBPOLYVOX_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# POLYVOX_FOUND - system found libpolyvox +# POLYVOX_INCLUDE_DIRS - the libpolyvox include directory +# POLYVOX_LIBRARIES - link to this to use libpolyvox +# +# Created on 1/22/2015 by Stephen Birarda +# Copyright 2015 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("${MACRO_DIR}/HifiLibrarySearchHints.cmake") +hifi_library_search_hints("polyvox") + +find_path(POLYVOX_CORE_INCLUDE_DIRS PolyVoxCore/SimpleVolume.h PATH_SUFFIXES include include/PolyVoxCore HINTS ${POLYVOX_SEARCH_DIRS}) +# find_path(POLYVOX_UTIL_INCLUDE_DIRS PolyVoxUtil/Serialization.h PATH_SUFFIXES include include/PolyVoxUtil HINTS ${POLYVOX_SEARCH_DIRS}) + +find_library(POLYVOX_CORE_LIBRARY NAMES PolyVoxCore PATH_SUFFIXES lib HINTS ${POLYVOX_SEARCH_DIRS}) +# find_library(POLYVOX_UTIL_LIBRARY NAMES PolyVoxUtil PATH_SUFFIXES lib HINTS ${POLYVOX_SEARCH_DIRS}) + + +# if (WIN32) +# find_path(POLYVOX_DLL_PATH polyvox.dll PATH_SUFFIXES bin HINTS ${POLYVOX_SEARCH_DIRS}) +# endif() + +# set(POLYVOX_REQUIREMENTS POLYVOX_CORE_INCLUDE_DIRS POLYVOX_UTIL_INCLUDE_DIRS POLYVOX_CORE_LIBRARY POLYVOX_UTIL_LIBRARY) +set(POLYVOX_REQUIREMENTS POLYVOX_CORE_INCLUDE_DIRS POLYVOX_CORE_LIBRARY) +# if (WIN32) +# list(APPEND POLYVOX_REQUIREMENTS POLYVOX_DLL_PATH) +# endif () + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Polyvox DEFAULT_MSG ${POLYVOX_REQUIREMENTS}) + +# if (WIN32) +# add_paths_to_fixup_libs(${POLYVOX_DLL_PATH}) +# endif () + +# set(POLYVOX_INCLUDE_DIRS ${POLYVOX_CORE_INCLUDE_DIRS} ${POLYVOX_UTIL_INCLUDE_DIRS}) +# set(POLYVOX_LIBRARIES ${POLYVOX_CORE_LIBRARY} ${POLYVOX_UTIL_LIBRARY}) + +set(POLYVOX_INCLUDE_DIRS ${POLYVOX_CORE_INCLUDE_DIRS}) +set(POLYVOX_LIBRARIES ${POLYVOX_CORE_LIBRARY}) + +mark_as_advanced(POLYVOX_INCLUDE_DIRS POLYVOX_LIBRARIES POLYVOX_SEARCH_DIRS) diff --git a/examples/blockWorld.js b/examples/blockWorld.js index e69de29bb2..53809612ed 100644 --- a/examples/blockWorld.js +++ b/examples/blockWorld.js @@ -0,0 +1,201 @@ +// blockWorld.js +// examples +// +// Created by Eric Levin on May 26, 2015 +// Copyright 2015 High Fidelity, Inc. +// +// Creates a floor of tiles and then drops planky blocks at random points above the tile floor +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var TILE_SIZE = 7 +var GENERATE_INTERVAL = 50; +var NUM_ROWS = 10; +var angVelRange = 4; + +var floorTiles = []; +var blocks = []; +var blockSpawner; + +HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; + + +var floorPos = Vec3.sum(MyAvatar.position, { + x: 0, + y: -2, + z: 0 +}); +var x = floorPos.x; + +var currentRowIndex = 0; +var currentColumnIndex = 0; + +var DROP_HEIGHT = floorPos.y + 5; +var BLOCK_GRAVITY = { + x: 0, + y: -9, + z: 0 +}; +var BLOCK_SIZE = { + x: 0.2, + y: 0.1, + z: 0.8 +}; + +var bounds = { + xMin: floorPos.x, + xMax: floorPos.x + (TILE_SIZE * NUM_ROWS) - TILE_SIZE, + zMin: floorPos.z, + zMax: floorPos.z + (TILE_SIZE * NUM_ROWS) - TILE_SIZE +}; + +var screenSize = Controller.getViewportDimensions(); + +var BUTTON_SIZE = 32; +var PADDING = 3; + +var offButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE * 2 + PADDING, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/close.png", + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1 +}); + +var deleteButton = Overlays.addOverlay("image", { + x: screenSize.x / 2 - BUTTON_SIZE, + y: screenSize.y - (BUTTON_SIZE + PADDING), + width: BUTTON_SIZE, + height: BUTTON_SIZE, + imageURL: HIFI_PUBLIC_BUCKET + "images/delete.png", + color: { + red: 255, + green: 255, + blue: 255 + }, + alpha: 1 +}); + + +function generateFloor() { + for (var z = floorPos.z; currentColumnIndex < NUM_ROWS; z += TILE_SIZE, currentColumnIndex++) { + floorTiles.push(Entities.addEntity({ + type: 'Box', + position: { + x: x, + y: floorPos.y, + z: z + }, + dimensions: { + x: TILE_SIZE, + y: 2, + z: TILE_SIZE + }, + color: { + red: randFloat(70, 120), + green: randFloat(70, 71), + blue: randFloat(70, 80) + }, + // collisionsWillMove: true + })); + } + + currentRowIndex++; + if (currentRowIndex < NUM_ROWS) { + currentColumnIndex = 0; + x += TILE_SIZE; + Script.setTimeout(generateFloor, GENERATE_INTERVAL); + } else { + //Once we're done generating floor, drop planky blocks at random points on floor + blockSpawner = Script.setInterval(function() { + dropBlock(); + }, GENERATE_INTERVAL) + } +} + +function dropBlock() { + var dropPos = floorPos; + dropPos.y = DROP_HEIGHT; + dropPos.x = randFloat(bounds.xMin, bounds.xMax); + dropPos.z = randFloat(bounds.zMin, bounds.zMax); + blocks.push(Entities.addEntity({ + type: "Model", + modelURL: 'http://s3.amazonaws.com/hifi-public/marketplace/hificontent/Games/blocks/block.fbx', + shapeType: 'box', + position: dropPos, + dimensions: BLOCK_SIZE, + collisionsWillMove: true, + gravity: { + x: 0, + y: -9, + z: 0 + }, + velocity: { + x: 0, + y: .1, + z: 0 + }, + angularVelocity: { + x: randFloat(-angVelRange, angVelRange), + y: randFloat(-angVelRange, angVelRange), + z: randFloat(-angVelRange, angVelRange), + } + })); +} + +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({ + x: event.x, + y: event.y + }); + if (clickedOverlay == offButton) { + Script.clearInterval(blockSpawner); + } + if(clickedOverlay == deleteButton){ + destroyStuff(); + } +} + +generateFloor(); + +function cleanup() { + // for (var i = 0; i < floorTiles.length; i++) { + // Entities.deleteEntity(floorTiles[i]); + // } + // for (var i = 0; i < blocks.length; i++) { + // Entities.deleteEntity(blocks[i]); + // } + Overlays.deleteOverlay(offButton); + Overlays.deleteOverlay(deleteButton) + Script.clearInterval(blockSpawner); +} + +function destroyStuff() { + for (var i = 0; i < floorTiles.length; i++) { + Entities.deleteEntity(floorTiles[i]); + } + for (var i = 0; i < blocks.length; i++) { + Entities.deleteEntity(blocks[i]); + } + Script.clearInterval(blockSpawner); + +} + +function randFloat(low, high) { + return Math.floor(low + Math.random() * (high - low)); +} + +function map(value, min1, max1, min2, max2) { + return min2 + (max2 - min2) * ((value - min1) / (max1 - min1)); +} + +Script.scriptEnding.connect(cleanup); +Controller.mousePressEvent.connect(mousePressEvent); \ No newline at end of file diff --git a/examples/pointer.js b/examples/pointer.js index eebe4ec5be..32698209a4 100644 --- a/examples/pointer.js +++ b/examples/pointer.js @@ -67,20 +67,6 @@ var pointerButton = Overlays.addOverlay("image", { var center = Vec3.sum(MyAvatar.position, Vec3.multiply(2.0, Quat.getFront(Camera.getOrientation()))); center.y += 0.5; -var whiteBoard = Entities.addEntity({ - type: "Box", - position: center, - dimensions: { - x: 1, - y: 1, - z: .001 - }, - color: { - red: 255, - green: 255, - blue: 255 - } -}); function calculateNearLinePosition(targetPosition) { var handPosition = MyAvatar.getRightPalmPosition(); @@ -243,7 +229,6 @@ function keyReleaseEvent(event) { } function cleanup() { - Entities.deleteEntity(whiteBoard); for (var i = 0; i < strokes.length; i++) { Entities.deleteEntity(strokes[i]); } diff --git a/examples/voxels.js b/examples/voxels.js new file mode 100644 index 0000000000..e274f1c4cc --- /dev/null +++ b/examples/voxels.js @@ -0,0 +1,43 @@ + +var controlHeld = false; + + +function mousePressEvent(event) { + if (!event.isLeftButton) { + return; + } + + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Entities.findRayIntersection(pickRay, true); // accurate picking + // var props = Entities.getEntityProperties(intersection.entityID); + if (intersection.intersects) { + var ids = Entities.findEntities(intersection.intersection, 10); + for (var i = 0; i < ids.length; i++) { + var id = ids[i]; + if (controlHeld) { + Entities.setVoxelSphere(id, intersection.intersection, 1.2, 0); + } else { + Entities.setVoxelSphere(id, intersection.intersection, 1.2, 255); + } + } + } +} + + +function keyPressEvent(event) { + if (event.text == "CONTROL") { + controlHeld = true; + } +} + + +function keyReleaseEvent(event) { + if (event.text == "CONTROL") { + controlHeld = false; + } +} + + +Controller.mousePressEvent.connect(mousePressEvent); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.keyReleaseEvent.connect(keyReleaseEvent); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3f05b0e4d1..385172ee2c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1948,7 +1948,9 @@ FaceTracker* Application::getSelectedFaceTracker() { } void Application::setActiveFaceTracker() { +#if defined(HAVE_FACESHIFT) || defined(HAVE_DDE) bool isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking); +#endif #ifdef HAVE_FACESHIFT auto faceshiftTracker = DependencyManager::get(); faceshiftTracker->setIsMuted(isMuted); diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 0cdaf36099..3c784fb925 100644 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -147,9 +147,9 @@ public: Q_INVOKABLE glm::vec3 getNeckPosition() const; - Q_INVOKABLE const glm::vec3& getAcceleration() const { return _acceleration; } - Q_INVOKABLE const glm::vec3& getAngularVelocity() const { return _angularVelocity; } - Q_INVOKABLE const glm::vec3& getAngularAcceleration() const { return _angularAcceleration; } + Q_INVOKABLE glm::vec3 getAcceleration() const { return _acceleration; } + Q_INVOKABLE glm::vec3 getAngularVelocity() const { return _angularVelocity; } + Q_INVOKABLE glm::vec3 getAngularAcceleration() const { return _angularAcceleration; } /// Scales a world space position vector relative to the avatar position and scale diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 03a49c069e..530225b319 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -123,17 +123,17 @@ glm::quat AvatarMotionState::getObjectRotation() const { } // virtual -const glm::vec3& AvatarMotionState::getObjectLinearVelocity() const { +glm::vec3 AvatarMotionState::getObjectLinearVelocity() const { return _avatar->getVelocity(); } // virtual -const glm::vec3& AvatarMotionState::getObjectAngularVelocity() const { +glm::vec3 AvatarMotionState::getObjectAngularVelocity() const { return _avatar->getAngularVelocity(); } // virtual -const glm::vec3& AvatarMotionState::getObjectGravity() const { +glm::vec3 AvatarMotionState::getObjectGravity() const { return _avatar->getAcceleration(); } diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index a4fc9db20a..68c687d65b 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -48,9 +48,9 @@ public: virtual glm::vec3 getObjectPosition() const; virtual glm::quat getObjectRotation() const; - virtual const glm::vec3& getObjectLinearVelocity() const; - virtual const glm::vec3& getObjectAngularVelocity() const; - virtual const glm::vec3& getObjectGravity() const; + virtual glm::vec3 getObjectLinearVelocity() const; + virtual glm::vec3 getObjectAngularVelocity() const; + virtual glm::vec3 getObjectGravity() const; virtual const QUuid& getObjectID() const; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index c65de2afb0..8a64630266 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -356,7 +356,7 @@ void ApplicationOverlay::displayOverlayTextureHmd(Camera& whichCamera) { }); if (!Application::getInstance()->isMouseHidden()) { - renderPointersOculus(myAvatar->getDefaultEyePosition()); + renderPointersOculus(); } glDepthMask(GL_TRUE); glDisable(GL_TEXTURE_2D); @@ -486,49 +486,40 @@ void ApplicationOverlay::computeHmdPickRay(glm::vec2 cursorPos, glm::vec3& origi direction = glm::normalize(intersectionWithUi - origin); } +glm::vec2 getPolarCoordinates(const PalmData& palm) { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); + auto avatarOrientation = myAvatar->getOrientation(); + auto eyePos = myAvatar->getDefaultEyePosition(); + glm::vec3 tip = myAvatar->getLaserPointerTipPosition(&palm); + // Direction of the tip relative to the eye + glm::vec3 tipDirection = tip - eyePos; + // orient into avatar space + tipDirection = glm::inverse(avatarOrientation) * tipDirection; + // Normalize for trig functions + tipDirection = glm::normalize(tipDirection); + // Convert to polar coordinates + glm::vec2 polar(glm::atan(tipDirection.x, -tipDirection.z), glm::asin(tipDirection.y)); + return polar; +} + //Caculate the click location using one of the sixense controllers. Scale is not applied QPoint ApplicationOverlay::getPalmClickLocation(const PalmData *palm) const { - MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); - - glm::vec3 tip = myAvatar->getLaserPointerTipPosition(palm); - glm::vec3 eyePos = myAvatar->getHead()->getEyePosition(); - glm::quat invOrientation = glm::inverse(myAvatar->getOrientation()); - //direction of ray goes towards camera - glm::vec3 dir = invOrientation * glm::normalize(qApp->getCamera()->getPosition() - tip); - glm::vec3 tipPos = invOrientation * (tip - eyePos); - QPoint rv; auto canvasSize = qApp->getCanvasSize(); if (qApp->isHMDMode()) { float t; - - //We back the ray up by dir to ensure that it will not start inside the UI. - glm::vec3 adjustedPos = tipPos - dir; - //Find intersection of crosshair ray. - if (raySphereIntersect(dir, adjustedPos, _oculusUIRadius * myAvatar->getScale(), &t)){ - glm::vec3 collisionPos = adjustedPos + dir * t; - //Normalize it in case its not a radius of 1 - collisionPos = glm::normalize(collisionPos); - //If we hit the back hemisphere, mark it as not a collision - if (collisionPos.z > 0) { - rv.setX(INT_MAX); - rv.setY(INT_MAX); - } else { - - float u = asin(collisionPos.x) / (_textureFov)+0.5f; - float v = 1.0 - (asin(collisionPos.y) / (_textureFov)+0.5f); - - rv.setX(u * canvasSize.x); - rv.setY(v * canvasSize.y); - } - } else { - //if they did not click on the overlay, just set the coords to INT_MAX - rv.setX(INT_MAX); - rv.setY(INT_MAX); - } + glm::vec2 polar = getPolarCoordinates(*palm); + glm::vec2 point = sphericalToScreen(-polar); + rv.rx() = point.x; + rv.ry() = point.y; } else { + MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); glm::dmat4 projection; qApp->getProjectionMatrix(&projection); + glm::quat invOrientation = glm::inverse(myAvatar->getOrientation()); + glm::vec3 eyePos = myAvatar->getDefaultEyePosition(); + glm::vec3 tip = myAvatar->getLaserPointerTipPosition(palm); + glm::vec3 tipPos = invOrientation * (tip - eyePos); glm::vec4 clipSpacePos = glm::vec4(projection * glm::dvec4(tipPos, 1.0)); glm::vec3 ndcSpacePos; @@ -729,7 +720,7 @@ void ApplicationOverlay::renderControllerPointers() { } } -void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) { +void ApplicationOverlay::renderPointersOculus() { glBindTexture(GL_TEXTURE_2D, gpu::GLBackend::getTextureID(_crosshairTexture)); glDisable(GL_DEPTH_TEST); glMatrixMode(GL_MODELVIEW); @@ -737,16 +728,12 @@ void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) { //Controller Pointers MyAvatar* myAvatar = DependencyManager::get()->getMyAvatar(); for (int i = 0; i < (int)myAvatar->getHand()->getNumPalms(); i++) { - PalmData& palm = myAvatar->getHand()->getPalms()[i]; if (palm.isActive()) { - glm::vec3 tip = myAvatar->getLaserPointerTipPosition(&palm); - glm::vec3 tipDirection = glm::normalize(glm::inverse(myAvatar->getOrientation()) * (tip - eyePos)); - float pitch = -glm::asin(tipDirection.y); - float yawSign = glm::sign(-tipDirection.x); - float yaw = glm::acos(-tipDirection.z) * - ((yawSign == 0.0f) ? 1.0f : yawSign); - glm::quat orientation = glm::quat(glm::vec3(pitch, yaw, 0.0f)); + glm::vec2 polar = getPolarCoordinates(palm); + // Convert to quaternion + glm::quat orientation = glm::quat(glm::vec3(polar.y, -polar.x, 0.0f)); + // Render reticle at location renderReticle(orientation, _alpha); } } diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index 34beb98682..864bd95b4e 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -102,7 +102,7 @@ private: void renderMagnifier(glm::vec2 magPos, float sizeMult, bool showBorder); void renderControllerPointers(); - void renderPointersOculus(const glm::vec3& eyePos); + void renderPointersOculus(); void renderAudioMeter(); void renderCameraToggle(); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 603b5d76ea..a27e4256ef 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -301,7 +301,7 @@ public: int getReceiveRate() const; void setVelocity(const glm::vec3 velocity) { _velocity = velocity; } - Q_INVOKABLE const glm::vec3& getVelocity() const { return _velocity; } + Q_INVOKABLE glm::vec3 getVelocity() const { return _velocity; } const glm::vec3& getTargetVelocity() const { return _targetVelocity; } bool shouldDie() const { return _owningAvatarMixer.isNull() || getUsecsSinceLastUpdate() > AVATAR_SILENCE_THRESHOLD_USECS; } diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 4a4fd4a0d7..dea184087b 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -39,6 +39,7 @@ #include "RenderableWebEntityItem.h" #include "RenderableZoneEntityItem.h" #include "RenderableLineEntityItem.h" +#include "RenderablePolyVoxEntityItem.h" #include "EntitiesRendererLogging.h" EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterface* viewState, @@ -65,6 +66,7 @@ EntityTreeRenderer::EntityTreeRenderer(bool wantScripts, AbstractViewStateInterf REGISTER_ENTITY_TYPE_WITH_FACTORY(ParticleEffect, RenderableParticleEffectEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Zone, RenderableZoneEntityItem::factory) REGISTER_ENTITY_TYPE_WITH_FACTORY(Line, RenderableLineEntityItem::factory) + REGISTER_ENTITY_TYPE_WITH_FACTORY(PolyVox, RenderablePolyVoxEntityItem::factory) _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; _currentClickingOnEntityID = UNKNOWN_ENTITY_ID; diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp new file mode 100644 index 0000000000..977a6511c8 --- /dev/null +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp @@ -0,0 +1,336 @@ +// +// RenderablePolyVoxEntityItem.cpp +// libraries/entities-renderer/src/ +// +// Created by Seth Alves on 5/19/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "model/Geometry.h" +#include "gpu/GLBackend.h" +#include "EntityTreeRenderer.h" +#include "RenderablePolyVoxEntityItem.h" + + +EntityItemPointer RenderablePolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return EntityItemPointer(new RenderablePolyVoxEntityItem(entityID, properties)); +} + +RenderablePolyVoxEntityItem::~RenderablePolyVoxEntityItem() { + delete _volData; +} + +void RenderablePolyVoxEntityItem::setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { + + if (_volData && voxelVolumeSize == _voxelVolumeSize) { + return; + } + + qDebug() << "resetting voxel-space size"; + + PolyVoxEntityItem::setVoxelVolumeSize(voxelVolumeSize); + + if (_volData) { + delete _volData; + } + + PolyVox::Vector3DInt32 lowCorner(0, 0, 0); + PolyVox::Vector3DInt32 highCorner(_voxelVolumeSize[0] - 1, // -1 because these corners are inclusive + _voxelVolumeSize[1] - 1, + _voxelVolumeSize[2] - 1); + + _volData = new PolyVox::SimpleVolume(PolyVox::Region(lowCorner, highCorner)); +} + + +void RenderablePolyVoxEntityItem::setVoxelData(QByteArray voxelData) { + if (voxelData == _voxelData) { + return; + } + PolyVoxEntityItem::setVoxelData(voxelData); + decompressVolumeData(); +} + + +glm::mat4 RenderablePolyVoxEntityItem::voxelToWorldMatrix() const { + glm::vec3 scale = _dimensions / _voxelVolumeSize; // meters / voxel-units + glm::mat4 scaled = glm::scale(glm::mat4(), scale); + glm::mat4 centerToCorner = glm::translate(scaled, _voxelVolumeSize / -2.0f); + glm::mat4 rotation = glm::mat4_cast(_rotation); + glm::mat4 translation = glm::translate(getCenter()); + return translation * rotation * centerToCorner; +} + +glm::mat4 RenderablePolyVoxEntityItem::worldToVoxelMatrix() const { + glm::mat4 worldToModelMatrix = glm::inverse(voxelToWorldMatrix()); + return worldToModelMatrix; + +} + +void RenderablePolyVoxEntityItem::setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) { + // This three-level for loop iterates over every voxel in the volume + for (int z = 0; z < _volData->getDepth(); z++) { + for (int y = 0; y < _volData->getHeight(); y++) { + for (int x = 0; x < _volData->getWidth(); x++) { + // Store our current position as a vector... + glm::vec3 pos(x, y, z); + // And compute how far the current position is from the center of the volume + float fDistToCenter = glm::distance(pos, center); + // If the current voxel is less than 'radius' units from the center then we make it solid. + if (fDistToCenter <= radius) { + _volData->setVoxelAt(x, y, z, toValue); + } + } + } + } + compressVolumeData(); + _needsModelReload = true; +} + +void RenderablePolyVoxEntityItem::setSphere(glm::vec3 centerWorldCoords, float radiusWorldCoords, uint8_t toValue) { + // glm::vec3 centerVoxelCoords = worldToVoxelCoordinates(centerWorldCoords); + glm::vec4 centerVoxelCoords = worldToVoxelMatrix() * glm::vec4(centerWorldCoords, 1.0f); + glm::vec3 scale = _dimensions / _voxelVolumeSize; // meters / voxel-units + float scaleY = scale[0]; + float radiusVoxelCoords = radiusWorldCoords / scaleY; + setSphereInVolume(glm::vec3(centerVoxelCoords), radiusVoxelCoords, toValue); +} + +void RenderablePolyVoxEntityItem::getModel() { + if (!_volData) { + // this will cause the allocation of _volData + setVoxelVolumeSize(_voxelVolumeSize); + } + + // A mesh object to hold the result of surface extraction + PolyVox::SurfaceMesh polyVoxMesh; + + switch (_voxelSurfaceStyle) { + case PolyVoxEntityItem::SURFACE_MARCHING_CUBES: { + PolyVox::MarchingCubesSurfaceExtractor> surfaceExtractor + (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + case PolyVoxEntityItem::SURFACE_CUBIC: { + PolyVox::CubicSurfaceExtractorWithNormals> surfaceExtractor + (_volData, _volData->getEnclosingRegion(), &polyVoxMesh); + surfaceExtractor.execute(); + break; + } + } + + // convert PolyVox mesh to a Sam mesh + model::Mesh* mesh = new model::Mesh(); + model::MeshPointer meshPtr(mesh); + + const std::vector& vecIndices = polyVoxMesh.getIndices(); + auto indexBuffer = new gpu::Buffer(vecIndices.size() * sizeof(uint32_t), (gpu::Byte*)vecIndices.data()); + auto indexBufferPtr = gpu::BufferPointer(indexBuffer); + mesh->setIndexBuffer(gpu::BufferView(indexBufferPtr, gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::RAW))); + + + const std::vector& vecVertices = polyVoxMesh.getVertices(); + auto vertexBuffer = new gpu::Buffer(vecVertices.size() * sizeof(PolyVox::PositionMaterialNormal), + (gpu::Byte*)vecVertices.data()); + auto vertexBufferPtr = gpu::BufferPointer(vertexBuffer); + mesh->setVertexBuffer(gpu::BufferView(vertexBufferPtr, + 0, + vertexBufferPtr->getSize() - sizeof(float) * 3, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); + mesh->addAttribute(gpu::Stream::NORMAL, + gpu::BufferView(vertexBufferPtr, + sizeof(float) * 3, + vertexBufferPtr->getSize() - sizeof(float) * 3, + sizeof(PolyVox::PositionMaterialNormal), + gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::RAW))); + + // auto normalAttrib = mesh->getAttributeBuffer(gpu::Stream::NORMAL); + // for (auto normal = normalAttrib.begin(); normal != normalAttrib.end(); normal++) { + // (*normal) = -(*normal); + // } + + qDebug() << "---- vecIndices.size() =" << vecIndices.size(); + qDebug() << "---- vecVertices.size() =" << vecVertices.size(); + + _modelGeometry.setMesh(meshPtr); + _needsModelReload = false; +} + +void RenderablePolyVoxEntityItem::render(RenderArgs* args) { + PerformanceTimer perfTimer("RenderablePolyVoxEntityItem::render"); + assert(getType() == EntityTypes::PolyVox); + + if (_needsModelReload) { + getModel(); + } + + glm::vec3 position = getPosition(); + glm::vec3 dimensions = getDimensions(); + glm::vec3 scale = dimensions / _voxelVolumeSize; + glm::vec3 center = getCenter(); + glm::quat rotation = getRotation(); + glPushMatrix(); + glTranslatef(position.x, position.y, position.z); + glm::vec3 axis = glm::axis(rotation); + glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); + glm::vec3 positionToCenter = center - position; + // make the rendered voxel volume be centered on the entity's position + positionToCenter -= _dimensions * glm::vec3(0.5f,0.5f,0.5f); + glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z); + glScalef(scale.x, scale.y, scale.z); + + auto mesh = _modelGeometry.getMesh(); + gpu::Batch batch; + batch.setInputFormat(mesh->getVertexFormat()); + batch.setInputBuffer(gpu::Stream::POSITION, mesh->getVertexBuffer()); + batch.setInputBuffer(gpu::Stream::NORMAL, + mesh->getVertexBuffer()._buffer, + sizeof(float) * 3, + mesh->getVertexBuffer()._stride); + batch.setIndexBuffer(gpu::UINT32, mesh->getIndexBuffer()._buffer, 0); + batch.drawIndexed(gpu::TRIANGLES, mesh->getNumIndices(), 0); + gpu::GLBackend::renderBatch(batch); + glPopMatrix(); + RenderableDebugableEntityItem::render(this, args); +} + +class RaycastFunctor +{ +public: + RaycastFunctor() : _result(glm::vec4(0.0f, 0.0f, 0.0f, 1.0f)) { } + bool operator()(PolyVox::SimpleVolume::Sampler& sampler) + { + if (sampler.getVoxel() == 0) { + return true; // keep raycasting + } + PolyVox::Vector3DInt32 positionIndex = sampler.getPosition(); + _result = glm::vec4(positionIndex.getX(), positionIndex.getY(), positionIndex.getZ(), 1.0f); + return false; + } + glm::vec4 _result; +}; + +bool RenderablePolyVoxEntityItem::findDetailedRayIntersection(const glm::vec3& origin, + const glm::vec3& direction, + bool& keepSearching, + OctreeElement*& element, + float& distance, BoxFace& face, + void** intersectedObject, + bool precisionPicking) const +{ + if (_needsModelReload || !precisionPicking) { + // just intersect with bounding box + return true; + } + + glm::mat4 wtvMatrix = worldToVoxelMatrix(); + glm::vec3 farPoint = origin + direction; + glm::vec4 originInVoxel = wtvMatrix * glm::vec4(origin, 1.0f); + glm::vec4 farInVoxel = wtvMatrix * glm::vec4(farPoint, 1.0f); + glm::vec4 directionInVoxel = farInVoxel - originInVoxel; + + PolyVox::Vector3DFloat start(originInVoxel[0], originInVoxel[1], originInVoxel[2]); + PolyVox::Vector3DFloat pvDirection(directionInVoxel[0], directionInVoxel[1], directionInVoxel[2]); + pvDirection.normalise(); + + // the PolyVox ray intersection code requires a near and far point. + glm::vec3 scale = _dimensions / _voxelVolumeSize; // meters / voxel-units + float distanceToEntity = glm::distance(origin, _position); + float largestDimension = glm::max(_dimensions[0], _dimensions[1], _dimensions[2]); + // set ray cast length to long enough to cover all of the voxel space + pvDirection *= (distanceToEntity + largestDimension) / glm::min(scale[0], scale[1], scale[2]); + + PolyVox::RaycastResult raycastResult; + RaycastFunctor callback; + raycastResult = PolyVox::raycastWithDirection(_volData, start, pvDirection, callback); + + if (raycastResult == PolyVox::RaycastResults::Completed) { + // the ray completed its path -- nothing was hit. + return false; + } + + glm::vec4 intersectedWorldPosition = voxelToWorldMatrix() * callback._result; + + distance = glm::distance(glm::vec3(intersectedWorldPosition), origin); + + face = BoxFace::MIN_X_FACE; // XXX + + return true; +} + + +// compress the data in _volData and save the results. The compressed form is used during +// saves to disk and for transmission over the wire +void RenderablePolyVoxEntityItem::compressVolumeData() { + int rawSize = _volData->getDepth() * _volData->getHeight() * _volData->getWidth(); + QByteArray uncompressedData = QByteArray(rawSize, '\0'); + + for (int z = 0; z < _volData->getDepth(); z++) { + for (int y = 0; y < _volData->getHeight(); y++) { + for (int x = 0; x < _volData->getWidth(); x++) { + uint8_t uVoxelValue = _volData->getVoxelAt(x, y, z); + int uncompressedIndex = z * _volData->getHeight() * _volData->getWidth() + y * _volData->getWidth() + x; + uncompressedData[uncompressedIndex] = uVoxelValue; + } + } + } + + QByteArray newVoxelData = qCompress(uncompressedData, 9); + // HACK -- until we have a way to allow for properties larger than MTU, don't update. + if (newVoxelData.length() < 1200) { + _voxelData = newVoxelData; + qDebug() << "-------------- voxel compresss --------------"; + qDebug() << "raw-size =" << rawSize << " compressed-size =" << newVoxelData.size(); + } else { + qDebug() << "voxel data too large, reverting change."; + // revert + decompressVolumeData(); + } +} + + +// take compressed data and decompreess it into _volData. +void RenderablePolyVoxEntityItem::decompressVolumeData() { + int rawSize = _volData->getDepth() * _volData->getHeight() * _volData->getWidth(); + QByteArray uncompressedData = QByteArray(rawSize, '\0'); + + uncompressedData = qUncompress(_voxelData); + + for (int z = 0; z < _volData->getDepth(); z++) { + for (int y = 0; y < _volData->getHeight(); y++) { + for (int x = 0; x < _volData->getWidth(); x++) { + int uncompressedIndex = z * _volData->getHeight() * _volData->getWidth() + y * _volData->getWidth() + x; + _volData->setVoxelAt(x, y, z, uncompressedData[uncompressedIndex]); + } + } + } + + _needsModelReload = true; + + qDebug() << "--------------- voxel decompress ---------------"; + qDebug() << "raw-size =" << rawSize << " compressed-size =" << _voxelData.size(); +} diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h new file mode 100644 index 0000000000..b04b32996b --- /dev/null +++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.h @@ -0,0 +1,68 @@ +// +// RenderablePolyVoxEntityItem.h +// libraries/entities-renderer/src/ +// +// Created by Seth Alves on 5/19/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_RenderablePolyVoxEntityItem_h +#define hifi_RenderablePolyVoxEntityItem_h + +#include + +#include "PolyVoxEntityItem.h" +#include "RenderableDebugableEntityItem.h" + +class RenderablePolyVoxEntityItem : public PolyVoxEntityItem { +public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + RenderablePolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + PolyVoxEntityItem(entityItemID, properties) { } + + virtual ~RenderablePolyVoxEntityItem(); + + virtual void somethingChangedNotification() { + // This gets called from EnityItem::readEntityDataFromBuffer every time a packet describing + // this entity comes from the entity-server. It gets called even if nothing has actually changed + // (see the comment in EntityItem.cpp). If that gets fixed, this could be used to know if we + // need to redo the voxel data. + // _needsModelReload = true; + } + + + void render(RenderArgs* args); + virtual bool supportsDetailedRayIntersection() const { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const; + + void getModel(); + + virtual void setVoxelData(QByteArray voxelData); + + virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize); + glm::mat4 voxelToWorldMatrix() const; + glm::mat4 worldToVoxelMatrix() const; + + // coords are in voxel-volume space + virtual void setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue); + + // coords are in world-space + virtual void setSphere(glm::vec3 center, float radius, uint8_t toValue); + +private: + void compressVolumeData(); + void decompressVolumeData(); + + PolyVox::SimpleVolume* _volData = nullptr; + model::Geometry _modelGeometry; + bool _needsModelReload = true; +}; + + +#endif // hifi_RenderablePolyVoxEntityItem_h diff --git a/libraries/entities/CMakeLists.txt b/libraries/entities/CMakeLists.txt index d21906fa3f..c4334edb3a 100644 --- a/libraries/entities/CMakeLists.txt +++ b/libraries/entities/CMakeLists.txt @@ -7,10 +7,14 @@ add_dependency_external_projects(glm) find_package(GLM REQUIRED) target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS}) -add_dependency_external_projects(bullet) +add_dependency_external_projects(bullet polyvox) find_package(Bullet REQUIRED) target_include_directories(${TARGET_NAME} SYSTEM PRIVATE ${BULLET_INCLUDE_DIRS}) target_link_libraries(${TARGET_NAME} ${BULLET_LIBRARIES}) +find_package(PolyVox REQUIRED) +target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${POLYVOX_INCLUDE_DIRS}) +target_link_libraries(${TARGET_NAME} ${POLYVOX_LIBRARIES}) + link_hifi_libraries(avatars shared octree gpu model fbx networking animation environment) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index e8210c7e79..e0297fe7d4 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -183,9 +183,9 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet QByteArray encodedPropertyFlags; int propertyCount = 0; - successIDFits = packetData->appendValue(encodedID); + successIDFits = packetData->appendRawData(encodedID); if (successIDFits) { - successTypeFits = packetData->appendValue(encodedType); + successTypeFits = packetData->appendRawData(encodedType); } if (successTypeFits) { successCreatedFits = packetData->appendValue(_created); @@ -194,17 +194,17 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet successLastEditedFits = packetData->appendValue(lastEdited); } if (successLastEditedFits) { - successLastUpdatedFits = packetData->appendValue(encodedUpdateDelta); + successLastUpdatedFits = packetData->appendRawData(encodedUpdateDelta); } if (successLastUpdatedFits) { - successLastSimulatedFits = packetData->appendValue(encodedSimulatedDelta); + successLastSimulatedFits = packetData->appendRawData(encodedSimulatedDelta); } if (successLastSimulatedFits) { propertyFlagsOffset = packetData->getUncompressedByteOffset(); encodedPropertyFlags = propertyFlags; oldPropertyFlagsLength = encodedPropertyFlags.length(); - successPropertyFlagsFits = packetData->appendValue(encodedPropertyFlags); + successPropertyFlagsFits = packetData->appendRawData(encodedPropertyFlags); } bool headerFits = successIDFits && successTypeFits && successCreatedFits && successLastEditedFits @@ -318,15 +318,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef return 0; } - // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. - glm::vec3 savePosition = _position; - glm::quat saveRotation = _rotation; - // glm::vec3 saveVelocity = _velocity; - // glm::vec3 saveAngularVelocity = _angularVelocity; - // glm::vec3 saveGravity = _gravity; - // glm::vec3 saveAcceleration = _acceleration; - - // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] @@ -337,299 +328,308 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef const int MINIMUM_HEADER_BYTES = 27; int bytesRead = 0; - if (bytesLeftToRead >= MINIMUM_HEADER_BYTES) { + if (bytesLeftToRead < MINIMUM_HEADER_BYTES) { + return 0; + } - int originalLength = bytesLeftToRead; - QByteArray originalDataBuffer((const char*)data, originalLength); + // if this bitstream indicates that this node is the simulation owner, ignore any physics-related updates. + glm::vec3 savePosition = _position; + glm::quat saveRotation = _rotation; + glm::vec3 saveVelocity = _velocity; + glm::vec3 saveAngularVelocity = _angularVelocity; - int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; + int originalLength = bytesLeftToRead; + QByteArray originalDataBuffer((const char*)data, originalLength); - const unsigned char* dataAt = data; + int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0; - // id - QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size - _id = QUuid::fromRfc4122(encodedID); - dataAt += encodedID.size(); - bytesRead += encodedID.size(); - - // type - QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded typeCoder = encodedType; - encodedType = typeCoder; // determine true length - dataAt += encodedType.size(); - bytesRead += encodedType.size(); - quint32 type = typeCoder; - _type = (EntityTypes::EntityType)type; + const unsigned char* dataAt = data; - bool overwriteLocalData = true; // assume the new content overwrites our local data + // id + QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size + _id = QUuid::fromRfc4122(encodedID); + dataAt += encodedID.size(); + bytesRead += encodedID.size(); + + // type + QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded typeCoder = encodedType; + encodedType = typeCoder; // determine true length + dataAt += encodedType.size(); + bytesRead += encodedType.size(); + quint32 type = typeCoder; + _type = (EntityTypes::EntityType)type; - // _created - quint64 createdFromBuffer = 0; - memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); - dataAt += sizeof(createdFromBuffer); - bytesRead += sizeof(createdFromBuffer); + bool overwriteLocalData = true; // assume the new content overwrites our local data - quint64 now = usecTimestampNow(); - if (_created == UNKNOWN_CREATED_TIME) { - // we don't yet have a _created timestamp, so we accept this one - createdFromBuffer -= clockSkew; - if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { - createdFromBuffer = now; - } - _created = createdFromBuffer; + // _created + quint64 createdFromBuffer = 0; + memcpy(&createdFromBuffer, dataAt, sizeof(createdFromBuffer)); + dataAt += sizeof(createdFromBuffer); + bytesRead += sizeof(createdFromBuffer); + + quint64 now = usecTimestampNow(); + if (_created == UNKNOWN_CREATED_TIME) { + // we don't yet have a _created timestamp, so we accept this one + createdFromBuffer -= clockSkew; + if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { + createdFromBuffer = now; } + _created = createdFromBuffer; + } + #ifdef WANT_DEBUG + quint64 lastEdited = getLastEdited(); + float editedAgo = getEditedAgo(); + QString agoAsString = formatSecondsElapsed(editedAgo); + QString ageAsString = formatSecondsElapsed(getAge()); + qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; + qCDebug(entities) << "------------------------------------------"; + debugDump(); + qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << " _created =" << _created; + qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; + qCDebug(entities) << " lastEdited =" << lastEdited; + qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; + #endif + + quint64 lastEditedFromBuffer = 0; + quint64 lastEditedFromBufferAdjusted = 0; + + // TODO: we could make this encoded as a delta from _created + // _lastEdited + memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); + dataAt += sizeof(lastEditedFromBuffer); + bytesRead += sizeof(lastEditedFromBuffer); + lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; + if (lastEditedFromBufferAdjusted > now) { + lastEditedFromBufferAdjusted = now; + } + + bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); + + #ifdef WANT_DEBUG + qCDebug(entities) << "data from server **************** "; + qCDebug(entities) << " entityItemID:" << getEntityItemID(); + qCDebug(entities) << " now:" << now; + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); + qCDebug(entities) << " clockSkew:" << debugTimeOnly(clockSkew); + qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); + qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); + qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; + #endif + + bool ignoreServerPacket = false; // assume we'll use this server packet + + // If this packet is from the same server edit as the last packet we accepted from the server + // we probably want to use it. + if (fromSameServerEdit) { + // If this is from the same sever packet, then check against any local changes since we got + // the most recent packet from this server time + if (_lastEdited > _lastEditedFromRemote) { + ignoreServerPacket = true; + } + } else { + // If this isn't from the same sever packet, then honor our skew adjusted times... + // If we've changed our local tree more recently than the new data from this packet + // then we will not be changing our values, instead we just read and skip the data + if (_lastEdited > lastEditedFromBufferAdjusted) { + ignoreServerPacket = true; + } + } + + if (ignoreServerPacket) { + overwriteLocalData = false; #ifdef WANT_DEBUG - quint64 lastEdited = getLastEdited(); - float editedAgo = getEditedAgo(); - QString agoAsString = formatSecondsElapsed(editedAgo); - QString ageAsString = formatSecondsElapsed(getAge()); - qCDebug(entities) << "------------------------------------------"; - qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; - qCDebug(entities) << "------------------------------------------"; + qCDebug(entities) << "IGNORING old data from server!!! ****************"; debugDump(); - qCDebug(entities) << "------------------------------------------"; - qCDebug(entities) << " _created =" << _created; - qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; - qCDebug(entities) << " lastEdited =" << lastEdited; - qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; #endif - - quint64 lastEditedFromBuffer = 0; - quint64 lastEditedFromBufferAdjusted = 0; - - // TODO: we could make this encoded as a delta from _created - // _lastEdited - memcpy(&lastEditedFromBuffer, dataAt, sizeof(lastEditedFromBuffer)); - dataAt += sizeof(lastEditedFromBuffer); - bytesRead += sizeof(lastEditedFromBuffer); - lastEditedFromBufferAdjusted = lastEditedFromBuffer - clockSkew; - if (lastEditedFromBufferAdjusted > now) { - lastEditedFromBufferAdjusted = now; - } - - bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); - + } else { #ifdef WANT_DEBUG - qCDebug(entities) << "data from server **************** "; - qCDebug(entities) << " entityItemID:" << getEntityItemID(); - qCDebug(entities) << " now:" << now; - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); - qCDebug(entities) << " clockSkew:" << debugTimeOnly(clockSkew); - qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); - qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); - qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); - qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; + qCDebug(entities) << "USING NEW data from server!!! ****************"; + debugDump(); #endif - bool ignoreServerPacket = false; // assume we'll use this server packet - - // If this packet is from the same server edit as the last packet we accepted from the server - // we probably want to use it. - if (fromSameServerEdit) { - // If this is from the same sever packet, then check against any local changes since we got - // the most recent packet from this server time - if (_lastEdited > _lastEditedFromRemote) { - ignoreServerPacket = true; - } - } else { - // If this isn't from the same sever packet, then honor our skew adjusted times... - // If we've changed our local tree more recently than the new data from this packet - // then we will not be changing our values, instead we just read and skip the data - if (_lastEdited > lastEditedFromBufferAdjusted) { - ignoreServerPacket = true; - } - } + // don't allow _lastEdited to be in the future + _lastEdited = lastEditedFromBufferAdjusted; + _lastEditedFromRemote = now; + _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - if (ignoreServerPacket) { - overwriteLocalData = false; - #ifdef WANT_DEBUG - qCDebug(entities) << "IGNORING old data from server!!! ****************"; - debugDump(); - #endif - } else { + // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed + // the properties out of the bitstream (see below)) + somethingChangedNotification(); // notify derived classes that something has changed + } - #ifdef WANT_DEBUG - qCDebug(entities) << "USING NEW data from server!!! ****************"; - debugDump(); - #endif - - // don't allow _lastEdited to be in the future - _lastEdited = lastEditedFromBufferAdjusted; - _lastEditedFromRemote = now; - _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - - // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed - // the properties out of the bitstream (see below)) - somethingChangedNotification(); // notify derived classes that something has changed - } - - // last updated is stored as ByteCountCoded delta from lastEdited - QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded updateDeltaCoder = encodedUpdateDelta; - quint64 updateDelta = updateDeltaCoder; + // last updated is stored as ByteCountCoded delta from lastEdited + QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded updateDeltaCoder = encodedUpdateDelta; + quint64 updateDelta = updateDeltaCoder; + if (overwriteLocalData) { + _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that + #ifdef WANT_DEBUG + qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); + qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); + qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + #endif + } + encodedUpdateDelta = updateDeltaCoder; // determine true length + dataAt += encodedUpdateDelta.size(); + bytesRead += encodedUpdateDelta.size(); + + // Newer bitstreams will have a last simulated and a last updated value + if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { + // last simulated is stored as ByteCountCoded delta from lastEdited + QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size + ByteCountCoded simulatedDeltaCoder = encodedSimulatedDelta; + quint64 simulatedDelta = simulatedDeltaCoder; if (overwriteLocalData) { - _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that + _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that #ifdef WANT_DEBUG - qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); + qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); #endif } - encodedUpdateDelta = updateDeltaCoder; // determine true length - dataAt += encodedUpdateDelta.size(); - bytesRead += encodedUpdateDelta.size(); - - // Newer bitstreams will have a last simulated and a last updated value - if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { - // last simulated is stored as ByteCountCoded delta from lastEdited - QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size - ByteCountCoded simulatedDeltaCoder = encodedSimulatedDelta; - quint64 simulatedDelta = simulatedDeltaCoder; + encodedSimulatedDelta = simulatedDeltaCoder; // determine true length + dataAt += encodedSimulatedDelta.size(); + bytesRead += encodedSimulatedDelta.size(); + } + + #ifdef WANT_DEBUG + if (overwriteLocalData) { + qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); + qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now); + qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); + } + #endif + + + // Property Flags + QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size + EntityPropertyFlags propertyFlags = encodedPropertyFlags; + dataAt += propertyFlags.getEncodedLength(); + bytesRead += propertyFlags.getEncodedLength(); + bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + } else { + READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); + } + + // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS + if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { + if (propertyFlags.getHasProperty(PROP_RADIUS)) { + float fromBuffer; + memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); + dataAt += sizeof(fromBuffer); + bytesRead += sizeof(fromBuffer); if (overwriteLocalData) { - _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that - #ifdef WANT_DEBUG - qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); - qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); - qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); - #endif + setRadius(fromBuffer); } - encodedSimulatedDelta = simulatedDeltaCoder; // determine true length - dataAt += encodedSimulatedDelta.size(); - bytesRead += encodedSimulatedDelta.size(); } - - #ifdef WANT_DEBUG - if (overwriteLocalData) { - qCDebug(entities) << "EntityItem::readEntityDataFromBuffer()... changed entity:" << getEntityItemID(); - qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); - qCDebug(entities) << " getLastSimulated:" << debugTime(getLastSimulated(), now); - qCDebug(entities) << " getLastUpdated:" << debugTime(getLastUpdated(), now); - } - #endif - - - // Property Flags - QByteArray encodedPropertyFlags = originalDataBuffer.mid(bytesRead); // maximum possible size - EntityPropertyFlags propertyFlags = encodedPropertyFlags; - dataAt += propertyFlags.getEncodedLength(); - bytesRead += propertyFlags.getEncodedLength(); - bool useMeters = (args.bitstreamVersion >= VERSION_ENTITIES_USE_METERS_AND_RADIANS); + } else { if (useMeters) { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePosition); + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); } else { - READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, updatePositionInDomainUnits); - } - - // Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS - if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) { - if (propertyFlags.getHasProperty(PROP_RADIUS)) { - float fromBuffer; - memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); - dataAt += sizeof(fromBuffer); - bytesRead += sizeof(fromBuffer); - if (overwriteLocalData) { - setRadius(fromBuffer); - } - } - } else { - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensions); - } else { - READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); - } - } - - READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); - READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); - } else { - READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); - READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); - } - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); - } - - READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); - READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); - READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); - READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); - READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); - READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); - if (useMeters) { - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); - } else { - READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); - } - READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); - READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); - READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); - READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); - READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); - READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); - - if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { - READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); - } - - if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - } - - READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); - READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); - bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); - - //////////////////////////////////// - // WARNING: Do not add stream content here after the subclass. Always add it before the subclass - // - // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover - // by doing this parsing here... but it's not likely going to fully recover the content. - // - // TODO: Remove this conde once we've sufficiently migrated content past this damaged version - if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { - READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); - } - - if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { - // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more - // closely match where the entities should be if they'd stepped forward in time to "now". The server - // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore - // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and - // use our simulation helper routine to get a best estimate of where the entity should be. - const float MIN_TIME_SKIP = 0.0f; - const float MAX_TIME_SKIP = 1.0f; // in seconds - float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND), - MIN_TIME_SKIP, MAX_TIME_SKIP); - if (skipTimeForward > 0.0f) { - #ifdef WANT_DEBUG - qCDebug(entities) << "skipTimeForward:" << skipTimeForward; - #endif - - // we want to extrapolate the motion forward to compensate for packet travel time, but - // we don't want the side effect of flag setting. - simulateKinematicMotion(skipTimeForward, false); - } - _lastSimulated = now; + READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, updateDimensionsInDomainUnits); } } + READ_ENTITY_PROPERTY(PROP_ROTATION, glm::quat, updateRotation); + READ_ENTITY_PROPERTY(PROP_DENSITY, float, updateDensity); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocity); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravity); + } else { + READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, updateVelocityInDomainUnits); + READ_ENTITY_PROPERTY(PROP_GRAVITY, glm::vec3, updateGravityInDomainUnits); + } + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + READ_ENTITY_PROPERTY(PROP_ACCELERATION, glm::vec3, setAcceleration); + } + + READ_ENTITY_PROPERTY(PROP_DAMPING, float, updateDamping); + READ_ENTITY_PROPERTY(PROP_RESTITUTION, float, updateRestitution); + READ_ENTITY_PROPERTY(PROP_FRICTION, float, updateFriction); + READ_ENTITY_PROPERTY(PROP_LIFETIME, float, updateLifetime); + READ_ENTITY_PROPERTY(PROP_SCRIPT, QString, setScript); + READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint); + if (useMeters) { + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocity); + } else { + READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, updateAngularVelocityInDegrees); + } + READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, updateAngularDamping); + READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, setVisible); + READ_ENTITY_PROPERTY(PROP_IGNORE_FOR_COLLISIONS, bool, updateIgnoreForCollisions); + READ_ENTITY_PROPERTY(PROP_COLLISIONS_WILL_MOVE, bool, updateCollisionsWillMove); + READ_ENTITY_PROPERTY(PROP_LOCKED, bool, setLocked); + READ_ENTITY_PROPERTY(PROP_USER_DATA, QString, setUserData); + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAVE_ACCELERATION) { + // we always accept the server's notion of simulatorID, so we fake overwriteLocalData as true + // before we try to READ_ENTITY_PROPERTY it + bool temp = overwriteLocalData; + overwriteLocalData = true; + READ_ENTITY_PROPERTY(PROP_SIMULATOR_ID, QUuid, updateSimulatorID); + overwriteLocalData = temp; + } + + if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_MARKETPLACE_ID) { + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + } + + READ_ENTITY_PROPERTY(PROP_NAME, QString, setName); + READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); + bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData); + + //////////////////////////////////// + // WARNING: Do not add stream content here after the subclass. Always add it before the subclass + // + // NOTE: we had a bad version of the stream that we added stream data after the subclass. We can attempt to recover + // by doing this parsing here... but it's not likely going to fully recover the content. + // + // TODO: Remove this conde once we've sufficiently migrated content past this damaged version + if (args.bitstreamVersion == VERSION_ENTITIES_HAS_MARKETPLACE_ID_DAMAGED) { + READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID); + } + + if (overwriteLocalData && (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES))) { + // NOTE: This code is attempting to "repair" the old data we just got from the server to make it more + // closely match where the entities should be if they'd stepped forward in time to "now". The server + // is sending us data with a known "last simulated" time. That time is likely in the past, and therefore + // this "new" data is actually slightly out of date. We calculate the time we need to skip forward and + // use our simulation helper routine to get a best estimate of where the entity should be. + const float MIN_TIME_SKIP = 0.0f; + const float MAX_TIME_SKIP = 1.0f; // in seconds + float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND), + MIN_TIME_SKIP, MAX_TIME_SKIP); + if (skipTimeForward > 0.0f) { + #ifdef WANT_DEBUG + qCDebug(entities) << "skipTimeForward:" << skipTimeForward; + #endif + + // we want to extrapolate the motion forward to compensate for packet travel time, but + // we don't want the side effect of flag setting. + simulateKinematicMotion(skipTimeForward, false); + } + _lastSimulated = now; + } auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); - if (_simulatorID == myNodeID && !_simulatorID.isNull()) { - // the packet that produced this bitstream originally came from physics simulations performed by - // this node, so our version has to be newer than what the packet contained. + if (overwriteLocalData && _simulatorID == myNodeID && !_simulatorID.isNull()) { + // we own the simulation, so we keep our transform+velocities and remove any related dirty flags + // rather than accept the values in the packet _position = savePosition; _rotation = saveRotation; - // _velocity = saveVelocity; - // _angularVelocity = saveAngularVelocity; - // _gravity = saveGravity; - // _acceleration = saveAcceleration; + _velocity = saveVelocity; + _angularVelocity = saveAngularVelocity; + _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); } return bytesRead; @@ -652,6 +652,7 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s // lastEdited quint64 lastEditedInLocalTime; memcpy(&lastEditedInLocalTime, dataAt, sizeof(lastEditedInLocalTime)); + assert(lastEditedInLocalTime > 0); quint64 lastEditedInServerTime = lastEditedInLocalTime + clockSkew; memcpy(dataAt, &lastEditedInServerTime, sizeof(lastEditedInServerTime)); #ifdef WANT_DEBUG @@ -948,40 +949,25 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(name, setName); if (somethingChanged) { - somethingChangedNotification(); // notify derived classes that something has changed uint64_t now = usecTimestampNow(); #ifdef WANT_DEBUG int elapsed = now - getLastEdited(); qCDebug(entities) << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " getLastEdited()=" << getLastEdited(); #endif - if (_created != UNKNOWN_CREATED_TIME) { - setLastEdited(now); + setLastEdited(now); + somethingChangedNotification(); // notify derived classes that something has changed + if (_created == UNKNOWN_CREATED_TIME) { + _created = now; } if (getDirtyFlags() & (EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES)) { - // TODO: Andrew & Brad to discuss. Is this correct? Maybe it is. Need to think through all cases. + // anything that sets the transform or velocity must update _lastSimulated which is used + // for kinematic extrapolation (e.g. we want to extrapolate forward from this moment + // when position and/or velocity was changed). _lastSimulated = now; } } - // timestamps - quint64 timestamp = properties.getCreated(); - if (_created == UNKNOWN_CREATED_TIME && timestamp != UNKNOWN_CREATED_TIME) { - quint64 now = usecTimestampNow(); - if (timestamp > now) { - timestamp = now; - } - _created = timestamp; - - timestamp = properties.getLastEdited(); - if (timestamp > now) { - timestamp = now; - } else if (timestamp < _created) { - timestamp = _created; - } - _lastEdited = timestamp; - } - return somethingChanged; } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 905fa7d104..856c1a1cb4 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -26,6 +26,7 @@ #include "ParticleEffectEntityItem.h" #include "TextEntityItem.h" #include "ZoneEntityItem.h" +#include "PolyVoxEntityItem.h" AtmospherePropertyGroup EntityItemProperties::_staticAtmosphere; SkyboxPropertyGroup EntityItemProperties::_staticSkybox; @@ -87,10 +88,14 @@ CONSTRUCT_PROPERTY(keyLightColor, ZoneEntityItem::DEFAULT_KEYLIGHT_COLOR), CONSTRUCT_PROPERTY(keyLightIntensity, ZoneEntityItem::DEFAULT_KEYLIGHT_INTENSITY), CONSTRUCT_PROPERTY(keyLightAmbientIntensity, ZoneEntityItem::DEFAULT_KEYLIGHT_AMBIENT_INTENSITY), CONSTRUCT_PROPERTY(keyLightDirection, ZoneEntityItem::DEFAULT_KEYLIGHT_DIRECTION), +CONSTRUCT_PROPERTY(voxelVolumeSize, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE), +CONSTRUCT_PROPERTY(voxelData, PolyVoxEntityItem::DEFAULT_VOXEL_DATA), +CONSTRUCT_PROPERTY(voxelSurfaceStyle, PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE), CONSTRUCT_PROPERTY(name, ENTITY_ITEM_DEFAULT_NAME), CONSTRUCT_PROPERTY(backgroundMode, BACKGROUND_MODE_INHERIT), CONSTRUCT_PROPERTY(sourceUrl, ""), + _id(UNKNOWN_ENTITY_ID), _idSet(false), _lastEdited(0), @@ -336,6 +341,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_KEYLIGHT_DIRECTION, keyLightDirection); CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode); CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl); + CHECK_PROPERTY_CHANGE(PROP_VOXEL_VOLUME_SIZE, voxelVolumeSize); + CHECK_PROPERTY_CHANGE(PROP_VOXEL_DATA, voxelData); + CHECK_PROPERTY_CHANGE(PROP_VOXEL_SURFACE_STYLE, voxelSurfaceStyle); changedProperties += _stage.getChangedProperties(); changedProperties += _atmosphere.getChangedProperties(); @@ -418,6 +426,10 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(keyLightDirection); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(backgroundMode, getBackgroundModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(sourceUrl); + + COPY_PROPERTY_TO_QSCRIPTVALUE(voxelVolumeSize); + COPY_PROPERTY_TO_QSCRIPTVALUE(voxelData); + COPY_PROPERTY_TO_QSCRIPTVALUE(voxelSurfaceStyle); // Sitting properties support if (!skipDefaults) { @@ -523,7 +535,11 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) { COPY_PROPERTY_FROM_QSCRIPTVALUE(keyLightDirection, glmVec3, setKeyLightDirection); COPY_PROPERTY_FROM_QSCRITPTVALUE_ENUM(backgroundMode, BackgroundMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(sourceUrl, QString, setSourceUrl); - + + COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelVolumeSize, glmVec3, setVoxelVolumeSize); + COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelData, QByteArray, setVoxelData); + COPY_PROPERTY_FROM_QSCRIPTVALUE(voxelSurfaceStyle, uint16_t, setVoxelSurfaceStyle); + _stage.copyFromScriptValue(object, _defaultSettings); _atmosphere.copyFromScriptValue(object, _defaultSettings); _skybox.copyFromScriptValue(object, _defaultSettings); @@ -619,23 +635,23 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem quint64 lastEdited = properties.getLastEdited(); bool successLastEditedFits = packetData->appendValue(lastEdited); - bool successIDFits = packetData->appendValue(encodedID); + bool successIDFits = packetData->appendRawData(encodedID); if (successIDFits) { - successIDFits = packetData->appendValue(encodedToken); + successIDFits = packetData->appendRawData(encodedToken); } - bool successTypeFits = packetData->appendValue(encodedType); + bool successTypeFits = packetData->appendRawData(encodedType); // NOTE: We intentionally do not send "created" times in edit messages. This is because: // 1) if the edit is to an existing entity, the created time can not be changed // 2) if the edit is to a new entity, the created time is the last edited time // TODO: Should we get rid of this in this in edit packets, since this has to always be 0? - bool successLastUpdatedFits = packetData->appendValue(encodedUpdateDelta); + bool successLastUpdatedFits = packetData->appendRawData(encodedUpdateDelta); int propertyFlagsOffset = packetData->getUncompressedByteOffset(); QByteArray encodedPropertyFlags = propertyFlags; int oldPropertyFlagsLength = encodedPropertyFlags.length(); - bool successPropertyFlagsFits = packetData->appendValue(encodedPropertyFlags); + bool successPropertyFlagsFits = packetData->appendRawData(encodedPropertyFlags); int propertyCount = 0; bool headerFits = successIDFits && successTypeFits && successLastEditedFits @@ -740,6 +756,12 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem _staticSkybox.setProperties(properties); _staticSkybox.appentToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState ); } + + if (properties.getType() == EntityTypes::PolyVox) { + APPEND_ENTITY_PROPERTY(PROP_VOXEL_VOLUME_SIZE, properties.getVoxelVolumeSize()); + APPEND_ENTITY_PROPERTY(PROP_VOXEL_DATA, properties.getVoxelData()); + APPEND_ENTITY_PROPERTY(PROP_VOXEL_SURFACE_STYLE, properties.getVoxelSurfaceStyle()); + } APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID()); APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); @@ -974,11 +996,17 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int properties.getAtmosphere().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); properties.getSkybox().decodeFromEditPacket(propertyFlags, dataAt , processedBytes); } + + if (properties.getType() == EntityTypes::PolyVox) { + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VOXEL_VOLUME_SIZE, glm::vec3, setVoxelVolumeSize); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VOXEL_DATA, QByteArray, setVoxelData); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VOXEL_SURFACE_STYLE, uint16_t, setVoxelSurfaceStyle); + } READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL); - + return valid; } @@ -1079,6 +1107,10 @@ void EntityItemProperties::markAllChanged() { _skybox.markAllChanged(); _sourceUrlChanged = true; + + _voxelVolumeSizeChanged = true; + _voxelDataChanged = true; + _voxelSurfaceStyleChanged = true; } /// The maximum bounding cube for the entity, independent of it's rotation. diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 2c052d0a53..26c26bd474 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -54,6 +54,7 @@ class EntityItemProperties { friend class ZoneEntityItem; // TODO: consider removing this friend relationship and use public methods friend class WebEntityItem; // TODO: consider removing this friend relationship and use public methods friend class LineEntityItem; // TODO: consider removing this friend relationship and use public methods + friend class PolyVoxEntityItem; // TODO: consider removing this friend relationship and use public methods public: EntityItemProperties(); virtual ~EntityItemProperties(); @@ -135,6 +136,9 @@ public: DEFINE_PROPERTY(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float); DEFINE_PROPERTY(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLightAmbientIntensity, keyLightAmbientIntensity, float); DEFINE_PROPERTY_REF(PROP_KEYLIGHT_DIRECTION, KeyLightDirection, keyLightDirection, glm::vec3); + DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3); + DEFINE_PROPERTY_REF(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray); + DEFINE_PROPERTY_REF(PROP_VOXEL_SURFACE_STYLE, VoxelSurfaceStyle, voxelSurfaceStyle, uint16_t); DEFINE_PROPERTY_REF(PROP_NAME, Name, name, QString); DEFINE_PROPERTY_REF_ENUM(PROP_BACKGROUND_MODE, BackgroundMode, backgroundMode, BackgroundMode); DEFINE_PROPERTY_GROUP(Stage, stage, StagePropertyGroup); @@ -189,6 +193,8 @@ public: QString getSimulatorIDAsString() const { return _simulatorID.toString().mid(1,36).toUpper(); } + void setVoxelDataDirty() { _voxelDataChanged = true; } + private: QUuid _id; bool _idSet; @@ -219,7 +225,6 @@ void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemP inline void EntityItemProperties::setPosition(const glm::vec3& value) { _position = glm::clamp(value, 0.0f, (float)TREE_SCALE); _positionChanged = true; } - inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { debug << "EntityItemProperties[" << "\n"; @@ -281,6 +286,9 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, ParticleRadius, particleRadius, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, MarketplaceID, marketplaceID, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, BackgroundMode, backgroundMode, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelVolumeSize, voxelVolumeSize, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelData, voxelData, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, VoxelSurfaceStyle, voxelSurfaceStyle, ""); properties.getStage().debugDump(); properties.getAtmosphere().debugDump(); diff --git a/libraries/entities/src/EntityItemPropertiesMacros.h b/libraries/entities/src/EntityItemPropertiesMacros.h index 8c4f329cc9..33abc59e4d 100644 --- a/libraries/entities/src/EntityItemPropertiesMacros.h +++ b/libraries/entities/src/EntityItemPropertiesMacros.h @@ -32,7 +32,7 @@ #define READ_ENTITY_PROPERTY(P,T,S) \ if (propertyFlags.getHasProperty(P)) { \ T fromBuffer; \ - int bytes = OctreePacketData::uppackDataFromBytes(dataAt, fromBuffer); \ + int bytes = OctreePacketData::unpackDataFromBytes(dataAt, fromBuffer); \ dataAt += bytes; \ bytesRead += bytes; \ if (overwriteLocalData) { \ @@ -49,7 +49,7 @@ #define READ_ENTITY_PROPERTY_TO_PROPERTIES(P,T,O) \ if (propertyFlags.getHasProperty(P)) { \ T fromBuffer; \ - int bytes = OctreePacketData::uppackDataFromBytes(dataAt, fromBuffer); \ + int bytes = OctreePacketData::unpackDataFromBytes(dataAt, fromBuffer); \ dataAt += bytes; \ processedBytes += bytes; \ properties.O(fromBuffer); \ @@ -91,10 +91,18 @@ inline QScriptValue convertScriptValue(QScriptEngine* e, float v) { return QScri inline QScriptValue convertScriptValue(QScriptEngine* e, int v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, quint32 v) { return QScriptValue(v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const QString& v) { return QScriptValue(v); } + inline QScriptValue convertScriptValue(QScriptEngine* e, const xColor& v) { return xColorToScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const glm::quat& v) { return quatToScriptValue(e, v); } inline QScriptValue convertScriptValue(QScriptEngine* e, const QScriptValue& v) { return v; } +inline QScriptValue convertScriptValue(QScriptEngine* e, const QByteArray& v) { + QByteArray b64 = v.toBase64(); + return QScriptValue(QString(b64)); +} + + + #define COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(G,g,P,p) \ if (!skipDefaults || defaultEntityProperties.get##G().get##P() != get##P()) { \ QScriptValue groupProperties = properties.property(#g); \ @@ -129,6 +137,16 @@ inline int int_convertFromScriptValue(const QScriptValue& v, bool& isValid) { re inline bool bool_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toBool(); } inline QString QString_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toString().trimmed(); } inline QUuid QUuid_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = true; return v.toVariant().toUuid(); } + + +inline QByteArray QByteArray_convertFromScriptValue(const QScriptValue& v, bool& isValid) { + isValid = true; + QString b64 = v.toVariant().toString().trimmed(); + return QByteArray::fromBase64(b64.toUtf8()); +} + + + inline glmVec3 glmVec3_convertFromScriptValue(const QScriptValue& v, bool& isValid) { isValid = false; /// assume it can't be converted QScriptValue x = v.property("x"); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 8a5d96e8d2..bcb1ec0886 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -110,6 +110,10 @@ enum EntityPropertyList { PROP_RESTITUTION, PROP_FRICTION, + PROP_VOXEL_VOLUME_SIZE, + PROP_VOXEL_DATA, + PROP_VOXEL_SURFACE_STYLE, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index e652c77d78..54d2ea705e 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -17,6 +17,7 @@ #include "ModelEntityItem.h" #include "ZoneEntityItem.h" #include "EntitiesLogging.h" +#include "PolyVoxEntityItem.h" EntityScriptingInterface::EntityScriptingInterface() : @@ -382,3 +383,40 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra vec3FromScriptValue(intersection, value.intersection); } } + + +bool EntityScriptingInterface::setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value) { + if (!_entityTree) { + return false; + } + + EntityItemPointer entity = static_cast(_entityTree->findEntityByEntityItemID(entityID)); + if (!entity) { + qCDebug(entities) << "EntityScriptingInterface::setVoxelSphere no entity with ID" << entityID; + return false; + } + + EntityTypes::EntityType entityType = entity->getType(); + if (entityType != EntityTypes::PolyVox) { + return false; + } + + auto now = usecTimestampNow(); + + PolyVoxEntityItem* polyVoxEntity = static_cast(entity.get()); + _entityTree->lockForWrite(); + polyVoxEntity->setSphere(center, radius, value); + entity->setLastEdited(now); + entity->setLastBroadcast(now); + _entityTree->unlock(); + + _entityTree->lockForRead(); + EntityItemProperties properties = entity->getProperties(); + _entityTree->unlock(); + + properties.setVoxelDataDirty(); + properties.setLastEdited(now); + + queueEntityMessage(PacketTypeEntityEdit, entityID, properties); + return true; +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 655c8982b5..6c2dc06579 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -117,6 +117,8 @@ public slots: Q_INVOKABLE void setSendPhysicsUpdates(bool value); Q_INVOKABLE bool getSendPhysicsUpdates() const; + Q_INVOKABLE bool setVoxelSphere(QUuid entityID, const glm::vec3& center, float radius, int value); + Q_INVOKABLE void dumpTree() const; signals: diff --git a/libraries/entities/src/EntityTypes.cpp b/libraries/entities/src/EntityTypes.cpp index b5b722cc6c..a41cc22d2e 100644 --- a/libraries/entities/src/EntityTypes.cpp +++ b/libraries/entities/src/EntityTypes.cpp @@ -27,6 +27,7 @@ #include "WebEntityItem.h" #include "ZoneEntityItem.h" #include "LineEntityItem.h" +#include "PolyVoxEntityItem.h" QMap EntityTypes::_typeToNameMap; QMap EntityTypes::_nameToTypeMap; @@ -45,6 +46,7 @@ REGISTER_ENTITY_TYPE(Text) REGISTER_ENTITY_TYPE(ParticleEffect) REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Line) +REGISTER_ENTITY_TYPE(PolyVox) const QString& EntityTypes::getEntityTypeName(EntityType entityType) { QMap::iterator matchedTypeName = _typeToNameMap.find(entityType); diff --git a/libraries/entities/src/EntityTypes.h b/libraries/entities/src/EntityTypes.h index 524e5b6e82..323a4eb92b 100644 --- a/libraries/entities/src/EntityTypes.h +++ b/libraries/entities/src/EntityTypes.h @@ -45,7 +45,8 @@ public: Zone, Web, Line, - LAST = Line + PolyVox, + LAST = PolyVox } EntityType; static const QString& getEntityTypeName(EntityType entityType); diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp new file mode 100644 index 0000000000..a46fdb2682 --- /dev/null +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -0,0 +1,117 @@ +// +// PolyVoxEntityItem.cpp +// libraries/entities/src +// +// Created by Seth Alves on 5/11/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include +#include + +#include + +#include "PolyVoxEntityItem.h" +#include "EntityTree.h" +#include "EntitiesLogging.h" +#include "EntityTreeElement.h" + + +const glm::vec3 PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE = glm::vec3(32, 32, 32); +const QByteArray PolyVoxEntityItem::DEFAULT_VOXEL_DATA(qCompress(QByteArray(0), 9)); +const PolyVoxEntityItem::PolyVoxSurfaceStyle PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE = + PolyVoxEntityItem::SURFACE_MARCHING_CUBES; + +EntityItemPointer PolyVoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { + return EntityItemPointer(new PolyVoxEntityItem(entityID, properties)); +} + +PolyVoxEntityItem::PolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) : + EntityItem(entityItemID), + _voxelVolumeSize(PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE), + _voxelData(PolyVoxEntityItem::DEFAULT_VOXEL_DATA), + _voxelSurfaceStyle(PolyVoxEntityItem::DEFAULT_VOXEL_SURFACE_STYLE) +{ + _type = EntityTypes::PolyVox; + _created = properties.getCreated(); + setProperties(properties); +} + +EntityItemProperties PolyVoxEntityItem::getProperties() const { + EntityItemProperties properties = EntityItem::getProperties(); // get the properties from our base class + COPY_ENTITY_PROPERTY_TO_PROPERTIES(voxelVolumeSize, getVoxelVolumeSize); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(voxelData, getVoxelData); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(voxelSurfaceStyle, getVoxelSurfaceStyle); + + return properties; +} + +bool PolyVoxEntityItem::setProperties(const EntityItemProperties& properties) { + bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class + SET_ENTITY_PROPERTY_FROM_PROPERTIES(voxelVolumeSize, setVoxelVolumeSize); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(voxelData, setVoxelData); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(voxelSurfaceStyle, setVoxelSurfaceStyle); + + if (somethingChanged) { + bool wantDebug = false; + if (wantDebug) { + uint64_t now = usecTimestampNow(); + int elapsed = now - getLastEdited(); + qCDebug(entities) << "PolyVoxEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << + "now=" << now << " getLastEdited()=" << getLastEdited(); + } + setLastEdited(properties._lastEdited); + } + return somethingChanged; +} + +int PolyVoxEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData) { + + int bytesRead = 0; + const unsigned char* dataAt = data; + + READ_ENTITY_PROPERTY(PROP_VOXEL_VOLUME_SIZE, glm::vec3, setVoxelVolumeSize); + READ_ENTITY_PROPERTY(PROP_VOXEL_DATA, QByteArray, setVoxelData); + READ_ENTITY_PROPERTY(PROP_VOXEL_SURFACE_STYLE, uint16_t, setVoxelSurfaceStyle); + + return bytesRead; +} + + +// TODO: eventually only include properties changed since the params.lastViewFrustumSent time +EntityPropertyFlags PolyVoxEntityItem::getEntityProperties(EncodeBitstreamParams& params) const { + EntityPropertyFlags requestedProperties = EntityItem::getEntityProperties(params); + requestedProperties += PROP_VOXEL_VOLUME_SIZE; + requestedProperties += PROP_VOXEL_DATA; + requestedProperties += PROP_VOXEL_SURFACE_STYLE; + return requestedProperties; +} + +void PolyVoxEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const { + bool successPropertyFits = true; + + APPEND_ENTITY_PROPERTY(PROP_VOXEL_VOLUME_SIZE, getVoxelVolumeSize()); + APPEND_ENTITY_PROPERTY(PROP_VOXEL_DATA, getVoxelData()); + APPEND_ENTITY_PROPERTY(PROP_VOXEL_SURFACE_STYLE, (uint16_t) getVoxelSurfaceStyle()); +} + +void PolyVoxEntityItem::debugDump() const { + quint64 now = usecTimestampNow(); + qCDebug(entities) << " POLYVOX EntityItem id:" << getEntityItemID() << "---------------------------------------------"; + qCDebug(entities) << " position:" << debugTreeVector(_position); + qCDebug(entities) << " dimensions:" << debugTreeVector(_dimensions); + qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); +} + diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h new file mode 100644 index 0000000000..53675e6efa --- /dev/null +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -0,0 +1,98 @@ +// +// PolyVoxEntityItem.h +// libraries/entities/src +// +// Created by Seth Alves on 5/11/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_PolyVoxEntityItem_h +#define hifi_PolyVoxEntityItem_h + +#include "EntityItem.h" + +class PolyVoxEntityItem : public EntityItem { + public: + static EntityItemPointer factory(const EntityItemID& entityID, const EntityItemProperties& properties); + + PolyVoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties); + + ALLOW_INSTANTIATION // This class can be instantiated + + // methods for getting/setting all properties of an entity + virtual EntityItemProperties getProperties() const; + virtual bool setProperties(const EntityItemProperties& properties); + + // TODO: eventually only include properties changed since the params.lastViewFrustumSent time + virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const; + + virtual void appendSubclassData(OctreePacketData* packetData, EncodeBitstreamParams& params, + EntityTreeElementExtraEncodeData* modelTreeElementExtraEncodeData, + EntityPropertyFlags& requestedProperties, + EntityPropertyFlags& propertyFlags, + EntityPropertyFlags& propertiesDidntFit, + int& propertyCount, + OctreeElement::AppendState& appendState) const; + + virtual int readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, + ReadBitstreamToTreeParams& args, + EntityPropertyFlags& propertyFlags, bool overwriteLocalData); + + const rgbColor& getColor() const { return _color; } + xColor getXColor() const { xColor color = { _color[RED_INDEX], _color[GREEN_INDEX], _color[BLUE_INDEX] }; return color; } + + void setColor(const rgbColor& value) { memcpy(_color, value, sizeof(_color)); } + void setXColor(const xColor& value) { + _color[RED_INDEX] = value.red; + _color[GREEN_INDEX] = value.green; + _color[BLUE_INDEX] = value.blue; + } + + virtual ShapeType getShapeType() const { return SHAPE_TYPE_POLYVOX; } + + // never have a ray intersection pick a PolyVoxEntityItem. + virtual bool supportsDetailedRayIntersection() const { return true; } + virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, + bool& keepSearching, OctreeElement*& element, float& distance, BoxFace& face, + void** intersectedObject, bool precisionPicking) const { return false; } + + virtual void debugDump() const; + + virtual void setVoxelVolumeSize(glm::vec3 voxelVolumeSize) { _voxelVolumeSize = voxelVolumeSize; } + virtual const glm::vec3& getVoxelVolumeSize() const { return _voxelVolumeSize; } + + virtual void setVoxelData(QByteArray voxelData) { _voxelData = voxelData; } + virtual const QByteArray& getVoxelData() const { return _voxelData; } + + enum PolyVoxSurfaceStyle { + SURFACE_MARCHING_CUBES, + SURFACE_CUBIC + }; + + virtual void setVoxelSurfaceStyle(PolyVoxSurfaceStyle voxelSurfaceStyle) { _voxelSurfaceStyle = voxelSurfaceStyle; } + virtual void setVoxelSurfaceStyle(uint16_t voxelSurfaceStyle) { + _voxelSurfaceStyle = (PolyVoxSurfaceStyle) voxelSurfaceStyle; + } + virtual PolyVoxSurfaceStyle getVoxelSurfaceStyle() const { return _voxelSurfaceStyle; } + + static const glm::vec3 DEFAULT_VOXEL_VOLUME_SIZE; + static const QByteArray DEFAULT_VOXEL_DATA; + static const PolyVoxSurfaceStyle DEFAULT_VOXEL_SURFACE_STYLE; + + // coords are in voxel-volume space + virtual void setSphereInVolume(glm::vec3 center, float radius, uint8_t toValue) {} + + // coords are in world-space + virtual void setSphere(glm::vec3 center, float radius, uint8_t toValue) {} + + protected: + rgbColor _color; + glm::vec3 _voxelVolumeSize; // this is always 3 bytes + QByteArray _voxelData; + PolyVoxSurfaceStyle _voxelSurfaceStyle; +}; + +#endif // hifi_PolyVoxEntityItem_h diff --git a/libraries/gpu/src/gpu/Resource.h b/libraries/gpu/src/gpu/Resource.h index 7f3f850339..42897e9947 100644 --- a/libraries/gpu/src/gpu/Resource.h +++ b/libraries/gpu/src/gpu/Resource.h @@ -228,13 +228,14 @@ public: { public: - Iterator(T* ptr = NULL) { _ptr = ptr; } + Iterator(T* ptr = NULL, int stride = sizeof(T)): _ptr(ptr), _stride(stride) { } Iterator(const Iterator& iterator) = default; ~Iterator() {} Iterator& operator=(const Iterator& iterator) = default; Iterator& operator=(T* ptr) { _ptr = ptr; + // stride is left unchanged return (*this); } @@ -249,42 +250,48 @@ public: bool operator==(const Iterator& iterator) const { return (_ptr == iterator.getConstPtr()); } bool operator!=(const Iterator& iterator) const { return (_ptr != iterator.getConstPtr()); } + void movePtr(const Index& movement) { + auto byteptr = ((Byte*)_ptr); + byteptr += _stride * movement; + _ptr = (T*)byteptr; + } + Iterator& operator+=(const Index& movement) { - _ptr += movement; + movePtr(movement); return (*this); } Iterator& operator-=(const Index& movement) { - _ptr -= movement; + movePtr(-movement); return (*this); } Iterator& operator++() { - ++_ptr; + movePtr(1); return (*this); } Iterator& operator--() { - --_ptr; + movePtr(-1); return (*this); } Iterator operator++(Index) { auto temp(*this); - ++_ptr; + movePtr(1); return temp; } Iterator operator--(Index) { auto temp(*this); - --_ptr; + movePtr(-1); return temp; } Iterator operator+(const Index& movement) { auto oldPtr = _ptr; - _ptr += movement; + movePtr(movement); auto temp(*this); _ptr = oldPtr; return temp; } Iterator operator-(const Index& movement) { auto oldPtr = _ptr; - _ptr -= movement; + movePtr(-movement); auto temp(*this); _ptr = oldPtr; return temp; @@ -302,16 +309,17 @@ public: protected: T* _ptr; + int _stride; }; - template Iterator begin() { return Iterator(&edit(0)); } - template Iterator end() { return Iterator(&edit(getNum())); } - template Iterator cbegin() const { return Iterator(&get(0)); } - template Iterator cend() const { return Iterator(&get(getNum())); } + template Iterator begin() { return Iterator(&edit(0), _stride); } + template Iterator end() { return Iterator(&edit(getNum()), _stride); } + template Iterator cbegin() const { return Iterator(&get(0), _stride); } + template Iterator cend() const { return Iterator(&get(getNum()), _stride); } // the number of elements of the specified type fitting in the view size template Index getNum() const { - return Index(_size / sizeof(T)); + return Index(_size / _stride); } template const T& get() const { @@ -347,7 +355,7 @@ public: } template const T& get(const Index index) const { - Resource::Size elementOffset = index * sizeof(T) + _offset; + Resource::Size elementOffset = index * _stride + _offset; #if _DEBUG if (!_buffer) { qDebug() << "Accessing null gpu::buffer!"; @@ -363,7 +371,7 @@ public: } template T& edit(const Index index) const { - Resource::Size elementOffset = index * sizeof(T) + _offset; + Resource::Size elementOffset = index * _stride + _offset; #if _DEBUG if (!_buffer) { qDebug() << "Accessing null gpu::buffer!"; diff --git a/libraries/model/src/model/Geometry.cpp b/libraries/model/src/model/Geometry.cpp index ed0201763a..7f0abbd9b9 100755 --- a/libraries/model/src/model/Geometry.cpp +++ b/libraries/model/src/model/Geometry.cpp @@ -42,6 +42,15 @@ void Mesh::addAttribute(Slot slot, const BufferView& buffer) { evalVertexFormat(); } +const BufferView Mesh::getAttributeBuffer(int attrib) const { + auto attribBuffer = _attributeBuffers.find(attrib); + if (attribBuffer != _attributeBuffers.end()) { + return attribBuffer->second; + } else { + return BufferView(); + } +} + void Mesh::evalVertexFormat() { auto vf = new VertexFormat(); int channelNum = 0; diff --git a/libraries/model/src/model/Geometry.h b/libraries/model/src/model/Geometry.h index 95f1c3bce7..ddefaf4e96 100755 --- a/libraries/model/src/model/Geometry.h +++ b/libraries/model/src/model/Geometry.h @@ -52,6 +52,7 @@ public: // Attribute Buffers int getNumAttributes() const { return _attributeBuffers.size(); } void addAttribute(Slot slot, const BufferView& buffer); + const BufferView getAttributeBuffer(int attrib) const; // Stream format const gpu::Stream::FormatPointer getVertexFormat() const { return _vertexFormat; } diff --git a/libraries/model/src/model/Skybox.cpp b/libraries/model/src/model/Skybox.cpp index a3f4220362..a34a3be3fd 100755 --- a/libraries/model/src/model/Skybox.cpp +++ b/libraries/model/src/model/Skybox.cpp @@ -107,7 +107,7 @@ void Skybox::render(gpu::Batch& batch, const ViewFrustum& viewFrustum, const Sky } else { // skybox has no cubemap, just clear the color buffer auto color = skybox.getColor(); - batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(skybox.getColor(),1.0f), 0.f, 0); + batch.clearFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, glm::vec4(color, 1.0f), 0.f, 0); } } diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 8bc27b872f..19fc278088 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -426,7 +426,12 @@ bool OctreePacketData::appendValue(const QUuid& uuid) { } bool OctreePacketData::appendValue(const QByteArray& bytes) { - bool success = appendRawData((const unsigned char*)bytes.constData(), bytes.size()); + // TODO: make this a ByteCountCoded leading byte + uint16_t length = bytes.size(); + bool success = appendValue(length); + if (success) { + success = appendRawData((const unsigned char*)bytes.constData(), bytes.size()); + } return success; } @@ -451,6 +456,12 @@ bool OctreePacketData::appendRawData(const unsigned char* data, int length) { return success; } + +bool OctreePacketData::appendRawData(QByteArray data) { + return appendRawData((unsigned char *)data.data(), data.size()); +} + + quint64 OctreePacketData::_compressContentTime = 0; quint64 OctreePacketData::_compressContentCalls = 0; @@ -545,7 +556,7 @@ void OctreePacketData::debugContent() { printf("\n"); } -int OctreePacketData::uppackDataFromBytes(const unsigned char* dataBytes, QString& result) { +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(length)); dataBytes += sizeof(length); @@ -554,7 +565,7 @@ int OctreePacketData::uppackDataFromBytes(const unsigned char* dataBytes, QStrin return sizeof(length) + length; } -int OctreePacketData::uppackDataFromBytes(const unsigned char* dataBytes, QUuid& result) { +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(length)); dataBytes += sizeof(length); @@ -567,9 +578,18 @@ int OctreePacketData::uppackDataFromBytes(const unsigned char* dataBytes, QUuid& return sizeof(length) + length; } -int OctreePacketData::uppackDataFromBytes(const unsigned char* dataBytes, xColor& result) { +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, xColor& result) { result.red = dataBytes[RED_INDEX]; result.green = dataBytes[GREEN_INDEX]; result.blue = dataBytes[BLUE_INDEX]; return sizeof(rgbColor); } + +int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result) { + uint16_t length; + memcpy(&length, dataBytes, sizeof(length)); + dataBytes += sizeof(length); + QByteArray value((const char*)dataBytes, length); + result = value; + return sizeof(length) + length; +} diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 28ea9aa681..5becb26ca2 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -183,6 +183,7 @@ public: /// appends raw bytes, might fail if byte would cause packet to be too large bool appendRawData(const unsigned char* data, int length); + bool appendRawData(QByteArray data); /// returns a byte offset from beginning of the uncompressed stream based on offset from end. /// Positive offsetFromEnd returns that many bytes before the end of uncompressed stream @@ -226,20 +227,21 @@ public: static quint64 getTotalBytesOfBitMasks() { return _totalBytesOfBitMasks; } /// total bytes of bitmasks static quint64 getTotalBytesOfColor() { return _totalBytesOfColor; } /// total bytes of color - static int uppackDataFromBytes(const unsigned char* dataBytes, float& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, bool& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, quint64& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, uint32_t& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, uint16_t& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, uint8_t& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, rgbColor& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, glm::quat& result) { int bytes = unpackOrientationQuatFromBytes(dataBytes, result); return bytes; } - static int uppackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, BackgroundMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } - static int uppackDataFromBytes(const unsigned char* dataBytes, QString& result); - static int uppackDataFromBytes(const unsigned char* dataBytes, QUuid& result); - static int uppackDataFromBytes(const unsigned char* dataBytes, xColor& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, float& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, glm::vec3& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, bool& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, quint64& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, uint32_t& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, uint16_t& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, uint8_t& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, rgbColor& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, glm::quat& result) { int bytes = unpackOrientationQuatFromBytes(dataBytes, result); return bytes; } + static int unpackDataFromBytes(const unsigned char* dataBytes, ShapeType& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, BackgroundMode& result) { memcpy(&result, dataBytes, sizeof(result)); return sizeof(result); } + static int unpackDataFromBytes(const unsigned char* dataBytes, QString& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, QUuid& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, xColor& result); + static int unpackDataFromBytes(const unsigned char* dataBytes, QByteArray& result); private: /// appends raw bytes, might fail if byte would cause packet to be too large diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 6c4b3dad75..9a24aabb34 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -132,7 +132,6 @@ void EntityMotionState::getWorldTransform(btTransform& worldTrans) const { uint32_t thisStep = ObjectMotionState::getWorldSimulationStep(); float dt = (thisStep - _lastKinematicStep) * PHYSICS_ENGINE_FIXED_SUBSTEP; _entity->simulateKinematicMotion(dt); - _entity->setLastSimulated(usecTimestampNow()); // bypass const-ness so we can remember the step const_cast(this)->_lastKinematicStep = thisStep; @@ -401,13 +400,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q properties.setAcceleration(_serverAcceleration); properties.setAngularVelocity(_serverAngularVelocity); - // we only update lastEdited when we're sending new physics data - quint64 lastSimulated = _entity->getLastSimulated(); - _entity->setLastEdited(lastSimulated); - properties.setLastEdited(lastSimulated); + // set the LastEdited of the properties but NOT the entity itself + quint64 now = usecTimestampNow(); + properties.setLastEdited(now); #ifdef WANT_DEBUG - quint64 now = usecTimestampNow(); + quint64 lastSimulated = _entity->getLastSimulated(); qCDebug(physics) << "EntityMotionState::sendUpdate()"; qCDebug(physics) << " EntityItemId:" << _entity->getEntityItemID() << "---------------------------------------------"; diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 80b56ccf2a..65279dc01a 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -61,9 +61,9 @@ public: virtual glm::vec3 getObjectPosition() const { return _entity->getPosition() - ObjectMotionState::getWorldOffset(); } virtual glm::quat getObjectRotation() const { return _entity->getRotation(); } - virtual const glm::vec3& getObjectLinearVelocity() const { return _entity->getVelocity(); } - virtual const glm::vec3& getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } - virtual const glm::vec3& getObjectGravity() const { return _entity->getGravity(); } + virtual glm::vec3 getObjectLinearVelocity() const { return _entity->getVelocity(); } + virtual glm::vec3 getObjectAngularVelocity() const { return _entity->getAngularVelocity(); } + virtual glm::vec3 getObjectGravity() const { return _entity->getGravity(); } virtual const QUuid& getObjectID() const { return _entity->getID(); } diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 337f09e719..246ed16627 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -112,9 +112,9 @@ public: virtual glm::vec3 getObjectPosition() const = 0; virtual glm::quat getObjectRotation() const = 0; - virtual const glm::vec3& getObjectLinearVelocity() const = 0; - virtual const glm::vec3& getObjectAngularVelocity() const = 0; - virtual const glm::vec3& getObjectGravity() const = 0; + virtual glm::vec3 getObjectLinearVelocity() const = 0; + virtual glm::vec3 getObjectAngularVelocity() const = 0; + virtual glm::vec3 getObjectGravity() const = 0; virtual const QUuid& getObjectID() const = 0; diff --git a/libraries/render-utils/src/DeferredLightingEffect.cpp b/libraries/render-utils/src/DeferredLightingEffect.cpp index 54e5388ec8..795b2a4389 100644 --- a/libraries/render-utils/src/DeferredLightingEffect.cpp +++ b/libraries/render-utils/src/DeferredLightingEffect.cpp @@ -93,16 +93,16 @@ void DeferredLightingEffect::init(AbstractViewStateInterface* viewState) { } void DeferredLightingEffect::bindSimpleProgram() { - DependencyManager::get()->setPrimaryDrawBuffers(true, true, true); + // DependencyManager::get()->setPrimaryDrawBuffers(true, true, true); _simpleProgram.bind(); _simpleProgram.setUniformValue(_glowIntensityLocation, DependencyManager::get()->getIntensity()); - glDisable(GL_BLEND); + // glDisable(GL_BLEND); } void DeferredLightingEffect::releaseSimpleProgram() { - glEnable(GL_BLEND); + // glEnable(GL_BLEND); _simpleProgram.release(); - DependencyManager::get()->setPrimaryDrawBuffers(true, false, false); + // DependencyManager::get()->setPrimaryDrawBuffers(true, false, false); } void DeferredLightingEffect::renderSolidSphere(float radius, int slices, int stacks, const glm::vec4& color) { diff --git a/libraries/shared/src/ShapeInfo.h b/libraries/shared/src/ShapeInfo.h index a3fbe55f36..e2a77fbba2 100644 --- a/libraries/shared/src/ShapeInfo.h +++ b/libraries/shared/src/ShapeInfo.h @@ -35,7 +35,8 @@ enum ShapeType { SHAPE_TYPE_CYLINDER_X, SHAPE_TYPE_CYLINDER_Y, SHAPE_TYPE_CYLINDER_Z, - SHAPE_TYPE_LINE + SHAPE_TYPE_LINE, + SHAPE_TYPE_POLYVOX }; class ShapeInfo { From 531ef1fa1c387c8a3f5d517c998b669cae1cc6e5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 14:05:43 -0700 Subject: [PATCH 29/46] change ice-server heartbeat behaviour --- domain-server/src/DomainServer.cpp | 21 +-- domain-server/src/DomainServer.h | 2 +- ice-server/src/IceServer.cpp | 164 ++++++++----------- ice-server/src/IceServer.h | 10 +- libraries/networking/src/LimitedNodeList.cpp | 25 ++- libraries/networking/src/LimitedNodeList.h | 9 +- libraries/networking/src/NodeList.cpp | 6 +- libraries/networking/src/PacketHeaders.cpp | 31 ++-- libraries/networking/src/PacketHeaders.h | 11 +- 9 files changed, 135 insertions(+), 144 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8eaf1a29f3..651685a3fd 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1357,24 +1357,21 @@ void DomainServer::sendICEPingPackets() { } } -void DomainServer::processICEHeartbeatResponse(const QByteArray& packet) { +void DomainServer::processICEPeerInformation(const QByteArray& packet) { // loop through the packet and pull out network peers // any peer we don't have we add to the hash, otherwise we update QDataStream iceResponseStream(packet); iceResponseStream.skipRawData(numBytesForPacketHeader(packet)); NetworkPeer receivedPeer; + iceResponseStream >> receivedPeer; - while (!iceResponseStream.atEnd()) { - iceResponseStream >> receivedPeer; - - if (!_connectedICEPeers.contains(receivedPeer.getUUID())) { - if (!_connectingICEPeers.contains(receivedPeer.getUUID())) { - qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; - } - - _connectingICEPeers[receivedPeer.getUUID()] = receivedPeer; + if (!_connectedICEPeers.contains(receivedPeer.getUUID())) { + if (!_connectingICEPeers.contains(receivedPeer.getUUID())) { + qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; } + + _connectingICEPeers[receivedPeer.getUUID()] = receivedPeer; } } @@ -1458,8 +1455,8 @@ void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiS processICEPingReply(receivedPacket, senderSockAddr); break; } - case PacketTypeIceServerHeartbeatResponse: - processICEHeartbeatResponse(receivedPacket); + case PacketTypeIceServerPeerInformation: + processICEPeerInformation(receivedPacket); break; default: break; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index c70c9cec13..302f683108 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -79,7 +79,7 @@ private: void setupAutomaticNetworking(); void sendHeartbeatToDataServer(const QString& networkAddress); void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr); - void processICEHeartbeatResponse(const QByteArray& packet); + void processICEPeerInformation(const QByteArray& packet); void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index e72555cac1..ec9943d2a1 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -33,133 +33,113 @@ IceServer::IceServer(int argc, char* argv[]) : qDebug() << "ice-server socket is listening on" << ICE_SERVER_DEFAULT_PORT; qDebug() << "monitoring http endpoint is listening on " << ICE_SERVER_MONITORING_PORT; _serverSocket.bind(QHostAddress::AnyIPv4, ICE_SERVER_DEFAULT_PORT); - + // call our process datagrams slot when the UDP socket has packets ready connect(&_serverSocket, &QUdpSocket::readyRead, this, &IceServer::processDatagrams); - + // setup our timer to clear inactive peers QTimer* inactivePeerTimer = new QTimer(this); connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers); inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS); - + } void IceServer::processDatagrams() { HifiSockAddr sendingSockAddr; QByteArray incomingPacket; - + while (_serverSocket.hasPendingDatagrams()) { incomingPacket.resize(_serverSocket.pendingDatagramSize()); - + _serverSocket.readDatagram(incomingPacket.data(), incomingPacket.size(), sendingSockAddr.getAddressPointer(), sendingSockAddr.getPortPointer()); - - - if (packetTypeForPacket(incomingPacket) == PacketTypeIceServerHeartbeat) { + + PacketType packetType = packetTypeForPacket(incomingPacket); + + if (packetType == PacketTypeIceServerHeartbeat) { + addOrUpdateHeartbeatingPeer(incomingPacket); + } else if (packetType == PacketTypeIceServerQuery) { + // this is a node hoping to connect to a heartbeating peer - do we have the heartbeating peer? QUuid senderUUID = uuidFromPacketHeader(incomingPacket); - + // pull the public and private sock addrs for this peer HifiSockAddr publicSocket, localSocket; - + QDataStream hearbeatStream(incomingPacket); hearbeatStream.skipRawData(numBytesForPacketHeader(incomingPacket)); - + hearbeatStream >> publicSocket >> localSocket; - - // make sure we have this sender in our peer hash - SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID); - - if (!matchingPeer) { - // if we don't have this sender we need to create them now - matchingPeer = SharedNetworkPeer(new NetworkPeer(senderUUID, publicSocket, localSocket)); - _activePeers.insert(senderUUID, matchingPeer); - - qDebug() << "Added a new network peer" << *matchingPeer; - } else { - // we already had the peer so just potentially update their sockets - matchingPeer->setPublicSocket(publicSocket); - matchingPeer->setLocalSocket(localSocket); - - qDebug() << "Matched hearbeat to existing network peer" << *matchingPeer; - } - - // update our last heard microstamp for this network peer to now - matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); - + // check if this node also included a UUID that they would like to connect to QUuid connectRequestID; hearbeatStream >> connectRequestID; - - // get the peers asking for connections with this peer - QSet& requestingConnections = _currentConnections[senderUUID]; - - if (!connectRequestID.isNull()) { - qDebug() << "Peer wants to connect to peer with ID" << uuidStringWithoutCurlyBraces(connectRequestID); - - // ensure this peer is in the set of current connections for the peer with ID it wants to connect with - _currentConnections[connectRequestID].insert(senderUUID); - - // add the ID of the node they have said they would like to connect to - requestingConnections.insert(connectRequestID); - } - - if (requestingConnections.size() > 0) { - // send a heartbeart response based on the set of connections - qDebug() << "Sending a heartbeat response to" << senderUUID << "who has" << requestingConnections.size() - << "potential connections"; - sendHeartbeatResponse(sendingSockAddr, requestingConnections); + + SharedNetworkPeer matchingPeer = _activePeers.value(connectRequestID); + + if (matchingPeer) { + // we have the peer they want to connect to - send them pack the information for that peer + sendPeerInformationPacket(matchingPeer, sendingSockAddr); } } } } -void IceServer::sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet& connections) { - QSet::iterator peerID = connections.begin(); - +SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(const QByteArray& incomingPacket) { + QUuid senderUUID = uuidFromPacketHeader(incomingPacket); + + // pull the public and private sock addrs for this peer + HifiSockAddr publicSocket, localSocket; + + QDataStream hearbeatStream(incomingPacket); + hearbeatStream.skipRawData(numBytesForPacketHeader(incomingPacket)); + + hearbeatStream >> publicSocket >> localSocket; + + // make sure we have this sender in our peer hash + SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID); + + if (!matchingPeer) { + // if we don't have this sender we need to create them now + matchingPeer = SharedNetworkPeer(new NetworkPeer(senderUUID, publicSocket, localSocket)); + _activePeers.insert(senderUUID, matchingPeer); + + qDebug() << "Added a new network peer" << *matchingPeer; + } else { + // we already had the peer so just potentially update their sockets + matchingPeer->setPublicSocket(publicSocket); + matchingPeer->setLocalSocket(localSocket); + + qDebug() << "Matched hearbeat to existing network peer" << *matchingPeer; + } + + // update our last heard microstamp for this network peer to now + matchingPeer->setLastHeardMicrostamp(usecTimestampNow()); + + return matchingPeer; +} + +void IceServer::sendPeerInformationPacket(const SharedNetworkPeer& peer, const HifiSockAddr& destinationSockAddr) { QByteArray outgoingPacket(MAX_PACKET_SIZE, 0); - int currentPacketSize = populatePacketHeaderWithUUID(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id); + int currentPacketSize = populatePacketHeaderWithUUID(outgoingPacket, PacketTypeIceServerPeerInformation, _id); int numHeaderBytes = currentPacketSize; - - // go through the connections, sending packets containing connection information for those nodes - while (peerID != connections.end()) { - SharedNetworkPeer matchingPeer = _activePeers.value(*peerID); - // if this node is inactive we remove it from the set - if (!matchingPeer) { - peerID = connections.erase(peerID); - } else { - // get the byte array for this peer - QByteArray peerBytes = matchingPeer->toByteArray(); - - if (currentPacketSize + peerBytes.size() > MAX_PACKET_SIZE) { - // write the current packet - _serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize, - destinationSockAddr.getAddress(), destinationSockAddr.getPort()); - - // reset the packet size to our number of header bytes - currentPacketSize = populatePacketHeaderWithUUID(outgoingPacket, PacketTypeIceServerHeartbeatResponse, _id); - } - - // append the current peer bytes - outgoingPacket.insert(currentPacketSize, peerBytes); - currentPacketSize += peerBytes.size(); - - ++peerID; - } - } - - if (currentPacketSize > numHeaderBytes) { - // write the last packet, if there is data in it - _serverSocket.writeDatagram(outgoingPacket.data(), currentPacketSize, - destinationSockAddr.getAddress(), destinationSockAddr.getPort()); - } + + // get the byte array for this peer + QByteArray peerBytes = peer->toByteArray(); + outgoingPacket.replace(numHeaderBytes, peerBytes.size(), peerBytes); + + currentPacketSize += peerBytes.size(); + + // write the current packet + _serverSocket.writeDatagram(outgoingPacket.data(), outgoingPacket.size(), + destinationSockAddr.getAddress(), destinationSockAddr.getPort()); } void IceServer::clearInactivePeers() { NetworkPeerHash::iterator peerItem = _activePeers.begin(); - + while (peerItem != _activePeers.end()) { SharedNetworkPeer peer = peerItem.value(); - + if ((usecTimestampNow() - peer->getLastHeardMicrostamp()) > (PEER_SILENCE_THRESHOLD_MSECS * 1000)) { qDebug() << "Removing peer from memory for inactivity -" << *peer; peerItem = _activePeers.erase(peerItem); @@ -171,11 +151,9 @@ void IceServer::clearInactivePeers() { } bool IceServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { - // // We need an HTTP handler in order to monitor the health of the ice server // The correct functioning of the ICE server will be determined by its HTTP availability, - // - + if (connection->requestOperation() == QNetworkAccessManager::GetOperation) { if (url.path() == "/status") { connection->respond(HTTPConnection::StatusCode200, QByteArray::number(_activePeers.size())); diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index be6d298e3d..454a867ec2 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -31,14 +31,14 @@ private slots: void processDatagrams(); void clearInactivePeers(); private: - - void sendHeartbeatResponse(const HifiSockAddr& destinationSockAddr, QSet& connections); - + + SharedNetworkPeer addOrUpdateHeartbeatingPeer(const QByteArray& incomingPacket); + void sendPeerInformationPacket(const SharedNetworkPeer& peer, const HifiSockAddr& destinationSockAddr); + QUuid _id; QUdpSocket _serverSocket; NetworkPeerHash _activePeers; - QHash > _currentConnections; HTTPManager _httpManager; }; -#endif // hifi_IceServer_h \ No newline at end of file +#endif // hifi_IceServer_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 7102df62e0..61d844c1be 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -814,23 +814,30 @@ void LimitedNodeList::updateLocalSockAddr() { } } -void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr, - QUuid headerID, const QUuid& connectionRequestID) { +void LimitedNodeList::sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr) { + sendPacketToIceServer(PacketTypeIceServerHeartbeat, iceServerSockAddr, _sessionUUID); +} - if (headerID.isNull()) { - headerID = _sessionUUID; - } +void LimitedNodeList::sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, + const QUuid& peerID) { + sendPacketToIceServer(PacketTypeIceServerQuery, iceServerSockAddr, clientID, peerID); +} - QByteArray iceRequestByteArray = byteArrayWithUUIDPopulatedHeader(PacketTypeIceServerHeartbeat, headerID); +void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, + const QUuid& headerID, const QUuid& peerID) { + + QByteArray iceRequestByteArray = byteArrayWithUUIDPopulatedHeader(packetType, headerID); QDataStream iceDataStream(&iceRequestByteArray, QIODevice::Append); iceDataStream << _publicSockAddr << _localSockAddr; - if (!connectionRequestID.isNull()) { - iceDataStream << connectionRequestID; + if (packetType == PacketTypeIceServerQuery) { + assert(!peerID.isNull()); + + iceDataStream << peerID; qCDebug(networking) << "Sending packet to ICE server to request connection info for peer with ID" - << uuidStringWithoutCurlyBraces(connectionRequestID); + << uuidStringWithoutCurlyBraces(peerID); } writeUnverifiedDatagram(iceRequestByteArray, iceServerSockAddr); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index fdfb1fa834..ec4b7546f0 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -89,7 +89,7 @@ public: SetPublicSocketFromSTUN, SetICEServerHostname, SetICEServerSocket, - SendICEServerHearbeat, + SendICEServerQuery, ReceiveDSPeerInformation, SendPingsToDS, SetDomainHostname, @@ -178,8 +178,8 @@ public: virtual bool processSTUNResponse(const QByteArray& packet); - void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr, - QUuid headerID = QUuid(), const QUuid& connectRequestID = QUuid()); + void sendHeartbeatToIceServer(const HifiSockAddr& iceServerSockAddr); + void sendPeerQueryToIceServer(const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerID); template void eachNode(NodeLambda functor) { @@ -276,6 +276,9 @@ protected: void stopInitialSTUNUpdate(bool success); + void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& headerID, + const QUuid& peerRequestID = QUuid()); + QUuid _sessionUUID; NodeHash _nodeHash; QReadWriteLock _nodeMutex; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index bfd0c64b96..dc6b33d0a9 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -192,7 +192,7 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr _domainHandler.parseDTLSRequirementPacket(packet); break; } - case PacketTypeIceServerHeartbeatResponse: { + case PacketTypeIceServerPeerInformation: { _domainHandler.processICEResponsePacket(packet); break; } @@ -482,9 +482,9 @@ void NodeList::handleICEConnectionToDomainServer() { _domainHandler.getICEPeer().resetConnectionAttempts(); - flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendICEServerHearbeat); + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendICEServerQuery); - LimitedNodeList::sendHeartbeatToIceServer(_domainHandler.getICEServerSockAddr(), + LimitedNodeList::sendPeerQueryToIceServer(_domainHandler.getICEServerSockAddr(), _domainHandler.getICEClientID(), _domainHandler.getICEDomainID()); } diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index c5030fb1e7..4b02a32cad 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -78,6 +78,9 @@ PacketVersion versionForPacketType(PacketType packetType) { return 2; case PacketTypeAudioStreamStats: return 1; + case PacketTypeIceServerHeartbeat: + case PacketTypeIceServerQuery: + return 1; default: return 0; } @@ -125,7 +128,9 @@ QString nameForPacketType(PacketType packetType) { PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityEditNack); PACKET_TYPE_NAME_LOOKUP(PacketTypeSignedTransactionPayment); PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerHeartbeat); - PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerHeartbeatResponse); + PACKET_TYPE_NAME_LOOKUP(PacketTypeDomainServerAddedNode); + PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerQuery); + PACKET_TYPE_NAME_LOOKUP(PacketTypeIceServerPeerInformation); PACKET_TYPE_NAME_LOOKUP(PacketTypeUnverifiedPing); PACKET_TYPE_NAME_LOOKUP(PacketTypeUnverifiedPingReply); PACKET_TYPE_NAME_LOOKUP(PacketTypeEntityAdd); @@ -149,33 +154,33 @@ int populatePacketHeaderWithUUID(QByteArray& packet, PacketType packetType, cons if (packet.size() < numBytesForPacketHeaderGivenPacketType(packetType)) { packet.resize(numBytesForPacketHeaderGivenPacketType(packetType)); } - + return populatePacketHeaderWithUUID(packet.data(), packetType, connectionUUID); } int populatePacketHeaderWithUUID(char* packet, PacketType packetType, const QUuid& connectionUUID) { int numTypeBytes = packArithmeticallyCodedValue(packetType, packet); packet[numTypeBytes] = versionForPacketType(packetType); - + char* position = packet + numTypeBytes + sizeof(PacketVersion); - + QByteArray rfcUUID = connectionUUID.toRfc4122(); memcpy(position, rfcUUID.constData(), NUM_BYTES_RFC4122_UUID); position += NUM_BYTES_RFC4122_UUID; - + if (!NON_VERIFIED_PACKETS.contains(packetType)) { // pack 16 bytes of zeros where the md5 hash will be placed once data is packed memset(position, 0, NUM_BYTES_MD5_HASH); position += NUM_BYTES_MD5_HASH; } - + if (SEQUENCE_NUMBERED_PACKETS.contains(packetType)) { // Pack zeros for the number of bytes that the sequence number requires. // The LimitedNodeList will handle packing in the sequence number when sending out the packet. memset(position, 0, sizeof(PacketSequenceNumber)); position += sizeof(PacketSequenceNumber); } - + // return the number of bytes written for pointer pushing return position - packet; } @@ -235,13 +240,13 @@ PacketSequenceNumber sequenceNumberFromHeader(const QByteArray& packet, PacketTy if (packetType == PacketTypeUnknown) { packetType = packetTypeForPacket(packet); } - + PacketSequenceNumber result = DEFAULT_SEQUENCE_NUMBER; - + if (SEQUENCE_NUMBERED_PACKETS.contains(packetType)) { memcpy(&result, packet.data() + sequenceNumberOffsetForPacketType(packetType), sizeof(PacketSequenceNumber)); } - + return result; } @@ -249,7 +254,7 @@ void replaceHashInPacket(QByteArray& packet, const QUuid& connectionUUID, Packet if (packetType == PacketTypeUnknown) { packetType = packetTypeForPacket(packet); } - + packet.replace(hashOffsetForPacketType(packetType), NUM_BYTES_MD5_HASH, hashForPacketAndConnectionUUID(packet, connectionUUID)); } @@ -258,7 +263,7 @@ void replaceSequenceNumberInPacket(QByteArray& packet, PacketSequenceNumber sequ if (packetType == PacketTypeUnknown) { packetType = packetTypeForPacket(packet); } - + packet.replace(sequenceNumberOffsetForPacketType(packetType), sizeof(PacketSequenceNumber), reinterpret_cast(&sequenceNumber), sizeof(PacketSequenceNumber)); } @@ -268,7 +273,7 @@ void replaceHashAndSequenceNumberInPacket(QByteArray& packet, const QUuid& conne if (packetType == PacketTypeUnknown) { packetType = packetTypeForPacket(packet); } - + replaceHashInPacket(packet, connectionUUID, packetType); replaceSequenceNumberInPacket(packet, sequenceNumber, packetType); } diff --git a/libraries/networking/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h index a702df7798..cf1a323741 100644 --- a/libraries/networking/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -25,6 +25,7 @@ // NOTE: if adding a new packet packetType, you can replace one marked usable or add at the end // NOTE: if you want the name of the packet packetType to be available for debugging or logging, update nameForPacketType() as well + enum PacketType { PacketTypeUnknown, // 0 PacketTypeStunResponse, @@ -50,8 +51,8 @@ enum PacketType { PacketTypeDomainServerPathQuery, PacketTypeDomainServerPathResponse, PacketTypeDomainServerAddedNode, - UNUSED_4, - UNUSED_5, // 25 + PacketTypeIceServerPeerInformation, + PacketTypeIceServerQuery, // 25 PacketTypeOctreeStats, PacketTypeJurisdiction, PacketTypeJurisdictionRequest, @@ -77,7 +78,6 @@ enum PacketType { PacketTypeEntityEditNack, PacketTypeSignedTransactionPayment, PacketTypeIceServerHeartbeat, // 50 - PacketTypeIceServerHeartbeatResponse, PacketTypeUnverifiedPing, PacketTypeUnverifiedPingReply, PacketTypeParticleEntitiesFix @@ -96,8 +96,9 @@ const QSet NON_VERIFIED_PACKETS = QSet() << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse << PacketTypeNodeJsonStats << PacketTypeEntityQuery << PacketTypeOctreeDataNack << PacketTypeEntityEditNack - << PacketTypeIceServerHeartbeat << PacketTypeIceServerHeartbeatResponse - << PacketTypeUnverifiedPing << PacketTypeUnverifiedPingReply << PacketTypeStopNode + << PacketTypeIceServerHeartbeat << PacketTypeIceServerPeerInformation + << PacketTypeIceServerQuery << PacketTypeUnverifiedPing + << PacketTypeUnverifiedPingReply << PacketTypeStopNode << PacketTypeDomainServerPathQuery << PacketTypeDomainServerPathResponse << PacketTypeDomainServerAddedNode; From 7be16da1a8d5affeb3790b4a1a033b067a69120a Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 28 May 2015 14:24:39 -0700 Subject: [PATCH 30/46] compute correct time-of-flight for extrapolation --- libraries/entities/src/EntityItem.cpp | 33 ++++++++++++++++----------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 64196d1c35..4a9b64d1ca 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -480,17 +480,21 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef bytesRead += encodedUpdateDelta.size(); // Newer bitstreams will have a last simulated and a last updated value + quint64 lastSimulatedFromBufferAdjusted = now; if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_LAST_SIMULATED_TIME) { // last simulated is stored as ByteCountCoded delta from lastEdited QByteArray encodedSimulatedDelta = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded simulatedDeltaCoder = encodedSimulatedDelta; quint64 simulatedDelta = simulatedDeltaCoder; if (overwriteLocalData) { - _lastSimulated = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that + lastSimulatedFromBufferAdjusted = lastEditedFromBufferAdjusted + simulatedDelta; // don't adjust for clock skew since we already did that + if (lastSimulatedFromBufferAdjusted > now) { + lastSimulatedFromBufferAdjusted = now; + } #ifdef WANT_DEBUG - qCDebug(entities) << " _lastSimulated:" << debugTime(_lastSimulated, now); qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); + qCDebug(entities) << " lastSimulatedFromBufferAdjusted:" << debugTime(lastSimulatedFromBufferAdjusted, now); #endif } encodedSimulatedDelta = simulatedDeltaCoder; // determine true length @@ -606,8 +610,8 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // use our simulation helper routine to get a best estimate of where the entity should be. const float MIN_TIME_SKIP = 0.0f; const float MAX_TIME_SKIP = 1.0f; // in seconds - float skipTimeForward = glm::clamp((float)(now - _lastSimulated) / (float)(USECS_PER_SECOND), - MIN_TIME_SKIP, MAX_TIME_SKIP); + float skipTimeForward = glm::clamp((float)(now - lastSimulatedFromBufferAdjusted) / (float)(USECS_PER_SECOND), + MIN_TIME_SKIP, MAX_TIME_SKIP); if (skipTimeForward > 0.0f) { #ifdef WANT_DEBUG qCDebug(entities) << "skipTimeForward:" << skipTimeForward; @@ -617,19 +621,22 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef // we don't want the side effect of flag setting. simulateKinematicMotion(skipTimeForward, false); } - _lastSimulated = now; } auto nodeList = DependencyManager::get(); const QUuid& myNodeID = nodeList->getSessionUUID(); - if (overwriteLocalData && _simulatorID == myNodeID && !_simulatorID.isNull()) { - // we own the simulation, so we keep our transform+velocities and remove any related dirty flags - // rather than accept the values in the packet - _position = savePosition; - _rotation = saveRotation; - _velocity = saveVelocity; - _angularVelocity = saveAngularVelocity; - _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); + if (overwriteLocalData) { + if (_simulatorID == myNodeID && !_simulatorID.isNull()) { + // we own the simulation, so we keep our transform+velocities and remove any related dirty flags + // rather than accept the values in the packet + _position = savePosition; + _rotation = saveRotation; + _velocity = saveVelocity; + _angularVelocity = saveAngularVelocity; + _dirtyFlags &= ~(EntityItem::DIRTY_TRANSFORM | EntityItem::DIRTY_VELOCITIES); + } else { + _lastSimulated = now; + } } return bytesRead; From e096cbe04033d14f043768cbf6404611499adde2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 14:25:18 -0700 Subject: [PATCH 31/46] have ice-server immediately send peer to DS --- domain-server/src/DomainServer.cpp | 11 +-- ice-server/src/IceServer.cpp | 20 +++-- ice-server/src/IceServer.h | 2 +- libraries/networking/src/NetworkPeer.cpp | 110 ++++++++++++++++++++++- libraries/networking/src/NetworkPeer.h | 22 ++++- libraries/networking/src/Node.cpp | 73 --------------- libraries/networking/src/Node.h | 16 ---- 7 files changed, 142 insertions(+), 112 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 651685a3fd..0ffdabcb8a 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -656,16 +656,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock canAdjustLocks, canRez); // So that we can send messages to this node at will - we need to activate the correct socket on this node now - if (senderSockAddr == publicSockAddr) { - newNode->activatePublicSocket(); - } else if (senderSockAddr == localSockAddr) { - newNode->activateLocalSocket(); - } else { - // set the Node's symmetric socket to the sender socket - newNode->setSymmetricSocket(senderSockAddr); - // activate that symmetric socket - newNode->activateSymmetricSocket(); - } + newNode->activateMatchingOrNewSymmetricSocket(senderSockAddr); // when the newNode is created the linked data is also created // if this was a static assignment set the UUID, set the sendingSockAddr diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index ec9943d2a1..c39a2259af 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -57,8 +57,12 @@ void IceServer::processDatagrams() { PacketType packetType = packetTypeForPacket(incomingPacket); if (packetType == PacketTypeIceServerHeartbeat) { - addOrUpdateHeartbeatingPeer(incomingPacket); + SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(incomingPacket); + + // so that we can send packets to the heartbeating peer when we need, we need to activate a socket now + peer->activateMatchingOrNewSymmetricSocket(sendingSockAddr); } else if (packetType == PacketTypeIceServerQuery) { + // this is a node hoping to connect to a heartbeating peer - do we have the heartbeating peer? QUuid senderUUID = uuidFromPacketHeader(incomingPacket); @@ -78,7 +82,13 @@ void IceServer::processDatagrams() { if (matchingPeer) { // we have the peer they want to connect to - send them pack the information for that peer - sendPeerInformationPacket(matchingPeer, sendingSockAddr); + sendPeerInformationPacket(matchingPeer.data(), &sendingSockAddr); + + // we also need to send them to the active peer they are hoping to connect to + // create a dummy peer object we can pass to sendPeerInformationPacket + + NetworkPeer dummyPeer(senderUUID, publicSocket, localSocket); + sendPeerInformationPacket(dummyPeer, matchingPeer->getActiveSocket()); } } } @@ -118,20 +128,20 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(const QByteArray& incom return matchingPeer; } -void IceServer::sendPeerInformationPacket(const SharedNetworkPeer& peer, const HifiSockAddr& destinationSockAddr) { +void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) { QByteArray outgoingPacket(MAX_PACKET_SIZE, 0); int currentPacketSize = populatePacketHeaderWithUUID(outgoingPacket, PacketTypeIceServerPeerInformation, _id); int numHeaderBytes = currentPacketSize; // get the byte array for this peer - QByteArray peerBytes = peer->toByteArray(); + QByteArray peerBytes = peer.toByteArray(); outgoingPacket.replace(numHeaderBytes, peerBytes.size(), peerBytes); currentPacketSize += peerBytes.size(); // write the current packet _serverSocket.writeDatagram(outgoingPacket.data(), outgoingPacket.size(), - destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + destinationSockAddr->getAddress(), destinationSockAddr->getPort()); } void IceServer::clearInactivePeers() { diff --git a/ice-server/src/IceServer.h b/ice-server/src/IceServer.h index 454a867ec2..1f213fa606 100644 --- a/ice-server/src/IceServer.h +++ b/ice-server/src/IceServer.h @@ -33,7 +33,7 @@ private slots: private: SharedNetworkPeer addOrUpdateHeartbeatingPeer(const QByteArray& incomingPacket); - void sendPeerInformationPacket(const SharedNetworkPeer& peer, const HifiSockAddr& destinationSockAddr); + void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr); QUuid _id; QUdpSocket _serverSocket; diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index a6ed7e44fb..77e3de13e0 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -9,12 +9,16 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#include +#include "NetworkPeer.h" + +#include +#include #include #include -#include "NetworkPeer.h" +#include "NetworkLogging.h" + #include "BandwidthRecorder.h" NetworkPeer::NetworkPeer(QObject* parent) : @@ -22,6 +26,8 @@ NetworkPeer::NetworkPeer(QObject* parent) : _uuid(), _publicSocket(), _localSocket(), + _symmetricSocket(), + _activeSocket(NULL), _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _lastHeardMicrostamp(usecTimestampNow()), _connectionAttempts(0) @@ -29,10 +35,13 @@ NetworkPeer::NetworkPeer(QObject* parent) : } -NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : +NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, QObject* parent) : + QObject(parent), _uuid(uuid), _publicSocket(publicSocket), _localSocket(localSocket), + _symmetricSocket(), + _activeSocket(NULL), _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _lastHeardMicrostamp(usecTimestampNow()), _connectionAttempts(0) @@ -44,6 +53,17 @@ NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) : QObject() { _uuid = otherPeer._uuid; _publicSocket = otherPeer._publicSocket; _localSocket = otherPeer._localSocket; + _symmetricSocket = otherPeer._symmetricSocket; + + if (otherPeer._activeSocket) { + if (otherPeer._activeSocket == &otherPeer._localSocket) { + _activeSocket = &_localSocket; + } else if (otherPeer._activeSocket == &otherPeer._publicSocket) { + _activeSocket = &_publicSocket; + } else if (otherPeer._activeSocket == &otherPeer._symmetricSocket) { + _activeSocket = &_symmetricSocket; + } + } _wakeTimestamp = otherPeer._wakeTimestamp; _lastHeardMicrostamp = otherPeer._lastHeardMicrostamp; @@ -62,11 +82,95 @@ void NetworkPeer::swap(NetworkPeer& otherPeer) { swap(_uuid, otherPeer._uuid); swap(_publicSocket, otherPeer._publicSocket); swap(_localSocket, otherPeer._localSocket); + swap(_symmetricSocket, otherPeer._symmetricSocket); + swap(_activeSocket, otherPeer._activeSocket); swap(_wakeTimestamp, otherPeer._wakeTimestamp); swap(_lastHeardMicrostamp, otherPeer._lastHeardMicrostamp); swap(_connectionAttempts, otherPeer._connectionAttempts); } +void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { + if (publicSocket != _publicSocket) { + if (_activeSocket == &_publicSocket) { + // if the active socket was the public socket then reset it to NULL + _activeSocket = NULL; + } + + if (!_publicSocket.isNull()) { + qCDebug(networking) << "Public socket change for node" << *this; + } + + _publicSocket = publicSocket; + } +} + +void NetworkPeer::setLocalSocket(const HifiSockAddr& localSocket) { + if (localSocket != _localSocket) { + if (_activeSocket == &_localSocket) { + // if the active socket was the local socket then reset it to NULL + _activeSocket = NULL; + } + + if (!_localSocket.isNull()) { + qCDebug(networking) << "Local socket change for node" << *this; + } + + _localSocket = localSocket; + } +} + +void NetworkPeer::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { + if (symmetricSocket != _symmetricSocket) { + if (_activeSocket == &_symmetricSocket) { + // if the active socket was the symmetric socket then reset it to NULL + _activeSocket = NULL; + } + + if (!_symmetricSocket.isNull()) { + qCDebug(networking) << "Symmetric socket change for node" << *this; + } + + _symmetricSocket = symmetricSocket; + } +} + +void NetworkPeer::setActiveSocket(HifiSockAddr* discoveredSocket) { + _activeSocket = discoveredSocket; + + // we have an active socket, stop our ping timer + stopPingTimer(); + + // we're now considered connected to this peer - reset the number of connection attemps + resetConnectionAttempts(); +} + +void NetworkPeer::activateLocalSocket() { + qCDebug(networking) << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_localSocket); +} + +void NetworkPeer::activatePublicSocket() { + qCDebug(networking) << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_publicSocket); +} + +void NetworkPeer::activateSymmetricSocket() { + qCDebug(networking) << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_symmetricSocket); +} + +void NetworkPeer::activateMatchingOrNewSymmetricSocket(const HifiSockAddr& matchableSockAddr) { + if (matchableSockAddr == _publicSocket) { + activatePublicSocket(); + } else if (matchableSockAddr == _localSocket) { + activateLocalSocket(); + } else { + // set the Node's symmetric socket to the passed socket + setSymmetricSocket(matchableSockAddr); + activateSymmetricSocket(); + } +} + void NetworkPeer::softReset() { // a soft reset should clear the sockets and reset the number of connection attempts _localSocket.clear(); diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 53b79c0126..a4db99ba9e 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -29,9 +29,8 @@ class NetworkPeer : public QObject { Q_OBJECT public: NetworkPeer(QObject* parent = 0); - NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); + NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, QObject* parent = 0); - // privatize copy and assignment operator to disallow peer copying NetworkPeer(const NetworkPeer &otherPeer); NetworkPeer& operator=(const NetworkPeer& otherPeer); @@ -44,9 +43,20 @@ public: void softReset(); const HifiSockAddr& getPublicSocket() const { return _publicSocket; } - virtual void setPublicSocket(const HifiSockAddr& publicSocket) { _publicSocket = publicSocket; } const HifiSockAddr& getLocalSocket() const { return _localSocket; } - virtual void setLocalSocket(const HifiSockAddr& localSocket) { _localSocket = localSocket; } + const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; } + + void setPublicSocket(const HifiSockAddr& publicSocket); + void setLocalSocket(const HifiSockAddr& localSocket); + void setSymmetricSocket(const HifiSockAddr& symmetricSocket); + + const HifiSockAddr* getActiveSocket() const { return _activeSocket; } + + void activatePublicSocket(); + void activateLocalSocket(); + void activateSymmetricSocket(); + + void activateMatchingOrNewSymmetricSocket(const HifiSockAddr& matchableSockAddr); quint64 getWakeTimestamp() const { return _wakeTimestamp; } void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; } @@ -74,10 +84,14 @@ public slots: signals: void pingTimerTimeout(); protected: + void setActiveSocket(HifiSockAddr* discoveredSocket); + QUuid _uuid; HifiSockAddr _publicSocket; HifiSockAddr _localSocket; + HifiSockAddr _symmetricSocket; + HifiSockAddr* _activeSocket; quint64 _wakeTimestamp; quint64 _lastHeardMicrostamp; diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index e4144d080c..94b57b3f59 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -16,7 +16,6 @@ #include "Node.h" #include "SharedUtil.h" -#include "NetworkLogging.h" #include #include @@ -45,8 +44,6 @@ Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez, const QUuid& connectionSecret) : NetworkPeer(uuid, publicSocket, localSocket), _type(type), - _activeSocket(NULL), - _symmetricSocket(), _connectionSecret(connectionSecret), _linkedData(NULL), _isAlive(true), @@ -69,76 +66,6 @@ void Node::updateClockSkewUsec(int clockSkewSample) { _clockSkewUsec = (int)_clockSkewMovingPercentile.getValueAtPercentile(); } -void Node::setPublicSocket(const HifiSockAddr& publicSocket) { - if (publicSocket != _publicSocket) { - if (_activeSocket == &_publicSocket) { - // if the active socket was the public socket then reset it to NULL - _activeSocket = NULL; - } - - if (!_publicSocket.isNull()) { - qCDebug(networking) << "Public socket change for node" << *this; - } - - _publicSocket = publicSocket; - } -} - -void Node::setLocalSocket(const HifiSockAddr& localSocket) { - if (localSocket != _localSocket) { - if (_activeSocket == &_localSocket) { - // if the active socket was the local socket then reset it to NULL - _activeSocket = NULL; - } - - if (!_localSocket.isNull()) { - qCDebug(networking) << "Local socket change for node" << *this; - } - - _localSocket = localSocket; - } -} - -void Node::setSymmetricSocket(const HifiSockAddr& symmetricSocket) { - if (symmetricSocket != _symmetricSocket) { - if (_activeSocket == &_symmetricSocket) { - // if the active socket was the symmetric socket then reset it to NULL - _activeSocket = NULL; - } - - if (!_symmetricSocket.isNull()) { - qCDebug(networking) << "Symmetric socket change for node" << *this; - } - - _symmetricSocket = symmetricSocket; - } -} - -void Node::setActiveSocket(HifiSockAddr* discoveredSocket) { - _activeSocket = discoveredSocket; - - // we have an active socket, stop our ping timer - stopPingTimer(); - - // we're now considered connected to this peer - reset the number of connection attemps - resetConnectionAttempts(); -} - -void Node::activateLocalSocket() { - qCDebug(networking) << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - setActiveSocket(&_localSocket); -} - -void Node::activatePublicSocket() { - qCDebug(networking) << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - setActiveSocket(&_publicSocket); -} - -void Node::activateSymmetricSocket() { - qCDebug(networking) << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - setActiveSocket(&_symmetricSocket); -} - PacketSequenceNumber Node::getLastSequenceNumberForPacketType(PacketType packetType) const { auto typeMatch = _lastSequenceNumbers.find(packetType); if (typeMatch != _lastSequenceNumbers.end()) { diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 884e65a626..1eaf1a02c7 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -72,23 +72,12 @@ public: void updateClockSkewUsec(int clockSkewSample); QMutex& getMutex() { return _mutex; } - virtual void setPublicSocket(const HifiSockAddr& publicSocket); - virtual void setLocalSocket(const HifiSockAddr& localSocket); - const HifiSockAddr& getSymmetricSocket() const { return _symmetricSocket; } - virtual void setSymmetricSocket(const HifiSockAddr& symmetricSocket); - - const HifiSockAddr* getActiveSocket() const { return _activeSocket; } - void setCanAdjustLocks(bool canAdjustLocks) { _canAdjustLocks = canAdjustLocks; } bool getCanAdjustLocks() { return _canAdjustLocks; } void setCanRez(bool canRez) { _canRez = canRez; } bool getCanRez() { return _canRez; } - void activatePublicSocket(); - void activateLocalSocket(); - void activateSymmetricSocket(); - void setLastSequenceNumberForPacketType(PacketSequenceNumber sequenceNumber, PacketType packetType) { _lastSequenceNumbers[packetType] = sequenceNumber; } PacketSequenceNumber getLastSequenceNumberForPacketType(PacketType packetType) const; @@ -101,13 +90,8 @@ private: Node(const Node &otherNode); Node& operator=(Node otherNode); - void setActiveSocket(HifiSockAddr* discoveredSocket); - NodeType_t _type; - HifiSockAddr* _activeSocket; - HifiSockAddr _symmetricSocket; - QUuid _connectionSecret; NodeData* _linkedData; bool _isAlive; From da0c9fbc31d79de19afcd1b42fca79224384d1e5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 14:37:16 -0700 Subject: [PATCH 32/46] fix send of existing peer to querier --- ice-server/src/IceServer.cpp | 4 +--- libraries/networking/src/LimitedNodeList.cpp | 2 +- libraries/networking/src/NetworkPeer.cpp | 19 ++++++++++++------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/ice-server/src/IceServer.cpp b/ice-server/src/IceServer.cpp index c39a2259af..8b5e5a4b39 100644 --- a/ice-server/src/IceServer.cpp +++ b/ice-server/src/IceServer.cpp @@ -82,7 +82,7 @@ void IceServer::processDatagrams() { if (matchingPeer) { // we have the peer they want to connect to - send them pack the information for that peer - sendPeerInformationPacket(matchingPeer.data(), &sendingSockAddr); + sendPeerInformationPacket(*(matchingPeer.data()), &sendingSockAddr); // we also need to send them to the active peer they are hoping to connect to // create a dummy peer object we can pass to sendPeerInformationPacket @@ -118,8 +118,6 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(const QByteArray& incom // we already had the peer so just potentially update their sockets matchingPeer->setPublicSocket(publicSocket); matchingPeer->setLocalSocket(localSocket); - - qDebug() << "Matched hearbeat to existing network peer" << *matchingPeer; } // update our last heard microstamp for this network peer to now diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 61d844c1be..c02eff4b75 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -840,7 +840,7 @@ void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSoc << uuidStringWithoutCurlyBraces(peerID); } - writeUnverifiedDatagram(iceRequestByteArray, iceServerSockAddr); + writeUnverifiedDatagram(iceRequestByteArray, HifiSockAddr("127.0.0.1", ICE_SERVER_DEFAULT_PORT)); } void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort) { diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 77e3de13e0..f1197e5da0 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -145,18 +145,24 @@ void NetworkPeer::setActiveSocket(HifiSockAddr* discoveredSocket) { } void NetworkPeer::activateLocalSocket() { - qCDebug(networking) << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - setActiveSocket(&_localSocket); + if (_activeSocket != &_localSocket) { + qCDebug(networking) << "Activating local socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_localSocket); + } } void NetworkPeer::activatePublicSocket() { - qCDebug(networking) << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - setActiveSocket(&_publicSocket); + if (_activeSocket != &_publicSocket) { + qCDebug(networking) << "Activating public socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_publicSocket); + } } void NetworkPeer::activateSymmetricSocket() { - qCDebug(networking) << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); - setActiveSocket(&_symmetricSocket); + if (_activeSocket != &_symmetricSocket) { + qCDebug(networking) << "Activating symmetric socket for network peer with ID" << uuidStringWithoutCurlyBraces(_uuid); + setActiveSocket(&_symmetricSocket); + } } void NetworkPeer::activateMatchingOrNewSymmetricSocket(const HifiSockAddr& matchableSockAddr) { @@ -182,7 +188,6 @@ void NetworkPeer::softReset() { _connectionAttempts = 0; } - QByteArray NetworkPeer::toByteArray() const { QByteArray peerByteArray; From 9d2e1773a09dc2cf9306585cd6da93dc16b06a8c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 15:31:21 -0700 Subject: [PATCH 33/46] fix memory issues for immediate ice pings --- domain-server/src/DomainServer.cpp | 116 ++++++++++----------- domain-server/src/DomainServer.h | 8 +- libraries/networking/src/DomainHandler.cpp | 11 +- libraries/networking/src/NetworkPeer.cpp | 50 +++------ libraries/networking/src/NetworkPeer.h | 6 +- libraries/networking/src/NodeList.cpp | 4 +- 6 files changed, 84 insertions(+), 111 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 0ffdabcb8a..7827271aa9 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -369,7 +369,7 @@ void DomainServer::setupAutomaticNetworking() { // setup a timer to heartbeat with the ice-server every so often QTimer* iceHeartbeatTimer = new QTimer(this); - connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::performICEUpdates); + connect(iceHeartbeatTimer, &QTimer::timeout, this, &DomainServer::sendHeartbeatToIceServer); iceHeartbeatTimer->start(ICE_HEARBEAT_INTERVAL_MSECS); } @@ -624,9 +624,17 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock QUuid nodeUUID; - if (_connectingICEPeers.contains(packetUUID) || _connectedICEPeers.contains(packetUUID)) { + HifiSockAddr discoveredSocket = senderSockAddr; + SharedNetworkPeer connectedPeer = _icePeers.value(packetUUID); + + if (connectedPeer) { // this user negotiated a connection with us via ICE, so re-use their ICE client ID nodeUUID = packetUUID; + + if (connectedPeer->getActiveSocket()) { + // set their discovered socket to whatever the activated socket on the network peer object was + discoveredSocket = *connectedPeer->getActiveSocket(); + } } else { // we got a packetUUID we didn't recognize, just add the node nodeUUID = QUuid::createUuid(); @@ -656,7 +664,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock canAdjustLocks, canRez); // So that we can send messages to this node at will - we need to activate the correct socket on this node now - newNode->activateMatchingOrNewSymmetricSocket(senderSockAddr); + newNode->activateMatchingOrNewSymmetricSocket(discoveredSocket); // when the newNode is created the linked data is also created // if this was a static assignment set the UUID, set the sendingSockAddr @@ -693,7 +701,6 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock } } - unsigned int DomainServer::countConnectedUsers() { unsigned int result = 0; auto nodeList = DependencyManager::get(); @@ -936,13 +943,6 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); - // if we've established a connection via ICE with this peer, use that socket - // otherwise just try to reply back to them on their sending socket (although that may not work) - HifiSockAddr destinationSockAddr = _connectedICEPeers.value(node->getUUID()); - if (destinationSockAddr.isNull()) { - destinationSockAddr = senderSockAddr; - } - if (nodeInterestList.size() > 0) { // DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; @@ -982,7 +982,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif } // always write the last broadcastPacket - limitedNodeList->writeUnverifiedDatagram(broadcastPacket, node, senderSockAddr); + limitedNodeList->writeUnverifiedDatagram(broadcastPacket, node); } QUuid DomainServer::connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) { @@ -1306,45 +1306,45 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) { domainUpdateJSON.toUtf8()); } -// todo: have data-web respond with ice-server hostname to use - -void DomainServer::performICEUpdates() { - sendHeartbeatToIceServer(); - sendICEPingPackets(); -} +// TODO: have data-web respond with ice-server hostname to use void DomainServer::sendHeartbeatToIceServer() { DependencyManager::get()->sendHeartbeatToIceServer(_iceServerSocket); } -void DomainServer::sendICEPingPackets() { - auto nodeList = DependencyManager::get(); +const int NUM_PEER_PINGS_BEFORE_DELETE = 2000 / UDP_PUNCH_PING_INTERVAL_MS; - QHash::iterator peer = _connectingICEPeers.begin(); +void DomainServer::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { - while (peer != _connectingICEPeers.end()) { + if (peer->getConnectionAttempts() > 0 && peer->getConnectionAttempts() % NUM_PEER_PINGS_BEFORE_DELETE == 0) { + // we've reached the maximum number of ping attempts + qDebug() << "Maximum number of ping attempts reached for peer with ID" << peer->getUUID(); + qDebug() << "Removing from list of connecting peers."; - if (peer->getConnectionAttempts() >= MAX_ICE_CONNECTION_ATTEMPTS) { - // we've already tried to connect to this peer enough times - // remove it from our list - if it wants to re-connect it'll come back through ice-server - peer = _connectingICEPeers.erase(peer); - } else { - // send ping packets to this peer's interfaces - qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" - << peer->getUUID(); + _icePeers.remove(peer->getUUID()); + } else { + auto nodeList = DependencyManager::get(); - // send the ping packet to the local and public sockets for this node - QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false); - nodeList->writeUnverifiedDatagram(localPingPacket, peer->getLocalSocket()); + // send ping packets to this peer's interfaces + qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" + << peer->getUUID(); - QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false); - nodeList->writeUnverifiedDatagram(publicPingPacket, peer->getPublicSocket()); + // send the ping packet to the local and public sockets for this node + QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false); + nodeList->writeUnverifiedDatagram(localPingPacket, peer->getLocalSocket()); - peer->incrementConnectionAttempts(); + QByteArray publicPingPacket = nodeList->constructPingPacket(PingType::Public, false); + nodeList->writeUnverifiedDatagram(publicPingPacket, peer->getPublicSocket()); - // go to next peer in hash - ++peer; - } + peer->incrementConnectionAttempts(); + } +} + +void DomainServer::handlePeerPingTimeout() { + SharedNetworkPeer senderPeer = _icePeers.value(qobject_cast(sender())->getUUID()); + + if (senderPeer && !senderPeer->getActiveSocket()) { + pingPunchForConnectingPeer(senderPeer); } } @@ -1354,31 +1354,32 @@ void DomainServer::processICEPeerInformation(const QByteArray& packet) { QDataStream iceResponseStream(packet); iceResponseStream.skipRawData(numBytesForPacketHeader(packet)); - NetworkPeer receivedPeer; - iceResponseStream >> receivedPeer; + NetworkPeer* receivedPeer = new NetworkPeer; + iceResponseStream >> *receivedPeer; - if (!_connectedICEPeers.contains(receivedPeer.getUUID())) { - if (!_connectingICEPeers.contains(receivedPeer.getUUID())) { - qDebug() << "New peer requesting connection being added to hash -" << receivedPeer; - } + if (!_icePeers.contains(receivedPeer->getUUID())) { + qDebug() << "New peer requesting ICE connection being added to hash -" << *receivedPeer; + SharedNetworkPeer newPeer = SharedNetworkPeer(receivedPeer); + _icePeers[receivedPeer->getUUID()] = newPeer; - _connectingICEPeers[receivedPeer.getUUID()] = receivedPeer; + // make sure we know when we should ping this peer + connect(newPeer.data(), &NetworkPeer::pingTimerTimeout, this, &DomainServer::handlePeerPingTimeout); + + // immediately ping the new peer, and start a timer to continue pinging it until we connect to it + newPeer->startPingTimer(); + pingPunchForConnectingPeer(newPeer); + } else { + delete receivedPeer; } } void DomainServer::processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { QUuid nodeUUID = uuidFromPacketHeader(packet); - NetworkPeer sendingPeer = _connectingICEPeers.take(nodeUUID); + SharedNetworkPeer sendingPeer = _icePeers.value(nodeUUID); - if (!sendingPeer.isNull()) { + if (sendingPeer) { // we had this NetworkPeer in our connecting list - add the right sock addr to our connected list - if (senderSockAddr == sendingPeer.getLocalSocket()) { - qDebug() << "Activating local socket for communication with network peer -" << sendingPeer; - _connectedICEPeers.insert(nodeUUID, sendingPeer.getLocalSocket()); - } else if (senderSockAddr == sendingPeer.getPublicSocket()) { - qDebug() << "Activating public socket for communication with network peer -" << sendingPeer; - _connectedICEPeers.insert(nodeUUID, sendingPeer.getPublicSocket()); - } + sendingPeer->activateMatchingOrNewSymmetricSocket(senderSockAddr); } } @@ -2106,9 +2107,8 @@ void DomainServer::nodeAdded(SharedNodePointer node) { void DomainServer::nodeKilled(SharedNodePointer node) { - // remove this node from the connecting / connected ICE lists (if they exist) - _connectingICEPeers.remove(node->getUUID()); - _connectedICEPeers.remove(node->getUUID()); + // if this peer connected via ICE then remove them from our ICE peers hash + _icePeers.remove(node->getUUID()); DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 302f683108..7a9fb2fe9b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -63,10 +63,9 @@ private slots: void sendPendingTransactionsToServer(); void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr); - void performICEUpdates(); void sendHeartbeatToDataServer() { sendHeartbeatToDataServer(QString()); } void sendHeartbeatToIceServer(); - void sendICEPingPackets(); + void handlePeerPingTimeout(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); bool optionallySetupOAuth(); @@ -81,6 +80,8 @@ private: void processICEPingReply(const QByteArray& packet, const HifiSockAddr& senderSockAddr); void processICEPeerInformation(const QByteArray& packet); + void pingPunchForConnectingPeer(const SharedNetworkPeer& peer); + void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); void handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr); @@ -153,8 +154,7 @@ private: QHash _userPublicKeys; - QHash _connectingICEPeers; - QHash _connectedICEPeers; + QHash _icePeers; QString _automaticNetworkingSetting; diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index e09dcb186b..38d1ade2ad 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -43,7 +43,7 @@ DomainHandler::DomainHandler(QObject* parent) : void DomainHandler::clearConnectionInfo() { _uuid = QUuid(); - _icePeer = NetworkPeer(); + _icePeer.reset(); if (requiresICE()) { // if we connected to this domain with ICE, re-set the socket so we reconnect through the ice-server @@ -299,16 +299,15 @@ void DomainHandler::processICEResponsePacket(const QByteArray& icePacket) { QDataStream iceResponseStream(icePacket); iceResponseStream.skipRawData(numBytesForPacketHeader(icePacket)); - NetworkPeer packetPeer; - iceResponseStream >> packetPeer; + iceResponseStream >> _icePeer; DependencyManager::get()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::ReceiveDSPeerInformation); - if (packetPeer.getUUID() != _iceDomainID) { + if (_icePeer.getUUID() != _iceDomainID) { qCDebug(networking) << "Received a network peer with ID that does not match current domain. Will not attempt connection."; + _icePeer.reset(); } else { - qCDebug(networking) << "Received network peer object for domain -" << packetPeer; - _icePeer = packetPeer; + qCDebug(networking) << "Received network peer object for domain -" << _icePeer; // ask the peer object to start its ping timer _icePeer.startPingTimer(); diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index f1197e5da0..83e5e72a87 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -49,44 +49,8 @@ NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, co } -NetworkPeer::NetworkPeer(const NetworkPeer& otherPeer) : QObject() { - _uuid = otherPeer._uuid; - _publicSocket = otherPeer._publicSocket; - _localSocket = otherPeer._localSocket; - _symmetricSocket = otherPeer._symmetricSocket; - - if (otherPeer._activeSocket) { - if (otherPeer._activeSocket == &otherPeer._localSocket) { - _activeSocket = &_localSocket; - } else if (otherPeer._activeSocket == &otherPeer._publicSocket) { - _activeSocket = &_publicSocket; - } else if (otherPeer._activeSocket == &otherPeer._symmetricSocket) { - _activeSocket = &_symmetricSocket; - } - } - - _wakeTimestamp = otherPeer._wakeTimestamp; - _lastHeardMicrostamp = otherPeer._lastHeardMicrostamp; - _connectionAttempts = otherPeer._connectionAttempts; -} - -NetworkPeer& NetworkPeer::operator=(const NetworkPeer& otherPeer) { - NetworkPeer temp(otherPeer); - swap(temp); - return *this; -} - -void NetworkPeer::swap(NetworkPeer& otherPeer) { - using std::swap; - - swap(_uuid, otherPeer._uuid); - swap(_publicSocket, otherPeer._publicSocket); - swap(_localSocket, otherPeer._localSocket); - swap(_symmetricSocket, otherPeer._symmetricSocket); - swap(_activeSocket, otherPeer._activeSocket); - swap(_wakeTimestamp, otherPeer._wakeTimestamp); - swap(_lastHeardMicrostamp, otherPeer._lastHeardMicrostamp); - swap(_connectionAttempts, otherPeer._connectionAttempts); +NetworkPeer::~NetworkPeer() { + qDebug() << "Removing network peer with ID" << _uuid; } void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { @@ -181,6 +145,8 @@ void NetworkPeer::softReset() { // a soft reset should clear the sockets and reset the number of connection attempts _localSocket.clear(); _publicSocket.clear(); + _symmetricSocket.clear(); + _activeSocket = NULL; // stop our ping timer since we don't have sockets to ping anymore anyways stopPingTimer(); @@ -188,6 +154,14 @@ void NetworkPeer::softReset() { _connectionAttempts = 0; } +void NetworkPeer::reset() { + softReset(); + + _uuid = QUuid(); + _wakeTimestamp = QDateTime::currentMSecsSinceEpoch(); + _lastHeardMicrostamp = usecTimestampNow(); +} + QByteArray NetworkPeer::toByteArray() const { QByteArray peerByteArray; diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index a4db99ba9e..8cbb6cbe24 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -31,8 +31,7 @@ public: NetworkPeer(QObject* parent = 0); NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, QObject* parent = 0); - NetworkPeer(const NetworkPeer &otherPeer); - NetworkPeer& operator=(const NetworkPeer& otherPeer); + ~NetworkPeer(); bool isNull() const { return _uuid.isNull(); } bool hasSockets() const { return !_localSocket.isNull() && !_publicSocket.isNull(); } @@ -41,6 +40,7 @@ public: void setUUID(const QUuid& uuid) { _uuid = uuid; } void softReset(); + void reset(); const HifiSockAddr& getPublicSocket() const { return _publicSocket; } const HifiSockAddr& getLocalSocket() const { return _localSocket; } @@ -99,8 +99,6 @@ protected: QTimer* _pingTimer = NULL; int _connectionAttempts; -private: - void swap(NetworkPeer& otherPeer); }; QDebug operator<<(QDebug debug, const NetworkPeer &peer); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index dc6b33d0a9..326573ef35 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -193,7 +193,9 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr break; } case PacketTypeIceServerPeerInformation: { - _domainHandler.processICEResponsePacket(packet); + if (!_domainHandler.getICEPeer().hasSockets()) { + _domainHandler.processICEResponsePacket(packet); + } break; } case PacketTypePing: { From 493a9da43eba8266eab3b5f45116fc02774e04f1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 16:23:14 -0700 Subject: [PATCH 34/46] make sure DS log handler is cleaned up --- domain-server/src/DomainServer.cpp | 18 ++++++++++++++---- domain-server/src/DomainServer.h | 2 ++ libraries/networking/src/NodeList.cpp | 9 ++++++--- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7827271aa9..47d629a8f1 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -68,6 +68,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : LogUtils::init(); Setting::init(); + connect(this, &QCoreApplication::aboutToQuit, this, &DomainServer::aboutToQuit); + setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); @@ -106,6 +108,11 @@ DomainServer::DomainServer(int argc, char* argv[]) : } } +void DomainServer::aboutToQuit() { + // clear the log handler so that Qt doesn't call the destructor on LogHandler + qInstallMessageHandler(0); +} + void DomainServer::restart() { qDebug() << "domain-server is restarting."; @@ -558,7 +565,6 @@ const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::EntityServer; void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { - NodeType_t nodeType; HifiSockAddr publicSockAddr, localSockAddr; @@ -1341,10 +1347,14 @@ void DomainServer::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { } void DomainServer::handlePeerPingTimeout() { - SharedNetworkPeer senderPeer = _icePeers.value(qobject_cast(sender())->getUUID()); + NetworkPeer* senderPeer = qobject_cast(sender()); - if (senderPeer && !senderPeer->getActiveSocket()) { - pingPunchForConnectingPeer(senderPeer); + if (senderPeer) { + SharedNetworkPeer sharedPeer = _icePeers.value(senderPeer->getUUID()); + + if (sharedPeer && !sharedPeer->getActiveSocket()) { + pingPunchForConnectingPeer(sharedPeer); + } } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 7a9fb2fe9b..74dbde8b4b 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -57,6 +57,8 @@ public slots: void restart(); private slots: + void aboutToQuit(); + void loginFailed(); void readAvailableDatagrams(); void setupPendingAssignmentCredits(); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 326573ef35..24a9225374 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -646,10 +646,13 @@ void NodeList::startNodeHolePunch(const SharedNodePointer& node) { } void NodeList::handleNodePingTimeout() { - SharedNodePointer senderNode = nodeWithUUID(qobject_cast(sender())->getUUID()); + Node* senderNode = qobject_cast(sender()); + if (senderNode) { + SharedNodePointer sharedNode = nodeWithUUID(senderNode->getUUID()); - if (senderNode && !senderNode->getActiveSocket()) { - pingPunchForInactiveNode(senderNode); + if (sharedNode && !sharedNode->getActiveSocket()) { + pingPunchForInactiveNode(sharedNode); + } } } From ac0609ea920bdfe0b54fc72af127c337700838cb Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 28 May 2015 16:29:43 -0700 Subject: [PATCH 35/46] also render bbox when debugging simulator owner --- .../src/RenderableModelEntityItem.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 75b29002f3..35309b0799 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -112,8 +112,6 @@ void RenderableModelEntityItem::render(RenderArgs* args) { PerformanceTimer perfTimer("RMEIrender"); assert(getType() == EntityTypes::Model); - bool drawAsModel = hasModel(); - glm::vec3 position = getPosition(); glm::vec3 dimensions = getDimensions(); @@ -125,8 +123,7 @@ void RenderableModelEntityItem::render(RenderArgs* args) { highlightSimulationOwnership = (getSimulatorID() == myNodeID); } - bool didDraw = false; - if (drawAsModel && !highlightSimulationOwnership) { + if (hasModel()) { remapTextures(); glPushMatrix(); { @@ -179,19 +176,20 @@ void RenderableModelEntityItem::render(RenderArgs* args) { if (args && (args->_renderMode == RenderArgs::SHADOW_RENDER_MODE)) { if (movingOrAnimating) { _model->renderInScene(alpha, args); - didDraw = true; } } else { _model->renderInScene(alpha, args); - didDraw = true; } } } } glPopMatrix(); - } - if (!didDraw) { + if (highlightSimulationOwnership) { + glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); + RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor); + } + } else { glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f); RenderableDebugableEntityItem::renderBoundingBox(this, args, 0.0f, greenColor); } From 265e0f3da6df688a01d37a0ff4e23b6f9a9cb398 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 16:44:34 -0700 Subject: [PATCH 36/46] remove NetworkPeer dtor debug, fix ICE socket --- libraries/networking/src/LimitedNodeList.cpp | 2 +- libraries/networking/src/NetworkPeer.cpp | 4 ---- libraries/networking/src/NetworkPeer.h | 2 -- 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index c02eff4b75..61d844c1be 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -840,7 +840,7 @@ void LimitedNodeList::sendPacketToIceServer(PacketType packetType, const HifiSoc << uuidStringWithoutCurlyBraces(peerID); } - writeUnverifiedDatagram(iceRequestByteArray, HifiSockAddr("127.0.0.1", ICE_SERVER_DEFAULT_PORT)); + writeUnverifiedDatagram(iceRequestByteArray, iceServerSockAddr); } void LimitedNodeList::putLocalPortIntoSharedMemory(const QString key, QObject* parent, quint16 localPort) { diff --git a/libraries/networking/src/NetworkPeer.cpp b/libraries/networking/src/NetworkPeer.cpp index 83e5e72a87..dfa4066dd2 100644 --- a/libraries/networking/src/NetworkPeer.cpp +++ b/libraries/networking/src/NetworkPeer.cpp @@ -49,10 +49,6 @@ NetworkPeer::NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, co } -NetworkPeer::~NetworkPeer() { - qDebug() << "Removing network peer with ID" << _uuid; -} - void NetworkPeer::setPublicSocket(const HifiSockAddr& publicSocket) { if (publicSocket != _publicSocket) { if (_activeSocket == &_publicSocket) { diff --git a/libraries/networking/src/NetworkPeer.h b/libraries/networking/src/NetworkPeer.h index 8cbb6cbe24..d2802a1308 100644 --- a/libraries/networking/src/NetworkPeer.h +++ b/libraries/networking/src/NetworkPeer.h @@ -31,8 +31,6 @@ public: NetworkPeer(QObject* parent = 0); NetworkPeer(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, QObject* parent = 0); - ~NetworkPeer(); - bool isNull() const { return _uuid.isNull(); } bool hasSockets() const { return !_localSocket.isNull() && !_publicSocket.isNull(); } From 29d3ca85d936a03271207128208f9172db71702b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 16:53:13 -0700 Subject: [PATCH 37/46] cleanup debug logs for DS/node ping punch --- domain-server/src/DomainServer.cpp | 8 ++++---- libraries/networking/src/NodeList.cpp | 17 +++++++++-------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 47d629a8f1..24d2d4812e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1331,10 +1331,6 @@ void DomainServer::pingPunchForConnectingPeer(const SharedNetworkPeer& peer) { } else { auto nodeList = DependencyManager::get(); - // send ping packets to this peer's interfaces - qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" - << peer->getUUID(); - // send the ping packet to the local and public sockets for this node QByteArray localPingPacket = nodeList->constructPingPacket(PingType::Local, false); nodeList->writeUnverifiedDatagram(localPingPacket, peer->getLocalSocket()); @@ -1377,6 +1373,10 @@ void DomainServer::processICEPeerInformation(const QByteArray& packet) { // immediately ping the new peer, and start a timer to continue pinging it until we connect to it newPeer->startPingTimer(); + + qDebug() << "Sending ping packets to establish connectivity with ICE peer with ID" + << newPeer->getUUID(); + pingPunchForConnectingPeer(newPeer); } else { delete receivedPeer; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 24a9225374..26cd3f1425 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -499,17 +499,18 @@ void NodeList::pingPunchForDomainServer() { // check if we've hit the number of pings we'll send to the DS before we consider it a fail const int NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET = 2000 / UDP_PUNCH_PING_INTERVAL_MS; - if (_domainHandler.getICEPeer().getConnectionAttempts() > 0 - && _domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { - // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat + if (_domainHandler.getICEPeer().getConnectionAttempts() == 0) { + qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" + << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); + } else { + if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { + // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat - _domainHandler.getICEPeer().softReset(); - handleICEConnectionToDomainServer(); + _domainHandler.getICEPeer().softReset(); + handleICEConnectionToDomainServer(); + } } - qCDebug(networking) << "Sending ping packets to establish connectivity with domain-server with ID" - << uuidStringWithoutCurlyBraces(_domainHandler.getICEDomainID()); - flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendPingsToDS); // send the ping packet to the local and public sockets for this node From 046828ee5de33b5284eb1a8df643f0ba8f8e468f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 16:58:58 -0700 Subject: [PATCH 38/46] make the LNL the parent of any Nodes it adds --- libraries/networking/src/LimitedNodeList.cpp | 2 +- libraries/networking/src/Node.cpp | 5 +++-- libraries/networking/src/Node.h | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 61d844c1be..aa0ab262d7 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -488,7 +488,7 @@ SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t return matchingNode; } else { // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez, connectionSecret); + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket, canAdjustLocks, canRez, connectionSecret, this); if (nodeType == NodeType::AudioMixer) { LimitedNodeList::flagTimeForConnectionStep(LimitedNodeList::AddedAudioMixer); diff --git a/libraries/networking/src/Node.cpp b/libraries/networking/src/Node.cpp index 94b57b3f59..05da87d69a 100644 --- a/libraries/networking/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -41,8 +41,9 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { } Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, - const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez, const QUuid& connectionSecret) : - NetworkPeer(uuid, publicSocket, localSocket), + const HifiSockAddr& localSocket, bool canAdjustLocks, bool canRez, const QUuid& connectionSecret, + QObject* parent) : + NetworkPeer(uuid, publicSocket, localSocket, parent), _type(type), _connectionSecret(connectionSecret), _linkedData(NULL), diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index 1eaf1a02c7..b1f5d8b037 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -47,7 +47,8 @@ class Node : public NetworkPeer { public: Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket, - bool canAdjustLocks, bool canRez, const QUuid& connectionSecret = QUuid()); + bool canAdjustLocks, bool canRez, const QUuid& connectionSecret = QUuid(), + QObject* parent = 0); ~Node(); bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; } From cb7b23f3467905aacff60469cbf64872c39aa761 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 17:04:15 -0700 Subject: [PATCH 39/46] install DS message handler in DomainServer --- domain-server/src/DomainServer.cpp | 2 ++ domain-server/src/main.cpp | 10 ++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 24d2d4812e..57d687f307 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -65,6 +65,8 @@ DomainServer::DomainServer(int argc, char* argv[]) : _settingsManager(), _iceServerSocket(ICE_SERVER_DEFAULT_HOSTNAME, ICE_SERVER_DEFAULT_PORT) { + qInstallMessageHandler(LogHandler::verboseMessageHandler); + LogUtils::init(); Setting::init(); diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index ba80e6fce0..790cc07c56 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -26,18 +26,16 @@ int main(int argc, char* argv[]) { #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif - - qInstallMessageHandler(LogHandler::verboseMessageHandler); - + int currentExitCode = 0; - + // use a do-while to handle domain-server restart do { DomainServer domainServer(argc, argv); currentExitCode = domainServer.exec(); } while (currentExitCode == DomainServer::EXIT_CODE_REBOOT); - - + + return currentExitCode; } From 7f86ca3f107b7eee041c1973d413362f094486a5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 17:07:20 -0700 Subject: [PATCH 40/46] refresh the network dialog if it is already up --- interface/src/ui/DialogsManager.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index ca7a13eb07..309c3e0ffe 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -178,11 +178,14 @@ void DialogsManager::showIRCLink() { } void DialogsManager::showDomainConnectionDialog() { - if (!_domainConnectionDialog) { - // if the dialog already exists we delete it so the connection data is refreshed - maybeCreateDialog(_domainConnectionDialog); - - _domainConnectionDialog->show(); - _domainConnectionDialog->raise(); + // if the dialog already exists we delete it so the connection data is refreshed + if (_domainConnectionDialog) { + _domainConnectionDialog->close(); + _domainConnectionDialog = NULL; } + + maybeCreateDialog(_domainConnectionDialog); + + _domainConnectionDialog->show(); + _domainConnectionDialog->raise(); } From 5749fefcd5ffbff09a70c7c451a0716462993b3c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 28 May 2015 17:07:57 -0700 Subject: [PATCH 41/46] have show DS connect refresh existing dialog --- interface/src/ui/DialogsManager.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/interface/src/ui/DialogsManager.cpp b/interface/src/ui/DialogsManager.cpp index 309c3e0ffe..1170e3c3a6 100644 --- a/interface/src/ui/DialogsManager.cpp +++ b/interface/src/ui/DialogsManager.cpp @@ -181,6 +181,7 @@ void DialogsManager::showDomainConnectionDialog() { // if the dialog already exists we delete it so the connection data is refreshed if (_domainConnectionDialog) { _domainConnectionDialog->close(); + _domainConnectionDialog->deleteLater(); _domainConnectionDialog = NULL; } From e91ee7e7e34f0250cb87505bec7949f72e199dfa Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 29 May 2015 09:49:54 -0700 Subject: [PATCH 42/46] only send add packet for nodes in interest set --- domain-server/src/DomainServer.cpp | 35 ++++++++-------------- domain-server/src/DomainServer.h | 5 ++-- domain-server/src/DomainServerNodeData.h | 26 +++++++++------- libraries/networking/src/LimitedNodeList.h | 2 -- libraries/networking/src/Node.h | 16 +--------- libraries/networking/src/NodeType.h | 34 +++++++++++++++++++++ 6 files changed, 65 insertions(+), 53 deletions(-) create mode 100644 libraries/networking/src/NodeType.h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 57d687f307..034b98e8db 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -918,26 +918,8 @@ int DomainServer::parseNodeDataFromByteArray(QDataStream& packetStream, NodeType return packetStream.device()->pos(); } -NodeSet DomainServer::nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes) { - QDataStream packetStream(packet); - packetStream.skipRawData(numPreceedingBytes); - - quint8 numInterestTypes = 0; - packetStream >> numInterestTypes; - - quint8 nodeType; - NodeSet nodeInterestSet; - - for (int i = 0; i < numInterestTypes; i++) { - packetStream >> nodeType; - nodeInterestSet.insert((NodeType_t) nodeType); - } - - return nodeInterestSet; -} - void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr &senderSockAddr, - const NodeSet& nodeInterestList) { + const NodeSet& nodeInterestSet) { auto limitedNodeList = DependencyManager::get(); QByteArray broadcastPacket = limitedNodeList->byteArrayWithPopulatedHeader(PacketTypeDomainList); @@ -951,7 +933,10 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); - if (nodeInterestList.size() > 0) { + // store the nodeInterestSet on this DomainServerNodeData, in case it has changed + nodeData->setNodeInterestSet(nodeInterestSet); + + if (nodeInterestSet.size() > 0) { // DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; int dataMTU = MAX_PACKET_SIZE; @@ -963,7 +948,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif QByteArray nodeByteArray; QDataStream nodeDataStream(&nodeByteArray, QIODevice::Append); - if (otherNode->getUUID() != node->getUUID() && nodeInterestList.contains(otherNode->getType())) { + if (otherNode->getUUID() != node->getUUID() && nodeInterestSet.contains(otherNode->getType())) { // don't send avatar nodes to other avatars, that will come from avatar mixer nodeDataStream << *otherNode.data(); @@ -1028,7 +1013,13 @@ void DomainServer::broadcastNewNode(const SharedNodePointer& addedNode) { limitedNodeList->eachMatchingNode( [&](const SharedNodePointer& node)->bool { - return (node->getLinkedData() && node->getActiveSocket() && node != addedNode); + if (node->getLinkedData() && node->getActiveSocket() && node != addedNode) { + // is the added Node in this node's interest list? + DomainServerNodeData* nodeData = dynamic_cast(node->getLinkedData()); + return nodeData->getNodeInterestSet().contains(addedNode->getType()); + } else { + return false; + } }, [&](const SharedNodePointer& node) { QByteArray rfcConnectionSecret = connectionSecretForNodes(node, addedNode).toRfc4122(); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 74dbde8b4b..f62ba89871 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -58,7 +58,7 @@ public slots: private slots: void aboutToQuit(); - + void loginFailed(); void readAvailableDatagrams(); void setupPendingAssignmentCredits(); @@ -100,9 +100,8 @@ private: HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr, const HifiSockAddr& senderSockAddr); - NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes); void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, - const NodeSet& nodeInterestList); + const NodeSet& nodeInterestSet); QUuid connectionSecretForNodes(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB); void broadcastNewNode(const SharedNodePointer& node); diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 366ee8c730..a91a7e0b9c 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -12,44 +12,47 @@ #ifndef hifi_DomainServerNodeData_h #define hifi_DomainServerNodeData_h - #include #include #include #include #include +#include class DomainServerNodeData : public NodeData { public: DomainServerNodeData(); int parseData(const QByteArray& packet) { return 0; } - + const QJsonObject& getStatsJSONObject() const { return _statsJSONObject; } - + void parseJSONStatsPacket(const QByteArray& statsPacket); - + void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } const QUuid& getAssignmentUUID() const { return _assignmentUUID; } - + void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; } const QUuid& getWalletUUID() const { return _walletUUID; } - + void setUsername(const QString& username) { _username = username; } const QString& getUsername() const { return _username; } - + QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; } - + void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } - + void setIsAuthenticated(bool isAuthenticated) { _isAuthenticated = isAuthenticated; } bool isAuthenticated() const { return _isAuthenticated; } - + QHash& getSessionSecretHash() { return _sessionSecretHash; } + + const NodeSet& getNodeInterestSet() const { return _nodeInterestSet; } + void setNodeInterestSet(const NodeSet& nodeInterestSet) { _nodeInterestSet = nodeInterestSet; } private: QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); - + QHash _sessionSecretHash; QUuid _assignmentUUID; QUuid _walletUUID; @@ -58,6 +61,7 @@ private: QJsonObject _statsJSONObject; HifiSockAddr _sendingSockAddr; bool _isAuthenticated; + NodeSet _nodeInterestSet; }; #endif // hifi_DomainServerNodeData_h diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index ec4b7546f0..94063f49b1 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -60,8 +60,6 @@ const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username"; class HifiSockAddr; -typedef QSet NodeSet; - typedef QSharedPointer SharedNodePointer; Q_DECLARE_METATYPE(SharedNodePointer) diff --git a/libraries/networking/src/Node.h b/libraries/networking/src/Node.h index b1f5d8b037..4c1b6e9d18 100644 --- a/libraries/networking/src/Node.h +++ b/libraries/networking/src/Node.h @@ -23,25 +23,11 @@ #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NodeData.h" +#include "NodeType.h" #include "PacketHeaders.h" #include "SimpleMovingAverage.h" #include "MovingPercentile.h" -typedef quint8 NodeType_t; - -namespace NodeType { - const NodeType_t DomainServer = 'D'; - const NodeType_t EntityServer = 'o'; // was ModelServer - const NodeType_t EnvironmentServer = 'E'; - const NodeType_t Agent = 'I'; - const NodeType_t AudioMixer = 'M'; - const NodeType_t AvatarMixer = 'W'; - const NodeType_t Unassigned = 1; - - void init(); - const QString& getNodeTypeName(NodeType_t nodeType); -} - class Node : public NetworkPeer { Q_OBJECT public: diff --git a/libraries/networking/src/NodeType.h b/libraries/networking/src/NodeType.h new file mode 100644 index 0000000000..4427b87158 --- /dev/null +++ b/libraries/networking/src/NodeType.h @@ -0,0 +1,34 @@ +// +// NodeType.h +// libraries/networking/src +// +// Created by Stephen Birarda on 05/29/15. +// Copyright 2015 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_NodeType_h +#define hifi_NodeType_h + +#pragma once + +typedef quint8 NodeType_t; + +namespace NodeType { + const NodeType_t DomainServer = 'D'; + const NodeType_t EntityServer = 'o'; // was ModelServer + const NodeType_t EnvironmentServer = 'E'; + const NodeType_t Agent = 'I'; + const NodeType_t AudioMixer = 'M'; + const NodeType_t AvatarMixer = 'W'; + const NodeType_t Unassigned = 1; + + void init(); + const QString& getNodeTypeName(NodeType_t nodeType); +} + +typedef QSet NodeSet; + +#endif // hifi_NodeType_h From 8e245ec4c2d76f8c045f50c6a125f8a6a54192ff Mon Sep 17 00:00:00 2001 From: Howard Stearns Date: Fri, 29 May 2015 09:59:21 -0700 Subject: [PATCH 43/46] getCollisionEvents has a side-effect on state (removing END events from further consideration), so it must be called once per step. Otherwise entities never get to see end events. Also, entity collision processing should only be done when there are outgoing changes (just like for avatar). --- interface/src/Application.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 385172ee2c..877b5ed931 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2489,19 +2489,20 @@ void Application::update(float deltaTime) { _entitySimulation.unlock(); avatarManager->handleOutgoingChanges(_physicsEngine.getOutgoingChanges()); - avatarManager->handleCollisionEvents(_physicsEngine.getCollisionEvents()); + auto collisionEvents = _physicsEngine.getCollisionEvents(); + avatarManager->handleCollisionEvents(collisionEvents); _physicsEngine.dumpStatsIfNecessary(); - } - } - if (!_aboutToQuit) { - PerformanceTimer perfTimer("entities"); - // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk deadlock.) - _entitySimulation.handleCollisionEvents(_physicsEngine.getCollisionEvents()); - // NOTE: the _entities.update() call below will wait for lock - // and will simulate entity motion (the EntityTree has been given an EntitySimulation). - _entities.update(); // update the models... + if (!_aboutToQuit) { + PerformanceTimer perfTimer("entities"); + // Collision events (and their scripts) must not be handled when we're locked, above. (That would risk deadlock.) + _entitySimulation.handleCollisionEvents(collisionEvents); + // NOTE: the _entities.update() call below will wait for lock + // and will simulate entity motion (the EntityTree has been given an EntitySimulation). + _entities.update(); // update the models... + } + } } { From 8177512432c7442036f48d641eb72543ee6df0ed Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Fri, 29 May 2015 11:35:50 -0700 Subject: [PATCH 44/46] send all TerseUpdate properties when one changes --- libraries/entities/src/EntityItem.cpp | 15 +++++++++++++++ libraries/entities/src/EntityItem.h | 2 ++ libraries/entities/src/EntityItemProperties.cpp | 5 ++++- libraries/entities/src/EntityItemProperties.h | 3 +++ .../entities/src/EntityScriptingInterface.cpp | 12 +++++++++++- 5 files changed, 35 insertions(+), 2 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 4a9b64d1ca..cc951ac16b 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -925,6 +925,21 @@ EntityItemProperties EntityItem::getProperties() const { return properties; } +void EntityItem::getAllTerseUpdateProperties(EntityItemProperties& properties) const { + // a TerseUpdate includes the transform and its derivatives + properties._position = _position; + properties._velocity = _velocity; + properties._rotation = _rotation; + properties._angularVelocity = _angularVelocity; + properties._acceleration = _acceleration; + + properties._positionChanged = true; + properties._velocityChanged = true; + properties._rotationChanged = true; + properties._angularVelocityChanged = true; + properties._accelerationChanged = true; +} + bool EntityItem::setProperties(const EntityItemProperties& properties) { bool somethingChanged = false; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 4f2132bef4..6f9dc54e7a 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -347,6 +347,8 @@ public: quint64 getLastEditedFromRemote() { return _lastEditedFromRemote; } + void getAllTerseUpdateProperties(EntityItemProperties& properties) const; + protected: static bool _sendPhysicsUpdates; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 856c1a1cb4..583cf15b9c 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1156,4 +1156,7 @@ AABox EntityItemProperties::getAABox() const { return AABox(rotatedExtentsRelativeToRegistrationPoint); } - +bool EntityItemProperties::hasTerseUpdateChanges() const { + // a TerseUpdate includes the transform and its derivatives + return _positionChanged || _velocityChanged || _rotationChanged || _angularVelocityChanged || _accelerationChanged; +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 26c26bd474..dbe2e926c9 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -195,6 +195,8 @@ public: void setVoxelDataDirty() { _voxelDataChanged = true; } + bool hasTerseUpdateChanges() const; + private: QUuid _id; bool _idSet; @@ -215,6 +217,7 @@ private: QStringList _textureNames; glm::vec3 _naturalDimensions; }; + Q_DECLARE_METATYPE(EntityItemProperties); QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); QScriptValue EntityItemNonDefaultPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 54d2ea705e..d684fbf2fc 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -146,7 +146,17 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& EntityItemProperties modifiedProperties = properties; entity->setLastBroadcast(usecTimestampNow()); modifiedProperties.setType(entity->getType()); - bidForSimulationOwnership(modifiedProperties); + if (modifiedProperties.hasTerseUpdateChanges()) { + // we make a bid for (or assert) our simulation ownership + auto nodeList = DependencyManager::get(); + const QUuid myNodeID = nodeList->getSessionUUID(); + modifiedProperties.setSimulatorID(myNodeID); + + if (entity->getSimulatorID() == myNodeID) { + // we think we already own simulation, so make sure we send ALL TerseUpdate properties + entity->getAllTerseUpdateProperties(modifiedProperties); + } + } queueEntityMessage(PacketTypeEntityEdit, entityID, modifiedProperties); return id; } From e253687dcd09b02c9b19184bc82c037fc4711109 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 29 May 2015 12:06:07 -0700 Subject: [PATCH 45/46] fix fail case for DS pings --- libraries/networking/src/NodeList.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 26cd3f1425..b6d7c2026a 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -505,12 +505,19 @@ void NodeList::pingPunchForDomainServer() { } else { if (_domainHandler.getICEPeer().getConnectionAttempts() % NUM_DOMAIN_SERVER_PINGS_BEFORE_RESET == 0) { // if we have then nullify the domain handler's network peer and send a fresh ICE heartbeat + qCDebug(networking) << "No ping replies received from domain-server with ID" + << uuidStringWithoutCurlyBraces(_domainHandler.getICEClientID()) << "-" << "re-sending ICE query."; _domainHandler.getICEPeer().softReset(); handleICEConnectionToDomainServer(); + + return; } } + qDebug() << "Sending domain server ping to" + << _domainHandler.getICEPeer().getLocalSocket() << _domainHandler.getICEPeer().getPublicSocket(); + flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendPingsToDS); // send the ping packet to the local and public sockets for this node From 33fde7c66d610bb05d2460352ee7fdd518be73e2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Fri, 29 May 2015 13:21:24 -0700 Subject: [PATCH 46/46] remove extra debug --- libraries/networking/src/NodeList.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index b6d7c2026a..44aa5bc644 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -515,9 +515,6 @@ void NodeList::pingPunchForDomainServer() { } } - qDebug() << "Sending domain server ping to" - << _domainHandler.getICEPeer().getLocalSocket() << _domainHandler.getICEPeer().getPublicSocket(); - flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendPingsToDS); // send the ping packet to the local and public sockets for this node