From 47f0af818a9c8def13753707b7b5704e477dde46 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Mon, 24 Mar 2014 16:45:44 -0700 Subject: [PATCH 01/41] Adjust avatar LOD more aggressively: raise detail slowly when above our target window, lower it in proportion to the ratio between desired and actual rates. --- interface/interface_en.ts | 8 ++++---- interface/src/Menu.cpp | 24 +++++++++++++++++++++++- interface/src/Menu.h | 4 +++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/interface/interface_en.ts b/interface/interface_en.ts index beada5df43..689b45afcf 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -113,18 +113,18 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 6eb03021b3..9b365afa8d 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -61,6 +61,7 @@ Menu* Menu::getInstance() { const ViewFrustumOffset DEFAULT_FRUSTUM_OFFSET = {-135.0f, 0.0f, 0.0f, 25.0f, 0.0f}; const float DEFAULT_FACESHIFT_EYE_DEFLECTION = 0.25f; +const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f; const int FIVE_SECONDS_OF_FRAMES = 5 * 60; Menu::Menu() : @@ -75,9 +76,11 @@ Menu::Menu() : _lodToolsDialog(NULL), _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), _voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), + _avatarLODDistanceMultiplier(DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER), _boundaryLevelAdjust(0), _maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS), _lastAdjust(usecTimestampNow()), + _lastAvatarDetailDrop(usecTimestampNow()), _fpsAverage(FIVE_SECONDS_OF_FRAMES), _loginAction(NULL) { @@ -386,6 +389,8 @@ void Menu::loadSettings(QSettings* settings) { _maxVoxels = loadSetting(settings, "maxVoxels", DEFAULT_MAX_VOXELS_PER_SYSTEM); _maxVoxelPacketsPerSecond = loadSetting(settings, "maxVoxelsPPS", DEFAULT_MAX_VOXEL_PPS); _voxelSizeScale = loadSetting(settings, "voxelSizeScale", DEFAULT_OCTREE_SIZE_SCALE); + _avatarLODDistanceMultiplier = loadSetting(settings, "avatarLODDistanceMultiplier", + DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER); _boundaryLevelAdjust = loadSetting(settings, "boundaryLevelAdjust", 0); settings->beginGroup("View Frustum Offset Camera"); @@ -425,6 +430,7 @@ void Menu::saveSettings(QSettings* settings) { settings->setValue("maxVoxels", _maxVoxels); settings->setValue("maxVoxelsPPS", _maxVoxelPacketsPerSecond); settings->setValue("voxelSizeScale", _voxelSizeScale); + settings->setValue("avatarLODDistanceMultiplier", _avatarLODDistanceMultiplier); settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust); settings->beginGroup("View Frustum Offset Camera"); settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw); @@ -1185,8 +1191,24 @@ void Menu::autoAdjustLOD(float currentFPS) { } _fpsAverage.updateAverage(currentFPS); - bool changed = false; quint64 now = usecTimestampNow(); + + if (_fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS) { + if (now - _lastAvatarDetailDrop > ADJUST_LOD_DOWN_DELAY) { + // attempt to lower the detail in proportion to the fps difference + float targetFps = (ADJUST_LOD_DOWN_FPS + ADJUST_LOD_UP_FPS) * 0.5f; + _avatarLODDistanceMultiplier *= (targetFps / _fpsAverage.getAverage()); + _lastAvatarDetailDrop = now; + } + } else if (_fpsAverage.getAverage() > ADJUST_LOD_UP_FPS) { + // let the detail level creep slowly upwards + const float DISTANCE_DECREASE_RATE = 0.01f; + const float MINIMUM_DISTANCE_MULTIPLIER = 0.1f; + _avatarLODDistanceMultiplier = qMax(MINIMUM_DISTANCE_MULTIPLIER, + _avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE); + } + + bool changed = false; quint64 elapsed = now - _lastAdjust; if (elapsed > ADJUST_LOD_DOWN_DELAY && _fpsAverage.getAverage() < ADJUST_LOD_DOWN_FPS diff --git a/interface/src/Menu.h b/interface/src/Menu.h index cb0ca4c5c4..9cd00db8e9 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -89,7 +89,7 @@ public: void autoAdjustLOD(float currentFPS); void setVoxelSizeScale(float sizeScale); float getVoxelSizeScale() const { return _voxelSizeScale; } - float getAvatarLODDistanceMultiplier() const { return DEFAULT_OCTREE_SIZE_SCALE / _voxelSizeScale; } + float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; } void setBoundaryLevelAdjust(int boundaryLevelAdjust); int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } @@ -202,12 +202,14 @@ private: LodToolsDialog* _lodToolsDialog; int _maxVoxels; float _voxelSizeScale; + float _avatarLODDistanceMultiplier; int _boundaryLevelAdjust; QAction* _useVoxelShader; int _maxVoxelPacketsPerSecond; QMenu* _activeScriptsMenu; QString replaceLastOccurrence(QChar search, QChar replace, QString string); quint64 _lastAdjust; + quint64 _lastAvatarDetailDrop; SimpleMovingAverage _fpsAverage; QAction* _loginAction; QAction* _chatAction; From a613da8032939171af048475ea143b08caf049d3 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Mon, 24 Mar 2014 21:25:59 -0700 Subject: [PATCH 02/41] add Script.include() --- examples/includeExample.js | 16 ++++++ libraries/script-engine/src/ScriptEngine.cpp | 55 +++++++++++++++++++- libraries/script-engine/src/ScriptEngine.h | 8 +-- 3 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 examples/includeExample.js diff --git a/examples/includeExample.js b/examples/includeExample.js new file mode 100644 index 0000000000..489928d759 --- /dev/null +++ b/examples/includeExample.js @@ -0,0 +1,16 @@ +// +// includeExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 3/24/14 +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates use of the Script.include() feature +// + +// You can include scripts from URLs +Script.include("http://public.highfidelity.io/scripts/lookWithTouch.js"); + +// You can also include scripts that are relative to the current script +Script.include("editVoxels.js"); +Script.include("../examples/selectAudioDevice.js"); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 38948071ff..834cb33158 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -113,11 +113,12 @@ void ScriptEngine::cleanupMenuItems() { } } -bool ScriptEngine::setScriptContents(const QString& scriptContents) { +bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { return false; } _scriptContents = scriptContents; + _fileNameString = fileNameString; return true; } @@ -436,3 +437,55 @@ void ScriptEngine::stopTimer(QTimer *timer) { delete timer; } } + +QUrl ScriptEngine::resolveInclude(const QString& include) const { + // first lets check to see if it's already a full URL + QUrl url(include); + if (!url.scheme().isEmpty()) { + return url; + } + + // we apparently weren't a fully qualified url, so, let's assume we're relative + // to the original URL of our script + QUrl parentURL(_fileNameString); + + // if the parent URL's scheme is empty, then this is probably a local file... + if (parentURL.scheme().isEmpty()) { + parentURL = QUrl::fromLocalFile(_fileNameString); + } + + // at this point we should have a legitimate fully qualified URL for our parent + url = parentURL.resolved(url); + return url; +} + +void ScriptEngine::include(const QString& includeFile) { + QUrl url = resolveInclude(includeFile); + QString includeContents; + + if (url.scheme() == "file") { + QString fileName = url.toLocalFile(); + QFile scriptFile(fileName); + if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { + qDebug() << "Loading file:" << fileName; + QTextStream in(&scriptFile); + includeContents = in.readAll(); + } else { + qDebug() << "ERROR Loading file:" << fileName; + } + } else { + QNetworkAccessManager* networkManager = new QNetworkAccessManager(this); + QNetworkReply* reply = networkManager->get(QNetworkRequest(url)); + qDebug() << "Downloading included script at" << includeFile; + QEventLoop loop; + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + includeContents = reply->readAll(); + } + + QScriptValue result = _engine.evaluate(includeContents); + if (_engine.hasUncaughtException()) { + int line = _engine.uncaughtExceptionLineNumber(); + qDebug() << "Uncaught exception at (" << includeFile << ") line" << line << ":" << result.toString(); + } +} diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 755418b0c1..2afe7b475b 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -33,8 +33,8 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 10 class ScriptEngine : public QObject { Q_OBJECT public: - ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, - const QString& scriptMenuName = QString(""), + ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, + const QString& fileNameString = QString(""), AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); /// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener @@ -44,7 +44,7 @@ public: static ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; } /// sets the script contents, will return false if failed, will fail if script is already running - bool setScriptContents(const QString& scriptContents); + bool setScriptContents(const QString& scriptContents, const QString& fileNameString = QString("")); const QString& getScriptMenuName() const { return _scriptMenuName; } void cleanupMenuItems(); @@ -75,6 +75,7 @@ public slots: QObject* setTimeout(const QScriptValue& function, int timeoutMS); void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } + void include(const QString& includeFile); signals: void update(float deltaTime); @@ -97,6 +98,7 @@ protected: int _numAvatarSoundSentBytes; private: + QUrl resolveInclude(const QString& include) const; void sendAvatarIdentityPacket(); void sendAvatarBillboardPacket(); From c34b64e296bdd2d92c9feae9e4b8b99e01e4eb57 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 25 Mar 2014 00:17:19 -0700 Subject: [PATCH 03/41] add load script from URL support --- interface/src/Application.cpp | 58 +++++++++------- interface/src/Application.h | 1 + interface/src/Menu.cpp | 2 + interface/src/Menu.h | 5 +- libraries/script-engine/src/ScriptEngine.cpp | 71 ++++++++++++++++++-- libraries/script-engine/src/ScriptEngine.h | 5 ++ 6 files changed, 112 insertions(+), 30 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2ca4ef74cd..8bcfb5151b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -3508,35 +3509,17 @@ void Application::cleanupScriptMenuItem(const QString& scriptMenuName) { Menu::getInstance()->removeAction(Menu::getInstance()->getActiveScriptsMenu(), scriptMenuName); } -void Application::loadScript(const QString& fileNameString) { - QByteArray fileNameAscii = fileNameString.toLocal8Bit(); - const char* fileName = fileNameAscii.data(); - - std::ifstream file(fileName, std::ios::in|std::ios::binary|std::ios::ate); - if(!file.is_open()) { - qDebug("Error loading file %s", fileName); - return; - } - qDebug("Loading file %s...", fileName); - _activeScripts.append(fileNameString); - - // get file length.... - unsigned long fileLength = file.tellg(); - file.seekg( 0, std::ios::beg ); - - // read the entire file into a buffer, WHAT!? Why not. - char* entireFile = new char[fileLength+1]; - file.read((char*)entireFile, fileLength); - file.close(); - - entireFile[fileLength] = 0;// null terminate - QString script(entireFile); - delete[] entireFile; +void Application::loadScript(const QString& scriptName) { // start the script on a new thread... bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself + ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), wantMenuItems, &_controllerScriptingInterface); - ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, &_controllerScriptingInterface); + if (!scriptEngine->hasScript()) { + qDebug() << "Application::loadScript(), script failed to load..."; + return; + } + _activeScripts.append(scriptName); // add a stop menu item Menu::getInstance()->addActionToQMenuAndActionHash(Menu::getInstance()->getActiveScriptsMenu(), @@ -3599,6 +3582,31 @@ void Application::loadDialog() { loadScript(fileNameString); } +void Application::loadScriptURLDialog() { + + QInputDialog scriptURLDialog(Application::getInstance()->getWindow()); + scriptURLDialog.setWindowTitle("Open and Run Script URL"); + scriptURLDialog.setLabelText("Script:"); + scriptURLDialog.setWindowFlags(Qt::Sheet); + const float DIALOG_RATIO_OF_WINDOW = 0.30f; + scriptURLDialog.resize(scriptURLDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, + scriptURLDialog.size().height()); + + int dialogReturn = scriptURLDialog.exec(); + QString newScript; + if (dialogReturn == QDialog::Accepted) { + if (scriptURLDialog.textValue().size() > 0) { + // the user input a new hostname, use that + newScript = scriptURLDialog.textValue(); + } + loadScript(newScript); + } + + sendFakeEnterEvent(); +} + + + void Application::toggleLogDialog() { if (! _logDialog) { _logDialog = new LogDialog(_glWidget, getLogger()); diff --git a/interface/src/Application.h b/interface/src/Application.h index caeea529af..ccd07d41b6 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -254,6 +254,7 @@ public slots: void setRenderVoxels(bool renderVoxels); void doKillLocalVoxels(); void loadDialog(); + void loadScriptURLDialog(); void toggleLogDialog(); void initAvatarAndViewFrustum(); void stopAllScripts(); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 7adef1be1c..49b839873f 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -107,6 +107,8 @@ Menu::Menu() : addDisabledActionAndSeparator(fileMenu, "Scripts"); addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog())); + addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScriptURL, + Qt::CTRL | Qt::SHIFT | Qt::Key_O, appInstance, SLOT(loadScriptURLDialog())); addActionToQMenuAndActionHash(fileMenu, MenuOption::StopAllScripts, 0, appInstance, SLOT(stopAllScripts())); addActionToQMenuAndActionHash(fileMenu, MenuOption::ReloadAllScripts, 0, appInstance, SLOT(reloadAllScripts())); _activeScriptsMenu = fileMenu->addMenu("Running Scripts"); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index cb0ca4c5c4..08887d7b18 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -272,7 +272,8 @@ namespace MenuOption { const QString OffAxisProjection = "Off-Axis Projection"; const QString OldVoxelCullingMode = "Old Voxel Culling Mode"; const QString TurnWithHead = "Turn using Head"; - const QString LoadScript = "Open and Run Script..."; + const QString LoadScript = "Open and Run Script File..."; + const QString LoadScriptURL = "Open and Run Script from URL..."; const QString Oscilloscope = "Audio Oscilloscope"; const QString Pair = "Pair"; const QString Particles = "Particles"; @@ -304,4 +305,6 @@ namespace MenuOption { const QString VoxelTextures = "Voxel Textures"; } +void sendFakeEnterEvent(); + #endif /* defined(__hifi__Menu__) */ diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 834cb33158..9bd00a0019 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -64,13 +64,10 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co _quatLibrary(), _vec3Library() { - QByteArray fileNameAscii = fileNameString.toLocal8Bit(); - const char* scriptMenuName = fileNameAscii.data(); - // some clients will use these menu features if (!fileNameString.isEmpty()) { _scriptMenuName = "Stop "; - _scriptMenuName.append(scriptMenuName); + _scriptMenuName.append(qPrintable(fileNameString)); _scriptMenuName.append(QString(" [%1]").arg(_scriptNumber)); } else { _scriptMenuName = "Stop Script "; @@ -79,6 +76,72 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co _scriptNumber++; } +ScriptEngine::ScriptEngine(const QUrl& scriptURL, bool wantMenuItems, + AbstractControllerScriptingInterface* controllerScriptingInterface) : + _scriptContents(), + _isFinished(false), + _isRunning(false), + _isInitialized(false), + _engine(), + _isAvatar(false), + _avatarIdentityTimer(NULL), + _avatarBillboardTimer(NULL), + _timerFunctionMap(), + _isListeningToAudioStream(false), + _avatarSound(NULL), + _numAvatarSoundSentBytes(0), + _controllerScriptingInterface(controllerScriptingInterface), + _avatarData(NULL), + _wantMenuItems(wantMenuItems), + _scriptMenuName(), + _fileNameString(), + _quatLibrary(), + _vec3Library() +{ + QString scriptURLString = scriptURL.toString(); + _fileNameString = scriptURLString; + // some clients will use these menu features + if (!scriptURLString.isEmpty()) { + _scriptMenuName = "Stop "; + _scriptMenuName.append(qPrintable(scriptURLString)); + _scriptMenuName.append(QString(" [%1]").arg(_scriptNumber)); + } else { + _scriptMenuName = "Stop Script "; + _scriptMenuName.append(_scriptNumber); + } + _scriptNumber++; + + QUrl url(scriptURL); + + // if the scheme is empty, maybe they typed in a file, let's try + if (url.scheme().isEmpty()) { + url = QUrl::fromLocalFile(scriptURLString); + } + + // ok, let's see if it's valid... and if so, load it + if (url.isValid()) { + if (url.scheme() == "file") { + QString fileName = url.toLocalFile(); + QFile scriptFile(fileName); + if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { + qDebug() << "Loading file:" << fileName; + QTextStream in(&scriptFile); + _scriptContents = in.readAll(); + } else { + qDebug() << "ERROR Loading file:" << fileName; + } + } else { + QNetworkAccessManager* networkManager = new QNetworkAccessManager(this); + QNetworkReply* reply = networkManager->get(QNetworkRequest(url)); + qDebug() << "Downloading included script at" << url; + QEventLoop loop; + QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); + loop.exec(); + _scriptContents = reply->readAll(); + } + } +} + void ScriptEngine::setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 2afe7b475b..4fc90d2959 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -33,6 +33,9 @@ const unsigned int SCRIPT_DATA_CALLBACK_USECS = floor(((1.0 / 60.0f) * 1000 * 10 class ScriptEngine : public QObject { Q_OBJECT public: + ScriptEngine(const QUrl& scriptURL, bool wantMenuItems = false, + AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); + ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, const QString& fileNameString = QString(""), AbstractControllerScriptingInterface* controllerScriptingInterface = NULL); @@ -68,6 +71,8 @@ public: void timerFired(); + bool hasScript() const { return !_scriptContents.isEmpty(); } + public slots: void stop(); From 571ecf096433ac0e19e1c48feea20d7f34dce1ef Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 25 Mar 2014 00:45:31 -0700 Subject: [PATCH 04/41] add first cut at firstrun support --- interface/src/Application.cpp | 24 ++++++++++++++++++++---- interface/src/Application.h | 1 + 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 8bcfb5151b..437f050c88 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -251,7 +251,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); _settings = new QSettings(this); - + // Check to see if the user passed in a command line option for loading a local // Voxel File. _voxelsFilename = getCmdOption(argc, constArgv, "-i"); @@ -330,9 +330,18 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : LocalVoxelsList::getInstance()->addPersistantTree(DOMAIN_TREE_NAME, _voxels.getTree()); LocalVoxelsList::getInstance()->addPersistantTree(CLIPBOARD_TREE_NAME, &_clipboard); - - // do this as late as possible so that all required subsystems are inialized - loadScripts(); + + // check first run... + QVariant firstRunValue = _settings->value("firstRun",QVariant(true)); + if (firstRunValue.isValid() && firstRunValue.toBool()) { + qDebug() << "This is a first run..."; + // clear the scripts, and set out script to our default scripts + clearScriptsBeforeRunning(); + loadScript("http://public.highfidelity.io/scripts/defaultScripts.js"); + } else { + // do this as late as possible so that all required subsystems are inialized + loadScripts(); + } } Application::~Application() { @@ -3454,6 +3463,13 @@ void Application::loadScripts() { settings->endArray(); } +void Application::clearScriptsBeforeRunning() { + // clears all scripts from the settings + QSettings* settings = new QSettings(this); + settings->beginWriteArray("Settings"); + settings->endArray(); +} + void Application::saveScripts() { // saves all current running scripts QSettings* settings = new QSettings(this); diff --git a/interface/src/Application.h b/interface/src/Application.h index ccd07d41b6..15778f2a17 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -115,6 +115,7 @@ public: void loadScript(const QString& fileNameString); void loadScripts(); void storeSizeAndPosition(); + void clearScriptsBeforeRunning(); void saveScripts(); void initializeGL(); void paintGL(); From f10d5e23f8732ff5594eefb6723ddd7e3d3317db Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 25 Mar 2014 00:57:29 -0700 Subject: [PATCH 05/41] set firstRun so it only happens once --- interface/src/Application.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 437f050c88..13c05fa702 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -338,6 +338,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : // clear the scripts, and set out script to our default scripts clearScriptsBeforeRunning(); loadScript("http://public.highfidelity.io/scripts/defaultScripts.js"); + + _settings->setValue("firstRun",QVariant(false)); } else { // do this as late as possible so that all required subsystems are inialized loadScripts(); From 7b50e6d4ef9362a2acb489d35a32094b8a7c2a67 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 25 Mar 2014 01:02:50 -0700 Subject: [PATCH 06/41] added hydraMove.js to default scripts --- examples/defaultScripts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 4b228492c7..eae3c2eb8c 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -3,3 +3,4 @@ Script.include("lookWithTouch.js"); Script.include("editVoxels.js"); Script.include("selectAudioDevice.js"); +Script.include("hydraMove.js"); \ No newline at end of file From d8bf5be68b99d768edbe8468b36ea14b77928929 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 25 Mar 2014 08:43:56 -0700 Subject: [PATCH 07/41] fix audio position in editVoxles.js --- examples/editVoxels.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/editVoxels.js b/examples/editVoxels.js index cb2553a8e3..5e4b77a10f 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -400,9 +400,9 @@ function calcScaleFromThumb(newThumbX) { } function setAudioPosition() { - var camera = Camera.getPosition(); + var position = MyAvatar.position; var forwardVector = Quat.getFront(MyAvatar.orientation); - audioOptions.position = Vec3.sum(camera, forwardVector); + audioOptions.position = Vec3.sum(position, forwardVector); } function getNewPasteVoxel(pickRay) { @@ -735,6 +735,7 @@ function trackKeyReleaseEvent(event) { if (event.text == "TAB") { editToolsOn = !editToolsOn; moveTools(); + setAudioPosition(); // make sure we set the audio position before playing sounds showPreviewGuides(); Audio.playSound(clickSound, audioOptions); } From e8b9594758215f74befd3491c5e5a9349ccbf562 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 09:39:43 -0700 Subject: [PATCH 08/41] spread out billboard and identity fallbacks --- assignment-client/src/avatars/AvatarMixer.cpp | 122 +++--------------- .../src/avatars/AvatarMixerClientData.cpp | 4 +- .../src/avatars/AvatarMixerClientData.h | 12 -- 3 files changed, 22 insertions(+), 116 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 0ec7c3e15e..34544af8ab 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -46,6 +46,8 @@ void attachAvatarDataToNode(Node* newNode) { } } +const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f; + // NOTE: some additional optimizations to consider. // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. @@ -99,6 +101,23 @@ void AvatarMixer::broadcastAvatarData() { // copy the avatar into the mixedAvatarByteArray packet mixedAvatarByteArray.append(avatarByteArray); + + if (randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY) { + QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); + billboardPacket.append(otherNode->getUUID().toRfc4122()); + billboardPacket.append(otherNodeData->getAvatar().getBillboard()); + nodeList->writeDatagram(billboardPacket, node); + } + + if (randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY) { + QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); + + QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); + individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122()); + identityPacket.append(individualData); + + nodeList->writeDatagram(identityPacket, node); + } } } } @@ -108,66 +127,6 @@ void AvatarMixer::broadcastAvatarData() { } } -void broadcastIdentityPacket() { - - NodeList* nodeList = NodeList::getInstance(); - - QByteArray avatarIdentityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); - int numPacketHeaderBytes = avatarIdentityPacket.size(); - - foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (node->getLinkedData() && node->getType() == NodeType::Agent) { - - AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); - AvatarData& avatar = nodeData->getAvatar(); - QByteArray individualData = avatar.identityByteArray(); - individualData.replace(0, NUM_BYTES_RFC4122_UUID, node->getUUID().toRfc4122()); - - if (avatarIdentityPacket.size() + individualData.size() > MAX_PACKET_SIZE) { - // we've hit MTU, send out the current packet before appending - nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent); - avatarIdentityPacket.resize(numPacketHeaderBytes); - } - - // append the individual data to the current the avatarIdentityPacket - avatarIdentityPacket.append(individualData); - - // re-set the bool in AvatarMixerClientData so a change between key frames gets sent out - nodeData->setHasSentIdentityBetweenKeyFrames(false); - } - } - - // send out the final packet - if (avatarIdentityPacket.size() > numPacketHeaderBytes) { - nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent); - } -} - -void broadcastBillboardPacket(const SharedNodePointer& sendingNode) { - AvatarMixerClientData* nodeData = static_cast(sendingNode->getLinkedData()); - AvatarData& avatar = nodeData->getAvatar(); - QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); - packet.append(sendingNode->getUUID().toRfc4122()); - packet.append(avatar.getBillboard()); - - NodeList* nodeList = NodeList::getInstance(); - foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (node->getType() == NodeType::Agent && node != sendingNode) { - nodeList->writeDatagram(packet, node); - } - } -} - -void broadcastBillboardPackets() { - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { - if (node->getLinkedData() && node->getType() == NodeType::Agent) { - AvatarMixerClientData* nodeData = static_cast(node->getLinkedData()); - broadcastBillboardPacket(node); - nodeData->setHasSentBillboardBetweenKeyFrames(false); - } - } -} - void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { if (killedNode->getType() == NodeType::Agent && killedNode->getLinkedData()) { @@ -202,19 +161,7 @@ void AvatarMixer::readPendingDatagrams() { if (avatarNode && avatarNode->getLinkedData()) { AvatarMixerClientData* nodeData = reinterpret_cast(avatarNode->getLinkedData()); AvatarData& avatar = nodeData->getAvatar(); - if (avatar.hasIdentityChangedAfterParsing(receivedPacket) - && !nodeData->hasSentIdentityBetweenKeyFrames()) { - // this avatar changed their identity in some way and we haven't sent a packet in this keyframe - QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); - - QByteArray individualByteArray = avatar.identityByteArray(); - individualByteArray.replace(0, NUM_BYTES_RFC4122_UUID, avatarNode->getUUID().toRfc4122()); - - identityPacket.append(individualByteArray); - - nodeData->setHasSentIdentityBetweenKeyFrames(true); - nodeList->broadcastToNodes(identityPacket, NodeSet() << NodeType::Agent); - } + avatar.hasIdentityChangedAfterParsing(receivedPacket); } break; } @@ -226,12 +173,7 @@ void AvatarMixer::readPendingDatagrams() { if (avatarNode && avatarNode->getLinkedData()) { AvatarMixerClientData* nodeData = static_cast(avatarNode->getLinkedData()); AvatarData& avatar = nodeData->getAvatar(); - if (avatar.hasBillboardChangedAfterParsing(receivedPacket) - && !nodeData->hasSentBillboardBetweenKeyFrames()) { - // this avatar changed their billboard and we haven't sent a packet in this keyframe - broadcastBillboardPacket(avatarNode); - nodeData->setHasSentBillboardBetweenKeyFrames(true); - } + avatar.hasBillboardChangedAfterParsing(receivedPacket); } break; } @@ -261,9 +203,6 @@ void AvatarMixer::sendStatsPacket() { _numStatFrames = 0; } -const qint64 AVATAR_IDENTITY_KEYFRAME_MSECS = 5000; -const qint64 AVATAR_BILLBOARD_KEYFRAME_MSECS = 5000; - void AvatarMixer::run() { ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); @@ -277,12 +216,6 @@ void AvatarMixer::run() { gettimeofday(&startTime, NULL); - QElapsedTimer identityTimer; - identityTimer.start(); - - QElapsedTimer billboardTimer; - billboardTimer.start(); - int usecToSleep = AVATAR_DATA_SEND_INTERVAL_USECS; const int TRAILING_AVERAGE_FRAMES = 100; @@ -338,19 +271,6 @@ void AvatarMixer::run() { broadcastAvatarData(); - if (identityTimer.elapsed() >= AVATAR_IDENTITY_KEYFRAME_MSECS) { - // it's time to broadcast the keyframe identity packets - broadcastIdentityPacket(); - - // restart the timer so we do it again in AVATAR_IDENTITY_KEYFRAME_MSECS - identityTimer.restart(); - } - - if (billboardTimer.elapsed() >= AVATAR_BILLBOARD_KEYFRAME_MSECS) { - broadcastBillboardPackets(); - billboardTimer.restart(); - } - QCoreApplication::processEvents(); if (_isFinished) { diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 90088173a9..5847f72ffb 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -9,9 +9,7 @@ #include "AvatarMixerClientData.h" AvatarMixerClientData::AvatarMixerClientData() : - NodeData(), - _hasSentIdentityBetweenKeyFrames(false), - _hasSentBillboardBetweenKeyFrames(false) + NodeData() { } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 854e8172d3..9accc70291 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -20,21 +20,9 @@ public: AvatarMixerClientData(); int parseData(const QByteArray& packet); - - bool hasSentIdentityBetweenKeyFrames() const { return _hasSentIdentityBetweenKeyFrames; } - void setHasSentIdentityBetweenKeyFrames(bool hasSentIdentityBetweenKeyFrames) - { _hasSentIdentityBetweenKeyFrames = hasSentIdentityBetweenKeyFrames; } - - bool hasSentBillboardBetweenKeyFrames() const { return _hasSentBillboardBetweenKeyFrames; } - void setHasSentBillboardBetweenKeyFrames(bool hasSentBillboardBetweenKeyFrames) - { _hasSentBillboardBetweenKeyFrames = hasSentBillboardBetweenKeyFrames; } - AvatarData& getAvatar() { return _avatar; } private: - - bool _hasSentIdentityBetweenKeyFrames; - bool _hasSentBillboardBetweenKeyFrames; AvatarData _avatar; }; From 17d878bc9479d6e0a3e9cb0c6e46af4af457c826 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 25 Mar 2014 09:55:43 -0700 Subject: [PATCH 09/41] sanitiy checking when unpacking AvatarData update --- libraries/avatars/src/AvatarData.cpp | 213 +++++++++++++++++++-------- libraries/avatars/src/AvatarData.h | 5 +- 2 files changed, 157 insertions(+), 61 deletions(-) diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 35e9938dc7..bb0fcd27e6 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -25,7 +25,7 @@ #include "AvatarData.h" -quint64 DEFAULT_FILTERED_LOG_EXPIRY = 20 * USECS_PER_SECOND; +quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND; using namespace std; @@ -46,7 +46,7 @@ AvatarData::AvatarData() : _displayNameTargetAlpha(0.0f), _displayNameAlpha(0.0f), _billboard(), - _debugLogExpiry(0) + _errorLogExpiry(0) { } @@ -178,6 +178,14 @@ QByteArray AvatarData::toByteArray() { return avatarDataByteArray.left(destinationBuffer - startPosition); } +bool AvatarData::shouldLogError(const quint64& now) { + if (now > _errorLogExpiry) { + _errorLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; + return true; + } + return false; +} + // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { // lazily allocate memory for HeadData in case we're not an Avatar instance @@ -192,37 +200,80 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { const unsigned char* startPosition = reinterpret_cast(packet.data()) + offset; const unsigned char* sourceBuffer = startPosition; + quint64 now = usecTimestampNow(); - // 50 bytes of "plain old data" (POD) - // 1 byte for messageSize (0) - // 1 byte for pupilSize - // 1 byte for numJoints (0) + // The absolute minimum size of the update data is as follows: + // 50 bytes of "plain old data" { + // position = 12 bytes + // bodyYaw = 2 (compressed float) + // bodyPitch = 2 (compressed float) + // bodyRoll = 2 (compressed float) + // targetScale = 2 (compressed float) + // headYaw = 2 (compressed float) + // headPitch = 2 (compressed float) + // headRoll = 2 (compressed float) + // leanSideways = 4 + // leanForward = 4 + // lookAt = 12 + // audioLoudness = 4 + // } + // + 1 byte for messageSize (0) + // + 1 byte for pupilSize + // + 1 byte for numJoints (0) + // = 53 bytes int minPossibleSize = 53; int maxAvailableSize = packet.size() - offset; if (minPossibleSize > maxAvailableSize) { - // this packet is malformed so we pretend to read to the end - quint64 now = usecTimestampNow(); - if (now > _debugLogExpiry) { - qDebug() << "Malformed AvatarData packet at the start: minPossibleSize = " << minPossibleSize - << " but maxAvailableSize = " << maxAvailableSize; - _debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet at the start; " + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; } + // this packet is malformed so we report all bytes as consumed return maxAvailableSize; } { // Body world position, rotation, and scale // position - memcpy(&_position, sourceBuffer, sizeof(float) * 3); - sourceBuffer += sizeof(float) * 3; + glm::vec3 position; + memcpy(&position, sourceBuffer, sizeof(position)); + sourceBuffer += sizeof(position); + + if (glm::isnan(position.x) || glm::isnan(position.y) || glm::isnan(position.z)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::position; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _position = position; // rotation (NOTE: This needs to become a quaternion to save two bytes) - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyYaw); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyPitch); - sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyRoll); + float yaw, pitch, roll; + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &yaw); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &pitch); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &roll); + if (glm::isnan(yaw) || glm::isnan(pitch) || glm::isnan(roll)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::yaw,pitch,roll; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _bodyYaw = yaw; + _bodyPitch = pitch; + _bodyRoll = roll; // scale - sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale); + float scale; + sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, scale); + if (glm::isnan(scale)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::scale; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _targetScale = scale; } // 20 bytes { // Head rotation @@ -231,6 +282,12 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw); sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch); sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll); + if (glm::isnan(headYaw) || glm::isnan(headPitch) || glm::isnan(headRoll)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::headYaw,headPitch,headRoll; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } _headData->setYaw(headYaw); _headData->setPitch(headPitch); _headData->setRoll(headRoll); @@ -238,33 +295,57 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { // Head lean (relative to pelvis) { - memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways)); + float leanSideways, leanForward; + memcpy(&leanSideways, sourceBuffer, sizeof(float)); sourceBuffer += sizeof(float); - memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward)); + memcpy(&leanForward, sourceBuffer, sizeof(float)); sourceBuffer += sizeof(float); + if (glm::isnan(leanSideways) || glm::isnan(leanForward)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::leanSideways,leanForward; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->_leanSideways = leanSideways; + _headData->_leanForward = leanForward; } // 8 bytes { // Lookat Position - memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition)); - sourceBuffer += sizeof(_headData->_lookAtPosition); + glm::vec3 lookAt; + memcpy(&lookAt, sourceBuffer, sizeof(lookAt)); + sourceBuffer += sizeof(lookAt); + if (glm::isnan(lookAt.x) || glm::isnan(lookAt.y) || glm::isnan(lookAt.z)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::lookAt; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->_lookAtPosition = lookAt; } // 12 bytes { // AudioLoudness // Instantaneous audio loudness (used to drive facial animation) - memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float)); + float audioLoudness; + memcpy(&audioLoudness, sourceBuffer, sizeof(float)); sourceBuffer += sizeof(float); + if (glm::isnan(audioLoudness)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::audioLoudness; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->_audioLoudness = audioLoudness; } // 4 bytes // chat int chatMessageSize = *sourceBuffer++; minPossibleSize += chatMessageSize; if (minPossibleSize > maxAvailableSize) { - // this packet is malformed so we pretend to read to the end - quint64 now = usecTimestampNow(); - if (now > _debugLogExpiry) { - qDebug() << "Malformed AvatarData packet before ChatMessage: minPossibleSize = " << minPossibleSize - << " but maxAvailableSize = " << maxAvailableSize; - _debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet before ChatMessage;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; } return maxAvailableSize; } @@ -287,40 +368,52 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { _isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED); if (_headData->_isFaceshiftConnected) { - minPossibleSize += 4 * sizeof(float) + 1; // four floats + one byte for blendDataSize + float leftEyeBlink, rightEyeBlink, averageLoudness, browAudioLift; + minPossibleSize += sizeof(leftEyeBlink) + sizeof(rightEyeBlink) + sizeof(averageLoudness) + sizeof(browAudioLift); + minPossibleSize++; // one byte for blendDataSize if (minPossibleSize > maxAvailableSize) { - // this packet is malformed so we pretend to read to the end - quint64 now = usecTimestampNow(); - if (now > _debugLogExpiry) { - qDebug() << "Malformed AvatarData packet after BitItems: minPossibleSize = " << minPossibleSize - << " but maxAvailableSize = " << maxAvailableSize; - _debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet after BitItems;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; } return maxAvailableSize; } // unpack face data - memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float)); + memcpy(&leftEyeBlink, sourceBuffer, sizeof(float)); sourceBuffer += sizeof(float); - memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float)); + memcpy(&rightEyeBlink, sourceBuffer, sizeof(float)); sourceBuffer += sizeof(float); - memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float)); + memcpy(&averageLoudness, sourceBuffer, sizeof(float)); sourceBuffer += sizeof(float); - memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float)); + memcpy(&browAudioLift, sourceBuffer, sizeof(float)); sourceBuffer += sizeof(float); + if (glm::isnan(leftEyeBlink) || glm::isnan(rightEyeBlink) + || glm::isnan(averageLoudness) || glm::isnan(browAudioLift)) { + if (shouldLogError(now)) { + qDebug() << "Discard nan AvatarData::faceData; displayName = '" << _displayName << "'"; + } + return maxAvailableSize; + } + _headData->_leftEyeBlink = leftEyeBlink; + _headData->_rightEyeBlink = rightEyeBlink; + _headData->_averageLoudness = averageLoudness; + _headData->_browAudioLift = browAudioLift; + int numCoefficients = (int)(*sourceBuffer++); int blendDataSize = numCoefficients * sizeof(float); minPossibleSize += blendDataSize; if (minPossibleSize > maxAvailableSize) { - // this packet is malformed so we pretend to read to the end - quint64 now = usecTimestampNow(); - if (now > _debugLogExpiry) { - qDebug() << "Malformed AvatarData packet after Blendshapes: minPossibleSize = " << minPossibleSize - << " but maxAvailableSize = " << maxAvailableSize; - _debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet after Blendshapes;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; } return maxAvailableSize; } @@ -339,15 +432,14 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { // joint data int numJoints = *sourceBuffer++; - int bytesOfValidity = (int)ceil((float)numJoints / 8.f); + int bytesOfValidity = (int)ceil((float)numJoints / (float)BITS_IN_BYTE); minPossibleSize += bytesOfValidity; if (minPossibleSize > maxAvailableSize) { - // this packet is malformed so we pretend to read to the end - quint64 now = usecTimestampNow(); - if (now > _debugLogExpiry) { - qDebug() << "Malformed AvatarData packet after JointValidityBits: minPossibleSize = " << minPossibleSize - << " but maxAvailableSize = " << maxAvailableSize; - _debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet after JointValidityBits;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; } return maxAvailableSize; } @@ -370,14 +462,15 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { } // 1 + bytesOfValidity bytes - minPossibleSize += numValidJoints * 8; // 8 bytes per quaternion + // each joint rotation component is stored in two bytes (sizeof(uint16_t)) + int COMPONENTS_PER_QUATERNION = 4; + minPossibleSize += numValidJoints * COMPONENTS_PER_QUATERNION * sizeof(uint16_t); if (minPossibleSize > maxAvailableSize) { - // this packet is malformed so we pretend to read to the end - quint64 now = usecTimestampNow(); - if (now > _debugLogExpiry) { - qDebug() << "Malformed AvatarData packet after JointData: minPossibleSize = " << minPossibleSize - << " but maxAvailableSize = " << maxAvailableSize; - _debugLogExpiry = now + DEFAULT_FILTERED_LOG_EXPIRY; + if (shouldLogError(now)) { + qDebug() << "Malformed AvatarData packet after JointData;" + << " displayName = '" << _displayName << "'" + << " minPossibleSize = " << minPossibleSize + << " maxAvailableSize = " << maxAvailableSize; } return maxAvailableSize; } diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index bcf32ec8e2..a89639d68d 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -105,6 +105,9 @@ public: QByteArray toByteArray(); + /// \return true if an error should be logged + bool shouldLogError(const quint64& now); + /// \param packet byte array of data /// \param offset number of bytes into packet where data starts /// \return number of bytes parsed @@ -255,7 +258,7 @@ protected: static QNetworkAccessManager* networkAccessManager; - quint64 _debugLogExpiry; + quint64 _errorLogExpiry; ///< time in future when to log an error private: // privatize the copy constructor and assignment operator so they cannot be called From c0177e80a7d4c0a440f3990167ecaf0d0c1da824 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 25 Mar 2014 09:56:15 -0700 Subject: [PATCH 10/41] use glm::isnan() everywhere for portable code --- interface/src/Util.cpp | 11 +++-------- libraries/audio/src/PositionalAudioRingBuffer.cpp | 9 ++------- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index f54bfb9d00..1921fe924b 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include @@ -24,12 +25,6 @@ #include "Util.h" -#ifdef _WIN32 -int isnan(double value) { return _isnan(value); } -#else -int isnan(double value) { return std::isnan(value); } -#endif - using namespace std; // no clue which versions are affected... @@ -88,7 +83,7 @@ float angleBetween(const glm::vec3& v1, const glm::vec3& v2) { // Helper function return the rotation from the first vector onto the second glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { float angle = angleBetween(v1, v2); - if (isnan(angle) || angle < EPSILON) { + if (glm::isnan(angle) || angle < EPSILON) { return glm::quat(); } glm::vec3 axis; @@ -586,7 +581,7 @@ void runTimingTests() { float loadSetting(QSettings* settings, const char* name, float defaultValue) { float value = settings->value(name, defaultValue).toFloat(); - if (isnan(value)) { + if (glm::isnan(value)) { value = defaultValue; } return value; diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 8fb3d64e7d..d1729ddfef 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -8,6 +8,7 @@ #include +#include #include #include @@ -16,12 +17,6 @@ #include "PositionalAudioRingBuffer.h" -#ifdef _WIN32 -int isnan(double value) { return _isnan(value); } -#else -int isnan(double value) { return std::isnan(value); } -#endif - PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type) : AudioRingBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL), _type(type), @@ -69,7 +64,7 @@ int PositionalAudioRingBuffer::parsePositionalData(const QByteArray& positionalB packetStream.readRawData(reinterpret_cast(&_orientation), sizeof(_orientation)); // if this node sent us a NaN for first float in orientation then don't consider this good audio and bail - if (isnan(_orientation.x)) { + if (glm::isnan(_orientation.x)) { reset(); return 0; } From e6b83ac52dda1a6a39951877c369f3ac27019ba4 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 10:34:38 -0700 Subject: [PATCH 11/41] send mesh and billboard on receiver's first connect --- assignment-client/src/avatars/AvatarMixer.cpp | 6 ++++-- assignment-client/src/avatars/AvatarMixerClientData.cpp | 9 ++++++++- assignment-client/src/avatars/AvatarMixerClientData.h | 5 ++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 34544af8ab..4d09228dd0 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -102,14 +102,16 @@ void AvatarMixer::broadcastAvatarData() { // copy the avatar into the mixedAvatarByteArray packet mixedAvatarByteArray.append(avatarByteArray); - if (randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY) { + bool forceSend = !myData->checkAndSetHasReceivedFirstPackets(); + + if (randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY || forceSend) { QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); billboardPacket.append(otherNode->getUUID().toRfc4122()); billboardPacket.append(otherNodeData->getAvatar().getBillboard()); nodeList->writeDatagram(billboardPacket, node); } - if (randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY) { + if (randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY || forceSend) { QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 5847f72ffb..84db17ecf4 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -9,7 +9,8 @@ #include "AvatarMixerClientData.h" AvatarMixerClientData::AvatarMixerClientData() : - NodeData() + NodeData(), + _hasReceivedFirstPackets(false) { } @@ -19,3 +20,9 @@ int AvatarMixerClientData::parseData(const QByteArray& packet) { int offset = numBytesForPacketHeader(packet); return _avatar.parseDataAtOffset(packet, offset); } + +bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() { + bool oldValue = _hasReceivedFirstPackets; + _hasReceivedFirstPackets = true; + return oldValue; +} diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 9accc70291..5cf61659a7 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -21,9 +21,12 @@ public: int parseData(const QByteArray& packet); AvatarData& getAvatar() { return _avatar; } - + + bool checkAndSetHasReceivedFirstPackets(); + private: AvatarData _avatar; + bool _hasReceivedFirstPackets; }; #endif /* defined(__hifi__AvatarMixerClientData__) */ From f763859fd0f8835b9587c0d554be5384f9228bf9 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 10:44:52 -0700 Subject: [PATCH 12/41] don't attempt to render avatars until they are initialized --- interface/src/avatar/AvatarManager.cpp | 76 +++++++++++++++----------- interface/src/avatar/AvatarManager.h | 6 +- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 9147a08dbd..bc1f826606 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -48,15 +48,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { Avatar* avatar = static_cast(avatarIterator.value().data()); - if (avatar == static_cast(_myAvatar.data())) { + if (avatar == static_cast(_myAvatar.data()) || !avatar->isInitialized()) { // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. - //updateMyAvatar(deltaTime); + // DO NOT update uninitialized Avatars ++avatarIterator; continue; } - if (!avatar->isInitialized()) { - avatar->init(); - } if (avatar->getOwningAvatarMixer()) { // this avatar's mixer is still around, go ahead and simulate it avatar->simulate(deltaTime); @@ -104,12 +101,14 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { while (fadingIterator != _avatarFades.end()) { Avatar* avatar = static_cast(fadingIterator->data()); - avatar->setTargetScale(avatar->getScale() * SHRINK_RATE); - if (avatar->getTargetScale() < MIN_FADE_SCALE) { - fadingIterator = _avatarFades.erase(fadingIterator); - } else { - avatar->simulate(deltaTime); - ++fadingIterator; + if (avatar->isInitialized()) { + avatar->setTargetScale(avatar->getScale() * SHRINK_RATE); + if (avatar->getTargetScale() < MIN_FADE_SCALE) { + fadingIterator = _avatarFades.erase(fadingIterator); + } else { + avatar->simulate(deltaTime); + ++fadingIterator; + } } } } @@ -120,22 +119,40 @@ void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::R foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) { Avatar* avatar = static_cast(fadingAvatar.data()); - if (avatar != static_cast(_myAvatar.data())) { + if (avatar != static_cast(_myAvatar.data()) && avatar->isInitialized()) { avatar->render(cameraPosition, renderMode); } } } +AvatarSharedPointer AvatarManager::matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer& mixerWeakPointer) { + AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); + + if (!matchingAvatar) { + // construct a new Avatar for this node + Avatar* avatar = new Avatar(); + avatar->setOwningAvatarMixer(mixerWeakPointer); + + // insert the new avatar into our hash + matchingAvatar = AvatarSharedPointer(avatar); + _avatarHash.insert(nodeUUID, matchingAvatar); + + qDebug() << "Adding avatar with UUID" << nodeUUID << "to AvatarManager hash."; + } + + return matchingAvatar; +} + void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { switch (packetTypeForPacket(datagram)) { case PacketTypeBulkAvatarData: processAvatarDataPacket(datagram, mixerWeakPointer); break; case PacketTypeAvatarIdentity: - processAvatarIdentityPacket(datagram); + processAvatarIdentityPacket(datagram, mixerWeakPointer); break; case PacketTypeAvatarBillboard: - processAvatarBillboardPacket(datagram); + processAvatarBillboardPacket(datagram, mixerWeakPointer); break; case PacketTypeKillAvatar: processKillAvatar(datagram); @@ -154,26 +171,21 @@ void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QW QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID)); bytesRead += NUM_BYTES_RFC4122_UUID; - AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); - - if (!matchingAvatar) { - // construct a new Avatar for this node - Avatar* avatar = new Avatar(); - avatar->setOwningAvatarMixer(mixerWeakPointer); - - // insert the new avatar into our hash - matchingAvatar = AvatarSharedPointer(avatar); - _avatarHash.insert(nodeUUID, matchingAvatar); - - qDebug() << "Adding avatar with UUID" << nodeUUID << "to AvatarManager hash."; - } + AvatarSharedPointer matchingAvatarData = matchingOrNewAvatar(nodeUUID, mixerWeakPointer); // have the matching (or new) avatar parse the data from the packet - bytesRead += matchingAvatar->parseDataAtOffset(datagram, bytesRead); + bytesRead += matchingAvatarData->parseDataAtOffset(datagram, bytesRead); + + Avatar* matchingAvatar = reinterpret_cast(matchingAvatarData.data()); + + if (!matchingAvatar->isInitialized()) { + // now that we have AvatarData for this Avatar we are go for init + matchingAvatar->init(); + } } } -void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) { +void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet, const QWeakPointer& mixerWeakPointer) { // setup a data stream to parse the packet QDataStream identityStream(packet); identityStream.skipRawData(numBytesForPacketHeader(packet)); @@ -187,7 +199,7 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) { identityStream >> nodeUUID >> faceMeshURL >> skeletonURL >> displayName; // mesh URL for a UUID, find avatar in our list - AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); + AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(nodeUUID, mixerWeakPointer); if (matchingAvatar) { Avatar* avatar = static_cast(matchingAvatar.data()); @@ -206,11 +218,11 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) { } } -void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet) { +void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer) { int headerSize = numBytesForPacketHeader(packet); QUuid nodeUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID)); - AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); + AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(nodeUUID, mixerWeakPointer); if (matchingAvatar) { Avatar* avatar = static_cast(matchingAvatar.data()); QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 06494f309c..bd04dddb78 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -39,9 +39,11 @@ public slots: private: AvatarManager(const AvatarManager& other); + AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer& mixerWeakPointer); + void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); - void processAvatarIdentityPacket(const QByteArray& packet); - void processAvatarBillboardPacket(const QByteArray& packet); + void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); + void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); void processKillAvatar(const QByteArray& datagram); void simulateAvatarFades(float deltaTime); From 786d9ce404a42c9c2ddf9f173a800ad62fb7ef46 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 10:56:06 -0700 Subject: [PATCH 13/41] force sending of identity and billboard packets changed in last frame --- assignment-client/src/avatars/AvatarMixer.cpp | 33 ++++++++++++++++--- assignment-client/src/avatars/AvatarMixer.h | 2 ++ .../src/avatars/AvatarMixerClientData.cpp | 4 ++- .../src/avatars/AvatarMixerClientData.h | 8 +++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 4d09228dd0..e14cb5868f 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -11,7 +11,7 @@ // nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms. #include -#include +#include #include #include @@ -31,6 +31,7 @@ const unsigned int AVATAR_DATA_SEND_INTERVAL_USECS = (1 / 60.0) * 1000 * 1000; AvatarMixer::AvatarMixer(const QByteArray& packet) : ThreadedAssignment(packet), + _lastFrameTimestamp(QDateTime::currentMSecsSinceEpoch()), _trailingSleepRatio(1.0f), _performanceThrottlingRatio(0.0f), _sumListeners(0), @@ -102,16 +103,27 @@ void AvatarMixer::broadcastAvatarData() { // copy the avatar into the mixedAvatarByteArray packet mixedAvatarByteArray.append(avatarByteArray); + // if the receiving avatar has just connected make sure we send out the mesh and billboard + // for this avatar (assuming they exist) bool forceSend = !myData->checkAndSetHasReceivedFirstPackets(); - if (randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY || forceSend) { + // we will also force a send of billboard or identity packet + // if either has changed in the last frame + + if (otherNodeData->getBillboardChangeTimestamp() > 0 + && (forceSend + || otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp + || randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { QByteArray billboardPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarBillboard); billboardPacket.append(otherNode->getUUID().toRfc4122()); billboardPacket.append(otherNodeData->getAvatar().getBillboard()); nodeList->writeDatagram(billboardPacket, node); } - if (randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY || forceSend) { + if (otherNodeData->getIdentityChangeTimestamp() > 0 + && (forceSend + || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp + || randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); @@ -127,6 +139,8 @@ void AvatarMixer::broadcastAvatarData() { nodeList->writeDatagram(mixedAvatarByteArray, node); } } + + _lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch(); } void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { @@ -163,7 +177,11 @@ void AvatarMixer::readPendingDatagrams() { if (avatarNode && avatarNode->getLinkedData()) { AvatarMixerClientData* nodeData = reinterpret_cast(avatarNode->getLinkedData()); AvatarData& avatar = nodeData->getAvatar(); - avatar.hasIdentityChangedAfterParsing(receivedPacket); + + // parse the identity packet and update the change timestamp if appropriate + if (avatar.hasIdentityChangedAfterParsing(receivedPacket)) { + nodeData->setIdentityChangeTimestamp(QDateTime::currentMSecsSinceEpoch()); + } } break; } @@ -175,7 +193,12 @@ void AvatarMixer::readPendingDatagrams() { if (avatarNode && avatarNode->getLinkedData()) { AvatarMixerClientData* nodeData = static_cast(avatarNode->getLinkedData()); AvatarData& avatar = nodeData->getAvatar(); - avatar.hasBillboardChangedAfterParsing(receivedPacket); + + // parse the billboard packet and update the change timestamp if appropriate + if (avatar.hasBillboardChangedAfterParsing(receivedPacket)) { + nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch()); + } + } break; } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 4d54b715f8..d4e354f347 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -30,6 +30,8 @@ public slots: private: void broadcastAvatarData(); + quint64 _lastFrameTimestamp; + float _trailingSleepRatio; float _performanceThrottlingRatio; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 84db17ecf4..d1449e956e 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -10,7 +10,9 @@ AvatarMixerClientData::AvatarMixerClientData() : NodeData(), - _hasReceivedFirstPackets(false) + _hasReceivedFirstPackets(false), + _billboardChangeTimestamp(0), + _identityChangeTimestamp(0) { } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 5cf61659a7..bc0a54f06b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -24,9 +24,17 @@ public: bool checkAndSetHasReceivedFirstPackets(); + quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; } + void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; } + + quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; } + void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; } + private: AvatarData _avatar; bool _hasReceivedFirstPackets; + quint64 _billboardChangeTimestamp; + quint64 _identityChangeTimestamp; }; #endif /* defined(__hifi__AvatarMixerClientData__) */ From 8a796be785562db948ca2f900a3c65c8ea80c456 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 11:10:28 -0700 Subject: [PATCH 14/41] only fade avatars if they have ever been initialized --- interface/src/avatar/AvatarManager.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index bc1f826606..c2ba28ac7b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -101,14 +101,12 @@ void AvatarManager::simulateAvatarFades(float deltaTime) { while (fadingIterator != _avatarFades.end()) { Avatar* avatar = static_cast(fadingIterator->data()); - if (avatar->isInitialized()) { - avatar->setTargetScale(avatar->getScale() * SHRINK_RATE); - if (avatar->getTargetScale() < MIN_FADE_SCALE) { - fadingIterator = _avatarFades.erase(fadingIterator); - } else { - avatar->simulate(deltaTime); - ++fadingIterator; - } + avatar->setTargetScale(avatar->getScale() * SHRINK_RATE); + if (avatar->getTargetScale() < MIN_FADE_SCALE) { + fadingIterator = _avatarFades.erase(fadingIterator); + } else { + avatar->simulate(deltaTime); + ++fadingIterator; } } } @@ -246,7 +244,9 @@ void AvatarManager::processKillAvatar(const QByteArray& datagram) { AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) { if (iterator.key() != MY_AVATAR_KEY) { qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash."; - _avatarFades.push_back(iterator.value()); + if (reinterpret_cast(iterator.value().data())->isInitialized()) { + _avatarFades.push_back(iterator.value()); + } return AvatarHashMap::erase(iterator); } else { // never remove _myAvatar from the list From ff788a340b300c585c20fdaf49d519b0bb39f4b8 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 11:38:00 -0700 Subject: [PATCH 15/41] add stat for average billboard and identity packets per frame --- assignment-client/src/avatars/AvatarMixer.cpp | 14 +++++++++++++- assignment-client/src/avatars/AvatarMixer.h | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index e14cb5868f..54f1943930 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -35,7 +35,9 @@ AvatarMixer::AvatarMixer(const QByteArray& packet) : _trailingSleepRatio(1.0f), _performanceThrottlingRatio(0.0f), _sumListeners(0), - _numStatFrames(0) + _numStatFrames(0), + _sumBillboardPackets(0), + _sumIdentityPackets(0) { // make sure we hear about node kills so we can tell the other nodes connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled); @@ -118,12 +120,15 @@ void AvatarMixer::broadcastAvatarData() { billboardPacket.append(otherNode->getUUID().toRfc4122()); billboardPacket.append(otherNodeData->getAvatar().getBillboard()); nodeList->writeDatagram(billboardPacket, node); + + ++_sumBillboardPackets; } if (otherNodeData->getIdentityChangeTimestamp() > 0 && (forceSend || otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp || randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) { + QByteArray identityPacket = byteArrayWithPopulatedHeader(PacketTypeAvatarIdentity); QByteArray individualData = otherNodeData->getAvatar().identityByteArray(); @@ -131,6 +136,8 @@ void AvatarMixer::broadcastAvatarData() { identityPacket.append(individualData); nodeList->writeDatagram(identityPacket, node); + + ++_sumIdentityPackets; } } } @@ -219,12 +226,17 @@ void AvatarMixer::sendStatsPacket() { QJsonObject statsObject; statsObject["average_listeners_last_second"] = (float) _sumListeners / (float) _numStatFrames; + statsObject["average_billboard_packets_per_frame"] = (float) _sumBillboardPackets / (float) _numStatFrames; + statsObject["average_identity_packets_per_frame"] = (float) _sumIdentityPackets / (float) _numStatFrames; + statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100; statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio; ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); _sumListeners = 0; + _sumBillboardPackets = 0; + _sumIdentityPackets = 0; _numStatFrames = 0; } diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index d4e354f347..4171df49af 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -37,6 +37,8 @@ private: int _sumListeners; int _numStatFrames; + int _sumBillboardPackets; + int _sumIdentityPackets; }; #endif /* defined(__hifi__AvatarMixer__) */ From 49fd4137226cf78a51036408e52d3e3f28f16b7a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 11:50:25 -0700 Subject: [PATCH 16/41] fix for stats styling --- domain-server/resources/web/css/style.css | 4 ++++ domain-server/resources/web/stats/js/stats.js | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/web/css/style.css b/domain-server/resources/web/css/style.css index fb295cffc3..ff33cc206b 100644 --- a/domain-server/resources/web/css/style.css +++ b/domain-server/resources/web/css/style.css @@ -40,6 +40,10 @@ span.port { color: #666666; } +.stats-key { + width: 400px; +} + .stale { color: red; } \ No newline at end of file diff --git a/domain-server/resources/web/stats/js/stats.js b/domain-server/resources/web/stats/js/stats.js index a7b0aecfcf..74d58c72c0 100644 --- a/domain-server/resources/web/stats/js/stats.js +++ b/domain-server/resources/web/stats/js/stats.js @@ -21,8 +21,9 @@ $(document).ready(function(){ $.each(json, function(key, value) { statsTableBody += ""; - statsTableBody += "" + key + ""; - statsTableBody += "" + value + ""; + statsTableBody += "" + key + ""; + var formattedValue = (typeof value == 'number' ? value.toLocaleString() : value); + statsTableBody += "" + formattedValue + ""; statsTableBody += ""; }); From 1fe01f3e61bb6168375313de27fe740b1fe69c17 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 12:16:07 -0700 Subject: [PATCH 17/41] increment framesSinceCutoffEvent in mixers to enable recovery --- assignment-client/src/audio/AudioMixer.cpp | 4 ++++ assignment-client/src/avatars/AvatarMixer.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 450b6e0ad9..82674e5141 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -455,6 +455,10 @@ void AudioMixer::run() { } } + if (!hasRatioChanged) { + ++framesSinceCutoffEvent; + } + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData() && ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) { diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 54f1943930..f0a2b68050 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -306,6 +306,10 @@ void AvatarMixer::run() { } } + if (!hasRatioChanged) { + ++framesSinceCutoffEvent; + } + broadcastAvatarData(); QCoreApplication::processEvents(); From 9d6dac6a76fe7612fcc24fa51e8d46a6b763f75f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 12:18:28 -0700 Subject: [PATCH 18/41] suppress sleep time debug in favour of stats in AvatarMixer --- assignment-client/src/avatars/AvatarMixer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index f0a2b68050..65e0acd4a6 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -322,8 +322,6 @@ void AvatarMixer::run() { if (usecToSleep > 0) { usleep(usecToSleep); - } else { - qDebug() << "AvatarMixer loop took too" << -usecToSleep << "of extra time. Won't sleep."; } } } From bc1b7045cbd02ae56e2d5816689b57028dad9bd7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 12:30:06 -0700 Subject: [PATCH 19/41] output count of unreplied domain-server check ins --- libraries/shared/src/ThreadedAssignment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/shared/src/ThreadedAssignment.cpp index f4ea383399..642f471cc5 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/shared/src/ThreadedAssignment.cpp @@ -82,9 +82,9 @@ void ThreadedAssignment::sendStatsPacket() { void ThreadedAssignment::checkInWithDomainServerOrExit() { if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { - qDebug() << "NRDC:" << NodeList::getInstance()->getNumNoReplyDomainCheckIns(); setFinished(true); } else { + qDebug() << "Sending DS check in. There are" << NodeList::getInstance()->getNumNoReplyDomainCheckIns() << "unreplied."; NodeList::getInstance()->sendDomainServerCheckIn(); } } From a256473ac5137e97c3c137b74af61bf1f366f600 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 25 Mar 2014 12:55:50 -0700 Subject: [PATCH 20/41] fixed lookWithTouch.js to not reset --- examples/lookWithTouch.js | 41 ++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/examples/lookWithTouch.js b/examples/lookWithTouch.js index 852573aea6..4406b4567e 100644 --- a/examples/lookWithTouch.js +++ b/examples/lookWithTouch.js @@ -9,6 +9,7 @@ // // +var startedTouching = false; var lastX = 0; var lastY = 0; var yawFromMouse = 0; @@ -21,12 +22,14 @@ function touchBeginEvent(event) { } lastX = event.x; lastY = event.y; + startedTouching = true; } function touchEndEvent(event) { if (wantDebugging) { print("touchEndEvent event.x,y=" + event.x + ", " + event.y); } + startedTouching = false; } function touchUpdateEvent(event) { @@ -44,24 +47,26 @@ function touchUpdateEvent(event) { } function update(deltaTime) { - // rotate body yaw for yaw received from mouse - var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollRadians(0, yawFromMouse, 0)); - if (wantDebugging) { - print("changing orientation" - + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + "," - + MyAvatar.orientation.z + "," + MyAvatar.orientation.w - + " newOrientation="+newOrientation.x + "," + newOrientation.y + "," + newOrientation.z + "," + newOrientation.w); - } - MyAvatar.orientation = newOrientation; - yawFromMouse = 0; + if (startedTouching) { + // rotate body yaw for yaw received from mouse + var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromPitchYawRollRadians(0, yawFromMouse, 0)); + if (wantDebugging) { + print("changing orientation" + + " [old]MyAvatar.orientation="+MyAvatar.orientation.x + "," + MyAvatar.orientation.y + "," + + MyAvatar.orientation.z + "," + MyAvatar.orientation.w + + " newOrientation="+newOrientation.x + "," + newOrientation.y + "," + newOrientation.z + "," + newOrientation.w); + } + MyAvatar.orientation = newOrientation; + yawFromMouse = 0; - // apply pitch from mouse - var newPitch = MyAvatar.headPitch + pitchFromMouse; - if (wantDebugging) { - print("changing pitch [old]MyAvatar.headPitch="+MyAvatar.headPitch+ " newPitch="+newPitch); + // apply pitch from mouse + var newPitch = MyAvatar.headPitch + pitchFromMouse; + if (wantDebugging) { + print("changing pitch [old]MyAvatar.headPitch="+MyAvatar.headPitch+ " newPitch="+newPitch); + } + MyAvatar.headPitch = newPitch; + pitchFromMouse = 0; } - MyAvatar.headPitch = newPitch; - pitchFromMouse = 0; } // Map the mouse events to our functions @@ -77,10 +82,6 @@ function scriptEnding() { Controller.releaseTouchEvents(); } -MyAvatar.bodyYaw = 0; -MyAvatar.bodyPitch = 0; -MyAvatar.bodyRoll = 0; - // would be nice to change to update Script.update.connect(update); Script.scriptEnding.connect(scriptEnding); From 9a3533a38fbbe5affa853330881adb77dd9f395c Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Tue, 25 Mar 2014 12:59:57 -0700 Subject: [PATCH 21/41] added inspect.js to defaultScripts.js --- examples/defaultScripts.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index eae3c2eb8c..722b21844f 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -3,4 +3,5 @@ Script.include("lookWithTouch.js"); Script.include("editVoxels.js"); Script.include("selectAudioDevice.js"); -Script.include("hydraMove.js"); \ No newline at end of file +Script.include("hydraMove.js"); +Script.include("inspect.js"); \ No newline at end of file From f0b87bebc2d70074a2ee3629797a4125d396feaa Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 13:02:09 -0700 Subject: [PATCH 22/41] cleanup debug and inactive node pinging --- libraries/shared/src/NodeList.cpp | 2 +- libraries/shared/src/ThreadedAssignment.cpp | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 95417f4f71..838e2f4fea 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -582,7 +582,7 @@ void NodeList::sendDomainServerCheckIn() { foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { packetStream << nodeTypeOfInterest; } - + writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; static unsigned int numDomainCheckins = 0; diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/shared/src/ThreadedAssignment.cpp index 642f471cc5..be49b18055 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/shared/src/ThreadedAssignment.cpp @@ -46,10 +46,6 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); - QTimer* pingNodesTimer = new QTimer(this); - connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes())); - pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000); - QTimer* silentNodeRemovalTimer = new QTimer(this); connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); @@ -84,7 +80,6 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { setFinished(true); } else { - qDebug() << "Sending DS check in. There are" << NodeList::getInstance()->getNumNoReplyDomainCheckIns() << "unreplied."; NodeList::getInstance()->sendDomainServerCheckIn(); } } From 08ed708ee6c27491f1ca1cf1c6e48235695eac2c Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 13:05:58 -0700 Subject: [PATCH 23/41] add domain IP and packet size debugging --- libraries/shared/src/NodeList.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 838e2f4fea..24fa68c618 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -582,7 +582,8 @@ void NodeList::sendDomainServerCheckIn() { foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { packetStream << nodeTypeOfInterest; } - + + qDebug() << "sending DS check in size" << domainServerPacket.size() << "to" << _domainInfo.getSockAddr(); writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; static unsigned int numDomainCheckins = 0; From ce169dd87d25e126a3f6805290d20fce36b81664 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 13:06:44 -0700 Subject: [PATCH 24/41] add code return debug to DS check in packet --- libraries/shared/src/NodeList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 24fa68c618..4d95bbc6e9 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -584,7 +584,7 @@ void NodeList::sendDomainServerCheckIn() { } qDebug() << "sending DS check in size" << domainServerPacket.size() << "to" << _domainInfo.getSockAddr(); - writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); + qDebug() << "Code returned is" << writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; static unsigned int numDomainCheckins = 0; From 6a8637230f372833157d58e3240762709c71812d Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 13:17:10 -0700 Subject: [PATCH 25/41] add socket error debugging --- libraries/shared/src/NodeList.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 4d95bbc6e9..ab049d7fae 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -584,7 +584,11 @@ void NodeList::sendDomainServerCheckIn() { } qDebug() << "sending DS check in size" << domainServerPacket.size() << "to" << _domainInfo.getSockAddr(); - qDebug() << "Code returned is" << writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); + qint64 code = writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); + qDebug() << "Code returned is" << code; + if (code == -1) { + qDebug() << "the socket error is" << _nodeSocket.error(); + } const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; static unsigned int numDomainCheckins = 0; From 08bfc15b704472fbe61d0cb151d486cb1ebf86a0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 13:20:45 -0700 Subject: [PATCH 26/41] output more descriptive socket error --- libraries/shared/src/NodeList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index ab049d7fae..cda2963f53 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -587,7 +587,7 @@ void NodeList::sendDomainServerCheckIn() { qint64 code = writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); qDebug() << "Code returned is" << code; if (code == -1) { - qDebug() << "the socket error is" << _nodeSocket.error(); + qDebug() << "the socket error is" << _nodeSocket.errorString(); } const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; static unsigned int numDomainCheckins = 0; From 68d55152b9bac0e3154d9c23efa450642c147832 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 13:28:53 -0700 Subject: [PATCH 27/41] flush after each call to writeDatagram --- libraries/shared/src/NodeList.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index cda2963f53..0cc343b460 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -171,7 +171,13 @@ qint64 NodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& d ++_numCollectedPackets; _numCollectedBytes += datagram.size(); - return _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, + destinationSockAddr.getAddress(), + destinationSockAddr.getPort()); + + // ask the underlying QUdpSocket to flush its buffers to avoid filling them up + _nodeSocket.flush(); + return bytesWritten; } From 4e77afb4b25e95b359b3bb0a12c4f7aa9914f3d5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 13:38:02 -0700 Subject: [PATCH 28/41] block and wait for bytes to be written during broadcastAvatarData loop --- assignment-client/src/avatars/AvatarMixer.cpp | 6 ++---- libraries/shared/src/NodeList.cpp | 8 +------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 65e0acd4a6..5eb3e54389 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -54,10 +54,6 @@ const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f; // NOTE: some additional optimizations to consider. // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. -// 2) after culling for view frustum, sort order the avatars by distance, send the closest ones first. -// 3) if we need to rate limit the amount of data we send, we can use a distance weighted "semi-random" function to -// determine which avatars are included in the packet stream -// 4) we should optimize the avatar data format to be more compact (100 bytes is pretty wasteful). void AvatarMixer::broadcastAvatarData() { static QByteArray mixedAvatarByteArray; @@ -139,6 +135,8 @@ void AvatarMixer::broadcastAvatarData() { ++_sumIdentityPackets; } + + nodeList->getNodeSocket().waitForBytesWritten(-1); } } } diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 0cc343b460..cda2963f53 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -171,13 +171,7 @@ qint64 NodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& d ++_numCollectedPackets; _numCollectedBytes += datagram.size(); - qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, - destinationSockAddr.getAddress(), - destinationSockAddr.getPort()); - - // ask the underlying QUdpSocket to flush its buffers to avoid filling them up - _nodeSocket.flush(); - return bytesWritten; + return _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort()); } From 0407c6d3244b2aab9bb62ae4ede64de5bad939fa Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 13:50:22 -0700 Subject: [PATCH 29/41] use QTimer to clock broadcastAvatarData method --- assignment-client/src/avatars/AvatarMixer.cpp | 135 ++++++++---------- 1 file changed, 59 insertions(+), 76 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 5eb3e54389..314c0f93ff 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -27,7 +28,7 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; -const unsigned int AVATAR_DATA_SEND_INTERVAL_USECS = (1 / 60.0) * 1000 * 1000; +const unsigned int AVATAR_DATA_SEND_INTERVAL_MSECS = (1.0f / 60.0f) * 1000; AvatarMixer::AvatarMixer(const QByteArray& packet) : ThreadedAssignment(packet), @@ -55,6 +56,58 @@ const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f; // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. void AvatarMixer::broadcastAvatarData() { + + int idleTime = QDateTime::currentMSecsSinceEpoch() - _lastFrameTimestamp; + + ++_numStatFrames; + + const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; + const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; + + const float RATIO_BACK_OFF = 0.02f; + + const int TRAILING_AVERAGE_FRAMES = 100; + int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; + + const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; + const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; + + _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) + + (idleTime * CURRENT_FRAME_RATIO / (float) AVATAR_DATA_SEND_INTERVAL_MSECS); + + float lastCutoffRatio = _performanceThrottlingRatio; + bool hasRatioChanged = false; + + if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { + if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { + // we're struggling - change our min required loudness to reduce some load + _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); + + qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" + << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; + hasRatioChanged = true; + } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { + // we've recovered and can back off the required loudness + _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; + + if (_performanceThrottlingRatio < 0) { + _performanceThrottlingRatio = 0; + } + + qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" + << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; + hasRatioChanged = true; + } + + if (hasRatioChanged) { + framesSinceCutoffEvent = 0; + } + } + + if (!hasRatioChanged) { + ++framesSinceCutoffEvent; + } + static QByteArray mixedAvatarByteArray; int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData); @@ -246,80 +299,10 @@ void AvatarMixer::run() { nodeList->linkedDataCreateCallback = attachAvatarDataToNode; - int nextFrame = 0; - timeval startTime; + // setup and start a timer to broadcast avatar data + QTimer* broadcastTimer = new QTimer(this); + broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS); - gettimeofday(&startTime, NULL); - - int usecToSleep = AVATAR_DATA_SEND_INTERVAL_USECS; - - const int TRAILING_AVERAGE_FRAMES = 100; - int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES; - - while (!_isFinished) { - - ++_numStatFrames; - - const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f; - const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f; - - const float RATIO_BACK_OFF = 0.02f; - - const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES; - const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO; - - if (usecToSleep < 0) { - usecToSleep = 0; - } - - _trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) - + (usecToSleep * CURRENT_FRAME_RATIO / (float) AVATAR_DATA_SEND_INTERVAL_USECS); - - float lastCutoffRatio = _performanceThrottlingRatio; - bool hasRatioChanged = false; - - if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) { - if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) { - // we're struggling - change our min required loudness to reduce some load - _performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio)); - - qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" - << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; - hasRatioChanged = true; - } else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) { - // we've recovered and can back off the required loudness - _performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF; - - if (_performanceThrottlingRatio < 0) { - _performanceThrottlingRatio = 0; - } - - qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was" - << lastCutoffRatio << "and is now" << _performanceThrottlingRatio; - hasRatioChanged = true; - } - - if (hasRatioChanged) { - framesSinceCutoffEvent = 0; - } - } - - if (!hasRatioChanged) { - ++framesSinceCutoffEvent; - } - - broadcastAvatarData(); - - QCoreApplication::processEvents(); - - if (_isFinished) { - break; - } - - usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow(); - - if (usecToSleep > 0) { - usleep(usecToSleep); - } - } + connect(broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData); + broadcastTimer->start(); } From 269615bc40c366f5a51adff14baa38ff23a8f22b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 14:02:24 -0700 Subject: [PATCH 30/41] thread broadcast of avatar data in AvatarMixer --- assignment-client/src/avatars/AvatarMixer.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 314c0f93ff..55a60194c0 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -299,10 +299,18 @@ void AvatarMixer::run() { nodeList->linkedDataCreateCallback = attachAvatarDataToNode; - // setup and start a timer to broadcast avatar data - QTimer* broadcastTimer = new QTimer(this); - broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS); + // create a thead for broadcast of avatar data + QThread* broadcastThread = new QThread(this); - connect(broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData); - broadcastTimer->start(); + // setup the timer that will be fired on the broadcast thread + QTimer* broadcastTimer = new QTimer(); + broadcastTimer->setInterval(AVATAR_DATA_SEND_INTERVAL_MSECS); + broadcastTimer->moveToThread(broadcastThread); + + // connect appropriate signals and slots + connect(broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection); + connect(broadcastThread, SIGNAL(started()), broadcastTimer, SLOT(start())); + + // start the broadcastThread + broadcastThread->start(); } From f8c479f9c54388b84500206b2259d247345de5b6 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 14:28:26 -0700 Subject: [PATCH 31/41] seed random number generator in ScriptEngine for Math.random use in JS --- libraries/script-engine/src/ScriptEngine.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 9bd00a0019..e880760a1d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -235,6 +235,11 @@ void ScriptEngine::init() { // let the VoxelPacketSender know how frequently we plan to call it _voxelsScriptingInterface.getVoxelPacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); _particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); + + // call srand to seed the random number generator + srand(QDateTime::currentMSecsSinceEpoch() + + QCoreApplication::applicationPid() + + NodeList::getInstance()->getNodeSocket().localPort()); } From 41e2ce2b1999e5bd8893e2f17c756adfc87f9681 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 14:29:59 -0700 Subject: [PATCH 32/41] remove DS check in debug --- libraries/shared/src/ThreadedAssignment.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/shared/src/ThreadedAssignment.cpp index 642f471cc5..1f443146f8 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/shared/src/ThreadedAssignment.cpp @@ -84,7 +84,6 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { setFinished(true); } else { - qDebug() << "Sending DS check in. There are" << NodeList::getInstance()->getNumNoReplyDomainCheckIns() << "unreplied."; NodeList::getInstance()->sendDomainServerCheckIn(); } } From 7190ce9a88fda177eb3cc6b43104ceffd7ac2291 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 14:33:53 -0700 Subject: [PATCH 33/41] remove loadRandomIdentifier for STUN requests, use UUID --- libraries/shared/src/NodeList.cpp | 5 ++--- libraries/shared/src/SharedUtil.cpp | 9 --------- libraries/shared/src/SharedUtil.h | 2 -- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 95417f4f71..b2a494aa32 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -413,9 +413,8 @@ void NodeList::sendSTUNRequest() { // transaction ID (random 12-byte unsigned integer) const uint NUM_TRANSACTION_ID_BYTES = 12; - unsigned char transactionID[NUM_TRANSACTION_ID_BYTES]; - loadRandomIdentifier(transactionID, NUM_TRANSACTION_ID_BYTES); - memcpy(stunRequestPacket + packetIndex, &transactionID, sizeof(transactionID)); + QUuid randomUUID = QUuid::createUuid(); + memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); // lookup the IP for the STUN server static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 882d4719c8..efd5180d03 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -206,15 +206,6 @@ bool isInEnvironment(const char* environment) { } } -void loadRandomIdentifier(unsigned char* identifierBuffer, int numBytes) { - // seed the the random number generator - srand(time(NULL)); - - for (int i = 0; i < numBytes; i++) { - identifierBuffer[i] = rand() % 256; - } -} - ////////////////////////////////////////////////////////////////////////////////////////// // Function: getCmdOption() // Description: Handy little function to tell you if a command line flag and option was diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 439b85aa54..d8d686c63b 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -96,8 +96,6 @@ int getNthBit(unsigned char byte, int ordinal); /// determines the bit placement bool isInEnvironment(const char* environment); -void loadRandomIdentifier(unsigned char* identifierBuffer, int numBytes); - const char* getCmdOption(int argc, const char * argv[],const char* option); bool cmdOptionExists(int argc, const char * argv[],const char* option); From f0f3cf7282d763e8e54b08578462cf739079273b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 14:36:02 -0700 Subject: [PATCH 34/41] remove seed to random number generator that is no longer needed --- libraries/script-engine/src/ScriptEngine.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index e880760a1d..c0834aad9d 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -235,12 +235,6 @@ void ScriptEngine::init() { // let the VoxelPacketSender know how frequently we plan to call it _voxelsScriptingInterface.getVoxelPacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); _particlesScriptingInterface.getParticlePacketSender()->setProcessCallIntervalHint(SCRIPT_DATA_CALLBACK_USECS); - - // call srand to seed the random number generator - srand(QDateTime::currentMSecsSinceEpoch() - + QCoreApplication::applicationPid() - + NodeList::getInstance()->getNodeSocket().localPort()); - } void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { From 56869a769ded54ed3e3300649762fe15d06b36f0 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 14:53:32 -0700 Subject: [PATCH 35/41] remove some extraneous domain-server check in debugging --- libraries/shared/src/NodeList.cpp | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index e6f542a358..b2a494aa32 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -582,12 +582,7 @@ void NodeList::sendDomainServerCheckIn() { packetStream << nodeTypeOfInterest; } - qDebug() << "sending DS check in size" << domainServerPacket.size() << "to" << _domainInfo.getSockAddr(); - qint64 code = writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); - qDebug() << "Code returned is" << code; - if (code == -1) { - qDebug() << "the socket error is" << _nodeSocket.errorString(); - } + writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; static unsigned int numDomainCheckins = 0; From d5be3f1c9f17d037b5ca62991111383e1a328b6b Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 15:01:24 -0700 Subject: [PATCH 36/41] output socket errors if they occur --- libraries/shared/src/NodeList.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index b2a494aa32..85e981490e 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -171,8 +171,13 @@ qint64 NodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& d ++_numCollectedPackets; _numCollectedBytes += datagram.size(); - return _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + if (bytesWritten < 0) { + qDebug() << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString(); + } + + return bytesWritten; } qint64 NodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, From 292fb51b43b4bca1f90c2005a5a783eeb706042e Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 15:05:17 -0700 Subject: [PATCH 37/41] use performance throttling instead of distance to decide when to send --- assignment-client/src/avatars/AvatarMixer.cpp | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 55a60194c0..ba95040ba9 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -133,13 +133,9 @@ void AvatarMixer::broadcastAvatarData() { AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); AvatarData& otherAvatar = otherNodeData->getAvatar(); glm::vec3 otherPosition = otherAvatar.getPosition(); - float distanceToAvatar = glm::length(myPosition - otherPosition); - // The full rate distance is the distance at which EVERY update will be sent for this avatar - // at a distance of twice the full rate distance, there will be a 50% chance of sending this avatar's update - const float FULL_RATE_DISTANCE = 2.f; - // Decide whether to send this avatar's data based on it's distance from us - if ((distanceToAvatar == 0.f) || (randFloat() < FULL_RATE_DISTANCE / distanceToAvatar) - * (1 - _performanceThrottlingRatio)) { + + // Decide whether to send this avatar's data based on current performance throttling + if (_performanceThrottlingRatio == 0 || randFloat() < (1.0f - _performanceThrottlingRatio)) { QByteArray avatarByteArray; avatarByteArray.append(otherNode->getUUID().toRfc4122()); avatarByteArray.append(otherAvatar.toByteArray()); From 6b8c60e964c97d817f18b9e29f2970cc527daf21 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 15:10:53 -0700 Subject: [PATCH 38/41] remove waitForBytesWritten for initial tests --- assignment-client/src/avatars/AvatarMixer.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index ba95040ba9..9df66d0362 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -184,8 +184,6 @@ void AvatarMixer::broadcastAvatarData() { ++_sumIdentityPackets; } - - nodeList->getNodeSocket().waitForBytesWritten(-1); } } } From 6f4f55038b3cb2c39dbe76c19ec1c4fde6d71cb7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 15:31:53 -0700 Subject: [PATCH 39/41] add back selective inclusion of Avatars based on distance --- assignment-client/src/avatars/AvatarMixer.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 9df66d0362..b9bc87ecf2 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -134,8 +134,14 @@ void AvatarMixer::broadcastAvatarData() { AvatarData& otherAvatar = otherNodeData->getAvatar(); glm::vec3 otherPosition = otherAvatar.getPosition(); - // Decide whether to send this avatar's data based on current performance throttling - if (_performanceThrottlingRatio == 0 || randFloat() < (1.0f - _performanceThrottlingRatio)) { + float distanceToAvatar = glm::length(myPosition - otherPosition); + // The full rate distance is the distance at which EVERY update will be sent for this avatar + // at a distance of twice the full rate distance, there will be a 50% chance of sending this avatar's update + const float FULL_RATE_DISTANCE = 2.f; + + // Decide whether to send this avatar's data based on it's distance from us + if ((_performanceThrottlingRatio == 0 || randFloat() < (1.0f - _performanceThrottlingRatio)) + && (distanceToAvatar == 0.f || randFloat() < FULL_RATE_DISTANCE / distanceToAvatar)) { QByteArray avatarByteArray; avatarByteArray.append(otherNode->getUUID().toRfc4122()); avatarByteArray.append(otherAvatar.toByteArray()); From c8b3ae0c405d43ff10a6dd532833979a68f8f4c7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 15:49:47 -0700 Subject: [PATCH 40/41] add a mutex to NodeData and leverage in AvatarMixer threads --- assignment-client/src/avatars/AvatarMixer.cpp | 20 ++++++++++++++----- .../src/avatars/AvatarMixerClientData.h | 1 + libraries/shared/src/NodeData.cpp | 6 ++++++ libraries/shared/src/NodeData.h | 7 ++++++- libraries/shared/src/NodeList.cpp | 2 ++ 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index b9bc87ecf2..aaf58a7f42 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -114,21 +114,25 @@ void AvatarMixer::broadcastAvatarData() { NodeList* nodeList = NodeList::getInstance(); + AvatarMixerClientData* nodeData = NULL; + AvatarMixerClientData* otherNodeData = NULL; + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket()) { + if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket() + && (nodeData = reinterpret_cast(node->getLinkedData()))->getMutex().tryLock()) { ++_sumListeners; // reset packet pointers for this node mixedAvatarByteArray.resize(numPacketHeaderBytes); - AvatarMixerClientData* myData = reinterpret_cast(node->getLinkedData()); - AvatarData& avatar = myData->getAvatar(); + AvatarData& avatar = nodeData->getAvatar(); glm::vec3 myPosition = avatar.getPosition(); // this is an AGENT we have received head data from // send back a packet with other active node data to this node foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { - if (otherNode->getLinkedData() && otherNode->getUUID() != node->getUUID()) { + if (otherNode->getLinkedData() && otherNode->getUUID() != node->getUUID() + && (otherNodeData = reinterpret_cast(otherNode->getLinkedData()))->getMutex().tryLock()) { AvatarMixerClientData* otherNodeData = reinterpret_cast(otherNode->getLinkedData()); AvatarData& otherAvatar = otherNodeData->getAvatar(); @@ -158,7 +162,7 @@ void AvatarMixer::broadcastAvatarData() { // if the receiving avatar has just connected make sure we send out the mesh and billboard // for this avatar (assuming they exist) - bool forceSend = !myData->checkAndSetHasReceivedFirstPackets(); + bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets(); // we will also force a send of billboard or identity packet // if either has changed in the last frame @@ -191,10 +195,14 @@ void AvatarMixer::broadcastAvatarData() { ++_sumIdentityPackets; } } + + otherNodeData->getMutex().unlock(); } } nodeList->writeDatagram(mixedAvatarByteArray, node); + + nodeData->getMutex().unlock(); } } @@ -238,6 +246,7 @@ void AvatarMixer::readPendingDatagrams() { // parse the identity packet and update the change timestamp if appropriate if (avatar.hasIdentityChangedAfterParsing(receivedPacket)) { + QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->setIdentityChangeTimestamp(QDateTime::currentMSecsSinceEpoch()); } } @@ -254,6 +263,7 @@ void AvatarMixer::readPendingDatagrams() { // parse the billboard packet and update the change timestamp if appropriate if (avatar.hasBillboardChangedAfterParsing(receivedPacket)) { + QMutexLocker nodeDataLocker(&nodeData->getMutex()); nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch()); } diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index bc0a54f06b..22ef69aa6e 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -9,6 +9,7 @@ #ifndef __hifi__AvatarMixerClientData__ #define __hifi__AvatarMixerClientData__ +#include #include #include diff --git a/libraries/shared/src/NodeData.cpp b/libraries/shared/src/NodeData.cpp index a7fa18f409..e3800f8b93 100644 --- a/libraries/shared/src/NodeData.cpp +++ b/libraries/shared/src/NodeData.cpp @@ -8,6 +8,12 @@ #include "NodeData.h" +NodeData::NodeData() : + _mutex() +{ + +} + NodeData::~NodeData() { } \ No newline at end of file diff --git a/libraries/shared/src/NodeData.h b/libraries/shared/src/NodeData.h index cf800fc3cd..99b5fda9be 100644 --- a/libraries/shared/src/NodeData.h +++ b/libraries/shared/src/NodeData.h @@ -16,9 +16,14 @@ class Node; class NodeData : public QObject { Q_OBJECT public: - + NodeData(); virtual ~NodeData() = 0; virtual int parseData(const QByteArray& packet) = 0; + + QMutex& getMutex() { return _mutex; } + +private: + QMutex _mutex; }; #endif diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 85e981490e..e80f25709a 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -317,6 +317,8 @@ int NodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode linkedDataCreateCallback(matchingNode.data()); } + QMutexLocker linkedDataLocker(&matchingNode->getLinkedData()->getMutex()); + return matchingNode->getLinkedData()->parseData(packet); } From 34634af975bbf940597ba3443de4413eedbe5bc5 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 25 Mar 2014 16:05:51 -0700 Subject: [PATCH 41/41] add mutex header to NodeData --- assignment-client/src/avatars/AvatarMixerClientData.h | 1 - libraries/shared/src/NodeData.h | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 22ef69aa6e..bc0a54f06b 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -9,7 +9,6 @@ #ifndef __hifi__AvatarMixerClientData__ #define __hifi__AvatarMixerClientData__ -#include #include #include diff --git a/libraries/shared/src/NodeData.h b/libraries/shared/src/NodeData.h index 99b5fda9be..b6b75443a2 100644 --- a/libraries/shared/src/NodeData.h +++ b/libraries/shared/src/NodeData.h @@ -9,6 +9,7 @@ #ifndef hifi_NodeData_h #define hifi_NodeData_h +#include #include class Node;