Merge pull request #14721 from Atlante45/feat/upstream-limitter

Limit upstream bandwidth to the Avatar Mixer
This commit is contained in:
Antonina Savinova 2019-01-18 13:32:03 -08:00 committed by GitHub
commit 8c4c488676
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 114 additions and 153 deletions

View file

@ -506,16 +506,6 @@ void Agent::executeScript() {
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree()); DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
// Agents should run at 45hz
static const int AVATAR_DATA_HZ = 45;
static const int AVATAR_DATA_IN_MSECS = MSECS_PER_SECOND / AVATAR_DATA_HZ;
QTimer* avatarDataTimer = new QTimer(this);
connect(avatarDataTimer, &QTimer::timeout, this, &Agent::processAgentAvatar);
avatarDataTimer->setSingleShot(false);
avatarDataTimer->setInterval(AVATAR_DATA_IN_MSECS);
avatarDataTimer->setTimerType(Qt::PreciseTimer);
avatarDataTimer->start();
_scriptEngine->run(); _scriptEngine->run();
Frame::clearFrameHandler(AUDIO_FRAME_TYPE); Frame::clearFrameHandler(AUDIO_FRAME_TYPE);
@ -529,8 +519,6 @@ void Agent::executeScript() {
recordingInterface->stopRecording(); recordingInterface->stopRecording();
} }
avatarDataTimer->stop();
setIsAvatar(false); // will stop timers for sending identity packets setIsAvatar(false); // will stop timers for sending identity packets
} }
@ -585,20 +573,16 @@ void Agent::setIsAvatar(bool isAvatar) {
auto scriptableAvatar = DependencyManager::get<ScriptableAvatar>(); auto scriptableAvatar = DependencyManager::get<ScriptableAvatar>();
if (_isAvatar) { if (_isAvatar) {
if (!_avatarIdentityTimer) { if (!_avatarQueryTimer) {
// set up the avatar timers // set up the avatar timers
_avatarIdentityTimer = new QTimer(this);
_avatarQueryTimer = new QTimer(this); _avatarQueryTimer = new QTimer(this);
// connect our slot // connect our slot
connect(_avatarIdentityTimer, &QTimer::timeout, this, &Agent::sendAvatarIdentityPacket);
connect(_avatarQueryTimer, &QTimer::timeout, this, &Agent::queryAvatars); connect(_avatarQueryTimer, &QTimer::timeout, this, &Agent::queryAvatars);
static const int AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS = 1000;
static const int AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS = 1000; static const int AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS = 1000;
// start the timers // start the timer
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); // FIXME - we shouldn't really need to constantly send identity packets
_avatarQueryTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS); _avatarQueryTimer->start(AVATAR_VIEW_PACKET_SEND_INTERVAL_MSECS);
connect(_scriptEngine.data(), &ScriptEngine::update, connect(_scriptEngine.data(), &ScriptEngine::update,
@ -610,11 +594,7 @@ void Agent::setIsAvatar(bool isAvatar) {
_entityEditSender.setMyAvatar(scriptableAvatar.data()); _entityEditSender.setMyAvatar(scriptableAvatar.data());
} else { } else {
if (_avatarIdentityTimer) { if (_avatarQueryTimer) {
_avatarIdentityTimer->stop();
delete _avatarIdentityTimer;
_avatarIdentityTimer = nullptr;
_avatarQueryTimer->stop(); _avatarQueryTimer->stop();
delete _avatarQueryTimer; delete _avatarQueryTimer;
_avatarQueryTimer = nullptr; _avatarQueryTimer = nullptr;
@ -647,14 +627,6 @@ void Agent::setIsAvatar(bool isAvatar) {
} }
} }
void Agent::sendAvatarIdentityPacket() {
if (_isAvatar) {
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
scriptedAvatar->markIdentityDataChanged();
scriptedAvatar->sendIdentityPacket();
}
}
void Agent::queryAvatars() { void Agent::queryAvatars() {
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>(); auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
@ -682,44 +654,6 @@ void Agent::queryAvatars() {
{ NodeType::AvatarMixer }); { NodeType::AvatarMixer });
} }
void Agent::processAgentAvatar() {
if (!_scriptEngine->isFinished() && _isAvatar) {
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
AvatarData::AvatarDataDetail dataDetail = (randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO) ? AvatarData::SendAllData : AvatarData::CullSmallData;
QByteArray avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail);
int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber);
if (avatarByteArray.size() > maximumByteArraySize) {
qWarning() << " scriptedAvatar->toByteArrayStateful() resulted in very large buffer:" << avatarByteArray.size() << "... attempt to drop facial data";
avatarByteArray = scriptedAvatar->toByteArrayStateful(dataDetail, true);
if (avatarByteArray.size() > maximumByteArraySize) {
qWarning() << " scriptedAvatar->toByteArrayStateful() without facial data resulted in very large buffer:" << avatarByteArray.size() << "... reduce to MinimumData";
avatarByteArray = scriptedAvatar->toByteArrayStateful(AvatarData::MinimumData, true);
if (avatarByteArray.size() > maximumByteArraySize) {
qWarning() << " scriptedAvatar->toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!";
return;
}
}
}
scriptedAvatar->doneEncoding(true);
static AvatarDataSequenceNumber sequenceNumber = 0;
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber));
avatarPacket->writePrimitive(sequenceNumber++);
avatarPacket->write(avatarByteArray);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
}
}
void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) { void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
_flushEncoder = false; _flushEncoder = false;
static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0); static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0);

