Merge branch 'master' of https://github.com/highfidelity/hifi into dde_face_tracker

This commit is contained in:
Atlante45 2014-08-04 12:24:44 -07:00
commit 1a27b4b645
16 changed files with 637 additions and 156 deletions

View file

@ -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;
}

View file

@ -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

View file

@ -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);

View file

@ -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;
}

View file

@ -14,6 +14,7 @@
#include <QUdpSocket>
#include <SimpleMovingAverage.h>
#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
#endif //endif hifi_CaraFaceTracker_h

View file

@ -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());

View file

@ -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);

View file

@ -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();

View file

@ -40,6 +40,15 @@ void ResourceCache::refresh(const QUrl& url) {
}
QSharedPointer<Resource> ResourceCache::getResource(const QUrl& url, const QUrl& fallback, bool delayLoad, void* extra) {
if (QThread::currentThread() != thread()) {
QSharedPointer<Resource> result;
QMetaObject::invokeMethod(this, "getResource", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QSharedPointer<Resource>, 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);
}

View file

@ -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<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl(),
Q_INVOKABLE QSharedPointer<Resource> getResource(const QUrl& url, const QUrl& fallback = QUrl(),
bool delayLoad = false, void* extra = NULL);
/// Creates a new resource.

View file

@ -13,19 +13,71 @@
#include <limits>
SequenceNumberStats::SequenceNumberStats(int statsHistoryLength)
: _lastReceived(std::numeric_limits<quint16>::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<uint16_t>::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<quint16>::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;
}

View file

@ -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<quint16>& getMissingSet() const { return _missingSet; }
private:
quint16 _lastReceived;
int _received;
quint16 _lastReceivedSequence;
QSet<quint16> _missingSet;
PacketStreamStats _stats;
@ -91,6 +99,14 @@ private:
QUuid _lastSenderUUID;
RingBufferHistory<PacketStreamStats> _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

View file

@ -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);

View file

@ -102,6 +102,7 @@ private:
void queueVoxelAdd(PacketType addPacketType, VoxelDetail& addVoxelDetails);
VoxelTree* _tree;
QUndoStack* _undoStack;
QMutex _undoStackMutex;
};
#endif // hifi_VoxelsScriptingInterface_h

View file

@ -16,11 +16,11 @@
void SequenceNumberStatsTests::runAllTests() {
rolloverTest();
earlyLateTest();
duplicateTest();
pruneTest();
recursiveTest();
}
const quint32 UINT16_RANGE = std::numeric_limits<quint16>::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);
}

View file

@ -23,6 +23,7 @@ namespace SequenceNumberStatsTests {
void earlyLateTest();
void duplicateTest();
void pruneTest();
void recursiveTest();
};
#endif // hifi_SequenceNumberStatsTests_h