diff --git a/examples/editModels.js b/examples/editModels.js index f55ea4bbbe..2b2e11756f 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -1051,6 +1051,11 @@ function checkController(deltaTime) { var numberOfTriggers = Controller.getNumberOfTriggers(); var numberOfSpatialControls = Controller.getNumberOfSpatialControls(); var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers; + + if (!isActive) { + // So that we hide the lasers bellow and keep updating the overlays position + numberOfButtons = 0; + } // this is expected for hydras if (numberOfButtons==12 && numberOfTriggers == 2 && controllersPerTrigger == 2) { @@ -1072,11 +1077,21 @@ function checkController(deltaTime) { moveOverlays(); } + +var isActive = false; +var active; var newModel; var browser; function initToolBar() { toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); // New Model + active = toolBar.addTool({ + imageURL: toolIconUrl + "models-tool.svg", + subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, + width: toolWidth, height: toolHeight, + visible: true, + alpha: 0.9 + }, true, false); newModel = toolBar.addTool({ imageURL: toolIconUrl + "add-model-tool.svg", subImage: { x: 0, y: Tool.IMAGE_WIDTH, width: Tool.IMAGE_WIDTH, height: Tool.IMAGE_HEIGHT }, @@ -1194,6 +1209,11 @@ function mousePressEvent(event) { modelSelected = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + if (active == toolBar.clicked(clickedOverlay)) { + isActive = !isActive; + return; + } + if (newModel == toolBar.clicked(clickedOverlay)) { var url = Window.prompt("Model URL", modelURLs[Math.floor(Math.random() * modelURLs.length)]); if (url == null || url == "") { @@ -1229,6 +1249,11 @@ function mousePressEvent(event) { } } else { + // If we aren't active and didn't click on an overlay: quit + if (!isActive) { + return; + } + var pickRay = Camera.computePickRay(event.x, event.y); Vec3.print("[Mouse] Looking at: ", pickRay.origin); var foundIntersection = Models.findRayIntersection(pickRay); @@ -1313,7 +1338,7 @@ var oldModifier = 0; var modifier = 0; var wasShifted = false; function mouseMoveEvent(event) { - if (event.isAlt) { + if (event.isAlt || !isActive) { return; } @@ -1456,7 +1481,7 @@ function mouseMoveEvent(event) { function mouseReleaseEvent(event) { - if (event.isAlt) { + if (event.isAlt || !isActive) { return; } diff --git a/examples/rockPaperScissorsCells.js b/examples/rockPaperScissorsCells.js new file mode 100644 index 0000000000..2a9cb00a0b --- /dev/null +++ b/examples/rockPaperScissorsCells.js @@ -0,0 +1,272 @@ +// rockPaperScissorsCells.js +// examples +// +// Created by Ben Arnold on 7/16/14. +// Copyright 2014 High Fidelity, Inc. +// +// This sample script creates a voxel wall that simulates the Rock Paper Scissors cellular +// automata. http://www.gamedev.net/blog/844/entry-2249737-another-cellular-automaton-video/ +// If multiple instances of this script are run, they will combine into a larger wall. +// NOTE: You must run each instance one at a time. If they all start at once there are race conditions. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var NUMBER_OF_CELLS_EACH_DIMENSION = 48; +var NUMBER_OF_CELLS_REGION_EACH_DIMESION = 16; +var REGIONS_EACH_DIMENSION = NUMBER_OF_CELLS_EACH_DIMENSION / NUMBER_OF_CELLS_REGION_EACH_DIMESION; + +var isLocal = false; + +var currentCells = []; +var nextCells = []; + +var cornerPosition = {x: 100, y: 0, z: 0 } +var position = {x: 0, y: 0, z: 0 }; + +var METER_LENGTH = 1; +var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION; + +var viewerPosition = {x: cornerPosition.x + (NUMBER_OF_CELLS_EACH_DIMENSION / 2) * cellScale, y: cornerPosition.y + (NUMBER_OF_CELLS_EACH_DIMENSION / 2) * cellScale, z: cornerPosition.z }; + +viewerPosition.z += 50; +var yaw = 0; +var orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0); + +//Feel free to add new cell types here. It can be more than three. +var cellTypes = []; +cellTypes[0] = { r: 255, g: 0, b: 0 }; +cellTypes[1] = { r: 0, g: 255, b: 0 }; +cellTypes[2] = { r: 0, g:0, b: 255 }; +cellTypes[3] = { r: 0, g: 255, b: 255 }; + + +//Check for free region for AC +var regionMarkerX = -1; +var regionMarkerY = -1; +var regionMarkerI = -1; +var regionMarkerJ = -1; + +var regionMarkerColor = {r: 254, g: 0, b: 253}; + +function setRegionToColor(startX, startY, width, height, color) { + for (var i = startY; i < startY + height; i++) { + for (var j = startX; j < startX + width; j++) { + + currentCells[i][j] = { changed: true, type: color }; + + // put the same value in the nextCells array for first board draw + nextCells[i][j] = { changed: true, type: color }; + } + } +} + +function init() { + + for (var i = 0; i < REGIONS_EACH_DIMENSION; i++) { + for (var j = 0; j < REGIONS_EACH_DIMENSION; j++) { + var x = cornerPosition.x + (j) * cellScale; + var y = cornerPosition.y + (i + NUMBER_OF_CELLS_EACH_DIMENSION) * cellScale; + var z = cornerPosition.z; + var voxel = Voxels.getVoxelAt(x, y, z, cellScale); + if (voxel.x != x || voxel.y != y || voxel.z != z || voxel.s != cellScale || + voxel.red != regionMarkerColor.r || voxel.green != regionMarkerColor.g || voxel.blue != regionMarkerColor.b) { + regionMarkerX = x; + regionMarkerY = y; + regionMarkerI = i; + regionMarkerJ = j; + i = REGIONS_EACH_DIMENSION; //force quit loop + break; + } + } + } + + //Didnt find an open spot, end script + if (regionMarkerX == -1) { + Script.stop(); + } + + position.x = cornerPosition.x + regionMarkerJ * NUMBER_OF_CELLS_REGION_EACH_DIMESION; + position.y = cornerPosition.y + regionMarkerI * NUMBER_OF_CELLS_REGION_EACH_DIMESION; + position.z = cornerPosition.z; + + Voxels.setVoxel(regionMarkerX, regionMarkerY, cornerPosition.z, cellScale, regionMarkerColor.r, regionMarkerColor.g, regionMarkerColor.b); + + for (var i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) { + // create the array to hold this row + currentCells[i] = []; + + // create the array to hold this row in the nextCells array + nextCells[i] = []; + } + + var width = NUMBER_OF_CELLS_REGION_EACH_DIMESION / 2; + setRegionToColor(0, 0, width, width, 0); + setRegionToColor(0, width, width, width, 1); + setRegionToColor(width, width, width, width, 2); + setRegionToColor(width, 0, width, width, 3); +} + +function updateCells() { + var i = 0; + var j = 0; + var cell; + var y = 0; + var x = 0; + + for (i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) { + for (j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) { + + cell = currentCells[i][j]; + + var r = Math.floor(Math.random() * 8); + + switch (r){ + case 0: + y = i - 1; + x = j - 1; + break; + case 1: + y = i; + x = j-1; + break; + case 2: + y = i + 1; + x = j - 1; + break; + case 3: + y = i + 1; + x = j; + break; + case 4: + y = i + 1; + x = j + 1; + break; + case 5: + y = i; + x = j + 1; + break; + case 6: + y = i - 1; + x = j + 1; + break; + case 7: + y = i - 1; + x = j; + break; + default: + continue; + + } + + //check the voxel grid instead of local array when on the edge + if (x == -1 || x == NUMBER_OF_CELLS_REGION_EACH_DIMESION || + y == -1 || y == NUMBER_OF_CELLS_REGION_EACH_DIMESION) { + + var voxel = Voxels.getVoxelAt(position.x + x * cellScale, position.y + y * cellScale, position.z, cellScale); + var predatorCellType = ((cell.type + 1) % cellTypes.length); + var predatorCellColor = cellTypes[predatorCellType]; + if (voxel.red == predatorCellColor.r && voxel.green == predatorCellColor.g && voxel.blue == predatorCellColor.b) { + nextCells[i][j].type = predatorCellType; + nextCells[i][j].changed = true; + } + } else { + + if (currentCells[y][x].type == ((cell.type + 1) % cellTypes.length)) { + nextCells[i][j].type = currentCells[y][x].type; + nextCells[i][j].changed = true; + } else { + //indicate no update + nextCells[i][j].changed = false; + } + } + } + } + + for (i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) { + for (j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) { + if (nextCells[i][j].changed == true) { + // there has been a change to this cell, change the value in the currentCells array + currentCells[i][j].type = nextCells[i][j].type; + currentCells[i][j].changed = true; + } + } + } +} + +function sendNextCells() { + for (var i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) { + for (var j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) { + if (nextCells[i][j].changed == true) { + // there has been a change to the state of this cell, send it + + // find the x and y position for this voxel, z = 0 + var x = j * cellScale; + var y = i * cellScale; + var type = nextCells[i][j].type; + + // queue a packet to add a voxel for the new cell + Voxels.setVoxel(position.x + x, position.y + y, position.z, cellScale, cellTypes[type].r, cellTypes[type].g, cellTypes[type].b); + } + } + } +} + +var sentFirstBoard = false; +var voxelViewerInit = false; + +var UPDATES_PER_SECOND = 6.0; +var frameIndex = 1.0; +var oldFrameIndex = 0; + +var framesToWait = UPDATES_PER_SECOND; + +function step(deltaTime) { + + if (isLocal == false) { + if (voxelViewerInit == false) { + VoxelViewer.setPosition(viewerPosition); + VoxelViewer.setOrientation(orientation); + voxelViewerInit = true; + } + VoxelViewer.queryOctree(); + } + + frameIndex += deltaTime * UPDATES_PER_SECOND; + if (Math.floor(frameIndex) == oldFrameIndex) { + return; + } + oldFrameIndex++; + + if (frameIndex <= framesToWait) { + return; + } + + if (sentFirstBoard) { + // we've already sent the first full board, perform a step in time + updateCells(); + } else { + // this will be our first board send + sentFirstBoard = true; + init(); + } + + if (isLocal == false) { + VoxelViewer.queryOctree(); + } + sendNextCells(); +} + +function scriptEnding() { + Voxels.eraseVoxel(regionMarkerX, regionMarkerY, position.z, cellScale); +} + +Script.scriptEnding.connect(scriptEnding); + +Script.update.connect(step); +Voxels.setPacketsPerSecond(2000); + +// test for local... +Menu.isOptionChecked("Voxels"); +isLocal = true; // will only get here on local client \ No newline at end of file diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index db55cc3e94..c16424e69c 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -724,7 +724,7 @@ void Audio::handleAudioInput() { delete[] inputAudioSamples; } - if (_receivedAudioStream.getPacketReceived() > 0) { + if (_receivedAudioStream.getPacketsReceived() > 0) { pushAudioToOutput(); } } @@ -1460,9 +1460,9 @@ void Audio::renderAudioStreamStats(const AudioStreamStats& streamStats, int hori sprintf(stringBuffer, " Packet loss | overall: %5.2f%% (%d lost), last_30s: %5.2f%% (%d lost)", streamStats._packetStreamStats.getLostRate() * 100.0f, - streamStats._packetStreamStats._numLost, + streamStats._packetStreamStats._lost, streamStats._packetStreamWindowStats.getLostRate() * 100.0f, - streamStats._packetStreamWindowStats._numLost); + streamStats._packetStreamWindowStats._lost); verticalOffset += STATS_HEIGHT_PER_LINE; drawText(horizontalOffset, verticalOffset, scale, rotation, font, stringBuffer, color); diff --git a/interface/src/devices/CaraFaceTracker.cpp b/interface/src/devices/CaraFaceTracker.cpp index 9c67163dca..c7ae322ae8 100644 --- a/interface/src/devices/CaraFaceTracker.cpp +++ b/interface/src/devices/CaraFaceTracker.cpp @@ -37,6 +37,7 @@ static QString sampleJson = "[{\"id\":1, \ static const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.0f); static const float TRANSLATION_SCALE = 1.0f; static const int NUM_BLENDSHAPE_COEFF = 30; +static const int NUM_SMOOTHING_SAMPLES = 3; struct CaraPerson { struct CaraPose { @@ -217,9 +218,9 @@ private: CaraFaceTracker::CaraFaceTracker() : _lastReceiveTimestamp(0), - _previousPitch(0.0f), - _previousYaw(0.0f), - _previousRoll(0.0f), + _pitchAverage(NUM_SMOOTHING_SAMPLES), + _yawAverage(NUM_SMOOTHING_SAMPLES), + _rollAverage(NUM_SMOOTHING_SAMPLES), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftYaw(0.0f), _eyeGazeRightPitch(0.0f), @@ -252,9 +253,9 @@ CaraFaceTracker::CaraFaceTracker() : CaraFaceTracker::CaraFaceTracker(const QHostAddress& host, quint16 port) : _lastReceiveTimestamp(0), - _previousPitch(0.0f), - _previousYaw(0.0f), - _previousRoll(0.0f), + _pitchAverage(NUM_SMOOTHING_SAMPLES), + _yawAverage(NUM_SMOOTHING_SAMPLES), + _rollAverage(NUM_SMOOTHING_SAMPLES), _eyeGazeLeftPitch(0.0f), _eyeGazeLeftYaw(0.0f), _eyeGazeRightPitch(0.0f), @@ -371,6 +372,7 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) { CaraPerson person = CaraPacketDecoder::extractOne(buffer, &jsonError); if(jsonError.error == QJsonParseError::NoError) { + //do some noise filtering to the head poses //reduce the noise first by truncating to 1 dp person.pose.roll = glm::floor(person.pose.roll * 10) / 10; @@ -386,43 +388,39 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) { float theta = 2 * acos(r.w); if (theta > EPSILON) { float rMag = glm::length(glm::vec3(r.x, r.y, r.z)); - const float AVERAGE_CARA_FRAME_TIME = 0.033f; + const float AVERAGE_CARA_FRAME_TIME = 0.04f; const float ANGULAR_VELOCITY_MIN = 1.2f; const float YAW_STANDARD_DEV_DEG = 2.5f; _headAngularVelocity = theta / AVERAGE_CARA_FRAME_TIME * glm::vec3(r.x, r.y, r.z) / rMag; + _pitchAverage.updateAverage(person.pose.pitch); + _rollAverage.updateAverage(person.pose.roll); - //use the angular velocity for roll and pitch, if it's below the threshold don't move - if(glm::abs(_headAngularVelocity.x) < ANGULAR_VELOCITY_MIN) { - person.pose.pitch = _previousPitch; - } + //could use the angular velocity to detemine whether to update pitch and roll to further remove the noise. + //use the angular velocity for roll and pitch, update if > THRESHOLD + //if(glm::abs(_headAngularVelocity.x) > ANGULAR_VELOCITY_MIN) { + // _pitchAverage.updateAverage(person.pose.pitch); + //} - if(glm::abs(_headAngularVelocity.z) < ANGULAR_VELOCITY_MIN) { - person.pose.roll = _previousRoll; - } + //if(glm::abs(_headAngularVelocity.z) > ANGULAR_VELOCITY_MIN) { + // _rollAverage.updateAverage(person.pose.roll);; + //} //for yaw, the jitter is great, you can't use angular velocity because it swings too much //use the previous and current yaw, calculate the //abs difference and move it the difference is above the standard deviation which is around 2.5 - // (this will introduce some jerks but will not encounter lag) - - // < the standard deviation 2.5 deg, no move - if(glm::abs(person.pose.yaw - _previousYaw) < YAW_STANDARD_DEV_DEG) { + // > the standard deviation 2.5 deg, update the yaw smoothing average + if(glm::abs(person.pose.yaw - _yawAverage.getAverage()) > YAW_STANDARD_DEV_DEG) { //qDebug() << "Yaw Diff: " << glm::abs(person.pose.yaw - _previousYaw); - person.pose.yaw = _previousYaw; + _yawAverage.updateAverage(person.pose.yaw); } - //update the previous angles - _previousPitch = person.pose.pitch; - _previousYaw = person.pose.yaw; - _previousRoll = person.pose.roll; - //set the new rotation - newRotation = glm::quat(glm::vec3(DEGTORAD(person.pose.pitch), DEGTORAD(person.pose.yaw), DEGTORAD(-person.pose.roll))); + newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage()))); } else { - //no change in position - newRotation = glm::quat(glm::vec3(DEGTORAD(_previousPitch), DEGTORAD(_previousYaw), DEGTORAD(-_previousRoll))); + //no change in position, use previous averages + newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage()))); _headAngularVelocity = glm::vec3(0,0,0); } @@ -456,4 +454,3 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) { float CaraFaceTracker::getBlendshapeCoefficient(int index) const { return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f; } - diff --git a/interface/src/devices/CaraFaceTracker.h b/interface/src/devices/CaraFaceTracker.h index 8fdba1f606..0c715afda9 100644 --- a/interface/src/devices/CaraFaceTracker.h +++ b/interface/src/devices/CaraFaceTracker.h @@ -14,6 +14,7 @@ #include +#include #include "FaceTracker.h" /*! @@ -90,10 +91,10 @@ private: //head tracking glm::vec3 _headAngularVelocity; - //pose history - float _previousPitch; - float _previousYaw; - float _previousRoll; + //pose average + SimpleMovingAverage _pitchAverage; + SimpleMovingAverage _yawAverage; + SimpleMovingAverage _rollAverage; // eye gaze degrees float _eyeGazeLeftPitch; @@ -120,4 +121,4 @@ private: int _jawOpenIndex; }; -#endif //endif hifi_CaraFaceTracker_h \ No newline at end of file +#endif //endif hifi_CaraFaceTracker_h diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index afa799815f..7abc42d0e3 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -366,13 +366,13 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser QString incomingBytesString = locale.toString((uint)stats.getIncomingBytes()); QString incomingWastedBytesString = locale.toString((uint)stats.getIncomingWastedBytes()); const SequenceNumberStats& seqStats = stats.getIncomingOctreeSequenceNumberStats(); - QString incomingOutOfOrderString = locale.toString((uint)seqStats.getNumOutOfOrder()); - QString incomingLateString = locale.toString((uint)seqStats.getNumLate()); - QString incomingUnreasonableString = locale.toString((uint)seqStats.getNumUnreasonable()); - QString incomingEarlyString = locale.toString((uint)seqStats.getNumEarly()); - QString incomingLikelyLostString = locale.toString((uint)seqStats.getNumLost()); - QString incomingRecovered = locale.toString((uint)seqStats.getNumRecovered()); - QString incomingDuplicateString = locale.toString((uint)seqStats.getNumDuplicate()); + QString incomingOutOfOrderString = locale.toString((uint)seqStats.getOutOfOrder()); + QString incomingLateString = locale.toString((uint)seqStats.getLate()); + QString incomingUnreasonableString = locale.toString((uint)seqStats.getUnreasonable()); + QString incomingEarlyString = locale.toString((uint)seqStats.getEarly()); + QString incomingLikelyLostString = locale.toString((uint)seqStats.getLost()); + QString incomingRecovered = locale.toString((uint)seqStats.getRecovered()); + QString incomingDuplicateString = locale.toString((uint)seqStats.getDuplicate()); int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC; QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage()); diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index 6ade4b17e9..36145a35f6 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -212,7 +212,7 @@ SequenceNumberStats::ArrivalInfo InboundAudioStream::frameReceivedUpdateNetworkS // discard the first few packets we receive since they usually have gaps that aren't represensative of normal jitter const int NUM_INITIAL_PACKETS_DISCARD = 3; quint64 now = usecTimestampNow(); - if (_incomingSequenceNumberStats.getNumReceived() > NUM_INITIAL_PACKETS_DISCARD) { + if (_incomingSequenceNumberStats.getReceived() > NUM_INITIAL_PACKETS_DISCARD) { quint64 gap = now - _lastFrameReceivedTime; _interframeTimeGapStatsForStatsPacket.update(gap); diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 06bd329fee..1a196a1c61 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -108,7 +108,7 @@ public: int getSilentFramesDropped() const { return _silentFramesDropped; } int getOverflowCount() const { return _ringBuffer.getOverflowCount(); } - int getPacketReceived() const { return _incomingSequenceNumberStats.getNumReceived(); } + int getPacketsReceived() const { return _incomingSequenceNumberStats.getReceived(); } private: void starved(); diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index a183e2f9a1..73c01ef582 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -40,6 +40,15 @@ void ResourceCache::refresh(const QUrl& url) { } QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) { + + if (QThread::currentThread() != thread()) { + QSharedPointer result; + QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QSharedPointer, result), Q_ARG(const QUrl&, url), Q_ARG(const QUrl&, fallback), + Q_ARG(bool, delayLoad), Q_ARG(void*, extra)); + return result; + } + if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { return getResource(fallback, QUrl(), delayLoad); } diff --git a/libraries/networking/src/ResourceCache.h b/libraries/networking/src/ResourceCache.h index 1593ad45fc..c3a5974da7 100644 --- a/libraries/networking/src/ResourceCache.h +++ b/libraries/networking/src/ResourceCache.h @@ -52,7 +52,7 @@ protected: /// \param fallback a fallback URL to load if the desired one is unavailable /// \param delayLoad if true, don't load the resource immediately; wait until load is first requested /// \param extra extra data to pass to the creator, if appropriate - QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), + Q_INVOKABLE QSharedPointer getResource(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false, void* extra = NULL); /// Creates a new resource. diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 66d57500a5..28ea06787a 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -13,19 +13,71 @@ #include -SequenceNumberStats::SequenceNumberStats(int statsHistoryLength) - : _lastReceived(std::numeric_limits::max()), +SequenceNumberStats::SequenceNumberStats(int statsHistoryLength, bool canDetectOutOfSync) + : _received(0), + _lastReceivedSequence(0), _missingSet(), _stats(), _lastSenderUUID(), - _statsHistory(statsHistoryLength) + _statsHistory(statsHistoryLength), + _canHaveChild(canDetectOutOfSync), + _childInstance(NULL), + _consecutiveReasonable(0) { } +SequenceNumberStats::SequenceNumberStats(const SequenceNumberStats& other) + : _received(other._received), + _lastReceivedSequence(other._lastReceivedSequence), + _missingSet(other._missingSet), + _stats(other._stats), + _lastSenderUUID(other._lastSenderUUID), + _statsHistory(other._statsHistory), + _childInstance(NULL), + _canHaveChild(other._canHaveChild), + _consecutiveReasonable(other._consecutiveReasonable) +{ + if (other._childInstance) { + _childInstance = new SequenceNumberStats(*other._childInstance); + } +} + +SequenceNumberStats& SequenceNumberStats::operator=(const SequenceNumberStats& rhs) { + _received = rhs._received; + _lastReceivedSequence = rhs._lastReceivedSequence; + _missingSet = rhs._missingSet; + _stats = rhs._stats; + _lastSenderUUID = rhs._lastSenderUUID; + _statsHistory = rhs._statsHistory; + _canHaveChild = rhs._canHaveChild; + _consecutiveReasonable = rhs._consecutiveReasonable; + + if (rhs._childInstance) { + _childInstance = new SequenceNumberStats(*rhs._childInstance); + } else { + _childInstance = NULL; + } + return *this; +} + +SequenceNumberStats::~SequenceNumberStats() { + if (_childInstance) { + delete _childInstance; + } +} + void SequenceNumberStats::reset() { + _received = 0; _missingSet.clear(); _stats = PacketStreamStats(); + _lastSenderUUID = QUuid(); _statsHistory.clear(); + + if (_childInstance) { + delete _childInstance; + _childInstance = NULL; + } + _consecutiveReasonable = 0; } static const int UINT16_RANGE = std::numeric_limits::max() + 1; @@ -36,7 +88,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui // if the sender node has changed, reset all stats if (senderUUID != _lastSenderUUID) { - if (_stats._numReceived > 0) { + if (_received > 0) { qDebug() << "sequence number stats was reset due to new sender node"; qDebug() << "previous:" << _lastSenderUUID << "current:" << senderUUID; reset(); @@ -45,13 +97,14 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui } // determine our expected sequence number... handle rollover appropriately - quint16 expected = _stats._numReceived > 0 ? _lastReceived + (quint16)1 : incoming; + quint16 expected = _received > 0 ? _lastReceivedSequence + (quint16)1 : incoming; - _stats._numReceived++; + _received++; if (incoming == expected) { // on time arrivalInfo._status = OnTime; - _lastReceived = incoming; + _lastReceivedSequence = incoming; + _stats._expectedReceived++; } else { // out of order if (wantExtraDebugging) { @@ -74,14 +127,76 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui } else if (absGap > MAX_REASONABLE_SEQUENCE_GAP) { arrivalInfo._status = Unreasonable; - // ignore packet if gap is unreasonable - qDebug() << "ignoring unreasonable sequence number:" << incoming - << "previous:" << _lastReceived; - _stats._numUnreasonable++; + qDebug() << "unreasonable sequence number:" << incoming << "previous:" << _lastReceivedSequence; + + _stats._unreasonable++; + _consecutiveReasonable = 0; + + + // if _canHaveChild, create a child instance of SequenceNumberStats to track this unreasonable seq num and ones in the future. + // if the child instance detects a valid stream of seq nums up to some length, the seq nums sender probably + // fell out of sync with us. + + if (_canHaveChild) { + + if (!_childInstance) { + _childInstance = new SequenceNumberStats(0, false); + } + + ArrivalInfo unreasonableTrackerArrivalInfo = _childInstance->sequenceNumberReceived(incoming); + + // the child instance will be used to detect some threshold number seq nums in a row that are perfectly + // in order. + + const int UNREASONABLE_TRACKER_RECEIVED_THRESHOLD = 8; + + if (unreasonableTrackerArrivalInfo._status != OnTime) { + _childInstance->reset(); + + } else if (_childInstance->getReceived() >= UNREASONABLE_TRACKER_RECEIVED_THRESHOLD) { + + // the child instance has detected a threshold number of consecutive seq nums. + // copy its state to this instance. + + _received = _childInstance->_received; + _lastReceivedSequence = _childInstance->_lastReceivedSequence; + _missingSet = _childInstance->_missingSet; + _stats = _childInstance->_stats; + + // don't copy _lastSenderUUID; _unreasonableTracker always has null UUID for that member. + // ours should be up-to-date. + + // don't copy _statsHistory; _unreasonableTracker keeps a history of length 0. + // simply clear ours. + _statsHistory.clear(); + + arrivalInfo = unreasonableTrackerArrivalInfo; + + // delete child instance; + delete _childInstance; + _childInstance = NULL; + } + } return arrivalInfo; } + _consecutiveReasonable++; + + // if we got a reasonable seq num but have a child instance tracking unreasonable seq nums, + // reset it. if many consecutive reasonable seq nums have occurred (implying the unreasonable seq num + // that caused the creation of the child instance was probably a fluke), delete our child instance. + if (_childInstance) { + const int CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD = 8; + if (_consecutiveReasonable >= CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD) { + _childInstance->reset(); + } else { + delete _childInstance; + _childInstance = NULL; + } + } + + // now that rollover has been corrected for (if it occurred), incomingInt and expectedInt can be // compared to each other directly, though one of them might be negative @@ -94,10 +209,11 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui qDebug() << "this packet is earlier than expected..."; qDebug() << ">>>>>>>> missing gap=" << (incomingInt - expectedInt); } - - _stats._numEarly++; - _stats._numLost += (incomingInt - expectedInt); - _lastReceived = incoming; + int skipped = incomingInt - expectedInt; + _stats._early++; + _stats._lost += skipped; + _stats._expectedReceived += (skipped + 1); + _lastReceivedSequence = incoming; // add all sequence numbers that were skipped to the missing sequence numbers list for (int missingInt = expectedInt; missingInt < incomingInt; missingInt++) { @@ -114,7 +230,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui qDebug() << "this packet is later than expected..."; } - _stats._numLate++; + _stats._late++; // do not update _lastReceived; it shouldn't become smaller @@ -125,18 +241,19 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui if (wantExtraDebugging) { qDebug() << "found it in _missingSet"; } - _stats._numLost--; - _stats._numRecovered++; + _stats._lost--; + _stats._recovered++; } else { arrivalInfo._status = Duplicate; if (wantExtraDebugging) { qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate"; } - _stats._numDuplicate++; + _stats._duplicate++; } } } + return arrivalInfo; } @@ -148,7 +265,7 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) { // some older sequence numbers may be from before a rollover point; this must be handled. // some sequence numbers in this list may be larger than _incomingLastSequence, indicating that they were received // before the most recent rollover. - int cutoff = (int)_lastReceived - MAX_REASONABLE_SEQUENCE_GAP; + int cutoff = (int)_lastReceivedSequence - MAX_REASONABLE_SEQUENCE_GAP; if (cutoff >= 0) { quint16 nonRolloverCutoff = (quint16)cutoff; QSet::iterator i = _missingSet.begin(); @@ -159,7 +276,7 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) { qDebug() << "old age cutoff:" << nonRolloverCutoff; } - if (missing > _lastReceived || missing < nonRolloverCutoff) { + if (missing > _lastReceivedSequence || missing < nonRolloverCutoff) { i = _missingSet.erase(i); if (wantExtraDebugging) { qDebug() << "pruning really old missing sequence:" << missing; @@ -178,7 +295,7 @@ void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) { qDebug() << "old age cutoff:" << rolloverCutoff; } - if (missing > _lastReceived && missing < rolloverCutoff) { + if (missing > _lastReceivedSequence && missing < rolloverCutoff) { i = _missingSet.erase(i); if (wantExtraDebugging) { qDebug() << "pruning really old missing sequence:" << missing; @@ -202,13 +319,13 @@ PacketStreamStats SequenceNumberStats::getStatsForHistoryWindow() const { // calculate difference between newest stats and oldest stats to get window stats PacketStreamStats windowStats; - windowStats._numReceived = newestStats->_numReceived - oldestStats->_numReceived; - windowStats._numUnreasonable = newestStats->_numUnreasonable - oldestStats->_numUnreasonable; - windowStats._numEarly = newestStats->_numEarly - oldestStats->_numEarly; - windowStats._numLate = newestStats->_numLate - oldestStats->_numLate; - windowStats._numLost = newestStats->_numLost - oldestStats->_numLost; - windowStats._numRecovered = newestStats->_numRecovered - oldestStats->_numRecovered; - windowStats._numDuplicate = newestStats->_numDuplicate - oldestStats->_numDuplicate; + windowStats._expectedReceived = newestStats->_expectedReceived - oldestStats->_expectedReceived; + windowStats._unreasonable = newestStats->_unreasonable - oldestStats->_unreasonable; + windowStats._early = newestStats->_early - oldestStats->_early; + windowStats._late = newestStats->_late - oldestStats->_late; + windowStats._lost = newestStats->_lost - oldestStats->_lost; + windowStats._recovered = newestStats->_recovered - oldestStats->_recovered; + windowStats._duplicate = newestStats->_duplicate - oldestStats->_duplicate; return windowStats; } diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h index f4e85b6fb3..fb3ff68f70 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -18,32 +18,31 @@ const int MAX_REASONABLE_SEQUENCE_GAP = 1000; + class PacketStreamStats { public: PacketStreamStats() - : _numReceived(0), - _numUnreasonable(0), - _numEarly(0), - _numLate(0), - _numLost(0), - _numRecovered(0), - _numDuplicate(0) + : _expectedReceived(0), + _unreasonable(0), + _early(0), + _late(0), + _lost(0), + _recovered(0), + _duplicate(0) {} - float getUnreasonableRate() const { return (float)_numUnreasonable / _numReceived; } - float getNumEaryRate() const { return (float)_numEarly / _numReceived; } - float getLateRate() const { return (float)_numLate / _numReceived; } - float getLostRate() const { return (float)_numLost / _numReceived; } - float getRecoveredRate() const { return (float)_numRecovered / _numReceived; } - float getDuplicateRate() const { return (float)_numDuplicate / _numReceived; } + float getOutOfOrderRate() const { return (float)(_early + _late) / _expectedReceived; } + float getEaryRate() const { return (float)_early / _expectedReceived; } + float getLateRate() const { return (float)_late / _expectedReceived; } + float getLostRate() const { return (float)_lost / _expectedReceived; } - quint32 _numReceived; - quint32 _numUnreasonable; - quint32 _numEarly; - quint32 _numLate; - quint32 _numLost; - quint32 _numRecovered; - quint32 _numDuplicate; + quint32 _expectedReceived; + quint32 _unreasonable; + quint32 _early; + quint32 _late; + quint32 _lost; + quint32 _recovered; + quint32 _duplicate; }; class SequenceNumberStats { @@ -63,27 +62,36 @@ public: }; - SequenceNumberStats(int statsHistoryLength = 0); + SequenceNumberStats(int statsHistoryLength = 0, bool canDetectOutOfSync = true); + SequenceNumberStats(const SequenceNumberStats& other); + SequenceNumberStats& operator=(const SequenceNumberStats& rhs); + ~SequenceNumberStats(); void reset(); ArrivalInfo sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false); void pruneMissingSet(const bool wantExtraDebugging = false); void pushStatsToHistory() { _statsHistory.insert(_stats); } - quint32 getNumReceived() const { return _stats._numReceived; } - quint32 getNumUnreasonable() const { return _stats._numUnreasonable; } - quint32 getNumOutOfOrder() const { return _stats._numEarly + _stats._numLate; } - quint32 getNumEarly() const { return _stats._numEarly; } - quint32 getNumLate() const { return _stats._numLate; } - quint32 getNumLost() const { return _stats._numLost; } - quint32 getNumRecovered() const { return _stats._numRecovered; } - quint32 getNumDuplicate() const { return _stats._numDuplicate; } + quint32 getReceived() const { return _received; } + float getUnreasonableRate() const { return _stats._unreasonable / _received; } + + quint32 getExpectedReceived() const { return _stats._expectedReceived; } + quint32 getUnreasonable() const { return _stats._unreasonable; } + quint32 getOutOfOrder() const { return _stats._early + _stats._late; } + quint32 getEarly() const { return _stats._early; } + quint32 getLate() const { return _stats._late; } + quint32 getLost() const { return _stats._lost; } + quint32 getRecovered() const { return _stats._recovered; } + quint32 getDuplicate() const { return _stats._duplicate; } + const PacketStreamStats& getStats() const { return _stats; } PacketStreamStats getStatsForHistoryWindow() const; const QSet& getMissingSet() const { return _missingSet; } private: - quint16 _lastReceived; + int _received; + + quint16 _lastReceivedSequence; QSet _missingSet; PacketStreamStats _stats; @@ -91,6 +99,14 @@ private: QUuid _lastSenderUUID; RingBufferHistory _statsHistory; + + + // to deal with the incoming seq nums going out of sync with this tracker, we'll create another instance + // of this class when we encounter an unreasonable + bool _canHaveChild; + SequenceNumberStats* _childInstance; + + int _consecutiveReasonable; }; #endif // hifi_SequenceNumberStats_h diff --git a/libraries/voxels/src/VoxelsScriptingInterface.cpp b/libraries/voxels/src/VoxelsScriptingInterface.cpp index 05be0fe350..c11aca2872 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.cpp +++ b/libraries/voxels/src/VoxelsScriptingInterface.cpp @@ -80,11 +80,16 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale, DeleteVoxelCommand* deleteCommand = new DeleteVoxelCommand(_tree, addVoxelDetail, getVoxelPacketSender()); + _undoStackMutex.lock(); + _undoStack->beginMacro(addCommand->text()); // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. _undoStack->push(deleteCommand); _undoStack->push(addCommand); _undoStack->endMacro(); + + //Unlock the mutex + _undoStackMutex.unlock(); } else { // queue the destructive add queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail); @@ -109,11 +114,15 @@ void VoxelsScriptingInterface::eraseVoxel(float x, float y, float z, float scale } if (_undoStack) { + DeleteVoxelCommand* command = new DeleteVoxelCommand(_tree, deleteVoxelDetail, getVoxelPacketSender()); + + _undoStackMutex.lock(); // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. _undoStack->push(command); + _undoStackMutex.unlock(); } else { getVoxelPacketSender()->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &deleteVoxelDetail); _tree->deleteVoxelAt(deleteVoxelDetail.x, deleteVoxelDetail.y, deleteVoxelDetail.z, deleteVoxelDetail.s); diff --git a/libraries/voxels/src/VoxelsScriptingInterface.h b/libraries/voxels/src/VoxelsScriptingInterface.h index 787c37fb20..2e1fc2a8d5 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.h +++ b/libraries/voxels/src/VoxelsScriptingInterface.h @@ -102,6 +102,7 @@ private: void queueVoxelAdd(PacketType addPacketType, VoxelDetail& addVoxelDetails); VoxelTree* _tree; QUndoStack* _undoStack; + QMutex _undoStackMutex; }; #endif // hifi_VoxelsScriptingInterface_h diff --git a/tests/networking/src/SequenceNumberStatsTests.cpp b/tests/networking/src/SequenceNumberStatsTests.cpp index de487267e0..1df48b781b 100644 --- a/tests/networking/src/SequenceNumberStatsTests.cpp +++ b/tests/networking/src/SequenceNumberStatsTests.cpp @@ -16,11 +16,11 @@ void SequenceNumberStatsTests::runAllTests() { - rolloverTest(); earlyLateTest(); duplicateTest(); pruneTest(); + recursiveTest(); } const quint32 UINT16_RANGE = std::numeric_limits::max() + 1; @@ -38,12 +38,12 @@ void SequenceNumberStatsTests::rolloverTest() { stats.sequenceNumberReceived(seq); seq = seq + (quint16)1; - assert(stats.getNumDuplicate() == 0); - assert(stats.getNumEarly() == 0); - assert(stats.getNumLate() == 0); - assert(stats.getNumLost() == 0); - assert(stats.getNumReceived() == i + 1); - assert(stats.getNumRecovered() == 0); + assert(stats.getDuplicate() == 0); + assert(stats.getEarly() == 0); + assert(stats.getLate() == 0); + assert(stats.getLost() == 0); + assert(stats.getReceived() == i + 1); + assert(stats.getRecovered() == 0); } stats.reset(); } @@ -69,12 +69,12 @@ void SequenceNumberStatsTests::earlyLateTest() { seq = seq + (quint16)1; numSent++; - assert(stats.getNumDuplicate() == 0); - assert(stats.getNumEarly() == numEarly); - assert(stats.getNumLate() == numLate); - assert(stats.getNumLost() == numLost); - assert(stats.getNumReceived() == numSent); - assert(stats.getNumRecovered() == numRecovered); + assert(stats.getDuplicate() == 0); + assert(stats.getEarly() == numEarly); + assert(stats.getLate() == numLate); + assert(stats.getLost() == numLost); + assert(stats.getReceived() == numSent); + assert(stats.getRecovered() == numRecovered); } // skip 10 @@ -89,12 +89,12 @@ void SequenceNumberStatsTests::earlyLateTest() { seq = seq + (quint16)1; numSent++; - assert(stats.getNumDuplicate() == 0); - assert(stats.getNumEarly() == numEarly); - assert(stats.getNumLate() == numLate); - assert(stats.getNumLost() == numLost); - assert(stats.getNumReceived() == numSent); - assert(stats.getNumRecovered() == numRecovered); + assert(stats.getDuplicate() == 0); + assert(stats.getEarly() == numEarly); + assert(stats.getLate() == numLate); + assert(stats.getLost() == numLost); + assert(stats.getReceived() == numSent); + assert(stats.getRecovered() == numRecovered); } // send ones we skipped @@ -106,12 +106,12 @@ void SequenceNumberStatsTests::earlyLateTest() { numLost--; numRecovered++; - assert(stats.getNumDuplicate() == 0); - assert(stats.getNumEarly() == numEarly); - assert(stats.getNumLate() == numLate); - assert(stats.getNumLost() == numLost); - assert(stats.getNumReceived() == numSent); - assert(stats.getNumRecovered() == numRecovered); + assert(stats.getDuplicate() == 0); + assert(stats.getEarly() == numEarly); + assert(stats.getLate() == numLate); + assert(stats.getLost() == numLost); + assert(stats.getReceived() == numSent); + assert(stats.getRecovered() == numRecovered); } } stats.reset(); @@ -145,12 +145,12 @@ void SequenceNumberStatsTests::duplicateTest() { seq = seq + (quint16)1; numSent++; - assert(stats.getNumDuplicate() == numDuplicate); - assert(stats.getNumEarly() == numEarly); - assert(stats.getNumLate() == numLate); - assert(stats.getNumLost() == numLost); - assert(stats.getNumReceived() == numSent); - assert(stats.getNumRecovered() == 0); + assert(stats.getDuplicate() == numDuplicate); + assert(stats.getEarly() == numEarly); + assert(stats.getLate() == numLate); + assert(stats.getLost() == numLost); + assert(stats.getReceived() == numSent); + assert(stats.getRecovered() == 0); } // skip 10 @@ -167,12 +167,12 @@ void SequenceNumberStatsTests::duplicateTest() { seq = seq + (quint16)1; numSent++; - assert(stats.getNumDuplicate() == numDuplicate); - assert(stats.getNumEarly() == numEarly); - assert(stats.getNumLate() == numLate); - assert(stats.getNumLost() == numLost); - assert(stats.getNumReceived() == numSent); - assert(stats.getNumRecovered() == 0); + assert(stats.getDuplicate() == numDuplicate); + assert(stats.getEarly() == numEarly); + assert(stats.getLate() == numLate); + assert(stats.getLost() == numLost); + assert(stats.getReceived() == numSent); + assert(stats.getRecovered() == 0); } // send 5 duplicates from before skip @@ -183,12 +183,12 @@ void SequenceNumberStatsTests::duplicateTest() { numDuplicate++; numLate++; - assert(stats.getNumDuplicate() == numDuplicate); - assert(stats.getNumEarly() == numEarly); - assert(stats.getNumLate() == numLate); - assert(stats.getNumLost() == numLost); - assert(stats.getNumReceived() == numSent); - assert(stats.getNumRecovered() == 0); + assert(stats.getDuplicate() == numDuplicate); + assert(stats.getEarly() == numEarly); + assert(stats.getLate() == numLate); + assert(stats.getLost() == numLost); + assert(stats.getReceived() == numSent); + assert(stats.getRecovered() == 0); } // send 5 duplicates from after skip @@ -199,12 +199,12 @@ void SequenceNumberStatsTests::duplicateTest() { numDuplicate++; numLate++; - assert(stats.getNumDuplicate() == numDuplicate); - assert(stats.getNumEarly() == numEarly); - assert(stats.getNumLate() == numLate); - assert(stats.getNumLost() == numLost); - assert(stats.getNumReceived() == numSent); - assert(stats.getNumRecovered() == 0); + assert(stats.getDuplicate() == numDuplicate); + assert(stats.getEarly() == numEarly); + assert(stats.getLate() == numLate); + assert(stats.getLost() == numLost); + assert(stats.getReceived() == numSent); + assert(stats.getRecovered() == 0); } } stats.reset(); @@ -278,3 +278,36 @@ void SequenceNumberStatsTests::pruneTest() { numLost = 0; } } + +void SequenceNumberStatsTests::recursiveTest() { + + SequenceNumberStats stats(0); + + quint16 sequence; + + sequence = 89; + stats.sequenceNumberReceived(sequence); + + assert(stats.getUnreasonable() == 0); + + sequence = 2990; + for (int i = 0; i < 10; i++) { + stats.sequenceNumberReceived(sequence); + sequence += (quint16)1; + } + + assert(stats.getUnreasonable() == 0); + + + sequence = 0; + for (int R = 0; R < 7; R++) { + stats.sequenceNumberReceived(sequence); + sequence += (quint16)2000; + } + + for (int i = 0; i < 10; i++) { + stats.sequenceNumberReceived(sequence); + sequence += (quint16)1; + } + assert(stats.getUnreasonable() == 0); +} diff --git a/tests/networking/src/SequenceNumberStatsTests.h b/tests/networking/src/SequenceNumberStatsTests.h index 53a0b66480..80053fd822 100644 --- a/tests/networking/src/SequenceNumberStatsTests.h +++ b/tests/networking/src/SequenceNumberStatsTests.h @@ -23,6 +23,7 @@ namespace SequenceNumberStatsTests { void earlyLateTest(); void duplicateTest(); void pruneTest(); + void recursiveTest(); }; #endif // hifi_SequenceNumberStatsTests_h