mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 18:23:54 +02:00
Merge branch 'master' of https://github.com/worklist/hifi
This commit is contained in:
commit
012c4a63de
8 changed files with 107 additions and 111 deletions
|
@ -14,6 +14,7 @@
|
|||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include <Logging.h>
|
||||
#include <NodeList.h>
|
||||
|
@ -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),
|
||||
|
@ -54,43 +55,97 @@ 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());
|
||||
|
@ -107,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
|
||||
|
@ -140,10 +195,14 @@ void AvatarMixer::broadcastAvatarData() {
|
|||
++_sumIdentityPackets;
|
||||
}
|
||||
}
|
||||
|
||||
otherNodeData->getMutex().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
nodeList->writeDatagram(mixedAvatarByteArray, node);
|
||||
|
||||
nodeData->getMutex().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,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());
|
||||
}
|
||||
}
|
||||
|
@ -203,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());
|
||||
}
|
||||
|
||||
|
@ -248,80 +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);
|
||||
|
||||
int usecToSleep = AVATAR_DATA_SEND_INTERVAL_USECS;
|
||||
// connect appropriate signals and slots
|
||||
connect(broadcastTimer, &QTimer::timeout, this, &AvatarMixer::broadcastAvatarData, Qt::DirectConnection);
|
||||
connect(broadcastThread, SIGNAL(started()), broadcastTimer, SLOT(start()));
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
// start the broadcastThread
|
||||
broadcastThread->start();
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
|
||||
#include "NodeData.h"
|
||||
|
||||
NodeData::NodeData() :
|
||||
_mutex()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
NodeData::~NodeData() {
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue