mirror of
https://github.com/overte-org/overte.git
synced 2025-08-08 03:37:17 +02:00
Merge pull request #13803 from birarda/feat/avatar-traits
introduce avatar traits system, migrate skeleton URL and avatar entities
This commit is contained in:
commit
1036f3c8b6
44 changed files with 1171 additions and 280 deletions
|
@ -25,16 +25,19 @@
|
||||||
#include <AudioInjectorManager.h>
|
#include <AudioInjectorManager.h>
|
||||||
#include <AssetClient.h>
|
#include <AssetClient.h>
|
||||||
#include <DebugDraw.h>
|
#include <DebugDraw.h>
|
||||||
|
#include <EntityScriptingInterface.h>
|
||||||
#include <LocationScriptingInterface.h>
|
#include <LocationScriptingInterface.h>
|
||||||
#include <MessagesClient.h>
|
#include <MessagesClient.h>
|
||||||
#include <NetworkAccessManager.h>
|
#include <NetworkAccessManager.h>
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
#include <ResourceCache.h>
|
#include <ResourceCache.h>
|
||||||
|
#include <ResourceScriptingInterface.h>
|
||||||
#include <ScriptCache.h>
|
#include <ScriptCache.h>
|
||||||
#include <ScriptEngines.h>
|
#include <ScriptEngines.h>
|
||||||
#include <SoundCacheScriptingInterface.h>
|
#include <SoundCacheScriptingInterface.h>
|
||||||
#include <SoundCache.h>
|
#include <SoundCache.h>
|
||||||
|
#include <UserActivityLoggerScriptingInterface.h>
|
||||||
#include <UsersScriptingInterface.h>
|
#include <UsersScriptingInterface.h>
|
||||||
#include <UUID.h>
|
#include <UUID.h>
|
||||||
|
|
||||||
|
@ -54,7 +57,6 @@
|
||||||
#include "AbstractAudioInterface.h"
|
#include "AbstractAudioInterface.h"
|
||||||
#include "AgentScriptingInterface.h"
|
#include "AgentScriptingInterface.h"
|
||||||
|
|
||||||
|
|
||||||
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
|
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
|
||||||
|
|
||||||
Agent::Agent(ReceivedMessage& message) :
|
Agent::Agent(ReceivedMessage& message) :
|
||||||
|
@ -63,6 +65,15 @@ Agent::Agent(ReceivedMessage& message) :
|
||||||
_audioGate(AudioConstants::SAMPLE_RATE, AudioConstants::MONO),
|
_audioGate(AudioConstants::SAMPLE_RATE, AudioConstants::MONO),
|
||||||
_avatarAudioTimer(this)
|
_avatarAudioTimer(this)
|
||||||
{
|
{
|
||||||
|
DependencyManager::set<ScriptableAvatar>();
|
||||||
|
|
||||||
|
DependencyManager::set<AnimationCache>();
|
||||||
|
DependencyManager::set<AnimationCacheScriptingInterface>();
|
||||||
|
DependencyManager::set<EntityScriptingInterface>(false);
|
||||||
|
|
||||||
|
DependencyManager::set<ResourceScriptingInterface>();
|
||||||
|
DependencyManager::set<UserActivityLoggerScriptingInterface>();
|
||||||
|
|
||||||
_entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT);
|
_entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT);
|
||||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||||
|
|
||||||
|
@ -99,7 +110,6 @@ Agent::Agent(ReceivedMessage& message) :
|
||||||
this, "handleOctreePacket");
|
this, "handleOctreePacket");
|
||||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||||
|
|
||||||
|
|
||||||
// 100Hz timer for audio
|
// 100Hz timer for audio
|
||||||
const int TARGET_INTERVAL_MSEC = 10; // 10ms
|
const int TARGET_INTERVAL_MSEC = 10; // 10ms
|
||||||
connect(&_avatarAudioTimer, &QTimer::timeout, this, &Agent::processAgentAvatarAudio);
|
connect(&_avatarAudioTimer, &QTimer::timeout, this, &Agent::processAgentAvatarAudio);
|
||||||
|
@ -351,6 +361,8 @@ void Agent::executeScript() {
|
||||||
// setup an Avatar for the script to use
|
// setup an Avatar for the script to use
|
||||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||||
|
|
||||||
|
scriptedAvatar->setID(getSessionUUID());
|
||||||
|
|
||||||
connect(_scriptEngine.data(), SIGNAL(update(float)),
|
connect(_scriptEngine.data(), SIGNAL(update(float)),
|
||||||
scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection);
|
scriptedAvatar.data(), SLOT(update(float)), Qt::ConnectionType::QueuedConnection);
|
||||||
scriptedAvatar->setForceFaceTrackerConnected(true);
|
scriptedAvatar->setForceFaceTrackerConnected(true);
|
||||||
|
@ -447,11 +459,6 @@ void Agent::executeScript() {
|
||||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||||
_scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data());
|
_scriptEngine->registerGlobalObject("AvatarList", avatarHashMap.data());
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
|
||||||
packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar");
|
|
||||||
packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket");
|
|
||||||
|
|
||||||
// register ourselves to the script engine
|
// register ourselves to the script engine
|
||||||
_scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this));
|
_scriptEngine->registerGlobalObject("Agent", new AgentScriptingInterface(this));
|
||||||
|
|
||||||
|
@ -597,6 +604,11 @@ void Agent::setIsAvatar(bool isAvatar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
||||||
|
|
||||||
|
_entityEditSender.setMyAvatar(nullptr);
|
||||||
|
} else {
|
||||||
|
auto scriptableAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||||
|
_entityEditSender.setMyAvatar(scriptableAvatar.data());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -847,6 +859,9 @@ void Agent::aboutToFinish() {
|
||||||
DependencyManager::destroy<recording::Recorder>();
|
DependencyManager::destroy<recording::Recorder>();
|
||||||
DependencyManager::destroy<recording::ClipCache>();
|
DependencyManager::destroy<recording::ClipCache>();
|
||||||
DependencyManager::destroy<ScriptEngine>();
|
DependencyManager::destroy<ScriptEngine>();
|
||||||
|
|
||||||
|
DependencyManager::destroy<ScriptableAvatar>();
|
||||||
|
|
||||||
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
QMetaObject::invokeMethod(&_avatarAudioTimer, "stop");
|
||||||
|
|
||||||
// cleanup codec & encoder
|
// cleanup codec & encoder
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#include <QtCore/QTimer>
|
#include <QtCore/QTimer>
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
|
#include <ClientTraitsHandler.h>
|
||||||
#include <EntityEditPacketSender.h>
|
#include <EntityEditPacketSender.h>
|
||||||
#include <EntityTree.h>
|
#include <EntityTree.h>
|
||||||
#include <ScriptEngine.h>
|
#include <ScriptEngine.h>
|
||||||
|
|
|
@ -21,10 +21,7 @@
|
||||||
#include <shared/QtHelpers.h>
|
#include <shared/QtHelpers.h>
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
#include <AddressManager.h>
|
#include <AddressManager.h>
|
||||||
#include <AnimationCacheScriptingInterface.h>
|
|
||||||
#include <Assignment.h>
|
#include <Assignment.h>
|
||||||
#include <AvatarHashMap.h>
|
|
||||||
#include <EntityScriptingInterface.h>
|
|
||||||
#include <LogHandler.h>
|
#include <LogHandler.h>
|
||||||
#include <LogUtils.h>
|
#include <LogUtils.h>
|
||||||
#include <LimitedNodeList.h>
|
#include <LimitedNodeList.h>
|
||||||
|
@ -32,16 +29,12 @@
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include <ShutdownEventListener.h>
|
#include <ShutdownEventListener.h>
|
||||||
#include <SoundCache.h>
|
|
||||||
#include <ResourceScriptingInterface.h>
|
|
||||||
#include <UserActivityLoggerScriptingInterface.h>
|
|
||||||
#include <Trace.h>
|
#include <Trace.h>
|
||||||
#include <StatTracker.h>
|
#include <StatTracker.h>
|
||||||
|
|
||||||
#include "AssignmentClientLogging.h"
|
#include "AssignmentClientLogging.h"
|
||||||
#include "AssignmentDynamicFactory.h"
|
|
||||||
#include "AssignmentFactory.h"
|
#include "AssignmentFactory.h"
|
||||||
#include "avatars/ScriptableAvatar.h"
|
|
||||||
|
|
||||||
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
|
const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client";
|
||||||
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
|
const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000;
|
||||||
|
@ -57,21 +50,11 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
||||||
DependencyManager::set<StatTracker>();
|
DependencyManager::set<StatTracker>();
|
||||||
DependencyManager::set<AccountManager>();
|
DependencyManager::set<AccountManager>();
|
||||||
|
|
||||||
auto scriptableAvatar = DependencyManager::set<ScriptableAvatar>();
|
|
||||||
auto addressManager = DependencyManager::set<AddressManager>();
|
auto addressManager = DependencyManager::set<AddressManager>();
|
||||||
|
|
||||||
// create a NodeList as an unassigned client, must be after addressManager
|
// create a NodeList as an unassigned client, must be after addressManager
|
||||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned, listenPort);
|
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned, listenPort);
|
||||||
|
|
||||||
auto animationCache = DependencyManager::set<AnimationCache>();
|
|
||||||
DependencyManager::set<AnimationCacheScriptingInterface>();
|
|
||||||
auto entityScriptingInterface = DependencyManager::set<EntityScriptingInterface>(false);
|
|
||||||
|
|
||||||
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
|
||||||
auto dynamicFactory = DependencyManager::set<AssignmentDynamicFactory>();
|
|
||||||
DependencyManager::set<ResourceScriptingInterface>();
|
|
||||||
DependencyManager::set<UserActivityLoggerScriptingInterface>();
|
|
||||||
|
|
||||||
nodeList->startThread();
|
nodeList->startThread();
|
||||||
// set the logging target to the the CHILD_TARGET_NAME
|
// set the logging target to the the CHILD_TARGET_NAME
|
||||||
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
|
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
|
||||||
|
|
|
@ -366,7 +366,7 @@ AudioMixerClientData* AudioMixer::getOrCreateClientData(Node* node) {
|
||||||
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||||
|
|
||||||
if (!clientData) {
|
if (!clientData) {
|
||||||
node->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(node->getUUID()) });
|
node->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(node->getUUID(), node->getLocalID()) });
|
||||||
clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||||
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,8 +25,8 @@
|
||||||
#include "AudioHelpers.h"
|
#include "AudioHelpers.h"
|
||||||
#include "AudioMixer.h"
|
#include "AudioMixer.h"
|
||||||
|
|
||||||
AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID) :
|
AudioMixerClientData::AudioMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) :
|
||||||
NodeData(nodeID),
|
NodeData(nodeID, nodeLocalID),
|
||||||
audioLimiter(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO),
|
audioLimiter(AudioConstants::SAMPLE_RATE, AudioConstants::STEREO),
|
||||||
_ignoreZone(*this),
|
_ignoreZone(*this),
|
||||||
_outgoingMixedAudioSequenceNumber(0),
|
_outgoingMixedAudioSequenceNumber(0),
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
class AudioMixerClientData : public NodeData {
|
class AudioMixerClientData : public NodeData {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AudioMixerClientData(const QUuid& nodeID);
|
AudioMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID);
|
||||||
~AudioMixerClientData();
|
~AudioMixerClientData();
|
||||||
|
|
||||||
using SharedStreamPointer = std::shared_ptr<PositionalAudioStream>;
|
using SharedStreamPointer = std::shared_ptr<PositionalAudioStream>;
|
||||||
|
|
|
@ -39,7 +39,8 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
||||||
const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
|
const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
|
||||||
|
|
||||||
AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
||||||
ThreadedAssignment(message)
|
ThreadedAssignment(message),
|
||||||
|
_slavePool(&_slaveSharedData)
|
||||||
{
|
{
|
||||||
// make sure we hear about node kills so we can tell the other nodes
|
// make sure we hear about node kills so we can tell the other nodes
|
||||||
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
|
connect(DependencyManager::get<NodeList>().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled);
|
||||||
|
@ -54,6 +55,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
||||||
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::AvatarIdentityRequest, this, "handleAvatarIdentityRequestPacket");
|
||||||
|
packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket");
|
||||||
|
|
||||||
packetReceiver.registerListenerForTypes({
|
packetReceiver.registerListenerForTypes({
|
||||||
PacketType::ReplicatedAvatarIdentity,
|
PacketType::ReplicatedAvatarIdentity,
|
||||||
|
@ -337,17 +339,7 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
||||||
sendIdentity = true;
|
sendIdentity = true;
|
||||||
qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
|
qCDebug(avatars) << "Giving session display name" << sessionDisplayName << "to node with ID" << node->getUUID();
|
||||||
}
|
}
|
||||||
if (nodeData && nodeData->getAvatarSkeletonModelUrlMustChange()) { // never true for an empty _avatarWhitelist
|
|
||||||
nodeData->setAvatarSkeletonModelUrlMustChange(false);
|
|
||||||
AvatarData& avatar = nodeData->getAvatar();
|
|
||||||
static const QUrl emptyURL("");
|
|
||||||
QUrl url = avatar.cannonicalSkeletonModelURL(emptyURL);
|
|
||||||
if (!isAvatarInWhitelist(url)) {
|
|
||||||
qCDebug(avatars) << "Forbidden avatar" << nodeData->getNodeID() << avatar.getSkeletonModelURL() << "replaced with" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar);
|
|
||||||
avatar.setSkeletonModelURL(_replacementAvatar);
|
|
||||||
sendIdentity = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (sendIdentity && !node->isUpstream()) {
|
if (sendIdentity && !node->isUpstream()) {
|
||||||
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar.
|
sendIdentityPacket(nodeData, node); // Tell node whose name changed about its new session display name or avatar.
|
||||||
// since this packet includes a change to either the skeleton model URL or the display name
|
// since this packet includes a change to either the skeleton model URL or the display name
|
||||||
|
@ -359,22 +351,6 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AvatarMixer::isAvatarInWhitelist(const QUrl& url) {
|
|
||||||
// The avatar is in the whitelist if:
|
|
||||||
// 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND
|
|
||||||
// 2. The avatar's URL's path starts with the path of that same URL in the whitelist
|
|
||||||
for (const auto& whiteListedPrefix : _avatarWhitelist) {
|
|
||||||
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
|
|
||||||
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
|
|
||||||
if (url.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
|
|
||||||
url.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) {
|
void AvatarMixer::throttle(std::chrono::microseconds duration, int frame) {
|
||||||
// throttle using a modified proportional-integral controller
|
// throttle using a modified proportional-integral controller
|
||||||
const float FRAME_TIME = USECS_PER_SECOND / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
|
const float FRAME_TIME = USECS_PER_SECOND / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
|
||||||
|
@ -494,7 +470,8 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) {
|
||||||
QMetaObject::invokeMethod(node->getLinkedData(),
|
QMetaObject::invokeMethod(node->getLinkedData(),
|
||||||
"cleanupKilledNode",
|
"cleanupKilledNode",
|
||||||
Qt::AutoConnection,
|
Qt::AutoConnection,
|
||||||
Q_ARG(const QUuid&, QUuid(avatarNode->getUUID())));
|
Q_ARG(const QUuid&, QUuid(avatarNode->getUUID())),
|
||||||
|
Q_ARG(Node::LocalID, avatarNode->getLocalID()));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -587,8 +564,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
||||||
// parse the identity packet and update the change timestamp if appropriate
|
// parse the identity packet and update the change timestamp if appropriate
|
||||||
bool identityChanged = false;
|
bool identityChanged = false;
|
||||||
bool displayNameChanged = false;
|
bool displayNameChanged = false;
|
||||||
bool skeletonModelUrlChanged = false;
|
avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged);
|
||||||
avatar.processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged, skeletonModelUrlChanged);
|
|
||||||
|
|
||||||
if (identityChanged) {
|
if (identityChanged) {
|
||||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||||
|
@ -596,9 +572,6 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
||||||
if (displayNameChanged) {
|
if (displayNameChanged) {
|
||||||
nodeData->setAvatarSessionDisplayNameMustChange(true);
|
nodeData->setAvatarSessionDisplayNameMustChange(true);
|
||||||
}
|
}
|
||||||
if (skeletonModelUrlChanged && !_avatarWhitelist.isEmpty()) {
|
|
||||||
nodeData->setAvatarSkeletonModelUrlMustChange(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -903,7 +876,7 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node
|
||||||
auto clientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
auto clientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||||
|
|
||||||
if (!clientData) {
|
if (!clientData) {
|
||||||
node->setLinkedData(std::unique_ptr<NodeData> { new AvatarMixerClientData(node->getUUID()) });
|
node->setLinkedData(std::unique_ptr<NodeData> { new AvatarMixerClientData(node->getUUID(), node->getLocalID()) });
|
||||||
clientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
clientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||||
auto& avatar = clientData->getAvatar();
|
auto& avatar = clientData->getAvatar();
|
||||||
avatar.setDomainMinimumHeight(_domainMinimumHeight);
|
avatar.setDomainMinimumHeight(_domainMinimumHeight);
|
||||||
|
@ -991,20 +964,22 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
||||||
qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight
|
qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight
|
||||||
<< "and a maximum avatar height of" << _domainMaximumHeight;
|
<< "and a maximum avatar height of" << _domainMaximumHeight;
|
||||||
|
|
||||||
const QString AVATAR_WHITELIST_DEFAULT{ "" };
|
|
||||||
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
|
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
|
||||||
_avatarWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION].toString(AVATAR_WHITELIST_DEFAULT).split(',', QString::KeepEmptyParts);
|
_slaveSharedData.skeletonURLWhitelist = domainSettings[AVATARS_SETTINGS_KEY].toObject()[AVATAR_WHITELIST_OPTION]
|
||||||
|
.toString().split(',', QString::KeepEmptyParts);
|
||||||
|
|
||||||
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar";
|
static const QString REPLACEMENT_AVATAR_OPTION = "replacement_avatar";
|
||||||
_replacementAvatar = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION].toString(REPLACEMENT_AVATAR_DEFAULT);
|
_slaveSharedData.skeletonReplacementURL = domainSettings[AVATARS_SETTINGS_KEY].toObject()[REPLACEMENT_AVATAR_OPTION]
|
||||||
|
.toString();
|
||||||
|
|
||||||
if ((_avatarWhitelist.count() == 1) && _avatarWhitelist[0].isEmpty()) {
|
if (_slaveSharedData.skeletonURLWhitelist.count() == 1 && _slaveSharedData.skeletonURLWhitelist[0].isEmpty()) {
|
||||||
_avatarWhitelist.clear(); // KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok).
|
// KeepEmptyParts above will parse "," as ["", ""] (which is ok), but "" as [""] (which is not ok).
|
||||||
|
_slaveSharedData.skeletonURLWhitelist.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_avatarWhitelist.isEmpty()) {
|
if (_slaveSharedData.skeletonURLWhitelist.isEmpty()) {
|
||||||
qCDebug(avatars) << "All avatars are allowed.";
|
qCDebug(avatars) << "All avatars are allowed.";
|
||||||
} else {
|
} else {
|
||||||
qCDebug(avatars) << "Avatars other than" << _avatarWhitelist << "will be replaced by" << (_replacementAvatar.isEmpty() ? "default" : _replacementAvatar);
|
qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -59,7 +59,6 @@ private slots:
|
||||||
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID);
|
||||||
void start();
|
void start();
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node);
|
AvatarMixerClientData* getOrCreateClientData(SharedNodePointer node);
|
||||||
std::chrono::microseconds timeFrame(p_high_resolution_clock::time_point& timestamp);
|
std::chrono::microseconds timeFrame(p_high_resolution_clock::time_point& timestamp);
|
||||||
|
@ -69,11 +68,6 @@ private:
|
||||||
void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
void sendIdentityPacket(AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
||||||
|
|
||||||
void manageIdentityData(const SharedNodePointer& node);
|
void manageIdentityData(const SharedNodePointer& node);
|
||||||
bool isAvatarInWhitelist(const QUrl& url);
|
|
||||||
|
|
||||||
const QString REPLACEMENT_AVATAR_DEFAULT{ "" };
|
|
||||||
QStringList _avatarWhitelist { };
|
|
||||||
QString _replacementAvatar { REPLACEMENT_AVATAR_DEFAULT };
|
|
||||||
|
|
||||||
void optionallyReplicatePacket(ReceivedMessage& message, const Node& node);
|
void optionallyReplicatePacket(ReceivedMessage& message, const Node& node);
|
||||||
|
|
||||||
|
@ -83,7 +77,6 @@ private:
|
||||||
float _trailingMixRatio { 0.0f };
|
float _trailingMixRatio { 0.0f };
|
||||||
float _throttlingRatio { 0.0f };
|
float _throttlingRatio { 0.0f };
|
||||||
|
|
||||||
|
|
||||||
int _sumListeners { 0 };
|
int _sumListeners { 0 };
|
||||||
int _numStatFrames { 0 };
|
int _numStatFrames { 0 };
|
||||||
int _numTightLoopFrames { 0 };
|
int _numTightLoopFrames { 0 };
|
||||||
|
@ -126,9 +119,8 @@ private:
|
||||||
|
|
||||||
RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs
|
RateCounter<> _loopRate; // this is the rate that the main thread tight loop runs
|
||||||
|
|
||||||
|
|
||||||
AvatarMixerSlavePool _slavePool;
|
AvatarMixerSlavePool _slavePool;
|
||||||
|
SlaveSharedData _slaveSharedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AvatarMixer_h
|
#endif // hifi_AvatarMixer_h
|
||||||
|
|
|
@ -16,8 +16,10 @@
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
|
|
||||||
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
|
#include "AvatarMixerSlave.h"
|
||||||
NodeData(nodeID)
|
|
||||||
|
AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID) :
|
||||||
|
NodeData(nodeID, nodeLocalID)
|
||||||
{
|
{
|
||||||
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
|
// in case somebody calls getSessionUUID on the AvatarData instance, make sure it has the right ID
|
||||||
_avatar->setID(nodeID);
|
_avatar->setID(nodeID);
|
||||||
|
@ -47,7 +49,7 @@ void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message,
|
||||||
_packetQueue.push(message);
|
_packetQueue.push(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
int AvatarMixerClientData::processPackets() {
|
int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData) {
|
||||||
int packetsProcessed = 0;
|
int packetsProcessed = 0;
|
||||||
SharedNodePointer node = _packetQueue.node;
|
SharedNodePointer node = _packetQueue.node;
|
||||||
assert(_packetQueue.empty() || node);
|
assert(_packetQueue.empty() || node);
|
||||||
|
@ -62,6 +64,9 @@ int AvatarMixerClientData::processPackets() {
|
||||||
case PacketType::AvatarData:
|
case PacketType::AvatarData:
|
||||||
parseData(*packet);
|
parseData(*packet);
|
||||||
break;
|
break;
|
||||||
|
case PacketType::SetAvatarTraits:
|
||||||
|
processSetTraitsMessage(*packet, slaveSharedData, *node);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
Q_UNREACHABLE();
|
Q_UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
@ -87,6 +92,113 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) {
|
||||||
// compute the offset to the data payload
|
// compute the offset to the data payload
|
||||||
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
|
return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
||||||
|
const SlaveSharedData& slaveSharedData, Node& sendingNode) {
|
||||||
|
// pull the trait version from the message
|
||||||
|
AvatarTraits::TraitVersion packetTraitVersion;
|
||||||
|
message.readPrimitive(&packetTraitVersion);
|
||||||
|
|
||||||
|
bool anyTraitsChanged = false;
|
||||||
|
|
||||||
|
while (message.getBytesLeftToRead() > 0) {
|
||||||
|
// for each trait in the packet, apply it if the trait version is newer than what we have
|
||||||
|
|
||||||
|
AvatarTraits::TraitType traitType;
|
||||||
|
message.readPrimitive(&traitType);
|
||||||
|
|
||||||
|
if (AvatarTraits::isSimpleTrait(traitType)) {
|
||||||
|
AvatarTraits::TraitWireSize traitSize;
|
||||||
|
message.readPrimitive(&traitSize);
|
||||||
|
|
||||||
|
if (packetTraitVersion > _lastReceivedTraitVersions[traitType]) {
|
||||||
|
_avatar->processTrait(traitType, message.read(traitSize));
|
||||||
|
_lastReceivedTraitVersions[traitType] = packetTraitVersion;
|
||||||
|
|
||||||
|
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||||
|
// special handling for skeleton model URL, since we need to make sure it is in the whitelist
|
||||||
|
checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
anyTraitsChanged = true;
|
||||||
|
} else {
|
||||||
|
message.seek(message.getPosition() + traitSize);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AvatarTraits::TraitInstanceID instanceID = QUuid::fromRfc4122(message.readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
||||||
|
AvatarTraits::TraitWireSize traitSize;
|
||||||
|
message.readPrimitive(&traitSize);
|
||||||
|
|
||||||
|
auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID);
|
||||||
|
|
||||||
|
if (packetTraitVersion > instanceVersionRef) {
|
||||||
|
if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) {
|
||||||
|
_avatar->processDeletedTraitInstance(traitType, instanceID);
|
||||||
|
|
||||||
|
// to track a deleted instance but keep version information
|
||||||
|
// the avatar mixer uses the negative value of the sent version
|
||||||
|
instanceVersionRef = -packetTraitVersion;
|
||||||
|
} else {
|
||||||
|
_avatar->processTraitInstance(traitType, instanceID, message.read(traitSize));
|
||||||
|
instanceVersionRef = packetTraitVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
anyTraitsChanged = true;
|
||||||
|
} else {
|
||||||
|
message.seek(message.getPosition() + traitSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyTraitsChanged) {
|
||||||
|
_lastReceivedTraitsChange = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarMixerClientData::checkSkeletonURLAgainstWhitelist(const SlaveSharedData &slaveSharedData, Node& sendingNode,
|
||||||
|
AvatarTraits::TraitVersion traitVersion) {
|
||||||
|
const auto& whitelist = slaveSharedData.skeletonURLWhitelist;
|
||||||
|
|
||||||
|
if (!whitelist.isEmpty()) {
|
||||||
|
bool inWhitelist = false;
|
||||||
|
auto avatarURL = _avatar->getSkeletonModelURL();
|
||||||
|
|
||||||
|
// The avatar is in the whitelist if:
|
||||||
|
// 1. The avatar's URL's host matches one of the hosts of the URLs in the whitelist AND
|
||||||
|
// 2. The avatar's URL's path starts with the path of that same URL in the whitelist
|
||||||
|
for (const auto& whiteListedPrefix : whitelist) {
|
||||||
|
auto whiteListURL = QUrl::fromUserInput(whiteListedPrefix);
|
||||||
|
// check if this script URL matches the whitelist domain and, optionally, is beneath the path
|
||||||
|
if (avatarURL.host().compare(whiteListURL.host(), Qt::CaseInsensitive) == 0 &&
|
||||||
|
avatarURL.path().startsWith(whiteListURL.path(), Qt::CaseInsensitive)) {
|
||||||
|
inWhitelist = true;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inWhitelist) {
|
||||||
|
// make sure we're not unecessarily overriding the default avatar with the default avatar
|
||||||
|
if (_avatar->getWireSafeSkeletonModelURL() != slaveSharedData.skeletonReplacementURL) {
|
||||||
|
// we need to change this avatar's skeleton URL, and send them a traits packet informing them of the change
|
||||||
|
qDebug() << "Overwriting avatar URL" << _avatar->getWireSafeSkeletonModelURL()
|
||||||
|
<< "to replacement" << slaveSharedData.skeletonReplacementURL << "for" << sendingNode.getUUID();
|
||||||
|
_avatar->setSkeletonModelURL(slaveSharedData.skeletonReplacementURL);
|
||||||
|
|
||||||
|
auto packet = NLPacket::create(PacketType::SetAvatarTraits, -1, true);
|
||||||
|
|
||||||
|
// the returned set traits packet uses the trait version from the incoming packet
|
||||||
|
// so the client knows they should not overwrite if they have since changed the trait
|
||||||
|
_avatar->packTrait(AvatarTraits::SkeletonModelURL, *packet, traitVersion);
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
nodeList->sendPacket(std::move(packet), sendingNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const {
|
uint64_t AvatarMixerClientData::getLastBroadcastTime(const QUuid& nodeUUID) const {
|
||||||
// return the matching PacketSequenceNumber, or the default if we don't have it
|
// return the matching PacketSequenceNumber, or the default if we don't have it
|
||||||
auto nodeMatch = _lastBroadcastTimes.find(nodeUUID);
|
auto nodeMatch = _lastBroadcastTimes.find(nodeUUID);
|
||||||
|
@ -164,3 +276,20 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
|
||||||
jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView;
|
jsonObject["recent_other_av_in_view"] = _recentOtherAvatarsInView;
|
||||||
jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView;
|
jsonObject["recent_other_av_out_of_view"] = _recentOtherAvatarsOutOfView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AvatarMixerClientData::TraitsCheckTimestamp AvatarMixerClientData::getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const {
|
||||||
|
auto it = _lastSentTraitsTimestamps.find(otherAvatar);
|
||||||
|
|
||||||
|
if (it != _lastSentTraitsTimestamps.end()) {
|
||||||
|
return it->second;
|
||||||
|
} else {
|
||||||
|
return TraitsCheckTimestamp();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarMixerClientData::cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID) {
|
||||||
|
removeLastBroadcastSequenceNumber(nodeUUID);
|
||||||
|
removeLastBroadcastTime(nodeUUID);
|
||||||
|
_lastSentTraitsTimestamps.erase(nodeLocalID);
|
||||||
|
_sentTraitVersions.erase(nodeLocalID);
|
||||||
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@
|
||||||
#include <QtCore/QUrl>
|
#include <QtCore/QUrl>
|
||||||
|
|
||||||
#include <AvatarData.h>
|
#include <AvatarData.h>
|
||||||
|
#include <AssociatedTraitValues.h>
|
||||||
#include <NodeData.h>
|
#include <NodeData.h>
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
#include <udt/PacketHeaders.h>
|
#include <udt/PacketHeaders.h>
|
||||||
|
@ -33,10 +34,12 @@
|
||||||
const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps";
|
const QString OUTBOUND_AVATAR_DATA_STATS_KEY = "outbound_av_data_kbps";
|
||||||
const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
|
const QString INBOUND_AVATAR_DATA_STATS_KEY = "inbound_av_data_kbps";
|
||||||
|
|
||||||
|
struct SlaveSharedData;
|
||||||
|
|
||||||
class AvatarMixerClientData : public NodeData {
|
class AvatarMixerClientData : public NodeData {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AvatarMixerClientData(const QUuid& nodeID = QUuid());
|
AvatarMixerClientData(const QUuid& nodeID, Node::LocalID nodeLocalID);
|
||||||
virtual ~AvatarMixerClientData() {}
|
virtual ~AvatarMixerClientData() {}
|
||||||
using HRCTime = p_high_resolution_clock::time_point;
|
using HRCTime = p_high_resolution_clock::time_point;
|
||||||
|
|
||||||
|
@ -54,10 +57,7 @@ public:
|
||||||
void setLastBroadcastTime(const QUuid& nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; }
|
void setLastBroadcastTime(const QUuid& nodeUUID, uint64_t broadcastTime) { _lastBroadcastTimes[nodeUUID] = broadcastTime; }
|
||||||
Q_INVOKABLE void removeLastBroadcastTime(const QUuid& nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); }
|
Q_INVOKABLE void removeLastBroadcastTime(const QUuid& nodeUUID) { _lastBroadcastTimes.erase(nodeUUID); }
|
||||||
|
|
||||||
Q_INVOKABLE void cleanupKilledNode(const QUuid& nodeUUID) {
|
Q_INVOKABLE void cleanupKilledNode(const QUuid& nodeUUID, Node::LocalID nodeLocalID);
|
||||||
removeLastBroadcastSequenceNumber(nodeUUID);
|
|
||||||
removeLastBroadcastTime(nodeUUID);
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; }
|
uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; }
|
||||||
|
|
||||||
|
@ -65,8 +65,6 @@ public:
|
||||||
void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); }
|
void flagIdentityChange() { _identityChangeTimestamp = usecTimestampNow(); }
|
||||||
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
|
bool getAvatarSessionDisplayNameMustChange() const { return _avatarSessionDisplayNameMustChange; }
|
||||||
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
|
void setAvatarSessionDisplayNameMustChange(bool set = true) { _avatarSessionDisplayNameMustChange = set; }
|
||||||
bool getAvatarSkeletonModelUrlMustChange() const { return _avatarSkeletonModelUrlMustChange; }
|
|
||||||
void setAvatarSkeletonModelUrlMustChange(bool set = true) { _avatarSkeletonModelUrlMustChange = set; }
|
|
||||||
|
|
||||||
void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; }
|
void resetNumAvatarsSentLastFrame() { _numAvatarsSentLastFrame = 0; }
|
||||||
void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; }
|
void incrementNumAvatarsSentLastFrame() { ++_numAvatarsSentLastFrame; }
|
||||||
|
@ -118,7 +116,24 @@ public:
|
||||||
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; }
|
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) { return _lastOtherAvatarSentJoints[otherAvatar]; }
|
||||||
|
|
||||||
void queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
|
void queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
|
||||||
int processPackets(); // returns number of packets processed
|
int processPackets(const SlaveSharedData& slaveSharedData); // returns number of packets processed
|
||||||
|
|
||||||
|
void processSetTraitsMessage(ReceivedMessage& message, const SlaveSharedData& slaveSharedData, Node& sendingNode);
|
||||||
|
void checkSkeletonURLAgainstWhitelist(const SlaveSharedData& slaveSharedData, Node& sendingNode,
|
||||||
|
AvatarTraits::TraitVersion traitVersion);
|
||||||
|
|
||||||
|
using TraitsCheckTimestamp = std::chrono::steady_clock::time_point;
|
||||||
|
|
||||||
|
TraitsCheckTimestamp getLastReceivedTraitsChange() const { return _lastReceivedTraitsChange; }
|
||||||
|
|
||||||
|
AvatarTraits::TraitVersions& getLastReceivedTraitVersions() { return _lastReceivedTraitVersions; }
|
||||||
|
const AvatarTraits::TraitVersions& getLastReceivedTraitVersions() const { return _lastReceivedTraitVersions; }
|
||||||
|
|
||||||
|
TraitsCheckTimestamp getLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar) const;
|
||||||
|
void setLastOtherAvatarTraitsSendPoint(Node::LocalID otherAvatar, TraitsCheckTimestamp sendPoint)
|
||||||
|
{ _lastSentTraitsTimestamps[otherAvatar] = sendPoint; }
|
||||||
|
|
||||||
|
AvatarTraits::TraitVersions& getLastSentTraitVersions(Node::LocalID otherAvatar) { return _sentTraitVersions[otherAvatar]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
|
struct PacketQueue : public std::queue<QSharedPointer<ReceivedMessage>> {
|
||||||
|
@ -156,6 +171,12 @@ private:
|
||||||
int _recentOtherAvatarsOutOfView { 0 };
|
int _recentOtherAvatarsOutOfView { 0 };
|
||||||
QString _baseDisplayName{}; // The santized key used in determinging unique sessionDisplayName, so that we can remove from dictionary.
|
QString _baseDisplayName{}; // The santized key used in determinging unique sessionDisplayName, so that we can remove from dictionary.
|
||||||
bool _requestsDomainListData { false };
|
bool _requestsDomainListData { false };
|
||||||
|
|
||||||
|
AvatarTraits::TraitVersions _lastReceivedTraitVersions;
|
||||||
|
TraitsCheckTimestamp _lastReceivedTraitsChange;
|
||||||
|
|
||||||
|
std::unordered_map<Node::LocalID, TraitsCheckTimestamp> _lastSentTraitsTimestamps;
|
||||||
|
std::unordered_map<Node::LocalID, AvatarTraits::TraitVersions> _sentTraitVersions;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AvatarMixerClientData_h
|
#endif // hifi_AvatarMixerClientData_h
|
||||||
|
|
|
@ -59,7 +59,7 @@ void AvatarMixerSlave::processIncomingPackets(const SharedNodePointer& node) {
|
||||||
auto nodeData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
auto nodeData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||||
if (nodeData) {
|
if (nodeData) {
|
||||||
_stats.nodesProcessed++;
|
_stats.nodesProcessed++;
|
||||||
_stats.packetsProcessed += nodeData->processPackets();
|
_stats.packetsProcessed += nodeData->processPackets(*_sharedData);
|
||||||
}
|
}
|
||||||
auto end = usecTimestampNow();
|
auto end = usecTimestampNow();
|
||||||
_stats.processIncomingPacketsElapsedTime += (end - start);
|
_stats.processIncomingPacketsElapsedTime += (end - start);
|
||||||
|
@ -79,6 +79,107 @@ int AvatarMixerSlave::sendIdentityPacket(const AvatarMixerClientData* nodeData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData,
|
||||||
|
const AvatarMixerClientData* sendingNodeData,
|
||||||
|
NLPacketList& traitsPacketList) {
|
||||||
|
|
||||||
|
auto otherNodeLocalID = sendingNodeData->getNodeLocalID();
|
||||||
|
|
||||||
|
// Perform a simple check with two server clock time points
|
||||||
|
// to see if there is any new traits data for this avatar that we need to send
|
||||||
|
auto timeOfLastTraitsSent = listeningNodeData->getLastOtherAvatarTraitsSendPoint(otherNodeLocalID);
|
||||||
|
auto timeOfLastTraitsChange = sendingNodeData->getLastReceivedTraitsChange();
|
||||||
|
|
||||||
|
qint64 bytesWritten = 0;
|
||||||
|
|
||||||
|
if (timeOfLastTraitsChange > timeOfLastTraitsSent) {
|
||||||
|
// there is definitely new traits data to send
|
||||||
|
|
||||||
|
// add the avatar ID to mark the beginning of traits for this avatar
|
||||||
|
bytesWritten += traitsPacketList.write(sendingNodeData->getNodeID().toRfc4122());
|
||||||
|
|
||||||
|
auto sendingAvatar = sendingNodeData->getAvatarSharedPointer();
|
||||||
|
|
||||||
|
// compare trait versions so we can see what exactly needs to go out
|
||||||
|
auto& lastSentVersions = listeningNodeData->getLastSentTraitVersions(otherNodeLocalID);
|
||||||
|
const auto& lastReceivedVersions = sendingNodeData->getLastReceivedTraitVersions();
|
||||||
|
|
||||||
|
auto simpleReceivedIt = lastReceivedVersions.simpleCBegin();
|
||||||
|
while (simpleReceivedIt != lastReceivedVersions.simpleCEnd()) {
|
||||||
|
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(lastReceivedVersions.simpleCBegin(),
|
||||||
|
simpleReceivedIt));
|
||||||
|
|
||||||
|
auto lastReceivedVersion = *simpleReceivedIt;
|
||||||
|
auto& lastSentVersionRef = lastSentVersions[traitType];
|
||||||
|
|
||||||
|
if (lastReceivedVersions[traitType] > lastSentVersionRef) {
|
||||||
|
// there is an update to this trait, add it to the traits packet
|
||||||
|
bytesWritten += sendingAvatar->packTrait(traitType, traitsPacketList, lastReceivedVersion);
|
||||||
|
|
||||||
|
// update the last sent version
|
||||||
|
lastSentVersionRef = lastReceivedVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
++simpleReceivedIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enumerate the received instanced trait versions
|
||||||
|
auto instancedReceivedIt = lastReceivedVersions.instancedCBegin();
|
||||||
|
while (instancedReceivedIt != lastReceivedVersions.instancedCEnd()) {
|
||||||
|
auto traitType = instancedReceivedIt->traitType;
|
||||||
|
|
||||||
|
// get or create the sent trait versions for this trait type
|
||||||
|
auto& sentIDValuePairs = lastSentVersions.getInstanceIDValuePairs(traitType);
|
||||||
|
|
||||||
|
// enumerate each received instance
|
||||||
|
for (auto& receivedInstance : instancedReceivedIt->instances) {
|
||||||
|
auto instanceID = receivedInstance.id;
|
||||||
|
const auto receivedVersion = receivedInstance.value;
|
||||||
|
|
||||||
|
// to track deletes and maintain version information for traits
|
||||||
|
// the mixer stores the negative value of the received version when a trait instance is deleted
|
||||||
|
bool isDeleted = receivedVersion < 0;
|
||||||
|
const auto absoluteReceivedVersion = std::abs(receivedVersion);
|
||||||
|
|
||||||
|
// look for existing sent version for this instance
|
||||||
|
auto sentInstanceIt = std::find_if(sentIDValuePairs.begin(), sentIDValuePairs.end(),
|
||||||
|
[instanceID](auto& sentInstance)
|
||||||
|
{
|
||||||
|
return sentInstance.id == instanceID;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!isDeleted && (sentInstanceIt == sentIDValuePairs.end() || receivedVersion > sentInstanceIt->value)) {
|
||||||
|
// this instance version exists and has never been sent or is newer so we need to send it
|
||||||
|
bytesWritten += sendingAvatar->packTraitInstance(traitType, instanceID, traitsPacketList, receivedVersion);
|
||||||
|
|
||||||
|
if (sentInstanceIt != sentIDValuePairs.end()) {
|
||||||
|
sentInstanceIt->value = receivedVersion;
|
||||||
|
} else {
|
||||||
|
sentIDValuePairs.emplace_back(instanceID, receivedVersion);
|
||||||
|
}
|
||||||
|
} else if (isDeleted && sentInstanceIt != sentIDValuePairs.end() && absoluteReceivedVersion > sentInstanceIt->value) {
|
||||||
|
// this instance version was deleted and we haven't sent the delete to this client yet
|
||||||
|
bytesWritten += AvatarTraits::packInstancedTraitDelete(traitType, instanceID, traitsPacketList, absoluteReceivedVersion);
|
||||||
|
|
||||||
|
// update the last sent version for this trait instance to the absolute value of the deleted version
|
||||||
|
sentInstanceIt->value = absoluteReceivedVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++instancedReceivedIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write a null trait type to mark the end of trait data for this avatar
|
||||||
|
bytesWritten += traitsPacketList.writePrimitive(AvatarTraits::NullTrait);
|
||||||
|
|
||||||
|
// since we send all traits for this other avatar, update the time of last traits sent
|
||||||
|
// to match the time of last traits change
|
||||||
|
listeningNodeData->setLastOtherAvatarTraitsSendPoint(otherNodeLocalID, timeOfLastTraitsChange);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode) {
|
int AvatarMixerSlave::sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode) {
|
||||||
if (AvatarMixer::shouldReplicateTo(agentNode, destinationNode)) {
|
if (AvatarMixer::shouldReplicateTo(agentNode, destinationNode)) {
|
||||||
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true);
|
QByteArray individualData = nodeData->getConstAvatarData()->identityByteArray(true);
|
||||||
|
@ -138,6 +239,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
// keep track of outbound data rate specifically for avatar data
|
// keep track of outbound data rate specifically for avatar data
|
||||||
int numAvatarDataBytes = 0;
|
int numAvatarDataBytes = 0;
|
||||||
int identityBytesSent = 0;
|
int identityBytesSent = 0;
|
||||||
|
int traitBytesSent = 0;
|
||||||
|
|
||||||
// max number of avatarBytes per frame
|
// max number of avatarBytes per frame
|
||||||
auto maxAvatarBytesPerFrame = (_maxKbpsPerNode * BYTES_PER_KILOBIT) / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
|
auto maxAvatarBytesPerFrame = (_maxKbpsPerNode * BYTES_PER_KILOBIT) / AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND;
|
||||||
|
@ -326,6 +428,7 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
||||||
|
|
||||||
int remainingAvatars = (int)sortedAvatars.size();
|
int remainingAvatars = (int)sortedAvatars.size();
|
||||||
|
auto traitsPacketList = NLPacketList::create(PacketType::BulkAvatarTraits, QByteArray(), true, true);
|
||||||
while (!sortedAvatars.empty()) {
|
while (!sortedAvatars.empty()) {
|
||||||
const auto avatarData = sortedAvatars.top().getAvatar();
|
const auto avatarData = sortedAvatars.top().getAvatar();
|
||||||
sortedAvatars.pop();
|
sortedAvatars.pop();
|
||||||
|
@ -392,11 +495,12 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
|
|
||||||
quint64 start = usecTimestampNow();
|
quint64 start = usecTimestampNow();
|
||||||
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
QByteArray bytes = otherAvatar->toByteArray(detail, lastEncodeForOther, lastSentJointsForOther,
|
||||||
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition, &lastSentJointsForOther);
|
hasFlagsOut, dropFaceTracking, distanceAdjust, viewerPosition,
|
||||||
|
&lastSentJointsForOther);
|
||||||
quint64 end = usecTimestampNow();
|
quint64 end = usecTimestampNow();
|
||||||
_stats.toByteArrayElapsedTime += (end - start);
|
_stats.toByteArrayElapsedTime += (end - start);
|
||||||
|
|
||||||
auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
|
static auto maxAvatarDataBytes = avatarPacketList->getMaxSegmentSize() - NUM_BYTES_RFC4122_UUID;
|
||||||
if (bytes.size() > maxAvatarDataBytes) {
|
if (bytes.size() > maxAvatarDataBytes) {
|
||||||
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
|
qCWarning(avatars) << "otherAvatar.toByteArray() for" << otherNode->getUUID()
|
||||||
<< "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data";
|
<< "resulted in very large buffer of" << bytes.size() << "bytes - dropping facial data";
|
||||||
|
@ -445,6 +549,11 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
|
|
||||||
quint64 endAvatarDataPacking = usecTimestampNow();
|
quint64 endAvatarDataPacking = usecTimestampNow();
|
||||||
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
|
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
|
||||||
|
|
||||||
|
// use helper to add any changed traits to our packet list
|
||||||
|
traitBytesSent += addChangedTraitsToBulkPacket(nodeData, otherNodeData, *traitsPacketList);
|
||||||
|
|
||||||
|
traitsPacketList->getDataSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 startPacketSending = usecTimestampNow();
|
quint64 startPacketSending = usecTimestampNow();
|
||||||
|
@ -461,6 +570,14 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
||||||
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
// record the bytes sent for other avatar data in the AvatarMixerClientData
|
||||||
nodeData->recordSentAvatarData(numAvatarDataBytes);
|
nodeData->recordSentAvatarData(numAvatarDataBytes);
|
||||||
|
|
||||||
|
// close the current traits packet list
|
||||||
|
traitsPacketList->closeCurrentPacket();
|
||||||
|
|
||||||
|
if (traitsPacketList->getNumPackets() >= 1) {
|
||||||
|
// send the traits packet list
|
||||||
|
nodeList->sendPacketList(std::move(traitsPacketList), *node);
|
||||||
|
}
|
||||||
|
|
||||||
// record the number of avatars held back this frame
|
// record the number of avatars held back this frame
|
||||||
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
nodeData->recordNumOtherAvatarStarves(numAvatarsHeldBack);
|
||||||
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
nodeData->recordNumOtherAvatarSkips(numAvatarsWithSkippedFrames);
|
||||||
|
|
|
@ -78,11 +78,16 @@ public:
|
||||||
jobElapsedTime += rhs.jobElapsedTime;
|
jobElapsedTime += rhs.jobElapsedTime;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SlaveSharedData {
|
||||||
|
QStringList skeletonURLWhitelist;
|
||||||
|
QUrl skeletonReplacementURL;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AvatarMixerSlave {
|
class AvatarMixerSlave {
|
||||||
public:
|
public:
|
||||||
|
AvatarMixerSlave(SlaveSharedData* sharedData) : _sharedData(sharedData) {};
|
||||||
using ConstIter = NodeList::const_iterator;
|
using ConstIter = NodeList::const_iterator;
|
||||||
|
|
||||||
void configure(ConstIter begin, ConstIter end);
|
void configure(ConstIter begin, ConstIter end);
|
||||||
|
@ -99,6 +104,10 @@ private:
|
||||||
int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
int sendIdentityPacket(const AvatarMixerClientData* nodeData, const SharedNodePointer& destinationNode);
|
||||||
int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode);
|
int sendReplicatedIdentityPacket(const Node& agentNode, const AvatarMixerClientData* nodeData, const Node& destinationNode);
|
||||||
|
|
||||||
|
qint64 addChangedTraitsToBulkPacket(AvatarMixerClientData* listeningNodeData,
|
||||||
|
const AvatarMixerClientData* sendingNodeData,
|
||||||
|
NLPacketList& traitsPacketList);
|
||||||
|
|
||||||
void broadcastAvatarDataToAgent(const SharedNodePointer& node);
|
void broadcastAvatarDataToAgent(const SharedNodePointer& node);
|
||||||
void broadcastAvatarDataToDownstreamMixer(const SharedNodePointer& node);
|
void broadcastAvatarDataToDownstreamMixer(const SharedNodePointer& node);
|
||||||
|
|
||||||
|
@ -111,6 +120,7 @@ private:
|
||||||
float _throttlingRatio { 0.0f };
|
float _throttlingRatio { 0.0f };
|
||||||
|
|
||||||
AvatarMixerSlaveStats _stats;
|
AvatarMixerSlaveStats _stats;
|
||||||
|
SlaveSharedData* _sharedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AvatarMixerSlave_h
|
#endif // hifi_AvatarMixerSlave_h
|
||||||
|
|
|
@ -168,7 +168,7 @@ void AvatarMixerSlavePool::resize(int numThreads) {
|
||||||
if (numThreads > _numThreads) {
|
if (numThreads > _numThreads) {
|
||||||
// start new slaves
|
// start new slaves
|
||||||
for (int i = 0; i < numThreads - _numThreads; ++i) {
|
for (int i = 0; i < numThreads - _numThreads; ++i) {
|
||||||
auto slave = new AvatarMixerSlaveThread(*this);
|
auto slave = new AvatarMixerSlaveThread(*this, _slaveSharedData);
|
||||||
slave->start();
|
slave->start();
|
||||||
_slaves.emplace_back(slave);
|
_slaves.emplace_back(slave);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,8 @@ class AvatarMixerSlaveThread : public QThread, public AvatarMixerSlave {
|
||||||
using Lock = std::unique_lock<Mutex>;
|
using Lock = std::unique_lock<Mutex>;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AvatarMixerSlaveThread(AvatarMixerSlavePool& pool) : _pool(pool) {}
|
AvatarMixerSlaveThread(AvatarMixerSlavePool& pool, SlaveSharedData* slaveSharedData) :
|
||||||
|
AvatarMixerSlave(slaveSharedData), _pool(pool) {};
|
||||||
|
|
||||||
void run() override final;
|
void run() override final;
|
||||||
|
|
||||||
|
@ -59,7 +60,8 @@ class AvatarMixerSlavePool {
|
||||||
public:
|
public:
|
||||||
using ConstIter = NodeList::const_iterator;
|
using ConstIter = NodeList::const_iterator;
|
||||||
|
|
||||||
AvatarMixerSlavePool(int numThreads = QThread::idealThreadCount()) { setNumThreads(numThreads); }
|
AvatarMixerSlavePool(SlaveSharedData* slaveSharedData, int numThreads = QThread::idealThreadCount()) :
|
||||||
|
_slaveSharedData(slaveSharedData) { setNumThreads(numThreads); }
|
||||||
~AvatarMixerSlavePool() { resize(0); }
|
~AvatarMixerSlavePool() { resize(0); }
|
||||||
|
|
||||||
// Jobs the slave pool can do...
|
// Jobs the slave pool can do...
|
||||||
|
@ -98,6 +100,8 @@ private:
|
||||||
Queue _queue;
|
Queue _queue;
|
||||||
ConstIter _begin;
|
ConstIter _begin;
|
||||||
ConstIter _end;
|
ConstIter _end;
|
||||||
|
|
||||||
|
SlaveSharedData* _slaveSharedData;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_AvatarMixerSlavePool_h
|
#endif // hifi_AvatarMixerSlavePool_h
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
// ScriptableAvatar.cpp
|
// ScriptableAvatar.cpp
|
||||||
//
|
// assignment-client/src/avatars
|
||||||
//
|
//
|
||||||
// Created by Clement on 7/22/14.
|
// Created by Clement on 7/22/14.
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
@ -16,9 +16,13 @@
|
||||||
#include <glm/gtx/transform.hpp>
|
#include <glm/gtx/transform.hpp>
|
||||||
|
|
||||||
#include <shared/QtHelpers.h>
|
#include <shared/QtHelpers.h>
|
||||||
#include <GLMHelpers.h>
|
|
||||||
#include <AnimUtil.h>
|
#include <AnimUtil.h>
|
||||||
|
#include <ClientTraitsHandler.h>
|
||||||
|
#include <GLMHelpers.h>
|
||||||
|
|
||||||
|
ScriptableAvatar::ScriptableAvatar() {
|
||||||
|
_clientTraitsHandler = std::unique_ptr<ClientTraitsHandler>(new ClientTraitsHandler(this));
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
|
QByteArray ScriptableAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) {
|
||||||
_globalPosition = getWorldPosition();
|
_globalPosition = getWorldPosition();
|
||||||
|
@ -61,6 +65,7 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() {
|
||||||
void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
_bind.reset();
|
_bind.reset();
|
||||||
_animSkeleton.reset();
|
_animSkeleton.reset();
|
||||||
|
|
||||||
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,4 +142,6 @@ void ScriptableAvatar::update(float deltatime) {
|
||||||
_animation.clear();
|
_animation.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_clientTraitsHandler->sendChangedTraitsToMixer();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
//
|
//
|
||||||
// ScriptableAvatar.h
|
// ScriptableAvatar.h
|
||||||
//
|
// assignment-client/src/avatars
|
||||||
//
|
//
|
||||||
// Created by Clement on 7/22/14.
|
// Created by Clement on 7/22/14.
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
@ -124,6 +124,8 @@ class ScriptableAvatar : public AvatarData, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
ScriptableAvatar();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function Avatar.startAnimation
|
* @function Avatar.startAnimation
|
||||||
* @param {string} url
|
* @param {string} url
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include <NetworkingConstants.h>
|
#include <NetworkingConstants.h>
|
||||||
#include <AddressManager.h>
|
#include <AddressManager.h>
|
||||||
|
|
||||||
|
#include "../AssignmentDynamicFactory.h"
|
||||||
#include "AssignmentParentFinder.h"
|
#include "AssignmentParentFinder.h"
|
||||||
#include "EntityNodeData.h"
|
#include "EntityNodeData.h"
|
||||||
#include "EntityServerConsts.h"
|
#include "EntityServerConsts.h"
|
||||||
|
@ -42,6 +43,9 @@ EntityServer::EntityServer(ReceivedMessage& message) :
|
||||||
DependencyManager::set<ResourceCacheSharedItems>();
|
DependencyManager::set<ResourceCacheSharedItems>();
|
||||||
DependencyManager::set<ScriptCache>();
|
DependencyManager::set<ScriptCache>();
|
||||||
|
|
||||||
|
DependencyManager::registerInheritance<EntityDynamicFactoryInterface, AssignmentDynamicFactory>();
|
||||||
|
DependencyManager::set<AssignmentDynamicFactory>();
|
||||||
|
|
||||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||||
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
|
||||||
PacketType::EntityClone,
|
PacketType::EntityClone,
|
||||||
|
@ -71,6 +75,8 @@ EntityServer::~EntityServer() {
|
||||||
void EntityServer::aboutToFinish() {
|
void EntityServer::aboutToFinish() {
|
||||||
DependencyManager::get<ResourceManager>()->cleanup();
|
DependencyManager::get<ResourceManager>()->cleanup();
|
||||||
|
|
||||||
|
DependencyManager::destroy<AssignmentDynamicFactory>();
|
||||||
|
|
||||||
OctreeServer::aboutToFinish();
|
OctreeServer::aboutToFinish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -81,9 +81,6 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
|
||||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||||
|
|
||||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarData, avatarHashMap.data(), "processAvatarDataPacket");
|
|
||||||
packetReceiver.registerListener(PacketType::KillAvatar, avatarHashMap.data(), "processKillAvatar");
|
|
||||||
packetReceiver.registerListener(PacketType::AvatarIdentity, avatarHashMap.data(), "processAvatarIdentityPacket");
|
|
||||||
|
|
||||||
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
|
packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket");
|
||||||
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
|
packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket");
|
||||||
|
|
|
@ -1830,14 +1830,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(getEntities()->getTree().get(), &EntityTree::deletingEntity, [](const EntityItemID& entityItemID) {
|
|
||||||
auto avatarManager = DependencyManager::get<AvatarManager>();
|
|
||||||
auto myAvatar = avatarManager ? avatarManager->getMyAvatar() : nullptr;
|
|
||||||
if (myAvatar) {
|
|
||||||
myAvatar->clearAvatarEntity(entityItemID);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
EntityTree::setAddMaterialToEntityOperator([this](const QUuid& entityID, graphics::MaterialLayer material, const std::string& parentMaterialName) {
|
||||||
// try to find the renderable
|
// try to find the renderable
|
||||||
auto renderable = getEntities()->renderableForEntityId(entityID);
|
auto renderable = getEntities()->renderableForEntityId(entityID);
|
||||||
|
@ -5496,10 +5488,7 @@ void Application::update(float deltaTime) {
|
||||||
// we haven't yet enabled physics. we wait until we think we have all the collision information
|
// we haven't yet enabled physics. we wait until we think we have all the collision information
|
||||||
// for nearby entities before starting bullet up.
|
// for nearby entities before starting bullet up.
|
||||||
quint64 now = usecTimestampNow();
|
quint64 now = usecTimestampNow();
|
||||||
// Check for flagged EntityData having arrived.
|
if (isServerlessMode() || _octreeProcessor.isLoadSequenceComplete()) {
|
||||||
auto entityTreeRenderer = getEntities();
|
|
||||||
if (isServerlessMode() ||
|
|
||||||
(_octreeProcessor.isLoadSequenceComplete() )) {
|
|
||||||
// we've received a new full-scene octree stats packet, or it's been long enough to try again anyway
|
// we've received a new full-scene octree stats packet, or it's been long enough to try again anyway
|
||||||
_lastPhysicsCheckTime = now;
|
_lastPhysicsCheckTime = now;
|
||||||
_fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter;
|
_fullSceneCounterAtLastPhysicsCheck = _fullSceneReceivedCounter;
|
||||||
|
@ -6393,8 +6382,8 @@ void Application::nodeActivated(SharedNodePointer node) {
|
||||||
if (_avatarOverrideUrl.isValid()) {
|
if (_avatarOverrideUrl.isValid()) {
|
||||||
getMyAvatar()->useFullAvatarURL(_avatarOverrideUrl);
|
getMyAvatar()->useFullAvatarURL(_avatarOverrideUrl);
|
||||||
}
|
}
|
||||||
static const QUrl empty{};
|
|
||||||
if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->cannonicalSkeletonModelURL(empty)) {
|
if (getMyAvatar()->getFullAvatarURLFromPreferences() != getMyAvatar()->getSkeletonModelURL()) {
|
||||||
getMyAvatar()->resetFullAvatarURL();
|
getMyAvatar()->resetFullAvatarURL();
|
||||||
}
|
}
|
||||||
getMyAvatar()->markIdentityDataChanged();
|
getMyAvatar()->markIdentityDataChanged();
|
||||||
|
|
|
@ -14,7 +14,6 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <QScriptEngine>
|
#include <QScriptEngine>
|
||||||
#include <QtCore/QJsonDocument>
|
|
||||||
|
|
||||||
#include "AvatarLogging.h"
|
#include "AvatarLogging.h"
|
||||||
|
|
||||||
|
@ -72,10 +71,6 @@ AvatarManager::AvatarManager(QObject* parent) :
|
||||||
qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer");
|
qRegisterMetaType<QWeakPointer<Node> >("NodeWeakPointer");
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
|
||||||
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
|
|
||||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
|
|
||||||
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
|
|
||||||
|
|
||||||
// when we hear that the user has ignored an avatar by session UUID
|
// when we hear that the user has ignored an avatar by session UUID
|
||||||
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
|
// immediately remove that avatar instead of waiting for the absence of packets from avatar mixer
|
||||||
|
|
|
@ -121,6 +121,7 @@ MyAvatar::MyAvatar(QThread* thread) :
|
||||||
_audioListenerMode(FROM_HEAD),
|
_audioListenerMode(FROM_HEAD),
|
||||||
_hmdAtRestDetector(glm::vec3(0), glm::quat())
|
_hmdAtRestDetector(glm::vec3(0), glm::quat())
|
||||||
{
|
{
|
||||||
|
_clientTraitsHandler = std::unique_ptr<ClientTraitsHandler>(new ClientTraitsHandler(this));
|
||||||
|
|
||||||
// give the pointer to our head to inherited _headData variable from AvatarData
|
// give the pointer to our head to inherited _headData variable from AvatarData
|
||||||
_headData = new MyHead(this);
|
_headData = new MyHead(this);
|
||||||
|
@ -513,6 +514,8 @@ void MyAvatar::update(float deltaTime) {
|
||||||
sendIdentityPacket();
|
sendIdentityPacket();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_clientTraitsHandler->sendChangedTraitsToMixer();
|
||||||
|
|
||||||
simulate(deltaTime);
|
simulate(deltaTime);
|
||||||
|
|
||||||
currentEnergy += energyChargeRate;
|
currentEnergy += energyChargeRate;
|
||||||
|
@ -1289,7 +1292,6 @@ void MyAvatar::loadData() {
|
||||||
// HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file
|
// HACK: manually remove empty 'avatarEntityData' else legacy data may persist in settings file
|
||||||
settings.remove("avatarEntityData");
|
settings.remove("avatarEntityData");
|
||||||
}
|
}
|
||||||
setAvatarEntityDataChanged(true);
|
|
||||||
|
|
||||||
// Flying preferences must be loaded before calling setFlyingEnabled()
|
// Flying preferences must be loaded before calling setFlyingEnabled()
|
||||||
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
|
Setting::Handle<bool> firstRunVal { Settings::firstRun, true };
|
||||||
|
@ -1666,7 +1668,10 @@ void MyAvatar::clearJointsData() {
|
||||||
void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
_skeletonModelChangeCount++;
|
_skeletonModelChangeCount++;
|
||||||
int skeletonModelChangeCount = _skeletonModelChangeCount;
|
int skeletonModelChangeCount = _skeletonModelChangeCount;
|
||||||
|
|
||||||
|
auto previousSkeletonModelURL = _skeletonModelURL;
|
||||||
Avatar::setSkeletonModelURL(skeletonModelURL);
|
Avatar::setSkeletonModelURL(skeletonModelURL);
|
||||||
|
|
||||||
_skeletonModel->setTagMask(render::hifi::TAG_NONE);
|
_skeletonModel->setTagMask(render::hifi::TAG_NONE);
|
||||||
_skeletonModel->setGroupCulled(true);
|
_skeletonModel->setGroupCulled(true);
|
||||||
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene());
|
_skeletonModel->setVisibleInScene(true, qApp->getMain3DScene());
|
||||||
|
@ -1693,9 +1698,9 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
}
|
}
|
||||||
QObject::disconnect(*skeletonConnection);
|
QObject::disconnect(*skeletonConnection);
|
||||||
});
|
});
|
||||||
|
|
||||||
saveAvatarUrl();
|
saveAvatarUrl();
|
||||||
emit skeletonChanged();
|
emit skeletonChanged();
|
||||||
emit skeletonModelURLChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::removeAvatarEntities(const std::function<bool(const QUuid& entityID)>& condition) {
|
void MyAvatar::removeAvatarEntities(const std::function<bool(const QUuid& entityID)>& condition) {
|
||||||
|
@ -1766,8 +1771,6 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
|
||||||
setSkeletonModelURL(fullAvatarURL);
|
setSkeletonModelURL(fullAvatarURL);
|
||||||
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
||||||
}
|
}
|
||||||
|
|
||||||
markIdentityDataChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::vec3 MyAvatar::getSkeletonPosition() const {
|
glm::vec3 MyAvatar::getSkeletonPosition() const {
|
||||||
|
@ -2111,7 +2114,10 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData)
|
||||||
attachmentDataToEntityProperties(data, properties);
|
attachmentDataToEntityProperties(data, properties);
|
||||||
newEntitiesProperties.push_back(properties);
|
newEntitiesProperties.push_back(properties);
|
||||||
}
|
}
|
||||||
removeAvatarEntities();
|
|
||||||
|
// clear any existing avatar entities
|
||||||
|
setAvatarEntityData(AvatarEntityMap());
|
||||||
|
|
||||||
for (auto& properties : newEntitiesProperties) {
|
for (auto& properties : newEntitiesProperties) {
|
||||||
DependencyManager::get<EntityScriptingInterface>()->addEntity(properties, true);
|
DependencyManager::get<EntityScriptingInterface>()->addEntity(properties, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,22 +18,22 @@
|
||||||
|
|
||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
#include <SettingHandle.h>
|
|
||||||
#include <Rig.h>
|
|
||||||
#include <Sound.h>
|
|
||||||
#include <ScriptEngine.h>
|
|
||||||
|
|
||||||
#include <controllers/Pose.h>
|
|
||||||
#include <controllers/Actions.h>
|
|
||||||
#include <AvatarConstants.h>
|
#include <AvatarConstants.h>
|
||||||
#include <avatars-renderer/Avatar.h>
|
#include <avatars-renderer/Avatar.h>
|
||||||
#include <avatars-renderer/ScriptAvatar.h>
|
#include <avatars-renderer/ScriptAvatar.h>
|
||||||
|
#include <ClientTraitsHandler.h>
|
||||||
|
#include <controllers/Pose.h>
|
||||||
|
#include <controllers/Actions.h>
|
||||||
|
#include <EntityItem.h>
|
||||||
|
#include <ThreadSafeValueCache.h>
|
||||||
|
#include <Rig.h>
|
||||||
|
#include <ScriptEngine.h>
|
||||||
|
#include <SettingHandle.h>
|
||||||
|
#include <Sound.h>
|
||||||
|
|
||||||
#include "AtRestDetector.h"
|
#include "AtRestDetector.h"
|
||||||
#include "MyCharacterController.h"
|
#include "MyCharacterController.h"
|
||||||
#include "RingBufferHistory.h"
|
#include "RingBufferHistory.h"
|
||||||
#include <ThreadSafeValueCache.h>
|
|
||||||
#include <EntityItem.h>
|
|
||||||
|
|
||||||
class AvatarActionHold;
|
class AvatarActionHold;
|
||||||
class ModelItemID;
|
class ModelItemID;
|
||||||
|
@ -1334,7 +1334,6 @@ public slots:
|
||||||
*/
|
*/
|
||||||
void setAnimGraphUrl(const QUrl& url); // thread-safe
|
void setAnimGraphUrl(const QUrl& url); // thread-safe
|
||||||
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function MyAvatar.getPositionForAudio
|
* @function MyAvatar.getPositionForAudio
|
||||||
* @returns {Vec3}
|
* @returns {Vec3}
|
||||||
|
@ -1347,7 +1346,6 @@ public slots:
|
||||||
*/
|
*/
|
||||||
glm::quat getOrientationForAudio();
|
glm::quat getOrientationForAudio();
|
||||||
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function MyAvatar.setModelScale
|
* @function MyAvatar.setModelScale
|
||||||
* @param {number} scale
|
* @param {number} scale
|
||||||
|
|
|
@ -224,14 +224,24 @@ void Avatar::setAvatarEntityDataChanged(bool value) {
|
||||||
|
|
||||||
void Avatar::updateAvatarEntities() {
|
void Avatar::updateAvatarEntities() {
|
||||||
PerformanceTimer perfTimer("attachments");
|
PerformanceTimer perfTimer("attachments");
|
||||||
|
|
||||||
|
// AVATAR ENTITY UPDATE FLOW
|
||||||
// - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity()
|
// - if queueEditEntityMessage sees clientOnly flag it does _myAvatar->updateAvatarEntity()
|
||||||
// - updateAvatarEntity saves the bytes and sets _avatarEntityDataLocallyEdited
|
// - updateAvatarEntity saves the bytes and flags the trait instance for the entity as updated
|
||||||
// - MyAvatar::update notices _avatarEntityDataLocallyEdited and calls sendIdentityPacket
|
// - ClientTraitsHandler::sendChangedTraitsToMixer sends the entity bytes to the mixer which relays them to other interfaces
|
||||||
// - sendIdentityPacket sends the entity bytes to the server which relays them to other interfaces
|
// - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processTraitInstace
|
||||||
// - AvatarHashMap::processAvatarIdentityPacket on other interfaces call avatar->setAvatarEntityData()
|
// - AvatarData::processTraitInstance calls updateAvatarEntity, which sets _avatarEntityDataChanged = true
|
||||||
// - setAvatarEntityData saves the bytes and sets _avatarEntityDataChanged = true
|
|
||||||
// - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are...
|
// - (My)Avatar::simulate notices _avatarEntityDataChanged and here we are...
|
||||||
|
|
||||||
|
// AVATAR ENTITY DELETE FLOW
|
||||||
|
// - EntityScriptingInterface::deleteEntity calls _myAvatar->clearAvatarEntity() for deleted avatar entities
|
||||||
|
// - clearAvatarEntity removes the avatar entity and flags the trait instance for the entity as deleted
|
||||||
|
// - ClientTraitsHandler::sendChangedTraitsToMixer sends a deletion to the mixer which relays to other interfaces
|
||||||
|
// - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processDeletedTraitInstace
|
||||||
|
// - AvatarData::processDeletedTraitInstance calls clearAvatarEntity
|
||||||
|
// - AvatarData::clearAvatarEntity sets _avatarEntityDataChanged = true and adds the ID to the detached list
|
||||||
|
// - Avatar::simulate notices _avatarEntityDataChanged and here we are...
|
||||||
|
|
||||||
if (!_avatarEntityDataChanged) {
|
if (!_avatarEntityDataChanged) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -345,21 +355,21 @@ void Avatar::updateAvatarEntities() {
|
||||||
stateItr.value().success = success;
|
stateItr.value().success = success;
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs();
|
AvatarEntityIDs recentlyDetachedAvatarEntities = getAndClearRecentlyDetachedIDs();
|
||||||
if (!recentlyDettachedAvatarEntities.empty()) {
|
if (!recentlyDetachedAvatarEntities.empty()) {
|
||||||
// only lock this thread when absolutely necessary
|
// only lock this thread when absolutely necessary
|
||||||
AvatarEntityMap avatarEntityData;
|
AvatarEntityMap avatarEntityData;
|
||||||
_avatarEntitiesLock.withReadLock([&] {
|
_avatarEntitiesLock.withReadLock([&] {
|
||||||
avatarEntityData = _avatarEntityData;
|
avatarEntityData = _avatarEntityData;
|
||||||
});
|
});
|
||||||
foreach (auto entityID, recentlyDettachedAvatarEntities) {
|
foreach (auto entityID, recentlyDetachedAvatarEntities) {
|
||||||
if (!avatarEntityData.contains(entityID)) {
|
if (!avatarEntityData.contains(entityID)) {
|
||||||
entityTree->deleteEntity(entityID, true, true);
|
entityTree->deleteEntity(entityID, true, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove stale data hashes
|
// remove stale data hashes
|
||||||
foreach (auto entityID, recentlyDettachedAvatarEntities) {
|
foreach (auto entityID, recentlyDetachedAvatarEntities) {
|
||||||
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
|
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
|
||||||
if (stateItr != _avatarEntityDataHashes.end()) {
|
if (stateItr != _avatarEntityDataHashes.end()) {
|
||||||
_avatarEntityDataHashes.erase(stateItr);
|
_avatarEntityDataHashes.erase(stateItr);
|
||||||
|
|
158
libraries/avatars/src/AssociatedTraitValues.h
Normal file
158
libraries/avatars/src/AssociatedTraitValues.h
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
//
|
||||||
|
// AssociatedTraitValues.h
|
||||||
|
// libraries/avatars/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 8/8/18.
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_AssociatedTraitValues_h
|
||||||
|
#define hifi_AssociatedTraitValues_h
|
||||||
|
|
||||||
|
#include "AvatarTraits.h"
|
||||||
|
|
||||||
|
namespace AvatarTraits {
|
||||||
|
template<typename T, T defaultValue>
|
||||||
|
class AssociatedTraitValues {
|
||||||
|
public:
|
||||||
|
AssociatedTraitValues() : _simpleTypes(FirstInstancedTrait, defaultValue) {}
|
||||||
|
|
||||||
|
void insert(TraitType type, T value) { _simpleTypes[type] = value; }
|
||||||
|
void erase(TraitType type) { _simpleTypes[type] = defaultValue; }
|
||||||
|
|
||||||
|
T& getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID);
|
||||||
|
void instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value);
|
||||||
|
|
||||||
|
struct InstanceIDValuePair {
|
||||||
|
TraitInstanceID id;
|
||||||
|
T value;
|
||||||
|
|
||||||
|
InstanceIDValuePair(TraitInstanceID id, T value) : id(id), value(value) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
using InstanceIDValuePairs = std::vector<InstanceIDValuePair>;
|
||||||
|
|
||||||
|
InstanceIDValuePairs& getInstanceIDValuePairs(TraitType traitType);
|
||||||
|
|
||||||
|
void instanceErase(TraitType traitType, TraitInstanceID instanceID);
|
||||||
|
void eraseAllInstances(TraitType traitType);
|
||||||
|
|
||||||
|
// will return defaultValue for instanced traits
|
||||||
|
T operator[](TraitType traitType) const { return _simpleTypes[traitType]; }
|
||||||
|
T& operator[](TraitType traitType) { return _simpleTypes[traitType]; }
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
std::fill(_simpleTypes.begin(), _simpleTypes.end(), defaultValue);
|
||||||
|
_instancedTypes.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
typename std::vector<T>::const_iterator simpleCBegin() const { return _simpleTypes.cbegin(); }
|
||||||
|
typename std::vector<T>::const_iterator simpleCEnd() const { return _simpleTypes.cend(); }
|
||||||
|
|
||||||
|
typename std::vector<T>::iterator simpleBegin() { return _simpleTypes.begin(); }
|
||||||
|
typename std::vector<T>::iterator simpleEnd() { return _simpleTypes.end(); }
|
||||||
|
|
||||||
|
struct TraitWithInstances {
|
||||||
|
TraitType traitType;
|
||||||
|
InstanceIDValuePairs instances;
|
||||||
|
|
||||||
|
TraitWithInstances(TraitType traitType) : traitType(traitType) {};
|
||||||
|
TraitWithInstances(TraitType traitType, TraitInstanceID instanceID, T value) :
|
||||||
|
traitType(traitType), instances({{ instanceID, value }}) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
typename std::vector<TraitWithInstances>::const_iterator instancedCBegin() const { return _instancedTypes.cbegin(); }
|
||||||
|
typename std::vector<TraitWithInstances>::const_iterator instancedCEnd() const { return _instancedTypes.cend(); }
|
||||||
|
|
||||||
|
typename std::vector<TraitWithInstances>::iterator instancedBegin() { return _instancedTypes.begin(); }
|
||||||
|
typename std::vector<TraitWithInstances>::iterator instancedEnd() { return _instancedTypes.end(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<T> _simpleTypes;
|
||||||
|
|
||||||
|
typename std::vector<TraitWithInstances>::iterator instancesForTrait(TraitType traitType) {
|
||||||
|
return std::find_if(_instancedTypes.begin(), _instancedTypes.end(),
|
||||||
|
[traitType](TraitWithInstances& traitWithInstances){
|
||||||
|
return traitWithInstances.traitType == traitType;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<TraitWithInstances> _instancedTypes;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T, T defaultValue>
|
||||||
|
inline typename AssociatedTraitValues<T, defaultValue>::InstanceIDValuePairs&
|
||||||
|
AssociatedTraitValues<T, defaultValue>::getInstanceIDValuePairs(TraitType traitType) {
|
||||||
|
auto it = instancesForTrait(traitType);
|
||||||
|
|
||||||
|
if (it != _instancedTypes.end()) {
|
||||||
|
return it->instances;
|
||||||
|
} else {
|
||||||
|
_instancedTypes.emplace_back(traitType);
|
||||||
|
return _instancedTypes.back().instances;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, T defaultValue>
|
||||||
|
inline T& AssociatedTraitValues<T, defaultValue>::getInstanceValueRef(TraitType traitType, TraitInstanceID instanceID) {
|
||||||
|
auto it = instancesForTrait(traitType);
|
||||||
|
|
||||||
|
if (it != _instancedTypes.end()) {
|
||||||
|
auto& instancesVector = it->instances;
|
||||||
|
auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(),
|
||||||
|
[instanceID](InstanceIDValuePair& idValuePair){
|
||||||
|
return idValuePair.id == instanceID;
|
||||||
|
});
|
||||||
|
if (instanceIt != instancesVector.end()) {
|
||||||
|
return instanceIt->value;
|
||||||
|
} else {
|
||||||
|
instancesVector.emplace_back(instanceID, defaultValue);
|
||||||
|
return instancesVector.back().value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_instancedTypes.emplace_back(traitType, instanceID, defaultValue);
|
||||||
|
return _instancedTypes.back().instances.back().value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, T defaultValue>
|
||||||
|
inline void AssociatedTraitValues<T, defaultValue>::instanceInsert(TraitType traitType, TraitInstanceID instanceID, T value) {
|
||||||
|
auto it = instancesForTrait(traitType);
|
||||||
|
|
||||||
|
if (it != _instancedTypes.end()) {
|
||||||
|
auto& instancesVector = it->instances;
|
||||||
|
auto instanceIt = std::find_if(instancesVector.begin(), instancesVector.end(),
|
||||||
|
[instanceID](InstanceIDValuePair& idValuePair){
|
||||||
|
return idValuePair.id == instanceID;
|
||||||
|
});
|
||||||
|
if (instanceIt != instancesVector.end()) {
|
||||||
|
instanceIt->value = value;
|
||||||
|
} else {
|
||||||
|
instancesVector.emplace_back(instanceID, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_instancedTypes.emplace_back(traitType, instanceID, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T, T defaultValue>
|
||||||
|
inline void AssociatedTraitValues<T, defaultValue>::instanceErase(TraitType traitType, TraitInstanceID instanceID) {
|
||||||
|
auto it = instancesForTrait(traitType);
|
||||||
|
|
||||||
|
if (it != _instancedTypes.end()) {
|
||||||
|
auto& instancesVector = it->instances;
|
||||||
|
instancesVector.erase(std::remove_if(instancesVector.begin(),
|
||||||
|
instancesVector.end(),
|
||||||
|
[&instanceID](InstanceIDValuePair& idValuePair){
|
||||||
|
return idValuePair.id == instanceID;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
using TraitVersions = AssociatedTraitValues<TraitVersion, DEFAULT_TRAIT_VERSION>;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_AssociatedTraitValues_h
|
|
@ -42,6 +42,8 @@
|
||||||
#include <BitVectorHelpers.h>
|
#include <BitVectorHelpers.h>
|
||||||
|
|
||||||
#include "AvatarLogging.h"
|
#include "AvatarLogging.h"
|
||||||
|
#include "AvatarTraits.h"
|
||||||
|
#include "ClientTraitsHandler.h"
|
||||||
|
|
||||||
//#define WANT_DEBUG
|
//#define WANT_DEBUG
|
||||||
|
|
||||||
|
@ -1749,14 +1751,8 @@ glm::quat AvatarData::getOrientationOutbound() const {
|
||||||
return (getLocalOrientation());
|
return (getLocalOrientation());
|
||||||
}
|
}
|
||||||
|
|
||||||
static const QUrl emptyURL("");
|
|
||||||
QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
|
|
||||||
// We don't put file urls on the wire, but instead convert to empty.
|
|
||||||
return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged,
|
void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& identityChanged,
|
||||||
bool& displayNameChanged, bool& skeletonModelUrlChanged) {
|
bool& displayNameChanged) {
|
||||||
|
|
||||||
QDataStream packetStream(identityData);
|
QDataStream packetStream(identityData);
|
||||||
|
|
||||||
|
@ -1777,28 +1773,17 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
||||||
if (incomingSequenceNumber > _identitySequenceNumber) {
|
if (incomingSequenceNumber > _identitySequenceNumber) {
|
||||||
Identity identity;
|
Identity identity;
|
||||||
|
|
||||||
packetStream >> identity.skeletonModelURL
|
packetStream
|
||||||
>> identity.attachmentData
|
>> identity.attachmentData
|
||||||
>> identity.displayName
|
>> identity.displayName
|
||||||
>> identity.sessionDisplayName
|
>> identity.sessionDisplayName
|
||||||
>> identity.isReplicated
|
>> identity.isReplicated
|
||||||
>> identity.avatarEntityData
|
|
||||||
>> identity.lookAtSnappingEnabled
|
>> identity.lookAtSnappingEnabled
|
||||||
;
|
;
|
||||||
|
|
||||||
// set the store identity sequence number to match the incoming identity
|
// set the store identity sequence number to match the incoming identity
|
||||||
_identitySequenceNumber = incomingSequenceNumber;
|
_identitySequenceNumber = incomingSequenceNumber;
|
||||||
|
|
||||||
if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) {
|
|
||||||
setSkeletonModelURL(identity.skeletonModelURL);
|
|
||||||
identityChanged = true;
|
|
||||||
skeletonModelUrlChanged = true;
|
|
||||||
if (_firstSkeletonCheck) {
|
|
||||||
displayNameChanged = true;
|
|
||||||
}
|
|
||||||
_firstSkeletonCheck = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identity.displayName != _displayName) {
|
if (identity.displayName != _displayName) {
|
||||||
_displayName = identity.displayName;
|
_displayName = identity.displayName;
|
||||||
identityChanged = true;
|
identityChanged = true;
|
||||||
|
@ -1816,16 +1801,6 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
||||||
identityChanged = true;
|
identityChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool avatarEntityDataChanged = false;
|
|
||||||
_avatarEntitiesLock.withReadLock([&] {
|
|
||||||
avatarEntityDataChanged = (identity.avatarEntityData != _avatarEntityData);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (avatarEntityDataChanged) {
|
|
||||||
setAvatarEntityData(identity.avatarEntityData);
|
|
||||||
identityChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identity.lookAtSnappingEnabled != _lookAtSnappingEnabled) {
|
if (identity.lookAtSnappingEnabled != _lookAtSnappingEnabled) {
|
||||||
setProperty("lookAtSnappingEnabled", identity.lookAtSnappingEnabled);
|
setProperty("lookAtSnappingEnabled", identity.lookAtSnappingEnabled);
|
||||||
identityChanged = true;
|
identityChanged = true;
|
||||||
|
@ -1834,7 +1809,6 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
||||||
#ifdef WANT_DEBUG
|
#ifdef WANT_DEBUG
|
||||||
qCDebug(avatars) << __FUNCTION__
|
qCDebug(avatars) << __FUNCTION__
|
||||||
<< "identity.uuid:" << identity.uuid
|
<< "identity.uuid:" << identity.uuid
|
||||||
<< "identity.skeletonModelURL:" << identity.skeletonModelURL
|
|
||||||
<< "identity.displayName:" << identity.displayName
|
<< "identity.displayName:" << identity.displayName
|
||||||
<< "identity.sessionDisplayName:" << identity.sessionDisplayName;
|
<< "identity.sessionDisplayName:" << identity.sessionDisplayName;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1846,26 +1820,105 @@ void AvatarData::processAvatarIdentity(const QByteArray& identityData, bool& ide
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl AvatarData::getWireSafeSkeletonModelURL() const {
|
||||||
|
if (_skeletonModelURL.scheme() != "file" && _skeletonModelURL.scheme() != "qrc") {
|
||||||
|
return _skeletonModelURL;
|
||||||
|
} else {
|
||||||
|
return QUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
|
||||||
|
AvatarTraits::TraitVersion traitVersion) {
|
||||||
|
qint64 bytesWritten = 0;
|
||||||
|
bytesWritten += destination.writePrimitive(traitType);
|
||||||
|
|
||||||
|
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
|
||||||
|
bytesWritten += destination.writePrimitive(traitVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||||
|
QByteArray encodedSkeletonURL = getWireSafeSkeletonModelURL().toEncoded();
|
||||||
|
|
||||||
|
AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size();
|
||||||
|
bytesWritten += destination.writePrimitive(encodedURLSize);
|
||||||
|
|
||||||
|
bytesWritten += destination.write(encodedSkeletonURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID,
|
||||||
|
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
||||||
|
qint64 bytesWritten = 0;
|
||||||
|
|
||||||
|
bytesWritten += destination.writePrimitive(traitType);
|
||||||
|
|
||||||
|
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
|
||||||
|
bytesWritten += destination.writePrimitive(traitVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesWritten += destination.write(traitInstanceID.toRfc4122());
|
||||||
|
|
||||||
|
if (traitType == AvatarTraits::AvatarEntity) {
|
||||||
|
// grab a read lock on the avatar entities and check for entity data for the given ID
|
||||||
|
QByteArray entityBinaryData;
|
||||||
|
|
||||||
|
_avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] {
|
||||||
|
if (_avatarEntityData.contains(traitInstanceID)) {
|
||||||
|
entityBinaryData = _avatarEntityData[traitInstanceID];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!entityBinaryData.isNull()) {
|
||||||
|
AvatarTraits::TraitWireSize entityBinarySize = entityBinaryData.size();
|
||||||
|
|
||||||
|
bytesWritten += destination.writePrimitive(entityBinarySize);
|
||||||
|
bytesWritten += destination.write(entityBinaryData);
|
||||||
|
} else {
|
||||||
|
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData) {
|
||||||
|
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||||
|
// get the URL from the binary data
|
||||||
|
auto skeletonModelURL = QUrl::fromEncoded(traitBinaryData);
|
||||||
|
setSkeletonModelURL(skeletonModelURL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) {
|
||||||
|
if (traitType == AvatarTraits::AvatarEntity) {
|
||||||
|
updateAvatarEntity(instanceID, traitBinaryData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) {
|
||||||
|
if (traitType == AvatarTraits::AvatarEntity) {
|
||||||
|
clearAvatarEntity(instanceID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QByteArray AvatarData::identityByteArray(bool setIsReplicated) const {
|
QByteArray AvatarData::identityByteArray(bool setIsReplicated) const {
|
||||||
QByteArray identityData;
|
QByteArray identityData;
|
||||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||||
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL
|
|
||||||
|
|
||||||
// when mixers send identity packets to agents, they simply forward along the last incoming sequence number they received
|
// when mixers send identity packets to agents, they simply forward along the last incoming sequence number they received
|
||||||
// whereas agents send a fresh outgoing sequence number when identity data has changed
|
// whereas agents send a fresh outgoing sequence number when identity data has changed
|
||||||
|
|
||||||
_avatarEntitiesLock.withReadLock([&] {
|
|
||||||
identityStream << getSessionUUID()
|
identityStream << getSessionUUID()
|
||||||
<< (udt::SequenceNumber::Type) _identitySequenceNumber
|
<< (udt::SequenceNumber::Type) _identitySequenceNumber
|
||||||
<< urlToSend
|
|
||||||
<< _attachmentData
|
<< _attachmentData
|
||||||
<< _displayName
|
<< _displayName
|
||||||
<< getSessionDisplayNameForTransport() // depends on _sessionDisplayName
|
<< getSessionDisplayNameForTransport() // depends on _sessionDisplayName
|
||||||
<< (_isReplicated || setIsReplicated)
|
<< (_isReplicated || setIsReplicated)
|
||||||
<< _avatarEntityData
|
<< _lookAtSnappingEnabled;
|
||||||
<< _lookAtSnappingEnabled
|
|
||||||
;
|
|
||||||
});
|
|
||||||
|
|
||||||
return identityData;
|
return identityData;
|
||||||
}
|
}
|
||||||
|
@ -1879,11 +1932,17 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
if (expanded == _skeletonModelURL) {
|
if (expanded == _skeletonModelURL) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_skeletonModelURL = expanded;
|
_skeletonModelURL = expanded;
|
||||||
qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString();
|
qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString();
|
||||||
|
|
||||||
updateJointMappings();
|
updateJointMappings();
|
||||||
markIdentityDataChanged();
|
|
||||||
|
if (_clientTraitsHandler) {
|
||||||
|
_clientTraitsHandler->markTraitUpdated(AvatarTraits::SkeletonModelURL);
|
||||||
|
}
|
||||||
|
|
||||||
|
emit skeletonModelURLChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::setDisplayName(const QString& displayName) {
|
void AvatarData::setDisplayName(const QString& displayName) {
|
||||||
|
@ -1978,6 +2037,13 @@ void AvatarData::setJointMappingsFromNetworkReply() {
|
||||||
|
|
||||||
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
QNetworkReply* networkReply = static_cast<QNetworkReply*>(sender());
|
||||||
|
|
||||||
|
// before we process this update, make sure that the skeleton model URL hasn't changed
|
||||||
|
// since we made the FST request
|
||||||
|
if (networkReply->url() != _skeletonModelURL) {
|
||||||
|
qCDebug(avatars) << "Refusing to set joint mappings for FST URL that does not match the current URL";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
QWriteLocker writeLock(&_jointDataLock);
|
QWriteLocker writeLock(&_jointDataLock);
|
||||||
QByteArray line;
|
QByteArray line;
|
||||||
|
@ -2076,7 +2142,6 @@ void AvatarData::sendIdentityPacket() {
|
||||||
nodeList->sendPacketList(std::move(packetList), *node);
|
nodeList->sendPacketList(std::move(packetList), *node);
|
||||||
});
|
});
|
||||||
|
|
||||||
_avatarEntityDataLocallyEdited = false;
|
|
||||||
_identityDataChanged = false;
|
_identityDataChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2631,23 +2696,36 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent
|
||||||
if (itr == _avatarEntityData.end()) {
|
if (itr == _avatarEntityData.end()) {
|
||||||
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
|
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
|
||||||
_avatarEntityData.insert(entityID, entityData);
|
_avatarEntityData.insert(entityID, entityData);
|
||||||
_avatarEntityDataLocallyEdited = true;
|
|
||||||
markIdentityDataChanged();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
itr.value() = entityData;
|
itr.value() = entityData;
|
||||||
_avatarEntityDataLocallyEdited = true;
|
|
||||||
markIdentityDataChanged();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_avatarEntityDataChanged = true;
|
||||||
|
|
||||||
|
if (_clientTraitsHandler) {
|
||||||
|
// we have a client traits handler, so we need to mark this instanced trait as changed
|
||||||
|
// so that changes will be sent next frame
|
||||||
|
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::clearAvatarEntity(const QUuid& entityID) {
|
void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) {
|
||||||
_avatarEntitiesLock.withWriteLock([&] {
|
|
||||||
_avatarEntityData.remove(entityID);
|
bool removedEntity = false;
|
||||||
_avatarEntityDataLocallyEdited = true;
|
|
||||||
markIdentityDataChanged();
|
_avatarEntitiesLock.withWriteLock([this, &removedEntity, &entityID] {
|
||||||
|
removedEntity = _avatarEntityData.remove(entityID);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
insertDetachedEntityID(entityID);
|
||||||
|
|
||||||
|
if (removedEntity && _clientTraitsHandler) {
|
||||||
|
// we have a client traits handler, so we need to mark this removed instance trait as deleted
|
||||||
|
// so that changes are sent next frame
|
||||||
|
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, entityID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AvatarEntityMap AvatarData::getAvatarEntityData() const {
|
AvatarEntityMap AvatarData::getAvatarEntityData() const {
|
||||||
|
@ -2662,6 +2740,8 @@ void AvatarData::insertDetachedEntityID(const QUuid entityID) {
|
||||||
_avatarEntitiesLock.withWriteLock([&] {
|
_avatarEntitiesLock.withWriteLock([&] {
|
||||||
_avatarEntityDetached.insert(entityID);
|
_avatarEntityDetached.insert(entityID);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_avatarEntityDataChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
|
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
|
||||||
|
@ -2681,6 +2761,20 @@ void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
|
||||||
foreach (auto entityID, previousAvatarEntityIDs) {
|
foreach (auto entityID, previousAvatarEntityIDs) {
|
||||||
if (!_avatarEntityData.contains(entityID)) {
|
if (!_avatarEntityData.contains(entityID)) {
|
||||||
_avatarEntityDetached.insert(entityID);
|
_avatarEntityDetached.insert(entityID);
|
||||||
|
|
||||||
|
if (_clientTraitsHandler) {
|
||||||
|
// we have a client traits handler, so we flag this removed entity as deleted
|
||||||
|
// so that changes are sent next frame
|
||||||
|
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, entityID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_clientTraitsHandler) {
|
||||||
|
// if we have a client traits handler, flag any updated or created entities
|
||||||
|
// so that we send changes for them next frame
|
||||||
|
foreach (auto entityID, _avatarEntityData) {
|
||||||
|
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@
|
||||||
#include <udt/SequenceNumber.h>
|
#include <udt/SequenceNumber.h>
|
||||||
|
|
||||||
#include "AABox.h"
|
#include "AABox.h"
|
||||||
|
#include "AvatarTraits.h"
|
||||||
#include "HeadData.h"
|
#include "HeadData.h"
|
||||||
#include "PathUtils.h"
|
#include "PathUtils.h"
|
||||||
|
|
||||||
|
@ -371,6 +372,8 @@ public:
|
||||||
bool operator<(const AvatarPriority& other) const { return priority < other.priority; }
|
bool operator<(const AvatarPriority& other) const { return priority < other.priority; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class ClientTraitsHandler;
|
||||||
|
|
||||||
class AvatarData : public QObject, public SpatiallyNestable {
|
class AvatarData : public QObject, public SpatiallyNestable {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
@ -425,7 +428,6 @@ public:
|
||||||
virtual ~AvatarData();
|
virtual ~AvatarData();
|
||||||
|
|
||||||
static const QUrl& defaultFullAvatarModelUrl();
|
static const QUrl& defaultFullAvatarModelUrl();
|
||||||
QUrl cannonicalSkeletonModelURL(const QUrl& empty) const;
|
|
||||||
|
|
||||||
virtual bool isMyAvatar() const { return false; }
|
virtual bool isMyAvatar() const { return false; }
|
||||||
|
|
||||||
|
@ -924,11 +926,12 @@ public:
|
||||||
* @param {string} entityData
|
* @param {string} entityData
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData);
|
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function MyAvatar.clearAvatarEntity
|
* @function MyAvatar.clearAvatarEntity
|
||||||
* @param {Uuid} entityID
|
* @param {Uuid} entityID
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID);
|
Q_INVOKABLE void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true);
|
||||||
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
@ -944,23 +947,32 @@ public:
|
||||||
const HeadData* getHeadData() const { return _headData; }
|
const HeadData* getHeadData() const { return _headData; }
|
||||||
|
|
||||||
struct Identity {
|
struct Identity {
|
||||||
QUrl skeletonModelURL;
|
|
||||||
QVector<AttachmentData> attachmentData;
|
QVector<AttachmentData> attachmentData;
|
||||||
QString displayName;
|
QString displayName;
|
||||||
QString sessionDisplayName;
|
QString sessionDisplayName;
|
||||||
bool isReplicated;
|
bool isReplicated;
|
||||||
AvatarEntityMap avatarEntityData;
|
|
||||||
bool lookAtSnappingEnabled;
|
bool lookAtSnappingEnabled;
|
||||||
};
|
};
|
||||||
|
|
||||||
// identityChanged returns true if identity has changed, false otherwise.
|
// identityChanged returns true if identity has changed, false otherwise.
|
||||||
// identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange.
|
// identityChanged returns true if identity has changed, false otherwise. Similarly for displayNameChanged and skeletonModelUrlChange.
|
||||||
void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged,
|
void processAvatarIdentity(const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged);
|
||||||
bool& displayNameChanged, bool& skeletonModelUrlChanged);
|
|
||||||
|
qint64 packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice& destination,
|
||||||
|
AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
|
||||||
|
qint64 packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID,
|
||||||
|
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion = AvatarTraits::NULL_TRAIT_VERSION);
|
||||||
|
|
||||||
|
void processTrait(AvatarTraits::TraitType traitType, QByteArray traitBinaryData);
|
||||||
|
void processTraitInstance(AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData);
|
||||||
|
void processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID);
|
||||||
|
|
||||||
QByteArray identityByteArray(bool setIsReplicated = false) const;
|
QByteArray identityByteArray(bool setIsReplicated = false) const;
|
||||||
|
|
||||||
|
QUrl getWireSafeSkeletonModelURL() const;
|
||||||
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
|
const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; }
|
||||||
|
|
||||||
const QString& getDisplayName() const { return _displayName; }
|
const QString& getDisplayName() const { return _displayName; }
|
||||||
const QString& getSessionDisplayName() const { return _sessionDisplayName; }
|
const QString& getSessionDisplayName() const { return _sessionDisplayName; }
|
||||||
bool getLookAtSnappingEnabled() const { return _lookAtSnappingEnabled; }
|
bool getLookAtSnappingEnabled() const { return _lookAtSnappingEnabled; }
|
||||||
|
@ -1327,7 +1339,6 @@ protected:
|
||||||
mutable HeadData* _headData { nullptr };
|
mutable HeadData* _headData { nullptr };
|
||||||
|
|
||||||
QUrl _skeletonModelURL;
|
QUrl _skeletonModelURL;
|
||||||
bool _firstSkeletonCheck { true };
|
|
||||||
QUrl _skeletonFBXURL;
|
QUrl _skeletonFBXURL;
|
||||||
QVector<AttachmentData> _attachmentData;
|
QVector<AttachmentData> _attachmentData;
|
||||||
QVector<AttachmentData> _oldAttachmentData;
|
QVector<AttachmentData> _oldAttachmentData;
|
||||||
|
@ -1411,7 +1422,6 @@ protected:
|
||||||
mutable ReadWriteLockable _avatarEntitiesLock;
|
mutable ReadWriteLockable _avatarEntitiesLock;
|
||||||
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar
|
||||||
AvatarEntityMap _avatarEntityData;
|
AvatarEntityMap _avatarEntityData;
|
||||||
bool _avatarEntityDataLocallyEdited { false };
|
|
||||||
bool _avatarEntityDataChanged { false };
|
bool _avatarEntityDataChanged { false };
|
||||||
|
|
||||||
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
|
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
|
||||||
|
@ -1434,6 +1444,9 @@ protected:
|
||||||
bool _hasProcessedFirstIdentity { false };
|
bool _hasProcessedFirstIdentity { false };
|
||||||
float _density;
|
float _density;
|
||||||
|
|
||||||
|
// null unless MyAvatar or ScriptableAvatar sending traits data to mixer
|
||||||
|
std::unique_ptr<ClientTraitsHandler> _clientTraitsHandler;
|
||||||
|
|
||||||
template <typename T, typename F>
|
template <typename T, typename F>
|
||||||
T readLockWithNamedJointIndex(const QString& name, const T& defaultValue, F f) const {
|
T readLockWithNamedJointIndex(const QString& name, const T& defaultValue, F f) const {
|
||||||
int index = getFauxJointIndex(name);
|
int index = getFauxJointIndex(name);
|
||||||
|
|
|
@ -19,10 +19,17 @@
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
|
|
||||||
#include "AvatarLogging.h"
|
#include "AvatarLogging.h"
|
||||||
|
#include "AvatarTraits.h"
|
||||||
|
|
||||||
AvatarHashMap::AvatarHashMap() {
|
AvatarHashMap::AvatarHashMap() {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
|
||||||
|
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||||
|
packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket");
|
||||||
|
packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar");
|
||||||
|
packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket");
|
||||||
|
packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits");
|
||||||
|
|
||||||
connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);
|
connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,9 +189,72 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
||||||
auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar);
|
auto avatar = newOrExistingAvatar(identityUUID, sendingNode, isNewAvatar);
|
||||||
bool identityChanged = false;
|
bool identityChanged = false;
|
||||||
bool displayNameChanged = false;
|
bool displayNameChanged = false;
|
||||||
bool skeletonModelUrlChanged = false;
|
|
||||||
// In this case, the "sendingNode" is the Avatar Mixer.
|
// In this case, the "sendingNode" is the Avatar Mixer.
|
||||||
avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged, skeletonModelUrlChanged);
|
avatar->processAvatarIdentity(message->getMessage(), identityChanged, displayNameChanged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarHashMap::processBulkAvatarTraits(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||||
|
while (message->getBytesLeftToRead()) {
|
||||||
|
// read the avatar ID to figure out which avatar this is for
|
||||||
|
auto avatarID = QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
||||||
|
// grab the avatar so we can ask it to process trait data
|
||||||
|
bool isNewAvatar;
|
||||||
|
auto avatar = newOrExistingAvatar(avatarID, sendingNode, isNewAvatar);
|
||||||
|
|
||||||
|
// read the first trait type for this avatar
|
||||||
|
AvatarTraits::TraitType traitType;
|
||||||
|
message->readPrimitive(&traitType);
|
||||||
|
|
||||||
|
// grab the last trait versions for this avatar
|
||||||
|
auto& lastProcessedVersions = _processedTraitVersions[avatarID];
|
||||||
|
|
||||||
|
while (traitType != AvatarTraits::NullTrait) {
|
||||||
|
AvatarTraits::TraitVersion packetTraitVersion;
|
||||||
|
message->readPrimitive(&packetTraitVersion);
|
||||||
|
|
||||||
|
AvatarTraits::TraitWireSize traitBinarySize;
|
||||||
|
bool skipBinaryTrait = false;
|
||||||
|
|
||||||
|
|
||||||
|
if (AvatarTraits::isSimpleTrait(traitType)) {
|
||||||
|
message->readPrimitive(&traitBinarySize);
|
||||||
|
|
||||||
|
// check if this trait version is newer than what we already have for this avatar
|
||||||
|
if (packetTraitVersion > lastProcessedVersions[traitType]) {
|
||||||
|
avatar->processTrait(traitType, message->read(traitBinarySize));
|
||||||
|
lastProcessedVersions[traitType] = packetTraitVersion;
|
||||||
|
} else {
|
||||||
|
skipBinaryTrait = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AvatarTraits::TraitInstanceID traitInstanceID =
|
||||||
|
QUuid::fromRfc4122(message->readWithoutCopy(NUM_BYTES_RFC4122_UUID));
|
||||||
|
|
||||||
|
message->readPrimitive(&traitBinarySize);
|
||||||
|
|
||||||
|
auto& processedInstanceVersion = lastProcessedVersions.getInstanceValueRef(traitType, traitInstanceID);
|
||||||
|
if (packetTraitVersion > processedInstanceVersion) {
|
||||||
|
if (traitBinarySize == AvatarTraits::DELETED_TRAIT_SIZE) {
|
||||||
|
avatar->processDeletedTraitInstance(traitType, traitInstanceID);
|
||||||
|
} else {
|
||||||
|
avatar->processTraitInstance(traitType, traitInstanceID, message->read(traitBinarySize));
|
||||||
|
}
|
||||||
|
processedInstanceVersion = packetTraitVersion;
|
||||||
|
} else {
|
||||||
|
skipBinaryTrait = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipBinaryTrait) {
|
||||||
|
// we didn't read this trait because it was older or because we didn't have an avatar to process it for
|
||||||
|
message->seek(message->getPosition() + traitBinarySize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// read the next trait type, which is null if there are no more traits for this avatar
|
||||||
|
message->readPrimitive(&traitType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +279,9 @@ void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason remo
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) {
|
||||||
|
// remove any information about processed traits for this avatar
|
||||||
|
_processedTraitVersions.erase(removedAvatar->getID());
|
||||||
|
|
||||||
qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
|
qCDebug(avatars) << "Removed avatar with UUID" << uuidStringWithoutCurlyBraces(removedAvatar->getSessionUUID())
|
||||||
<< "from AvatarHashMap" << removalReason;
|
<< "from AvatarHashMap" << removalReason;
|
||||||
emit avatarRemovedEvent(removedAvatar->getSessionUUID());
|
emit avatarRemovedEvent(removedAvatar->getSessionUUID());
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
#include "ScriptAvatarData.h"
|
#include "ScriptAvatarData.h"
|
||||||
|
|
||||||
#include "AvatarData.h"
|
#include "AvatarData.h"
|
||||||
|
#include "AssociatedTraitValues.h"
|
||||||
|
|
||||||
/**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
|
||||||
|
@ -133,6 +134,8 @@ protected slots:
|
||||||
*/
|
*/
|
||||||
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
void processAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
|
void processBulkAvatarTraits(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function AvatarList.processKillAvatar
|
* @function AvatarList.processKillAvatar
|
||||||
* @param {} message
|
* @param {} message
|
||||||
|
@ -163,6 +166,7 @@ protected:
|
||||||
AvatarPendingHash _pendingAvatars;
|
AvatarPendingHash _pendingAvatars;
|
||||||
mutable QReadWriteLock _hashLock;
|
mutable QReadWriteLock _hashLock;
|
||||||
|
|
||||||
|
std::unordered_map<QUuid, AvatarTraits::TraitVersions> _processedTraitVersions;
|
||||||
private:
|
private:
|
||||||
QUuid _lastOwnerSessionUUID;
|
QUuid _lastOwnerSessionUUID;
|
||||||
};
|
};
|
||||||
|
|
61
libraries/avatars/src/AvatarTraits.h
Normal file
61
libraries/avatars/src/AvatarTraits.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// AvatarTraits.h
|
||||||
|
// libraries/avatars/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 7/30/18.
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_AvatarTraits_h
|
||||||
|
#define hifi_AvatarTraits_h
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <QtCore/QUuid>
|
||||||
|
|
||||||
|
namespace AvatarTraits {
|
||||||
|
enum TraitType : int8_t {
|
||||||
|
NullTrait = -1,
|
||||||
|
SkeletonModelURL,
|
||||||
|
FirstInstancedTrait,
|
||||||
|
AvatarEntity = FirstInstancedTrait,
|
||||||
|
TotalTraitTypes
|
||||||
|
};
|
||||||
|
|
||||||
|
using TraitInstanceID = QUuid;
|
||||||
|
|
||||||
|
inline bool isSimpleTrait(TraitType traitType) {
|
||||||
|
return traitType > NullTrait && traitType < FirstInstancedTrait;
|
||||||
|
}
|
||||||
|
|
||||||
|
using TraitVersion = int32_t;
|
||||||
|
const TraitVersion DEFAULT_TRAIT_VERSION = 0;
|
||||||
|
const TraitVersion NULL_TRAIT_VERSION = -1;
|
||||||
|
|
||||||
|
using TraitWireSize = int16_t;
|
||||||
|
const TraitWireSize DELETED_TRAIT_SIZE = -1;
|
||||||
|
|
||||||
|
inline qint64 packInstancedTraitDelete(TraitType traitType, TraitInstanceID instanceID, ExtendedIODevice& destination,
|
||||||
|
TraitVersion traitVersion = NULL_TRAIT_VERSION) {
|
||||||
|
qint64 bytesWritten = 0;
|
||||||
|
|
||||||
|
bytesWritten += destination.writePrimitive(traitType);
|
||||||
|
|
||||||
|
if (traitVersion > DEFAULT_TRAIT_VERSION) {
|
||||||
|
bytesWritten += destination.writePrimitive(traitVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesWritten += destination.write(instanceID.toRfc4122());
|
||||||
|
|
||||||
|
bytesWritten += destination.writePrimitive(DELETED_TRAIT_SIZE);
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_AvatarTraits_h
|
143
libraries/avatars/src/ClientTraitsHandler.cpp
Normal file
143
libraries/avatars/src/ClientTraitsHandler.cpp
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
//
|
||||||
|
// ClientTraitsHandler.cpp
|
||||||
|
// libraries/avatars/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 7/30/18.
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "ClientTraitsHandler.h"
|
||||||
|
|
||||||
|
#include <QtCore/QObject>
|
||||||
|
|
||||||
|
#include <NodeList.h>
|
||||||
|
#include <NLPacketList.h>
|
||||||
|
|
||||||
|
#include "AvatarData.h"
|
||||||
|
|
||||||
|
ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) :
|
||||||
|
_owningAvatar(owningAvatar)
|
||||||
|
{
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
QObject::connect(nodeList.data(), &NodeList::nodeAdded, [this](SharedNodePointer addedNode){
|
||||||
|
if (addedNode->getType() == NodeType::AvatarMixer) {
|
||||||
|
resetForNewMixer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientTraitsHandler::resetForNewMixer() {
|
||||||
|
// re-set the current version to 0
|
||||||
|
_currentTraitVersion = AvatarTraits::DEFAULT_TRAIT_VERSION;
|
||||||
|
|
||||||
|
// mark that all traits should be sent next time
|
||||||
|
_shouldPerformInitialSend = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientTraitsHandler::sendChangedTraitsToMixer() {
|
||||||
|
if (hasChangedTraits() || _shouldPerformInitialSend) {
|
||||||
|
// we have at least one changed trait to send
|
||||||
|
|
||||||
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
|
auto avatarMixer = nodeList->soloNodeOfType(NodeType::AvatarMixer);
|
||||||
|
if (!avatarMixer || !avatarMixer->getActiveSocket()) {
|
||||||
|
// we don't have an avatar mixer with an active socket, we can't send changed traits at this time
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have a mixer to send to, setup our set traits packet
|
||||||
|
auto traitsPacketList = NLPacketList::create(PacketType::SetAvatarTraits, QByteArray(), true, true);
|
||||||
|
|
||||||
|
// bump and write the current trait version to an extended header
|
||||||
|
// the trait version is the same for all traits in this packet list
|
||||||
|
traitsPacketList->writePrimitive(++_currentTraitVersion);
|
||||||
|
|
||||||
|
// take a copy of the set of changed traits and clear the stored set
|
||||||
|
auto traitStatusesCopy { _traitStatuses };
|
||||||
|
_traitStatuses.reset();
|
||||||
|
_hasChangedTraits = false;
|
||||||
|
|
||||||
|
auto simpleIt = traitStatusesCopy.simpleCBegin();
|
||||||
|
while (simpleIt != traitStatusesCopy.simpleCEnd()) {
|
||||||
|
// because the vector contains all trait types (for access using trait type as index)
|
||||||
|
// we double check that it is a simple iterator here
|
||||||
|
auto traitType = static_cast<AvatarTraits::TraitType>(std::distance(traitStatusesCopy.simpleCBegin(), simpleIt));
|
||||||
|
|
||||||
|
if (_shouldPerformInitialSend || *simpleIt == Updated) {
|
||||||
|
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||||
|
_owningAvatar->packTrait(traitType, *traitsPacketList);
|
||||||
|
|
||||||
|
// keep track of our skeleton version in case we get an override back
|
||||||
|
_currentSkeletonVersion = _currentTraitVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++simpleIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto instancedIt = traitStatusesCopy.instancedCBegin();
|
||||||
|
while (instancedIt != traitStatusesCopy.instancedCEnd()) {
|
||||||
|
for (auto& instanceIDValuePair : instancedIt->instances) {
|
||||||
|
if ((_shouldPerformInitialSend && instanceIDValuePair.value != Deleted)
|
||||||
|
|| instanceIDValuePair.value == Updated) {
|
||||||
|
// 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
|
||||||
|
_owningAvatar->packTraitInstance(instancedIt->traitType, instanceIDValuePair.id, *traitsPacketList);
|
||||||
|
} else if (!_shouldPerformInitialSend && instanceIDValuePair.value == Deleted) {
|
||||||
|
// pack delete for this trait instance
|
||||||
|
AvatarTraits::packInstancedTraitDelete(instancedIt->traitType, instanceIDValuePair.id,
|
||||||
|
*traitsPacketList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
++instancedIt;
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeList->sendPacketList(std::move(traitsPacketList), *avatarMixer);
|
||||||
|
|
||||||
|
// if this was an initial send of all traits, consider it completed
|
||||||
|
_shouldPerformInitialSend = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientTraitsHandler::processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||||
|
if (sendingNode->getType() == NodeType::AvatarMixer) {
|
||||||
|
while (message->getBytesLeftToRead()) {
|
||||||
|
AvatarTraits::TraitType traitType;
|
||||||
|
message->readPrimitive(&traitType);
|
||||||
|
|
||||||
|
AvatarTraits::TraitVersion traitVersion;
|
||||||
|
message->readPrimitive(&traitVersion);
|
||||||
|
|
||||||
|
AvatarTraits::TraitWireSize traitBinarySize;
|
||||||
|
message->readPrimitive(&traitBinarySize);
|
||||||
|
|
||||||
|
// only accept an override if this is for a trait type we override
|
||||||
|
// and the version matches what we last sent for skeleton
|
||||||
|
if (traitType == AvatarTraits::SkeletonModelURL
|
||||||
|
&& traitVersion == _currentSkeletonVersion
|
||||||
|
&& _traitStatuses[AvatarTraits::SkeletonModelURL] != Updated) {
|
||||||
|
|
||||||
|
// override the skeleton URL but do not mark the trait as having changed
|
||||||
|
// so that we don't unecessarily send a new trait packet to the mixer with the overriden URL
|
||||||
|
auto encodedSkeletonURL = QUrl::fromEncoded(message->readWithoutCopy(traitBinarySize));
|
||||||
|
|
||||||
|
auto hasChangesBefore = _hasChangedTraits;
|
||||||
|
|
||||||
|
_owningAvatar->setSkeletonModelURL(encodedSkeletonURL);
|
||||||
|
|
||||||
|
// setSkeletonModelURL will flag us for changes to the SkeletonModelURL so we reset some state here to
|
||||||
|
// avoid unnecessarily sending the overriden skeleton model URL back to the mixer
|
||||||
|
_traitStatuses.erase(AvatarTraits::SkeletonModelURL);
|
||||||
|
_hasChangedTraits = hasChangesBefore;
|
||||||
|
} else {
|
||||||
|
message->seek(message->getPosition() + traitBinarySize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
61
libraries/avatars/src/ClientTraitsHandler.h
Normal file
61
libraries/avatars/src/ClientTraitsHandler.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
//
|
||||||
|
// ClientTraitsHandler.h
|
||||||
|
// libraries/avatars/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 7/30/18.
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_ClientTraitsHandler_h
|
||||||
|
#define hifi_ClientTraitsHandler_h
|
||||||
|
|
||||||
|
#include <ReceivedMessage.h>
|
||||||
|
|
||||||
|
#include "AssociatedTraitValues.h"
|
||||||
|
#include "Node.h"
|
||||||
|
|
||||||
|
class AvatarData;
|
||||||
|
|
||||||
|
class ClientTraitsHandler : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ClientTraitsHandler(AvatarData* owningAvatar);
|
||||||
|
|
||||||
|
void sendChangedTraitsToMixer();
|
||||||
|
|
||||||
|
bool hasChangedTraits() { return _hasChangedTraits; }
|
||||||
|
|
||||||
|
void markTraitUpdated(AvatarTraits::TraitType updatedTrait)
|
||||||
|
{ _traitStatuses[updatedTrait] = Updated; _hasChangedTraits = true; }
|
||||||
|
void markInstancedTraitUpdated(AvatarTraits::TraitType traitType, QUuid updatedInstanceID)
|
||||||
|
{ _traitStatuses.instanceInsert(traitType, updatedInstanceID, Updated); _hasChangedTraits = true; }
|
||||||
|
void markInstancedTraitDeleted(AvatarTraits::TraitType traitType, QUuid deleteInstanceID)
|
||||||
|
{ _traitStatuses.instanceInsert(traitType, deleteInstanceID, Deleted); _hasChangedTraits = true; }
|
||||||
|
|
||||||
|
void resetForNewMixer();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void processTraitOverride(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum ClientTraitStatus {
|
||||||
|
Unchanged,
|
||||||
|
Updated,
|
||||||
|
Deleted
|
||||||
|
};
|
||||||
|
|
||||||
|
AvatarData* _owningAvatar;
|
||||||
|
|
||||||
|
AvatarTraits::AssociatedTraitValues<ClientTraitStatus, Unchanged> _traitStatuses;
|
||||||
|
AvatarTraits::TraitVersion _currentTraitVersion { AvatarTraits::DEFAULT_TRAIT_VERSION };
|
||||||
|
|
||||||
|
AvatarTraits::TraitVersion _currentSkeletonVersion { AvatarTraits::NULL_TRAIT_VERSION };
|
||||||
|
|
||||||
|
bool _shouldPerformInitialSend { false };
|
||||||
|
bool _hasChangedTraits { false };
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // hifi_ClientTraitsHandler_h
|
|
@ -269,6 +269,26 @@ QVector<AttachmentData> ScriptAvatarData::getAttachmentData() const {
|
||||||
// END
|
// END
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#if PR_BUILD || DEV_BUILD
|
||||||
|
//
|
||||||
|
// ENTITY PROPERTIES
|
||||||
|
// START
|
||||||
|
//
|
||||||
|
AvatarEntityMap ScriptAvatarData::getAvatarEntities() const {
|
||||||
|
AvatarEntityMap scriptEntityData;
|
||||||
|
|
||||||
|
if (AvatarSharedPointer sharedAvatarData = _avatarData.lock()) {
|
||||||
|
return sharedAvatarData->getAvatarEntityData();
|
||||||
|
}
|
||||||
|
|
||||||
|
return scriptEntityData;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// ENTITY PROPERTIES
|
||||||
|
// END
|
||||||
|
//
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// AUDIO PROPERTIES
|
// AUDIO PROPERTIES
|
||||||
|
|
|
@ -116,6 +116,10 @@ public:
|
||||||
Q_INVOKABLE QStringList getJointNames() const;
|
Q_INVOKABLE QStringList getJointNames() const;
|
||||||
Q_INVOKABLE QVector<AttachmentData> getAttachmentData() const;
|
Q_INVOKABLE QVector<AttachmentData> getAttachmentData() const;
|
||||||
|
|
||||||
|
#if DEV_BUILD || PR_BUILD
|
||||||
|
Q_INVOKABLE AvatarEntityMap getAvatarEntities() const;
|
||||||
|
#endif
|
||||||
|
|
||||||
//
|
//
|
||||||
// AUDIO PROPERTIES
|
// AUDIO PROPERTIES
|
||||||
//
|
//
|
||||||
|
|
|
@ -149,11 +149,6 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
|
||||||
|
|
||||||
void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityItemID) {
|
void EntityEditPacketSender::queueEraseEntityMessage(const EntityItemID& entityItemID) {
|
||||||
|
|
||||||
// in case this was a clientOnly entity:
|
|
||||||
if(_myAvatar) {
|
|
||||||
_myAvatar->clearAvatarEntity(entityItemID);
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityErase), 0);
|
QByteArray bufferOut(NLPacket::maxPayloadSize(PacketType::EntityErase), 0);
|
||||||
|
|
||||||
if (EntityItemProperties::encodeEraseEntityMessage(entityItemID, bufferOut)) {
|
if (EntityItemProperties::encodeEraseEntityMessage(entityItemID, bufferOut)) {
|
||||||
|
|
|
@ -27,7 +27,6 @@ public:
|
||||||
|
|
||||||
void setMyAvatar(AvatarData* myAvatar) { _myAvatar = myAvatar; }
|
void setMyAvatar(AvatarData* myAvatar) { _myAvatar = myAvatar; }
|
||||||
AvatarData* getMyAvatar() { return _myAvatar; }
|
AvatarData* getMyAvatar() { return _myAvatar; }
|
||||||
void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); }
|
|
||||||
|
|
||||||
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
|
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
|
||||||
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
|
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
|
||||||
|
|
|
@ -574,7 +574,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
|
||||||
_activityTracking.deletedEntityCount++;
|
_activityTracking.deletedEntityCount++;
|
||||||
|
|
||||||
EntityItemID entityID(id);
|
EntityItemID entityID(id);
|
||||||
bool shouldDelete = true;
|
bool shouldSendDeleteToServer = true;
|
||||||
|
|
||||||
// If we have a local entity tree set, then also update it.
|
// If we have a local entity tree set, then also update it.
|
||||||
if (_entityTree) {
|
if (_entityTree) {
|
||||||
|
@ -591,16 +591,21 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
|
||||||
auto avatarHashMap = DependencyManager::get<AvatarHashMap>();
|
auto avatarHashMap = DependencyManager::get<AvatarHashMap>();
|
||||||
AvatarSharedPointer myAvatar = avatarHashMap->getAvatarBySessionID(myNodeID);
|
AvatarSharedPointer myAvatar = avatarHashMap->getAvatarBySessionID(myNodeID);
|
||||||
myAvatar->insertDetachedEntityID(id);
|
myAvatar->insertDetachedEntityID(id);
|
||||||
shouldDelete = false;
|
shouldSendDeleteToServer = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entity->getLocked()) {
|
if (entity->getLocked()) {
|
||||||
shouldDelete = false;
|
shouldSendDeleteToServer = false;
|
||||||
} else {
|
} else {
|
||||||
// only delete local entities, server entities will round trip through the server filters
|
// only delete local entities, server entities will round trip through the server filters
|
||||||
if (entity->getClientOnly() || _entityTree->isServerlessMode()) {
|
if (entity->getClientOnly() || _entityTree->isServerlessMode()) {
|
||||||
|
shouldSendDeleteToServer = false;
|
||||||
_entityTree->deleteEntity(entityID);
|
_entityTree->deleteEntity(entityID);
|
||||||
|
|
||||||
|
if (entity->getClientOnly() && getEntityPacketSender()->getMyAvatar()) {
|
||||||
|
getEntityPacketSender()->getMyAvatar()->clearAvatarEntity(entityID, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -608,7 +613,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// if at this point, we know the id, and we should still delete the entity, send the update to the entity server
|
// if at this point, we know the id, and we should still delete the entity, send the update to the entity server
|
||||||
if (shouldDelete) {
|
if (shouldSendDeleteToServer) {
|
||||||
getEntityPacketSender()->queueEraseEntityMessage(entityID);
|
getEntityPacketSender()->queueEraseEntityMessage(entityID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
39
libraries/networking/src/ExtendedIODevice.h
Normal file
39
libraries/networking/src/ExtendedIODevice.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// ExtendedIODevice.h
|
||||||
|
// libraries/networking/src
|
||||||
|
//
|
||||||
|
// Created by Stephen Birarda on 8/7/18.
|
||||||
|
// Copyright 2018 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef hifi_ExtendedIODevice_h
|
||||||
|
#define hifi_ExtendedIODevice_h
|
||||||
|
|
||||||
|
#include <QtCore/QIODevice>
|
||||||
|
|
||||||
|
class ExtendedIODevice : public QIODevice {
|
||||||
|
public:
|
||||||
|
ExtendedIODevice(QObject* parent = nullptr) : QIODevice(parent) {};
|
||||||
|
|
||||||
|
template<typename T> qint64 peekPrimitive(T* data);
|
||||||
|
template<typename T> qint64 readPrimitive(T* data);
|
||||||
|
template<typename T> qint64 writePrimitive(const T& data);
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T> qint64 ExtendedIODevice::peekPrimitive(T* data) {
|
||||||
|
return peek(reinterpret_cast<char*>(data), sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> qint64 ExtendedIODevice::readPrimitive(T* data) {
|
||||||
|
return read(reinterpret_cast<char*>(data), sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> qint64 ExtendedIODevice::writePrimitive(const T& data) {
|
||||||
|
static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
|
||||||
|
return write(reinterpret_cast<const char*>(&data), sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // hifi_ExtendedIODevice_h
|
|
@ -11,13 +11,10 @@
|
||||||
|
|
||||||
#include "NodeData.h"
|
#include "NodeData.h"
|
||||||
|
|
||||||
NodeData::NodeData(const QUuid& nodeID) :
|
NodeData::NodeData(const QUuid& nodeID, NetworkPeer::LocalID nodeLocalID) :
|
||||||
_mutex(),
|
_mutex(),
|
||||||
_nodeID(nodeID)
|
_nodeID(nodeID),
|
||||||
|
_nodeLocalID(nodeLocalID)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NodeData::~NodeData() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#include <QtCore/QObject>
|
#include <QtCore/QObject>
|
||||||
#include <QtCore/QSharedPointer>
|
#include <QtCore/QSharedPointer>
|
||||||
|
|
||||||
|
#include "NetworkPeer.h"
|
||||||
#include "NLPacket.h"
|
#include "NLPacket.h"
|
||||||
#include "ReceivedMessage.h"
|
#include "ReceivedMessage.h"
|
||||||
|
|
||||||
|
@ -24,17 +25,19 @@ class Node;
|
||||||
class NodeData : public QObject {
|
class NodeData : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
NodeData(const QUuid& nodeID = QUuid());
|
NodeData(const QUuid& nodeID = QUuid(), NetworkPeer::LocalID localID = NetworkPeer::NULL_LOCAL_ID);
|
||||||
virtual ~NodeData() = 0;
|
virtual ~NodeData() = default;
|
||||||
virtual int parseData(ReceivedMessage& message) { return 0; }
|
virtual int parseData(ReceivedMessage& message) { return 0; }
|
||||||
|
|
||||||
const QUuid& getNodeID() const { return _nodeID; }
|
const QUuid& getNodeID() const { return _nodeID; }
|
||||||
|
NetworkPeer::LocalID getNodeLocalID() const { return _nodeLocalID; }
|
||||||
|
|
||||||
QMutex& getMutex() { return _mutex; }
|
QMutex& getMutex() { return _mutex; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMutex _mutex;
|
QMutex _mutex;
|
||||||
QUuid _nodeID;
|
QUuid _nodeID;
|
||||||
|
NetworkPeer::LocalID _nodeLocalID;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_NodeData_h
|
#endif // hifi_NodeData_h
|
||||||
|
|
|
@ -77,12 +77,6 @@ BasePacket::BasePacket(std::unique_ptr<char[]> data, qint64 size, const HifiSock
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
BasePacket::BasePacket(const BasePacket& other) :
|
|
||||||
QIODevice()
|
|
||||||
{
|
|
||||||
*this = other;
|
|
||||||
}
|
|
||||||
|
|
||||||
BasePacket& BasePacket::operator=(const BasePacket& other) {
|
BasePacket& BasePacket::operator=(const BasePacket& other) {
|
||||||
_packetSize = other._packetSize;
|
_packetSize = other._packetSize;
|
||||||
_packet = std::unique_ptr<char[]>(new char[_packetSize]);
|
_packet = std::unique_ptr<char[]>(new char[_packetSize]);
|
||||||
|
|
|
@ -16,16 +16,15 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QtCore/QIODevice>
|
|
||||||
|
|
||||||
#include <PortableHighResolutionClock.h>
|
#include <PortableHighResolutionClock.h>
|
||||||
|
|
||||||
#include "../HifiSockAddr.h"
|
#include "../HifiSockAddr.h"
|
||||||
#include "Constants.h"
|
#include "Constants.h"
|
||||||
|
#include "../ExtendedIODevice.h"
|
||||||
|
|
||||||
namespace udt {
|
namespace udt {
|
||||||
|
|
||||||
class BasePacket : public QIODevice {
|
class BasePacket : public ExtendedIODevice {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
static const qint64 PACKET_WRITE_ERROR;
|
static const qint64 PACKET_WRITE_ERROR;
|
||||||
|
@ -86,14 +85,10 @@ public:
|
||||||
void setReceiveTime(p_high_resolution_clock::time_point receiveTime) { _receiveTime = receiveTime; }
|
void setReceiveTime(p_high_resolution_clock::time_point receiveTime) { _receiveTime = receiveTime; }
|
||||||
p_high_resolution_clock::time_point getReceiveTime() const { return _receiveTime; }
|
p_high_resolution_clock::time_point getReceiveTime() const { return _receiveTime; }
|
||||||
|
|
||||||
template<typename T> qint64 peekPrimitive(T* data);
|
|
||||||
template<typename T> qint64 readPrimitive(T* data);
|
|
||||||
template<typename T> qint64 writePrimitive(const T& data);
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
BasePacket(qint64 size);
|
BasePacket(qint64 size);
|
||||||
BasePacket(std::unique_ptr<char[]> data, qint64 size, const HifiSockAddr& senderSockAddr);
|
BasePacket(std::unique_ptr<char[]> data, qint64 size, const HifiSockAddr& senderSockAddr);
|
||||||
BasePacket(const BasePacket& other);
|
BasePacket(const BasePacket& other) : ExtendedIODevice() { *this = other; }
|
||||||
BasePacket& operator=(const BasePacket& other);
|
BasePacket& operator=(const BasePacket& other);
|
||||||
BasePacket(BasePacket&& other);
|
BasePacket(BasePacket&& other);
|
||||||
BasePacket& operator=(BasePacket&& other);
|
BasePacket& operator=(BasePacket&& other);
|
||||||
|
@ -117,19 +112,6 @@ protected:
|
||||||
p_high_resolution_clock::time_point _receiveTime; // captures the time the packet received (only used on receiving end)
|
p_high_resolution_clock::time_point _receiveTime; // captures the time the packet received (only used on receiving end)
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename T> qint64 BasePacket::peekPrimitive(T* data) {
|
|
||||||
return peek(reinterpret_cast<char*>(data), sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T> qint64 BasePacket::readPrimitive(T* data) {
|
|
||||||
return read(reinterpret_cast<char*>(data), sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T> qint64 BasePacket::writePrimitive(const T& data) {
|
|
||||||
static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
|
|
||||||
return write(reinterpret_cast<const char*>(&data), sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace udt
|
} // namespace udt
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::AvatarData:
|
case PacketType::AvatarData:
|
||||||
case PacketType::BulkAvatarData:
|
case PacketType::BulkAvatarData:
|
||||||
case PacketType::KillAvatar:
|
case PacketType::KillAvatar:
|
||||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::FarGrabJoints);
|
return static_cast<PacketVersion>(AvatarMixerPacketVersion::MigrateAvatarEntitiesToTraits);
|
||||||
case PacketType::MessagesData:
|
case PacketType::MessagesData:
|
||||||
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
||||||
// ICE packets
|
// ICE packets
|
||||||
|
|
|
@ -56,7 +56,7 @@ public:
|
||||||
ICEServerPeerInformation,
|
ICEServerPeerInformation,
|
||||||
ICEServerQuery,
|
ICEServerQuery,
|
||||||
OctreeStats,
|
OctreeStats,
|
||||||
UNUSED_PACKET_TYPE_1,
|
SetAvatarTraits,
|
||||||
AvatarIdentityRequest,
|
AvatarIdentityRequest,
|
||||||
AssignmentClientStatus,
|
AssignmentClientStatus,
|
||||||
NoisyMute,
|
NoisyMute,
|
||||||
|
@ -133,6 +133,7 @@ public:
|
||||||
|
|
||||||
EntityClone,
|
EntityClone,
|
||||||
EntityQueryInitialResultsComplete,
|
EntityQueryInitialResultsComplete,
|
||||||
|
BulkAvatarTraits,
|
||||||
|
|
||||||
NUM_PACKET_TYPE
|
NUM_PACKET_TYPE
|
||||||
};
|
};
|
||||||
|
@ -289,7 +290,9 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
||||||
FBXReaderNodeReparenting,
|
FBXReaderNodeReparenting,
|
||||||
FixMannequinDefaultAvatarFeet,
|
FixMannequinDefaultAvatarFeet,
|
||||||
ProceduralFaceMovementFlagsAndBlendshapes,
|
ProceduralFaceMovementFlagsAndBlendshapes,
|
||||||
FarGrabJoints
|
FarGrabJoints,
|
||||||
|
MigrateSkeletonURLToTraits,
|
||||||
|
MigrateAvatarEntitiesToTraits
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DomainConnectRequestVersion : PacketVersion {
|
enum class DomainConnectRequestVersion : PacketVersion {
|
||||||
|
|
|
@ -14,8 +14,7 @@
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QtCore/QIODevice>
|
#include "../ExtendedIODevice.h"
|
||||||
|
|
||||||
#include "Packet.h"
|
#include "Packet.h"
|
||||||
#include "PacketHeaders.h"
|
#include "PacketHeaders.h"
|
||||||
|
|
||||||
|
@ -25,7 +24,7 @@ namespace udt {
|
||||||
|
|
||||||
class Packet;
|
class Packet;
|
||||||
|
|
||||||
class PacketList : public QIODevice {
|
class PacketList : public ExtendedIODevice {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
using MessageNumber = uint32_t;
|
using MessageNumber = uint32_t;
|
||||||
|
@ -59,9 +58,6 @@ public:
|
||||||
virtual bool isSequential() const override { return false; }
|
virtual bool isSequential() const override { return false; }
|
||||||
virtual qint64 size() const override { return getDataSize(); }
|
virtual qint64 size() const override { return getDataSize(); }
|
||||||
|
|
||||||
template<typename T> qint64 readPrimitive(T* data);
|
|
||||||
template<typename T> qint64 writePrimitive(const T& data);
|
|
||||||
|
|
||||||
qint64 writeString(const QString& string);
|
qint64 writeString(const QString& string);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -105,16 +101,6 @@ private:
|
||||||
QByteArray _extendedHeader;
|
QByteArray _extendedHeader;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename T> qint64 PacketList::readPrimitive(T* data) {
|
|
||||||
static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
|
|
||||||
return read(reinterpret_cast<char*>(data), sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> qint64 PacketList::writePrimitive(const T& data) {
|
|
||||||
static_assert(!std::is_pointer<T>::value, "T must not be a pointer");
|
|
||||||
return write(reinterpret_cast<const char*>(&data), sizeof(T));
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T> std::unique_ptr<T> PacketList::takeFront() {
|
template<typename T> std::unique_ptr<T> PacketList::takeFront() {
|
||||||
static_assert(std::is_base_of<Packet, T>::value, "T must derive from Packet.");
|
static_assert(std::is_base_of<Packet, T>::value, "T must derive from Packet.");
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue