Merge branch 'master' of https://github.com/worklist/hifi into 19507

This commit is contained in:
stojce 2014-03-26 18:26:39 +01:00
commit 04c149eabd
30 changed files with 669 additions and 391 deletions

View file

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

View file

@ -11,9 +11,10 @@
// nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms.
#include <QtCore/QCoreApplication>
#include <QtCore/QElapsedTimer>
#include <QtCore/QDateTime>
#include <QtCore/QJsonObject>
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <Logging.h>
#include <NodeList.h>
@ -27,14 +28,17 @@
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),
_lastFrameTimestamp(QDateTime::currentMSecsSinceEpoch()),
_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);
@ -46,46 +50,102 @@ 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.
// 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() {
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);
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<AvatarMixerClientData*>(node->getLinkedData()))->getMutex().tryLock()) {
++_sumListeners;
// reset packet pointers for this node
mixedAvatarByteArray.resize(numPacketHeaderBytes);
AvatarMixerClientData* myData = reinterpret_cast<AvatarMixerClientData*>(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<AvatarMixerClientData*>(otherNode->getLinkedData()))->getMutex().tryLock()) {
AvatarMixerClientData* otherNodeData = reinterpret_cast<AvatarMixerClientData*>(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)) {
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());
@ -99,73 +159,54 @@ 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 = !nodeData->checkAndSetHasReceivedFirstPackets();
// 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);
++_sumBillboardPackets;
}
if (otherNodeData->getIdentityChangeTimestamp() > 0
&& (forceSend
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|| 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);
++_sumIdentityPackets;
}
}
otherNodeData->getMutex().unlock();
}
}
nodeList->writeDatagram(mixedAvatarByteArray, node);
}
}
}
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<AvatarMixerClientData*>(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);
nodeData->getMutex().unlock();
}
}
// send out the final packet
if (avatarIdentityPacket.size() > numPacketHeaderBytes) {
nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent);
}
}
void broadcastBillboardPacket(const SharedNodePointer& sendingNode) {
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(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<AvatarMixerClientData*>(node->getLinkedData());
broadcastBillboardPacket(node);
nodeData->setHasSentBillboardBetweenKeyFrames(false);
}
}
_lastFrameTimestamp = QDateTime::currentMSecsSinceEpoch();
}
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
@ -202,18 +243,11 @@ void AvatarMixer::readPendingDatagrams() {
if (avatarNode && avatarNode->getLinkedData()) {
AvatarMixerClientData* nodeData = reinterpret_cast<AvatarMixerClientData*>(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);
// parse the identity packet and update the change timestamp if appropriate
if (avatar.hasIdentityChangedAfterParsing(receivedPacket)) {
QMutexLocker nodeDataLocker(&nodeData->getMutex());
nodeData->setIdentityChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
}
}
break;
@ -226,12 +260,13 @@ void AvatarMixer::readPendingDatagrams() {
if (avatarNode && avatarNode->getLinkedData()) {
AvatarMixerClientData* nodeData = static_cast<AvatarMixerClientData*>(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);
// parse the billboard packet and update the change timestamp if appropriate
if (avatar.hasBillboardChangedAfterParsing(receivedPacket)) {
QMutexLocker nodeDataLocker(&nodeData->getMutex());
nodeData->setBillboardChangeTimestamp(QDateTime::currentMSecsSinceEpoch());
}
}
break;
}
@ -252,18 +287,20 @@ 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;
}
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);
@ -272,97 +309,18 @@ void AvatarMixer::run() {
nodeList->linkedDataCreateCallback = attachAvatarDataToNode;
int nextFrame = 0;
timeval startTime;
// create a thead for broadcast of avatar data
QThread* broadcastThread = new QThread(this);
gettimeofday(&startTime, NULL);
// 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);
QElapsedTimer identityTimer;
identityTimer.start();
// connect appropriate signals and slots
connect(broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection);
connect(broadcastThread, SIGNAL(started()), broadcastTimer, SLOT(start()));
QElapsedTimer billboardTimer;
billboardTimer.start();
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;
}
}
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) {
break;
}
usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow();
if (usecToSleep > 0) {
usleep(usecToSleep);
} else {
qDebug() << "AvatarMixer loop took too" << -usecToSleep << "of extra time. Won't sleep.";
}
}
// start the broadcastThread
broadcastThread->start();
}

View file

