From 2fea3c58cdc99249dee7cfbd0eacbc6a9a64555e Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 17 Jul 2014 15:25:54 -0700 Subject: [PATCH 01/17] Added working rockPaperScissors.js that works for multiple ACs --- examples/rockPaperScissorsCells.js | 225 +++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 examples/rockPaperScissorsCells.js diff --git a/examples/rockPaperScissorsCells.js b/examples/rockPaperScissorsCells.js new file mode 100644 index 0000000000..9227278455 --- /dev/null +++ b/examples/rockPaperScissorsCells.js @@ -0,0 +1,225 @@ +// 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/ +// +// 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 = 64; +var NUMBER_OF_CELLS_REGION_EACH_DIMESION = 16; +var REGIONS_EACH_DIMENSION = NUMBER_OF_CELLS_EACH_DIMENSION / NUMBER_OF_CELLS_REGION_EACH_DIMESION; + +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; + +//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: 255, g: 255, b: 255 }; +cellTypes[4] = { r: 255, g: 0, b: 255 }; +cellTypes[5] = { 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: 255, g: 0, b: 255}; + +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; + } + } +} + +if (regionMarkerX == -1) { + print("No available Cellular Automata regions found!") + Script.stop(); +} + +position.x = cornerPosition.x + regionMarkerJ * NUMBER_OF_CELLS_REGION_EACH_DIMESION * cellScale; +position.y = cornerPosition.y + regionMarkerI * NUMBER_OF_CELLS_REGION_EACH_DIMESION * cellScale; +position.z = cornerPosition.z; + +Voxels.setVoxel(regionMarkerX, regionMarkerY, position.z, cellScale, regionMarkerColor.r, regionMarkerColor.g, regionMarkerColor.b); + +// randomly populate the cell start values +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] = []; + + for (var j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) { + currentCells[i][j] = { changed: true, type: Math.floor(Math.random() * cellTypes.length) }; + + // put the same value in the nextCells array for first board draw + nextCells[i][j] = currentCells[i][j]; + } +} + +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] = currentCells[y][x]; + nextCells[i][j].changed = true; + } else { + //indicate no update + nextCells[i][j].changed = true; + } + } + } + } + + 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] = nextCells[i][j]; + } + } + } +} + +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 UPDATES_PER_SECOND = 3.0; +var frameIndex = 1.0; +var oldFrameIndex = 0; + +function step(deltaTime) { + frameIndex += deltaTime * UPDATES_PER_SECOND; + if (Math.floor(frameIndex) == oldFrameIndex) { + return; + } + oldFrameIndex++; + + 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; + } + + sendNextCells(); +} + +function scriptEnding() { + Voxels.eraseVoxel(regionMarkerX, regionMarkerY, position.z, cellScale); +} + +Script.scriptEnding.connect(scriptEnding); + +Script.update.connect(step); +//Voxels.setMaxPacketSize(1); //this is needed or a bug occurs :( +Voxels.setPacketsPerSecond(2000); From 592c571fcb6d3c9d71916dd4fd3d82c3c2fdc8a2 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 17 Jul 2014 18:15:34 -0700 Subject: [PATCH 02/17] Fixed crash bug in voxelScriptingInterface --- examples/rockPaperScissorsCells.js | 10 ++++------ libraries/voxels/src/VoxelsScriptingInterface.cpp | 6 ++++++ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/rockPaperScissorsCells.js b/examples/rockPaperScissorsCells.js index 9227278455..4f6de888a1 100644 --- a/examples/rockPaperScissorsCells.js +++ b/examples/rockPaperScissorsCells.js @@ -29,9 +29,7 @@ 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: 255, g: 255, b: 255 }; -cellTypes[4] = { r: 255, g: 0, b: 255 }; -cellTypes[5] = { r: 0, g: 255, b: 255 }; + //Check for free region for AC var regionMarkerX = -1; @@ -152,11 +150,11 @@ function updateCells() { } else { if (currentCells[y][x].type == ((cell.type + 1) % cellTypes.length)) { - nextCells[i][j] = currentCells[y][x]; + nextCells[i][j].type = currentCells[y][x].type; nextCells[i][j].changed = true; } else { //indicate no update - nextCells[i][j].changed = true; + nextCells[i][j].changed = false; } } } @@ -192,7 +190,7 @@ function sendNextCells() { var sentFirstBoard = false; -var UPDATES_PER_SECOND = 3.0; +var UPDATES_PER_SECOND = 6.0; var frameIndex = 1.0; var oldFrameIndex = 0; diff --git a/libraries/voxels/src/VoxelsScriptingInterface.cpp b/libraries/voxels/src/VoxelsScriptingInterface.cpp index e877f99760..ba1c3d72de 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.cpp +++ b/libraries/voxels/src/VoxelsScriptingInterface.cpp @@ -81,11 +81,17 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale, DeleteVoxelCommand* deleteCommand = new DeleteVoxelCommand(_tree, addVoxelDetail, getVoxelPacketSender()); + static QMutex mutex; + mutex.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 + mutex.unlock(); } else { // queue the destructive add queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail); From 814dd0fcb6408ed9b07fdf4be9ea5d22c5a1fcbb Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 18 Jul 2014 11:37:13 -0700 Subject: [PATCH 03/17] Fixed another crash relating to undo stack. Made script support the VoxelViewer --- examples/rockPaperScissorsCells.js | 31 +++++++++++++++++++--- libraries/networking/src/ResourceCache.cpp | 9 +++++++ libraries/networking/src/ResourceCache.h | 2 +- 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/examples/rockPaperScissorsCells.js b/examples/rockPaperScissorsCells.js index 4f6de888a1..aa7d4ef040 100644 --- a/examples/rockPaperScissorsCells.js +++ b/examples/rockPaperScissorsCells.js @@ -11,10 +11,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -var NUMBER_OF_CELLS_EACH_DIMENSION = 64; +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 = []; @@ -24,11 +26,20 @@ 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 }; +cellTypes[4] = { r: 255, g:0, b: 255 }; +//cellTypes[5] = { r: 255, g:255, b: 255 }; //Check for free region for AC @@ -76,8 +87,10 @@ for (var i = 0; i < NUMBER_OF_CELLS_REGION_EACH_DIMESION; i++) { // create the array to hold this row in the nextCells array nextCells[i] = []; + var randomColor = Math.floor(Math.random() * cellTypes.length); + for (var j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) { - currentCells[i][j] = { changed: true, type: Math.floor(Math.random() * cellTypes.length) }; + currentCells[i][j] = { changed: true, type: randomColor }; // put the same value in the nextCells array for first board draw nextCells[i][j] = currentCells[i][j]; @@ -207,6 +220,15 @@ function step(deltaTime) { } else { // this will be our first board send sentFirstBoard = true; + + print("AHHHH"); + print(viewerPosition.x + " " + viewerPosition.y + " " + viewerPosition.z); + + if (isLocal == false) { + VoxelViewer.setPosition(viewerPosition); + VoxelViewer.setOrientation(orientation); + VoxelViewer.queryOctree(); + } } sendNextCells(); @@ -219,5 +241,8 @@ function scriptEnding() { Script.scriptEnding.connect(scriptEnding); Script.update.connect(step); -//Voxels.setMaxPacketSize(1); //this is needed or a bug occurs :( 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/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. From 4410411d9d4c2f32eeaafca369e572a1c37e8d86 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Fri, 18 Jul 2014 14:51:53 -0700 Subject: [PATCH 04/17] Script improvements for AC --- examples/rockPaperScissorsCells.js | 118 ++++++++++-------- .../voxels/src/VoxelsScriptingInterface.cpp | 5 + 2 files changed, 68 insertions(+), 55 deletions(-) diff --git a/examples/rockPaperScissorsCells.js b/examples/rockPaperScissorsCells.js index aa7d4ef040..d7101ce4c7 100644 --- a/examples/rockPaperScissorsCells.js +++ b/examples/rockPaperScissorsCells.js @@ -11,7 +11,7 @@ // 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_EACH_DIMENSION = 64; var NUMBER_OF_CELLS_REGION_EACH_DIMESION = 16; var REGIONS_EACH_DIMENSION = NUMBER_OF_CELLS_EACH_DIMENSION / NUMBER_OF_CELLS_REGION_EACH_DIMESION; @@ -37,8 +37,8 @@ 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 }; -cellTypes[4] = { r: 255, g:0, b: 255 }; +cellTypes[3] = { r: 0, g: 255, b: 255 }; +//cellTypes[4] = { r: 255, g:0, b: 255 }; //cellTypes[5] = { r: 255, g:255, b: 255 }; @@ -50,51 +50,45 @@ var regionMarkerJ = -1; var regionMarkerColor = {r: 255, g: 0, b: 255}; -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; + +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; + } } } -} + + Voxels.setVoxel(regionMarkerX, regionMarkerY, position.z, cellScale, regionMarkerColor.r, regionMarkerColor.g, regionMarkerColor.b); -if (regionMarkerX == -1) { - print("No available Cellular Automata regions found!") - Script.stop(); -} - -position.x = cornerPosition.x + regionMarkerJ * NUMBER_OF_CELLS_REGION_EACH_DIMESION * cellScale; -position.y = cornerPosition.y + regionMarkerI * NUMBER_OF_CELLS_REGION_EACH_DIMESION * cellScale; -position.z = cornerPosition.z; - -Voxels.setVoxel(regionMarkerX, regionMarkerY, position.z, cellScale, regionMarkerColor.r, regionMarkerColor.g, regionMarkerColor.b); - -// randomly populate the cell start values -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 randomColor = Math.floor(Math.random() * cellTypes.length); - - for (var j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) { - currentCells[i][j] = { changed: true, type: randomColor }; - - // put the same value in the nextCells array for first board draw - nextCells[i][j] = currentCells[i][j]; - } + // randomly populate the cell start values + var randomColor = Math.floor(Math.random() * cellTypes.length); + 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] = []; + + for (var j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) { + currentCells[i][j] = { changed: true, type: randomColor }; + + // put the same value in the nextCells array for first board draw + nextCells[i][j] = currentCells[i][j]; + } + } } function updateCells() { @@ -202,35 +196,49 @@ function sendNextCells() { } 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; + } + print("UPDATE"); + 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; - - print("AHHHH"); - print(viewerPosition.x + " " + viewerPosition.y + " " + viewerPosition.z); - - if (isLocal == false) { - VoxelViewer.setPosition(viewerPosition); - VoxelViewer.setOrientation(orientation); - VoxelViewer.queryOctree(); - } + init(); + } + + if (isLocal == false) { + VoxelViewer.queryOctree(); } - sendNextCells(); } diff --git a/libraries/voxels/src/VoxelsScriptingInterface.cpp b/libraries/voxels/src/VoxelsScriptingInterface.cpp index ba1c3d72de..093d736720 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.cpp +++ b/libraries/voxels/src/VoxelsScriptingInterface.cpp @@ -116,11 +116,16 @@ void VoxelsScriptingInterface::eraseVoxel(float x, float y, float z, float scale } if (_undoStack) { + DeleteVoxelCommand* command = new DeleteVoxelCommand(_tree, deleteVoxelDetail, getVoxelPacketSender()); + + static QMutex mutex; + mutex.lock(); // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. _undoStack->push(command); + mutex.unlock(); } else { getVoxelPacketSender()->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &deleteVoxelDetail); _tree->deleteVoxelAt(deleteVoxelDetail.x, deleteVoxelDetail.y, deleteVoxelDetail.z, deleteVoxelDetail.s); From ab52f384b7fddc64da65f762ba2f4cf01f3a3aff Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Thu, 31 Jul 2014 11:15:20 -0700 Subject: [PATCH 05/17] Improvements on RPS script --- examples/rockPaperScissorsCells.js | 50 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/examples/rockPaperScissorsCells.js b/examples/rockPaperScissorsCells.js index d7101ce4c7..2a9cb00a0b 100644 --- a/examples/rockPaperScissorsCells.js +++ b/examples/rockPaperScissorsCells.js @@ -6,12 +6,14 @@ // // 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 = 64; +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; @@ -38,8 +40,6 @@ 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 }; -//cellTypes[4] = { r: 255, g:0, b: 255 }; -//cellTypes[5] = { r: 255, g:255, b: 255 }; //Check for free region for AC @@ -48,8 +48,19 @@ var regionMarkerY = -1; var regionMarkerI = -1; var regionMarkerJ = -1; -var regionMarkerColor = {r: 255, g: 0, b: 255}; +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() { @@ -71,24 +82,30 @@ function init() { } } - Voxels.setVoxel(regionMarkerX, regionMarkerY, position.z, cellScale, regionMarkerColor.r, regionMarkerColor.g, regionMarkerColor.b); + //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); - // randomly populate the cell start values - var randomColor = Math.floor(Math.random() * cellTypes.length); 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] = []; - - for (var j = 0; j < NUMBER_OF_CELLS_REGION_EACH_DIMESION; j++) { - currentCells[i][j] = { changed: true, type: randomColor }; - - // put the same value in the nextCells array for first board draw - nextCells[i][j] = currentCells[i][j]; - } } + + 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() { @@ -171,7 +188,8 @@ function updateCells() { 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] = nextCells[i][j]; + currentCells[i][j].type = nextCells[i][j].type; + currentCells[i][j].changed = true; } } } @@ -224,8 +242,6 @@ function step(deltaTime) { if (frameIndex <= framesToWait) { return; } - - print("UPDATE"); if (sentFirstBoard) { // we've already sent the first full board, perform a step in time From 779298b7dc63830d9d7f87fa77c1299823dbd31d Mon Sep 17 00:00:00 2001 From: Zu Date: Fri, 1 Aug 2014 14:11:50 +0800 Subject: [PATCH 06/17] improve: reduce animation jerks in avatar head rotation --- interface/src/devices/CaraFaceTracker.cpp | 53 +++++++++++------------ interface/src/devices/CaraFaceTracker.h | 9 ++-- 2 files changed, 31 insertions(+), 31 deletions(-) diff --git a/interface/src/devices/CaraFaceTracker.cpp b/interface/src/devices/CaraFaceTracker.cpp index 9c67163dca..8160200641 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,8 +372,9 @@ 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 + //reduce the noise first by truncating to 1 dp person.pose.roll = glm::floor(person.pose.roll * 10) / 10; person.pose.pitch = glm::floor(person.pose.pitch * 10) / 10; person.pose.yaw = glm::floor(person.pose.yaw * 10) / 10; @@ -386,43 +388,40 @@ 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; - //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; - } + _pitchAverage.updateAverage(person.pose.pitch); + _rollAverage.updateAverage(person.pose.roll); - if(glm::abs(_headAngularVelocity.z) < ANGULAR_VELOCITY_MIN) { - person.pose.roll = _previousRoll; - } + //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) { + // _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))); + newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage()))); _headAngularVelocity = glm::vec3(0,0,0); } diff --git a/interface/src/devices/CaraFaceTracker.h b/interface/src/devices/CaraFaceTracker.h index f51fed0f1b..41dd205d22 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; From 4aa5fb27954e0101fc1800fd9d200ec6b9915e9f Mon Sep 17 00:00:00 2001 From: Zu Date: Fri, 1 Aug 2014 16:12:17 +0800 Subject: [PATCH 07/17] fix: comments, extra white spaces --- interface/src/devices/CaraFaceTracker.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/interface/src/devices/CaraFaceTracker.cpp b/interface/src/devices/CaraFaceTracker.cpp index 8160200641..c7ae322ae8 100644 --- a/interface/src/devices/CaraFaceTracker.cpp +++ b/interface/src/devices/CaraFaceTracker.cpp @@ -374,7 +374,7 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) { if(jsonError.error == QJsonParseError::NoError) { //do some noise filtering to the head poses - //reduce the noise first by truncating to 1 dp + //reduce the noise first by truncating to 1 dp person.pose.roll = glm::floor(person.pose.roll * 10) / 10; person.pose.pitch = glm::floor(person.pose.pitch * 10) / 10; person.pose.yaw = glm::floor(person.pose.yaw * 10) / 10; @@ -393,7 +393,6 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) { 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); @@ -413,14 +412,14 @@ void CaraFaceTracker::decodePacket(const QByteArray& buffer) { // > 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); - _yawAverage.updateAverage(person.pose.yaw); + _yawAverage.updateAverage(person.pose.yaw); } //set the new rotation newRotation = glm::quat(glm::vec3(DEGTORAD(_pitchAverage.getAverage()), DEGTORAD(_yawAverage.getAverage()), DEGTORAD(-_rollAverage.getAverage()))); } else { - //no change in position + //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); } @@ -455,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; } - From e1f905cb3667fafaa9e9935a7a6db333fb38639b Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 1 Aug 2014 10:23:03 -0700 Subject: [PATCH 08/17] fixed >100% loss rate bug in SequenceNumberStats --- interface/src/Audio.cpp | 4 +- interface/src/ui/OctreeStatsDialog.cpp | 14 ++--- libraries/audio/src/InboundAudioStream.cpp | 2 +- libraries/audio/src/InboundAudioStream.h | 2 +- .../networking/src/SequenceNumberStats.cpp | 55 +++++++++-------- .../networking/src/SequenceNumberStats.h | 61 ++++++++++--------- 6 files changed, 73 insertions(+), 65 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 4ed1f7aeb3..4fcab5b949 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1424,9 +1424,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/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/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 66d57500a5..00a3e66877 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -14,7 +14,8 @@ #include SequenceNumberStats::SequenceNumberStats(int statsHistoryLength) - : _lastReceived(std::numeric_limits::max()), + : _received(0), + _lastReceivedSequence(0), _missingSet(), _stats(), _lastSenderUUID(), @@ -23,8 +24,10 @@ SequenceNumberStats::SequenceNumberStats(int statsHistoryLength) } void SequenceNumberStats::reset() { + _received = 0; _missingSet.clear(); _stats = PacketStreamStats(); + _lastSenderUUID = QUuid(); _statsHistory.clear(); } @@ -36,7 +39,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 +48,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) { @@ -76,8 +80,8 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui // ignore packet if gap is unreasonable qDebug() << "ignoring unreasonable sequence number:" << incoming - << "previous:" << _lastReceived; - _stats._numUnreasonable++; + << "previous:" << _lastReceivedSequence; + _stats._unreasonable++; return arrivalInfo; } @@ -94,10 +98,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 +119,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,15 +130,15 @@ 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++; } } } @@ -148,7 +153,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 +164,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 +183,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 +207,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..d611a494ad 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -21,29 +21,27 @@ 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 getUnreasonableRate() const { return (float)_unreasonable / _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 { @@ -70,20 +68,25 @@ public: 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; } + + 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; From cdcc6ece04ef0fe9e5e3576abc27729280ebe2ef Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 1 Aug 2014 10:34:19 -0700 Subject: [PATCH 09/17] fixed compile errors caused by SequenceNumberStats changes --- interface/src/Audio.cpp | 2 +- .../src/SequenceNumberStatsTests.cpp | 96 +++++++++---------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 4fcab5b949..1391045568 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -709,7 +709,7 @@ void Audio::handleAudioInput() { delete[] inputAudioSamples; } - if (_receivedAudioStream.getPacketReceived() > 0) { + if (_receivedAudioStream.getPacketsReceived() > 0) { pushAudioToOutput(); } } diff --git a/tests/networking/src/SequenceNumberStatsTests.cpp b/tests/networking/src/SequenceNumberStatsTests.cpp index de487267e0..c4205f5fb5 100644 --- a/tests/networking/src/SequenceNumberStatsTests.cpp +++ b/tests/networking/src/SequenceNumberStatsTests.cpp @@ -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(); From eba07b03e29a4d2901ccdecb0044a2334359412d Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 1 Aug 2014 12:00:41 -0700 Subject: [PATCH 10/17] recursive SequenceNumberStats; untested --- .../networking/src/SequenceNumberStats.cpp | 103 +++++++++++++++++- .../networking/src/SequenceNumberStats.h | 21 +++- 2 files changed, 120 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 00a3e66877..a700e01f07 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -13,22 +13,67 @@ #include -SequenceNumberStats::SequenceNumberStats(int statsHistoryLength) +SequenceNumberStats::SequenceNumberStats(int statsHistoryLength, int maxRecursion) : _received(0), _lastReceivedSequence(0), _missingSet(), _stats(), _lastSenderUUID(), - _statsHistory(statsHistoryLength) + _statsHistory(statsHistoryLength), + + _unreasonableTracker(NULL), + _maxRecursion(maxRecursion) { } +SequenceNumberStats::SequenceNumberStats(const SequenceNumberStats& other) + : _received(other._received), + _lastReceivedSequence(other._lastReceivedSequence), + _missingSet(other._missingSet), + _stats(other._stats), + _lastSenderUUID(other._lastSenderUUID), + _statsHistory(other._statsHistory), + _unreasonableTracker(NULL), + _maxRecursion(other._maxRecursion) +{ + if (other._unreasonableTracker) { + _unreasonableTracker = new SequenceNumberStats(*other._unreasonableTracker); + } +} + +SequenceNumberStats& SequenceNumberStats::operator=(const SequenceNumberStats& rhs) { + _received = rhs._received; + _lastReceivedSequence = rhs._lastReceivedSequence; + _missingSet = rhs._missingSet; + _stats = rhs._stats; + _lastSenderUUID = rhs._lastSenderUUID; + _statsHistory = rhs._statsHistory; + _maxRecursion = rhs._maxRecursion; + + if (rhs._unreasonableTracker) { + _unreasonableTracker = new SequenceNumberStats(*rhs._unreasonableTracker); + } else { + _unreasonableTracker = NULL; + } + return *this; +} + +SequenceNumberStats::~SequenceNumberStats() { + if (_unreasonableTracker) { + delete _unreasonableTracker; + } +} + void SequenceNumberStats::reset() { _received = 0; _missingSet.clear(); _stats = PacketStreamStats(); _lastSenderUUID = QUuid(); _statsHistory.clear(); + + if (_unreasonableTracker) { + delete _unreasonableTracker; + } } static const int UINT16_RANGE = std::numeric_limits::max() + 1; @@ -78,10 +123,64 @@ 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:" << _lastReceivedSequence; _stats._unreasonable++; + */ + + // do not create a child tracker for unreasonable seq nums if this instance is the last one in the chain. + // otherwise, create one if we don't have one. + + if (!_unreasonableTracker && _maxRecursion > 0) { + _unreasonableTracker = new SequenceNumberStats(0, _maxRecursion - 1); + } + + + if (_unreasonableTracker) { + + // track this unreasonable seq number with our _unreasonableTracker. + ArrivalInfo unreasonableTrackerArrivalInfo = _unreasonableTracker->sequenceNumberReceived(incoming); + + + const int UNREASONABLE_TRACKER_RECEIVED_THRESHOLD = 10; + const float UNRUNREASONABLE_TRACKER_UNREASONABLE_RATE_THRESHOLD = 0.1f; + const float UNREASONABLE_TRACKER_OUT_OF_ORDER_RATE_THRESHOLD = 0.1f; + + // when our _unreasonableTracker has received enough seq nums and doesn't have an _unreasonableTracker of its own, + // we'll either inherit its state only if we think its stream is plausible. it will then be deleted. + // (if it has an _unreasonableTracker of its own, its _unreasonableTracker may be detecting a plausible stream + // while its parent does not, so we should let it accrue seq nums and decide plausibility first) + + if (!_unreasonableTracker->hasUnreasonableTracker() && + _unreasonableTracker->_received >= UNREASONABLE_TRACKER_RECEIVED_THRESHOLD) { + + if (_unreasonableTracker->getUnreasonableRate() < UNRUNREASONABLE_TRACKER_UNREASONABLE_RATE_THRESHOLD && + _unreasonableTracker->getStats().getOutOfOrderRate() < UNREASONABLE_TRACKER_OUT_OF_ORDER_RATE_THRESHOLD) { + + // the _unreasonableTracker has detected a plausible stream of seq numbers; + // copy its state to this tracker. + + _received = _unreasonableTracker->_received; + _lastReceivedSequence = _unreasonableTracker->_lastReceivedSequence; + _missingSet = _unreasonableTracker->_missingSet; + _stats = _unreasonableTracker->_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; + + } + // remove our _unreasonableTracker + delete _unreasonableTracker; + } + } return arrivalInfo; } diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h index d611a494ad..1f740d7af8 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -18,6 +18,10 @@ const int MAX_REASONABLE_SEQUENCE_GAP = 1000; + +const int DEFAULT_MAX_RECURSION = 5; + + class PacketStreamStats { public: PacketStreamStats() @@ -30,7 +34,7 @@ public: _duplicate(0) {} - float getUnreasonableRate() const { return (float)_unreasonable / _expectedReceived; } + 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; } @@ -61,14 +65,19 @@ public: }; - SequenceNumberStats(int statsHistoryLength = 0); + SequenceNumberStats(int statsHistoryLength = 0, int maxRecursion = DEFAULT_MAX_RECURSION); + SequenceNumberStats(const SequenceNumberStats& other); + SequenceNumberStats& operator=(const SequenceNumberStats& rhs); + ~SequenceNumberStats(); +public: 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 getReceived() const { return _received; } + float getUnreasonableRate() const { return _stats._unreasonable / _received; } quint32 getExpectedReceived() const { return _stats._expectedReceived; } quint32 getUnreasonable() const { return _stats._unreasonable; } @@ -94,6 +103,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 + SequenceNumberStats* _unreasonableTracker; + int _maxRecursion; + + bool hasUnreasonableTracker() const { return _unreasonableTracker != NULL; } }; #endif // hifi_SequenceNumberStats_h From f9ec7b6c18e9444c5d304d91d7e4282ca62dd0a5 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 1 Aug 2014 16:13:52 -0700 Subject: [PATCH 11/17] redesiged SequenceNumberStats child instance handling --- .../networking/src/SequenceNumberStats.cpp | 128 ++++++++++-------- .../networking/src/SequenceNumberStats.h | 11 +- .../src/SequenceNumberStatsTests.cpp | 38 +++++- .../networking/src/SequenceNumberStatsTests.h | 1 + 4 files changed, 112 insertions(+), 66 deletions(-) diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index a700e01f07..7e9022934b 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -13,16 +13,16 @@ #include -SequenceNumberStats::SequenceNumberStats(int statsHistoryLength, int maxRecursion) +SequenceNumberStats::SequenceNumberStats(int statsHistoryLength, bool canDetectOutOfSync) : _received(0), _lastReceivedSequence(0), _missingSet(), _stats(), _lastSenderUUID(), _statsHistory(statsHistoryLength), - - _unreasonableTracker(NULL), - _maxRecursion(maxRecursion) + _canHaveChild(canDetectOutOfSync), + _childInstance(NULL), + _consecutiveReasonable(0) { } @@ -33,11 +33,12 @@ SequenceNumberStats::SequenceNumberStats(const SequenceNumberStats& other) _stats(other._stats), _lastSenderUUID(other._lastSenderUUID), _statsHistory(other._statsHistory), - _unreasonableTracker(NULL), - _maxRecursion(other._maxRecursion) + _childInstance(NULL), + _canHaveChild(other._canHaveChild), + _consecutiveReasonable(other._consecutiveReasonable) { - if (other._unreasonableTracker) { - _unreasonableTracker = new SequenceNumberStats(*other._unreasonableTracker); + if (other._childInstance) { + _childInstance = new SequenceNumberStats(*other._childInstance); } } @@ -48,19 +49,20 @@ SequenceNumberStats& SequenceNumberStats::operator=(const SequenceNumberStats& r _stats = rhs._stats; _lastSenderUUID = rhs._lastSenderUUID; _statsHistory = rhs._statsHistory; - _maxRecursion = rhs._maxRecursion; + _canHaveChild = rhs._canHaveChild; + _consecutiveReasonable = rhs._consecutiveReasonable; - if (rhs._unreasonableTracker) { - _unreasonableTracker = new SequenceNumberStats(*rhs._unreasonableTracker); + if (rhs._childInstance) { + _childInstance = new SequenceNumberStats(*rhs._childInstance); } else { - _unreasonableTracker = NULL; + _childInstance = NULL; } return *this; } SequenceNumberStats::~SequenceNumberStats() { - if (_unreasonableTracker) { - delete _unreasonableTracker; + if (_childInstance) { + delete _childInstance; } } @@ -71,8 +73,9 @@ void SequenceNumberStats::reset() { _lastSenderUUID = QUuid(); _statsHistory.clear(); - if (_unreasonableTracker) { - delete _unreasonableTracker; + if (_childInstance) { + delete _childInstance; + _childInstance = NULL; } } @@ -123,68 +126,78 @@ 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:" << _lastReceivedSequence; + qDebug() << "unreasonable sequence number:" << incoming << "previous:" << _lastReceivedSequence; + _stats._unreasonable++; - */ + _consecutiveReasonable = 0; - // do not create a child tracker for unreasonable seq nums if this instance is the last one in the chain. - // otherwise, create one if we don't have one. + + // 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 (!_unreasonableTracker && _maxRecursion > 0) { - _unreasonableTracker = new SequenceNumberStats(0, _maxRecursion - 1); - } + if (_canHaveChild) { + + if (!_childInstance) { + _childInstance = new SequenceNumberStats(0, false); + } - if (_unreasonableTracker) { + ArrivalInfo unreasonableTrackerArrivalInfo = _childInstance->sequenceNumberReceived(incoming); - // track this unreasonable seq number with our _unreasonableTracker. - ArrivalInfo unreasonableTrackerArrivalInfo = _unreasonableTracker->sequenceNumberReceived(incoming); - - const int UNREASONABLE_TRACKER_RECEIVED_THRESHOLD = 10; - const float UNRUNREASONABLE_TRACKER_UNREASONABLE_RATE_THRESHOLD = 0.1f; - const float UNREASONABLE_TRACKER_OUT_OF_ORDER_RATE_THRESHOLD = 0.1f; - - // when our _unreasonableTracker has received enough seq nums and doesn't have an _unreasonableTracker of its own, - // we'll either inherit its state only if we think its stream is plausible. it will then be deleted. - // (if it has an _unreasonableTracker of its own, its _unreasonableTracker may be detecting a plausible stream - // while its parent does not, so we should let it accrue seq nums and decide plausibility first) + // the child instance will be used to detect some threshold number seq nums in a row that are perfectly + // in order. - if (!_unreasonableTracker->hasUnreasonableTracker() && - _unreasonableTracker->_received >= UNREASONABLE_TRACKER_RECEIVED_THRESHOLD) { + const int UNREASONABLE_TRACKER_RECEIVED_THRESHOLD = 8; - if (_unreasonableTracker->getUnreasonableRate() < UNRUNREASONABLE_TRACKER_UNREASONABLE_RATE_THRESHOLD && - _unreasonableTracker->getStats().getOutOfOrderRate() < UNREASONABLE_TRACKER_OUT_OF_ORDER_RATE_THRESHOLD) { + if (unreasonableTrackerArrivalInfo._status != OnTime) { + _childInstance->reset(); - // the _unreasonableTracker has detected a plausible stream of seq numbers; - // copy its state to this tracker. + } else if (_childInstance->getReceived() >= UNREASONABLE_TRACKER_RECEIVED_THRESHOLD) { - _received = _unreasonableTracker->_received; - _lastReceivedSequence = _unreasonableTracker->_lastReceivedSequence; - _missingSet = _unreasonableTracker->_missingSet; - _stats = _unreasonableTracker->_stats; + // the child instance has detected a threshold number of consecutive seq nums. + // copy its state to this instance. - // don't copy _lastSenderUUID; _unreasonableTracker always has null UUID for that member. - // ours should be up-to-date. + _received = _childInstance->_received; + _lastReceivedSequence = _childInstance->_lastReceivedSequence; + _missingSet = _childInstance->_missingSet; + _stats = _childInstance->_stats; - // don't copy _statsHistory; _unreasonableTracker keeps a history of length 0. - // simply clear ours. - _statsHistory.clear(); + // don't copy _lastSenderUUID; _unreasonableTracker always has null UUID for that member. + // ours should be up-to-date. - arrivalInfo = unreasonableTrackerArrivalInfo; + // don't copy _statsHistory; _unreasonableTracker keeps a history of length 0. + // simply clear ours. + _statsHistory.clear(); - } - // remove our _unreasonableTracker - delete _unreasonableTracker; + 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 just a fluke), delete our child instance. + if (_childInstance) { + const int CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD = 4; + 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 @@ -241,6 +254,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui } } } + return arrivalInfo; } diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h index 1f740d7af8..71092d7409 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -19,9 +19,6 @@ const int MAX_REASONABLE_SEQUENCE_GAP = 1000; -const int DEFAULT_MAX_RECURSION = 5; - - class PacketStreamStats { public: PacketStreamStats() @@ -65,7 +62,7 @@ public: }; - SequenceNumberStats(int statsHistoryLength = 0, int maxRecursion = DEFAULT_MAX_RECURSION); + SequenceNumberStats(int statsHistoryLength = 0, bool canDetectOutOfSync = true); SequenceNumberStats(const SequenceNumberStats& other); SequenceNumberStats& operator=(const SequenceNumberStats& rhs); ~SequenceNumberStats(); @@ -107,10 +104,10 @@ private: // 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 - SequenceNumberStats* _unreasonableTracker; - int _maxRecursion; + bool _canHaveChild; + SequenceNumberStats* _childInstance; - bool hasUnreasonableTracker() const { return _unreasonableTracker != NULL; } + int _consecutiveReasonable; }; #endif // hifi_SequenceNumberStats_h diff --git a/tests/networking/src/SequenceNumberStatsTests.cpp b/tests/networking/src/SequenceNumberStatsTests.cpp index c4205f5fb5..38aa901a6b 100644 --- a/tests/networking/src/SequenceNumberStatsTests.cpp +++ b/tests/networking/src/SequenceNumberStatsTests.cpp @@ -16,11 +16,12 @@ void SequenceNumberStatsTests::runAllTests() { - + /* rolloverTest(); earlyLateTest(); duplicateTest(); - pruneTest(); + pruneTest();*/ + recursiveTest(); } const quint32 UINT16_RANGE = std::numeric_limits::max() + 1; @@ -278,3 +279,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 From fc670d9edb19662a91f4fd9e9cd49fb4dbcc5556 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 1 Aug 2014 16:44:01 -0700 Subject: [PATCH 12/17] minor threshold change --- libraries/networking/src/SequenceNumberStats.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 7e9022934b..4d6e0bb688 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -186,9 +186,9 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui // 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 just a fluke), delete our child instance. + // 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 = 4; + const int CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD = 8; if (_consecutiveReasonable >= CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD) { _childInstance->reset(); } else { From 1d74ae8197e4ba5d2451a4640b5d22f9a8eeabb9 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 1 Aug 2014 16:49:21 -0700 Subject: [PATCH 13/17] code cleanup --- libraries/networking/src/SequenceNumberStats.cpp | 2 -- libraries/networking/src/SequenceNumberStats.h | 1 - tests/networking/src/SequenceNumberStatsTests.cpp | 3 +-- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 4d6e0bb688..12901e11cd 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -142,10 +142,8 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui _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. diff --git a/libraries/networking/src/SequenceNumberStats.h b/libraries/networking/src/SequenceNumberStats.h index 71092d7409..fb3ff68f70 100644 --- a/libraries/networking/src/SequenceNumberStats.h +++ b/libraries/networking/src/SequenceNumberStats.h @@ -67,7 +67,6 @@ public: SequenceNumberStats& operator=(const SequenceNumberStats& rhs); ~SequenceNumberStats(); -public: void reset(); ArrivalInfo sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false); void pruneMissingSet(const bool wantExtraDebugging = false); diff --git a/tests/networking/src/SequenceNumberStatsTests.cpp b/tests/networking/src/SequenceNumberStatsTests.cpp index 38aa901a6b..1df48b781b 100644 --- a/tests/networking/src/SequenceNumberStatsTests.cpp +++ b/tests/networking/src/SequenceNumberStatsTests.cpp @@ -16,11 +16,10 @@ void SequenceNumberStatsTests::runAllTests() { - /* rolloverTest(); earlyLateTest(); duplicateTest(); - pruneTest();*/ + pruneTest(); recursiveTest(); } From 99df05f770f73b7b5fc9ed6ed52b800a1fdedd81 Mon Sep 17 00:00:00 2001 From: wangyix Date: Fri, 1 Aug 2014 16:54:29 -0700 Subject: [PATCH 14/17] forgot member in reset() --- libraries/networking/src/SequenceNumberStats.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/networking/src/SequenceNumberStats.cpp b/libraries/networking/src/SequenceNumberStats.cpp index 12901e11cd..28ea06787a 100644 --- a/libraries/networking/src/SequenceNumberStats.cpp +++ b/libraries/networking/src/SequenceNumberStats.cpp @@ -77,6 +77,7 @@ void SequenceNumberStats::reset() { delete _childInstance; _childInstance = NULL; } + _consecutiveReasonable = 0; } static const int UINT16_RANGE = std::numeric_limits::max() + 1; From bd1fbaaf762a25635674d23ff73f24a965efc220 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Sat, 2 Aug 2014 16:02:08 -0700 Subject: [PATCH 15/17] Made static mutex into member variable --- libraries/voxels/src/VoxelsScriptingInterface.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/libraries/voxels/src/VoxelsScriptingInterface.cpp b/libraries/voxels/src/VoxelsScriptingInterface.cpp index 093d736720..1b2df471f4 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.cpp +++ b/libraries/voxels/src/VoxelsScriptingInterface.cpp @@ -81,8 +81,7 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale, DeleteVoxelCommand* deleteCommand = new DeleteVoxelCommand(_tree, addVoxelDetail, getVoxelPacketSender()); - static QMutex mutex; - mutex.lock(); + _undoStackMutex.lock(); _undoStack->beginMacro(addCommand->text()); // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. @@ -91,7 +90,7 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale, _undoStack->endMacro(); //Unlock the mutex - mutex.unlock(); + _undoStackMutex.unlock(); } else { // queue the destructive add queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail); @@ -121,11 +120,10 @@ void VoxelsScriptingInterface::eraseVoxel(float x, float y, float z, float scale deleteVoxelDetail, getVoxelPacketSender()); - static QMutex mutex; - mutex.lock(); + _undoStackMutex.lock(); // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. _undoStack->push(command); - mutex.unlock(); + _undoStackMutex.unlock(); } else { getVoxelPacketSender()->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &deleteVoxelDetail); _tree->deleteVoxelAt(deleteVoxelDetail.x, deleteVoxelDetail.y, deleteVoxelDetail.z, deleteVoxelDetail.s); From 5829e3ad1ede5aa80abee796509928d4991c6bb1 Mon Sep 17 00:00:00 2001 From: barnold1953 Date: Sat, 2 Aug 2014 16:03:13 -0700 Subject: [PATCH 16/17] Forgot to save header --- libraries/voxels/src/VoxelsScriptingInterface.h | 1 + 1 file changed, 1 insertion(+) 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 From 74edf6557f5915412939a86dbe7b86fe1c13d72b Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Mon, 4 Aug 2014 12:05:31 -0700 Subject: [PATCH 17/17] turn off editModels by default --- examples/editModels.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) 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; }