View file

@ -81,7 +81,6 @@ private slots:
void nodeActivated(SharedNodePointer activatedNode); void nodeActivated(SharedNodePointer activatedNode);
void nodeKilled(SharedNodePointer killedNode); void nodeKilled(SharedNodePointer killedNode);
void processAgentAvatar();
void processAgentAvatarAudio(); void processAgentAvatarAudio();
private: private:
@ -99,7 +98,6 @@ private:
void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; } void setAvatarSound(SharedSoundPointer avatarSound) { _avatarSound = avatarSound; }
void sendAvatarIdentityPacket();
void queryAvatars(); void queryAvatars();
QString _scriptContents; QString _scriptContents;
@ -110,7 +108,6 @@ private:
bool _shouldMuteRecordingAudio { false }; bool _shouldMuteRecordingAudio { false };
int _numAvatarSoundSentBytes = 0; int _numAvatarSoundSentBytes = 0;
bool _isAvatar = false; bool _isAvatar = false;
QTimer* _avatarIdentityTimer = nullptr;
QTimer* _avatarQueryTimer = nullptr; QTimer* _avatarQueryTimer = nullptr;
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers; QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;

View file

@ -54,7 +54,6 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket");
packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket");
packetReceiver.registerListener(PacketType::AvatarIdentityRequest, this, "handleAvatarIdentityRequestPacket");
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket");
@ -582,36 +581,6 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
_handleAvatarIdentityPacketElapsedTime += (end - start); _handleAvatarIdentityPacketElapsedTime += (end - start);
} }
void AvatarMixer::handleAvatarIdentityRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
if (message->getSize() < NUM_BYTES_RFC4122_UUID) {
qCDebug(avatars) << "Malformed AvatarIdentityRequest received from" << message->getSenderSockAddr().toString();
return;
}
QUuid avatarID(QUuid::fromRfc4122(message->getMessage()) );
if (!avatarID.isNull()) {
auto nodeList = DependencyManager::get<NodeList>();
auto requestedNode = nodeList->nodeWithUUID(avatarID);
if (requestedNode) {
AvatarMixerClientData* avatarClientData = static_cast<AvatarMixerClientData*>(requestedNode->getLinkedData());
if (avatarClientData) {
const AvatarData& avatarData = avatarClientData->getAvatar();
QByteArray serializedAvatar = avatarData.identityByteArray();
auto identityPackets = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
identityPackets->write(serializedAvatar);
nodeList->sendPacketList(std::move(identityPackets), *senderNode);
++_sumIdentityPackets;
}
AvatarMixerClientData* senderData = static_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
if (senderData) {
senderData->resetSentTraitData(requestedNode->getLocalID());
}
}
}
}
void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) { void AvatarMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
auto start = usecTimestampNow(); auto start = usecTimestampNow();
handleAvatarKilled(node); handleAvatarKilled(node);

View file

@ -54,7 +54,6 @@ private slots:
void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode); void handleRequestsDomainListDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void handleReplicatedPacket(QSharedPointer<ReceivedMessage> message); void handleReplicatedPacket(QSharedPointer<ReceivedMessage> message);
void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message); void handleReplicatedBulkAvatarPacket(QSharedPointer<ReceivedMessage> message);
void handleAvatarIdentityRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void domainSettingsRequestComplete(); void domainSettingsRequestComplete();
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
void start(); void start();