@ -30,11 +30,15 @@ public slots:
private:
void broadcastAvatarData();
quint64 _lastFrameTimestamp;
float _trailingSleepRatio;
float _performanceThrottlingRatio;
int _sumListeners;
int _numStatFrames;
int _sumBillboardPackets;
int _sumIdentityPackets;
};
#endif /* defined(__hifi__AvatarMixer__) */

View file

@ -10,8 +10,9 @@
AvatarMixerClientData::AvatarMixerClientData() :
NodeData(),
_hasSentIdentityBetweenKeyFrames(false),
_hasSentBillboardBetweenKeyFrames(false)
_hasReceivedFirstPackets(false),
_billboardChangeTimestamp(0),
_identityChangeTimestamp(0)
{
}
@ -21,3 +22,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;
}

View file

@ -20,22 +20,21 @@ 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; }
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:
bool _hasSentIdentityBetweenKeyFrames;
bool _hasSentBillboardBetweenKeyFrames;
AvatarData _avatar;
bool _hasReceivedFirstPackets;
quint64 _billboardChangeTimestamp;
quint64 _identityChangeTimestamp;
};
#endif /* defined(__hifi__AvatarMixerClientData__) */

View file

@ -40,6 +40,10 @@ span.port {
color: #666666;
}
.stats-key {
width: 400px;
}
.stale {
color: red;
}

View file

@ -21,8 +21,9 @@ $(document).ready(function(){
$.each(json, function(key, value) {
statsTableBody += "<tr>";
statsTableBody += "<td>" + key + "</td>";
statsTableBody += "<td>" + value + "</td>";
statsTableBody += "<td class='stats-key'>" + key + "</td>";
var formattedValue = (typeof value == 'number' ? value.toLocaleString() : value);
statsTableBody += "<td>" + formattedValue + "</td>";
statsTableBody += "</tr>";
});

View file

@ -3,3 +3,5 @@
Script.include("lookWithTouch.js");
Script.include("editVoxels.js");
Script.include("selectAudioDevice.js");
Script.include("hydraMove.js");
Script.include("inspect.js");

View file

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

View file

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

View file

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

View file

@ -113,18 +113,18 @@
<context>
<name>Menu</name>
<message>
<location filename="src/Menu.cpp" line="449"/>
<location filename="src/Menu.cpp" line="455"/>
<source>Open .ini config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="451"/>
<location filename="src/Menu.cpp" line="463"/>
<location filename="src/Menu.cpp" line="457"/>
<location filename="src/Menu.cpp" line="469"/>
<source>Text files (*.ini)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="src/Menu.cpp" line="461"/>
<location filename="src/Menu.cpp" line="467"/>
<source>Save .ini config file</source>
<translation type="unfinished"></translation>
</message>

View file

@ -28,6 +28,7 @@
#include <QDesktopWidget>
#include <QCheckBox>
#include <QImage>
#include <QInputDialog>
#include <QKeyEvent>
#include <QMainWindow>
#include <QMenuBar>
@ -250,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");
@ -329,9 +330,20 @@ 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");
_settings->setValue("firstRun",QVariant(false));
} else {
// do this as late as possible so that all required subsystems are inialized
loadScripts();
}
}
Application::~Application() {
@ -3453,6 +3465,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);
@ -3508,35 +3527,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 +3600,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());

View file

@ -116,6 +116,7 @@ public:
void loadScript(const QString& fileNameString);
void loadScripts();
void storeSizeAndPosition();
void clearScriptsBeforeRunning();
void saveScripts();
void initializeGL();
void paintGL();
@ -255,6 +256,7 @@ public slots:
void setRenderVoxels(bool renderVoxels);
void doKillLocalVoxels();
void loadDialog();
void loadScriptURLDialog();
void toggleLogDialog();
void initAvatarAndViewFrustum();
void stopAllScripts();

View file

@ -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)
{
@ -107,6 +110,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");
@ -386,6 +391,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 +432,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 +1193,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

View file

@ -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;
@ -272,7 +274,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 +307,6 @@ namespace MenuOption {
const QString VoxelTextures = "Voxel Textures";
}
void sendFakeEnterEvent();
#endif /* defined(__hifi__Menu__) */

View file

@ -14,6 +14,7 @@
#include <glm/glm.hpp>
#include <glm/gtc/noise.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/detail/func_common.hpp>
#include <SharedUtil.h>
@ -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;

View file

@ -48,15 +48,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
Avatar* avatar = static_cast<Avatar*>(avatarIterator.value().data());
if (avatar == static_cast<Avatar*>(_myAvatar.data())) {
if (avatar == static_cast<Avatar*>(_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);
@ -120,22 +117,40 @@ void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::R
foreach(const AvatarSharedPointer& fadingAvatar, _avatarFades) {
Avatar* avatar = static_cast<Avatar*>(fadingAvatar.data());
if (avatar != static_cast<Avatar*>(_myAvatar.data())) {
if (avatar != static_cast<Avatar*>(_myAvatar.data()) && avatar->isInitialized()) {
avatar->render(cameraPosition, renderMode);
}
}
}
AvatarSharedPointer AvatarManager::matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer<Node>& 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<Node>& 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 +169,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<Avatar*>(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<Node>& mixerWeakPointer) {
// setup a data stream to parse the packet
QDataStream identityStream(packet);
identityStream.skipRawData(numBytesForPacketHeader(packet));
@ -187,7 +197,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<Avatar*>(matchingAvatar.data());
@ -206,11 +216,11 @@ void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) {
}
}
void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet) {
void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer<Node>& 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<Avatar*>(matchingAvatar.data());
QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID);
@ -234,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<Avatar*>(iterator.value().data())->isInitialized()) {
_avatarFades.push_back(iterator.value());
}
return AvatarHashMap::erase(iterator);
} else {
// never remove _myAvatar from the list

View file

@ -39,9 +39,11 @@ public slots:
private:
AvatarManager(const AvatarManager& other);
AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer<Node>& mixerWeakPointer);
void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
void processAvatarIdentityPacket(const QByteArray& packet);
void processAvatarBillboardPacket(const QByteArray& packet);
void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer<Node>& mixerWeakPointer);
void processKillAvatar(const QByteArray& datagram);
void simulateAvatarFades(float deltaTime);

View file

@ -8,6 +8,7 @@
#include <cstring>
#include <glm/detail/func_common.hpp>
#include <QtCore/QDataStream>
#include <Node.h>
@ -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<char*>(&_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;
}

View file

@ -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<const unsigned char*>(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;
}

View file

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

View file

@ -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;
@ -113,11 +176,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;
}
@ -171,7 +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);
}
void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {
@ -436,3 +499,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();
}
}

View file

@ -33,8 +33,11 @@ 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 QUrl& scriptURL, bool wantMenuItems = false,
AbstractControllerScriptingInterface* controllerScriptingInterface = NULL);
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 +47,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();
@ -68,6 +71,8 @@ public:
void timerFired();
bool hasScript() const { return !_scriptContents.isEmpty(); }
public slots:
void stop();
@ -75,6 +80,7 @@ public slots:
QObject* setTimeout(const QScriptValue& function, int timeoutMS);
void clearInterval(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast<QTimer*>(timer)); }
void include(const QString& includeFile);
signals:
void update(float deltaTime);
@ -97,6 +103,7 @@ protected:
int _numAvatarSoundSentBytes;
private:
QUrl resolveInclude(const QString& include) const;
void sendAvatarIdentityPacket();
void sendAvatarBillboardPacket();

View file

@ -8,6 +8,12 @@
#include "NodeData.h"
NodeData::NodeData() :
_mutex()
{
}
NodeData::~NodeData() {
}

View file

@ -9,6 +9,7 @@
#ifndef hifi_NodeData_h
#define hifi_NodeData_h
#include <QtCore/QMutex>
#include <QtCore/QObject>
class Node;
@ -16,9 +17,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

View file

@ -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,
@ -312,6 +317,8 @@ int NodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode
linkedDataCreateCallback(matchingNode.data());
}
QMutexLocker linkedDataLocker(&matchingNode->getLinkedData()->getMutex());
return matchingNode->getLinkedData()->parseData(packet);
}
@ -413,9 +420,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);

View file

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

View file

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

View file

@ -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);
@ -82,7 +78,6 @@ void ThreadedAssignment::sendStatsPacket() {
void ThreadedAssignment::checkInWithDomainServerOrExit() {
if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) {
qDebug() << "NRDC:" << NodeList::getInstance()->getNumNoReplyDomainCheckIns();
setFinished(true);
} else {
NodeList::getInstance()->sendDomainServerCheckIn();