View file

@ -91,6 +91,39 @@ void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
updateJointMappings(); updateJointMappings();
} }
int ScriptableAvatar::sendAvatarDataPacket(bool sendAll) {
using namespace std::chrono;
auto now = Clock::now();
int MAX_DATA_RATE_MBPS = 3;
int maxDataRateBytesPerSeconds = MAX_DATA_RATE_MBPS * BYTES_PER_KILOBYTE * KILO_PER_MEGA / BITS_IN_BYTE;
int maxDataRateBytesPerMilliseconds = maxDataRateBytesPerSeconds / MSECS_PER_SECOND;
auto bytesSent = 0;
if (now > _nextTraitsSendWindow) {
if (getIdentityDataChanged()) {
bytesSent += sendIdentityPacket();
}
bytesSent += _clientTraitsHandler->sendChangedTraitsToMixer();
// Compute the next send window based on how much data we sent and what
// data rate we're trying to max at.
milliseconds timeUntilNextSend { bytesSent / maxDataRateBytesPerMilliseconds };
_nextTraitsSendWindow += timeUntilNextSend;
// Don't let the next send window lag behind if we're not sending a lot of data.
if (_nextTraitsSendWindow < now) {
_nextTraitsSendWindow = now;
}
}
bytesSent += AvatarData::sendAvatarDataPacket(sendAll);
return bytesSent;
}
static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) { static AnimPose composeAnimPose(const HFMJoint& joint, const glm::quat rotation, const glm::vec3 translation) {
glm::mat4 translationMat = glm::translate(translation); glm::mat4 translationMat = glm::translate(translation);
glm::mat4 rotationMat = glm::mat4_cast(joint.preRotation * rotation * joint.postRotation); glm::mat4 rotationMat = glm::mat4_cast(joint.preRotation * rotation * joint.postRotation);
@ -161,7 +194,13 @@ void ScriptableAvatar::update(float deltatime) {
} }
} }
_clientTraitsHandler->sendChangedTraitsToMixer(); quint64 now = usecTimestampNow();
quint64 dt = now - _lastSendAvatarDataTime;
if (dt > MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS) {
sendAvatarDataPacket();
_lastSendAvatarDataTime = now;
}
} }
void ScriptableAvatar::updateJointMappings() { void ScriptableAvatar::updateJointMappings() {

View file

@ -123,6 +123,10 @@
class ScriptableAvatar : public AvatarData, public Dependency { class ScriptableAvatar : public AvatarData, public Dependency {
Q_OBJECT Q_OBJECT
using Clock = std::chrono::system_clock;
using TimePoint = Clock::time_point;
public: public:
ScriptableAvatar(); ScriptableAvatar();
@ -177,6 +181,8 @@ public:
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
int sendAvatarDataPacket(bool sendAll = false) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override; virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement); void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement);
@ -228,6 +234,10 @@ private:
/// Loads the joint indices, names from the FST file (if any) /// Loads the joint indices, names from the FST file (if any)
void updateJointMappings(); void updateJointMappings();
quint64 _lastSendAvatarDataTime { 0 };
TimePoint _nextTraitsSendWindow;
}; };
#endif // hifi_ScriptableAvatar_h #endif // hifi_ScriptableAvatar_h

View file

@ -2201,7 +2201,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|| ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose)); || ((rightHandPose.valid || lastRightHandPose.valid) && (rightHandPose != lastRightHandPose));
lastLeftHandPose = leftHandPose; lastLeftHandPose = leftHandPose;
lastRightHandPose = rightHandPose; lastRightHandPose = rightHandPose;
properties["avatar_identity_requests_sent"] = DependencyManager::get<AvatarManager>()->getIdentityRequestsSent();
UserActivityLogger::getInstance().logAction("stats", properties); UserActivityLogger::getInstance().logAction("stats", properties);
}); });

View file

@ -48,8 +48,6 @@
// 50 times per second - target is 45hz, but this helps account for any small deviations // 50 times per second - target is 45hz, but this helps account for any small deviations
// in the update loop - this also results in ~30hz when in desktop mode which is essentially // in the update loop - this also results in ~30hz when in desktop mode which is essentially
// what we want // what we want
const int CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 50;
static const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
// We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key. // We add _myAvatar into the hash with all the other AvatarData, and we use the default NULL QUid as the key.
const QUuid MY_AVATAR_KEY; // NULL key const QUuid MY_AVATAR_KEY; // NULL key
@ -352,25 +350,6 @@ void AvatarManager::postUpdate(float deltaTime, const render::ScenePointer& scen
} }
} }
void AvatarManager::sendIdentityRequest(const QUuid& avatarID) const {
auto nodeList = DependencyManager::get<NodeList>();
QWeakPointer<NodeList> nodeListWeak = nodeList;
nodeList->eachMatchingNode(
[](const SharedNodePointer& node)->bool {
return node->getType() == NodeType::AvatarMixer && node->getActiveSocket();
},
[this, avatarID, nodeListWeak](const SharedNodePointer& node) {
auto nodeList = nodeListWeak.lock();
if (nodeList) {
auto packet = NLPacket::create(PacketType::AvatarIdentityRequest, NUM_BYTES_RFC4122_UUID, true);
packet->write(avatarID.toRfc4122());
nodeList->sendPacket(std::move(packet), *node);
++_identityRequestsSent;
}
}
);
}
void AvatarManager::simulateAvatarFades(float deltaTime) { void AvatarManager::simulateAvatarFades(float deltaTime) {
if (_avatarsToFadeOut.empty()) { if (_avatarsToFadeOut.empty()) {
return; return;

View file

@ -92,7 +92,6 @@ public:
void updateMyAvatar(float deltaTime); void updateMyAvatar(float deltaTime);
void updateOtherAvatars(float deltaTime); void updateOtherAvatars(float deltaTime);
void sendIdentityRequest(const QUuid& avatarID) const;
void setMyAvatarDataPacketsPaused(bool puase); void setMyAvatarDataPacketsPaused(bool puase);
@ -191,7 +190,6 @@ public:
Q_INVOKABLE QVariantMap getPalData(const QStringList& specificAvatarIdentifiers = QStringList()); Q_INVOKABLE QVariantMap getPalData(const QStringList& specificAvatarIdentifiers = QStringList());
float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); } float getMyAvatarSendRate() const { return _myAvatarSendRate.rate(); }
int getIdentityRequestsSent() const { return _identityRequestsSent; }
void queuePhysicsChange(const OtherAvatarPointer& avatar); void queuePhysicsChange(const OtherAvatarPointer& avatar);
void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction); void buildPhysicsTransaction(PhysicsEngine::Transaction& transaction);
@ -241,7 +239,6 @@ private:
float _avatarSimulationTime { 0.0f }; float _avatarSimulationTime { 0.0f };
bool _shouldRender { true }; bool _shouldRender { true };
bool _myAvatarDataPacketsPaused { false }; bool _myAvatarDataPacketsPaused { false };
mutable int _identityRequestsSent { 0 };
mutable std::mutex _spaceLock; mutable std::mutex _spaceLock;
workload::SpacePointer _space; workload::SpacePointer _space;

View file

@ -668,12 +668,6 @@ void MyAvatar::update(float deltaTime) {
Q_ARG(glm::vec3, (getWorldPosition() - halfBoundingBoxDimensions)), Q_ARG(glm::vec3, (getWorldPosition() - halfBoundingBoxDimensions)),
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f))); Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
if (getIdentityDataChanged()) {
sendIdentityPacket();
}
_clientTraitsHandler->sendChangedTraitsToMixer();
simulate(deltaTime, true); simulate(deltaTime, true);
currentEnergy += energyChargeRate; currentEnergy += energyChargeRate;
@ -3103,6 +3097,39 @@ void MyAvatar::preDisplaySide(const RenderArgs* renderArgs) {
_prevShouldDrawHead = shouldDrawHead; _prevShouldDrawHead = shouldDrawHead;
} }
int MyAvatar::sendAvatarDataPacket(bool sendAll) {
using namespace std::chrono;
auto now = Clock::now();
int MAX_DATA_RATE_MBPS = 3;
int maxDataRateBytesPerSeconds = MAX_DATA_RATE_MBPS * BYTES_PER_KILOBYTE * KILO_PER_MEGA / BITS_IN_BYTE;
int maxDataRateBytesPerMilliseconds = maxDataRateBytesPerSeconds / MSECS_PER_SECOND;
auto bytesSent = 0;
if (now > _nextTraitsSendWindow) {
if (getIdentityDataChanged()) {
bytesSent += sendIdentityPacket();
}
bytesSent += _clientTraitsHandler->sendChangedTraitsToMixer();
// Compute the next send window based on how much data we sent and what
// data rate we're trying to max at.
milliseconds timeUntilNextSend { bytesSent / maxDataRateBytesPerMilliseconds };
_nextTraitsSendWindow += timeUntilNextSend;
// Don't let the next send window lag behind if we're not sending a lot of data.
if (_nextTraitsSendWindow < now) {
_nextTraitsSendWindow = now;
}
}
bytesSent += Avatar::sendAvatarDataPacket(sendAll);
return bytesSent;
}
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.47f; const float RENDER_HEAD_CUTOFF_DISTANCE = 0.47f;
bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const { bool MyAvatar::cameraInsideHead(const glm::vec3& cameraPosition) const {

View file

@ -253,6 +253,9 @@ class MyAvatar : public Avatar {
const QString DOMINANT_LEFT_HAND = "left"; const QString DOMINANT_LEFT_HAND = "left";
const QString DOMINANT_RIGHT_HAND = "right"; const QString DOMINANT_RIGHT_HAND = "right";
using Clock = std::chrono::system_clock;
using TimePoint = Clock::time_point;
public: public:
enum DriveKeys { enum DriveKeys {
TRANSLATE_X = 0, TRANSLATE_X = 0,
@ -1213,6 +1216,7 @@ public:
void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override; void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override;
void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override; void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override;
void avatarEntityDataToJson(QJsonObject& root) const override; void avatarEntityDataToJson(QJsonObject& root) const override;
int sendAvatarDataPacket(bool sendAll = false) override;
public slots: public slots:
@ -1937,6 +1941,8 @@ private:
bool _skeletonModelLoaded { false }; bool _skeletonModelLoaded { false };
bool _reloadAvatarEntityDataFromSettings { true }; bool _reloadAvatarEntityDataFromSettings { true };
TimePoint _nextTraitsSendWindow;
Setting::Handle<QString> _dominantHandSetting; Setting::Handle<QString> _dominantHandSetting;
Setting::Handle<float> _headPitchSetting; Setting::Handle<float> _headPitchSetting;
Setting::Handle<float> _scaleSetting; Setting::Handle<float> _scaleSetting;

View file

@ -2157,7 +2157,7 @@ void AvatarData::detachAll(const QString& modelURL, const QString& jointName) {
setAttachmentData(attachmentData); setAttachmentData(attachmentData);
} }
void AvatarData::sendAvatarDataPacket(bool sendAll) { int AvatarData::sendAvatarDataPacket(bool sendAll) {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
// about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed. // about 2% of the time, we send a full update (meaning, we transmit all the joint data), even if nothing has changed.
@ -2170,16 +2170,14 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) {
int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber); int maximumByteArraySize = NLPacket::maxPayloadSize(PacketType::AvatarData) - sizeof(AvatarDataSequenceNumber);
if (avatarByteArray.size() > maximumByteArraySize) { if (avatarByteArray.size() > maximumByteArraySize) {
qCWarning(avatars) << "toByteArrayStateful() resulted in very large buffer:" << avatarByteArray.size() << "... attempt to drop facial data";
avatarByteArray = toByteArrayStateful(dataDetail, true); avatarByteArray = toByteArrayStateful(dataDetail, true);
if (avatarByteArray.size() > maximumByteArraySize) { if (avatarByteArray.size() > maximumByteArraySize) {
qCWarning(avatars) << "toByteArrayStateful() without facial data resulted in very large buffer:" << avatarByteArray.size() << "... reduce to MinimumData";
avatarByteArray = toByteArrayStateful(MinimumData, true); avatarByteArray = toByteArrayStateful(MinimumData, true);
if (avatarByteArray.size() > maximumByteArraySize) { if (avatarByteArray.size() > maximumByteArraySize) {
qCWarning(avatars) << "toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!"; qCWarning(avatars) << "toByteArrayStateful() MinimumData resulted in very large buffer:" << avatarByteArray.size() << "... FAIL!!";
return; return 0;
} }
} }
} }
@ -2191,18 +2189,20 @@ void AvatarData::sendAvatarDataPacket(bool sendAll) {
auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber)); auto avatarPacket = NLPacket::create(PacketType::AvatarData, avatarByteArray.size() + sizeof(sequenceNumber));
avatarPacket->writePrimitive(sequenceNumber++); avatarPacket->writePrimitive(sequenceNumber++);
avatarPacket->write(avatarByteArray); avatarPacket->write(avatarByteArray);
auto packetSize = avatarPacket->getWireSize();
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer); nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
return packetSize;
} }
void AvatarData::sendIdentityPacket() { int AvatarData::sendIdentityPacket() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
if (_identityDataChanged) { if (_identityDataChanged) {
// if the identity data has changed, push the sequence number forwards // if the identity data has changed, push the sequence number forwards
++_identitySequenceNumber; ++_identitySequenceNumber;
} }
QByteArray identityData = identityByteArray(); QByteArray identityData = identityByteArray();
auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true); auto packetList = NLPacketList::create(PacketType::AvatarIdentity, QByteArray(), true, true);
@ -2216,6 +2216,7 @@ void AvatarData::sendIdentityPacket() {
}); });
_identityDataChanged = false; _identityDataChanged = false;
return identityData.size();
} }
static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl"); static const QString JSON_ATTACHMENT_URL = QStringLiteral("modelUrl");

View file

@ -1271,12 +1271,12 @@ public slots:
* @function MyAvatar.sendAvatarDataPacket * @function MyAvatar.sendAvatarDataPacket
* @param {boolean} [sendAll=false] * @param {boolean} [sendAll=false]
*/ */
void sendAvatarDataPacket(bool sendAll = false); virtual int sendAvatarDataPacket(bool sendAll = false);
/**jsdoc /**jsdoc
* @function MyAvatar.sendIdentityPacket * @function MyAvatar.sendIdentityPacket
*/ */
void sendIdentityPacket(); int sendIdentityPacket();
/**jsdoc /**jsdoc
* @function MyAvatar.setSessionUUID * @function MyAvatar.setSessionUUID

View file

@ -32,6 +32,9 @@
#include "AvatarData.h" #include "AvatarData.h"
#include "AssociatedTraitValues.h" #include "AssociatedTraitValues.h"
const int CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 50;
const quint64 MIN_TIME_BETWEEN_MY_AVATAR_DATA_SENDS = USECS_PER_SECOND / CLIENT_TO_AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
/**jsdoc /**jsdoc
* <strong>Note:</strong> An <code>AvatarList</code> API is also provided for Interface and client entity scripts: it is a * <strong>Note:</strong> An <code>AvatarList</code> API is also provided for Interface and client entity scripts: it is a
* synonym for the {@link AvatarManager} API. * synonym for the {@link AvatarManager} API.

View file

@ -65,8 +65,9 @@ void ClientTraitsHandler::resetForNewMixer() {
_owningAvatar->prepareResetTraitInstances(); _owningAvatar->prepareResetTraitInstances();
} }
void ClientTraitsHandler::sendChangedTraitsToMixer() { int ClientTraitsHandler::sendChangedTraitsToMixer() {
std::unique_lock<Mutex> lock(_traitLock); std::unique_lock<Mutex> lock(_traitLock);
int bytesWritten = 0;
if (hasChangedTraits() || _shouldPerformInitialSend) { if (hasChangedTraits() || _shouldPerformInitialSend) {
// we have at least one changed trait to send // we have at least one changed trait to send
@ -75,7 +76,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
auto avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer); auto avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer);
if (!avatarMixer || !avatarMixer->getActiveSocket()) { if (!avatarMixer || !avatarMixer->getActiveSocket()) {
// we don't have an avatar mixer with an active socket, we can't send changed traits at this time // we don't have an avatar mixer with an active socket, we can't send changed traits at this time
return; return 0;
} }
// we have a mixer to send to, setup our set traits packet // we have a mixer to send to, setup our set traits packet
@ -106,7 +107,7 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
if (initialSend || *simpleIt == Updated) { if (initialSend || *simpleIt == Updated) {
if (traitType == AvatarTraits::SkeletonModelURL) { if (traitType == AvatarTraits::SkeletonModelURL) {
_owningAvatar->packTrait(traitType, *traitsPacketList); bytesWritten += _owningAvatar->packTrait(traitType, *traitsPacketList);
// keep track of our skeleton version in case we get an override back // keep track of our skeleton version in case we get an override back
_currentSkeletonVersion = _currentTraitVersion; _currentSkeletonVersion = _currentTraitVersion;
@ -123,10 +124,10 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
|| instanceIDValuePair.value == Updated) { || instanceIDValuePair.value == Updated) {
// this is a changed trait we need to send or we haven't send out trait information yet // this is a changed trait we need to send or we haven't send out trait information yet
// ask the owning avatar to pack it // ask the owning avatar to pack it
_owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList); bytesWritten += _owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList);
} else if (!initialSend && instanceIDValuePair.value == Deleted) { } else if (!initialSend && instanceIDValuePair.value == Deleted) {
// pack delete for this trait instance // pack delete for this trait instance
AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id, bytesWritten += AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id,
*traitsPacketList); *traitsPacketList);
} }
} }
@ -136,6 +137,8 @@ void ClientTraitsHandler::sendChangedTraitsToMixer() {
nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer); nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer);
} }
return bytesWritten;
} }
void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) { void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {

View file

@ -24,7 +24,7 @@ class ClientTraitsHandler : public QObject {
public: public:
ClientTraitsHandler(AvatarData* owningAvatar); ClientTraitsHandler(AvatarData* owningAvatar);
void sendChangedTraitsToMixer(); int sendChangedTraitsToMixer();
bool hasChangedTraits() const { return _hasChangedTraits; } bool hasChangedTraits() const { return _hasChangedTraits; }

View file

@ -93,8 +93,6 @@ PacketVersion versionForPacketType(PacketType packetType) {
return static_cast<PacketVersion>(PingVersion::IncludeConnectionID); return static_cast<PacketVersion>(PingVersion::IncludeConnectionID);
case PacketType::AvatarQuery: case PacketType::AvatarQuery:
return static_cast<PacketVersion>(AvatarQueryVersion::ConicalFrustums); return static_cast<PacketVersion>(AvatarQueryVersion::ConicalFrustums);
case PacketType::AvatarIdentityRequest:
return 22;
case PacketType::EntityQueryInitialResultsComplete: case PacketType::EntityQueryInitialResultsComplete:
return static_cast<PacketVersion>(EntityVersion::ParticleSpin); return static_cast<PacketVersion>(EntityVersion::ParticleSpin);
case PacketType::BulkAvatarTraitsAck: case PacketType::BulkAvatarTraitsAck:

View file

@ -57,7 +57,7 @@ public:
ICEServerQuery, ICEServerQuery,
OctreeStats, OctreeStats,
SetAvatarTraits, SetAvatarTraits,
AvatarIdentityRequest, UNUSED_PACKET_TYPE,
AssignmentClientStatus, AssignmentClientStatus,
NoisyMute, NoisyMute,
AvatarIdentity, AvatarIdentity,