Merge pull request #9642 from AndrewMeadows/beware-humbletim

fix performance problem for invalid "avatarEntityData"
This commit is contained in:
Seth Alves 2017-02-14 06:33:48 -08:00 committed by GitHub
commit ba025d44e0
9 changed files with 87 additions and 38 deletions

View file

@ -869,10 +869,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// connect to the packet sent signal of the _entityEditSender
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
// send the identity packet for our avatar each second to our avatar mixer
connect(&identityPacketTimer, &QTimer::timeout, myAvatar.get(), &MyAvatar::sendIdentityPacket);
identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
const char** constArgv = const_cast<const char**>(argv);
QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
bool success;

View file

@ -228,17 +228,43 @@ void Avatar::updateAvatarEntities() {
return;
}
bool success = true;
QScriptEngine scriptEngine;
entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData();
for (auto entityID : avatarEntities.keys()) {
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin();
while (dataItr != avatarEntities.end()) {
// compute hash of data. TODO? cache this?
QByteArray data = dataItr.value();
uint32_t newHash = qHash(data);
// check to see if we recognize this hash and whether it was already successfully processed
QUuid entityID = dataItr.key();
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
if (stateItr != _avatarEntityDataHashes.end()) {
if (stateItr.value().success) {
if (newHash == stateItr.value().hash) {
// data hasn't changed --> nothing to do
++dataItr;
continue;
}
} else {
// NOTE: if the data was unsuccessful in producing an entity in the past
// we will try again just in case something changed (unlikely).
// Unfortunately constantly trying to build the entity for this data costs
// CPU cycles that we'd rather not spend.
// TODO? put a maximum number of tries on this?
}
} else {
// remember this hash for the future
stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash));
}
++dataItr;
// see EntityEditPacketSender::queueEditEntityMessage for the other end of this. unpack properties
// and either add or update the entity.
QByteArray jsonByteArray = avatarEntities.value(entityID);
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(jsonByteArray);
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(data);
if (!jsonProperties.isObject()) {
qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(jsonByteArray.toHex());
qCDebug(interfaceapp) << "got bad avatarEntity json" << QString(data.toHex());
continue;
}
@ -266,8 +292,9 @@ void Avatar::updateAvatarEntities() {
properties.setScript(noScript);
}
// try to build the entity
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
bool success = true;
if (entity) {
if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote();
@ -280,6 +307,7 @@ void Avatar::updateAvatarEntities() {
success = false;
}
}
stateItr.value().success = success;
}
AvatarEntityIDs recentlyDettachedAvatarEntities = getAndClearRecentlyDetachedIDs();
@ -292,12 +320,18 @@ void Avatar::updateAvatarEntities() {
}
}
});
// remove stale data hashes
foreach (auto entityID, recentlyDettachedAvatarEntities) {
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
if (stateItr != _avatarEntityDataHashes.end()) {
_avatarEntityDataHashes.erase(stateItr);
}
}
}
});
if (success) {
setAvatarEntityDataChanged(false);
}
setAvatarEntityDataChanged(false);
}
bool Avatar::shouldDie() const {
@ -364,6 +398,9 @@ void Avatar::simulate(float deltaTime, bool inView) {
measureMotionDerivatives(deltaTime);
simulateAttachments(deltaTime);
updatePalms();
}
{
PROFILE_RANGE(simulation, "entities");
updateAvatarEntities();
}
}

View file

@ -269,6 +269,16 @@ protected:
private:
class AvatarEntityDataHash {
public:
AvatarEntityDataHash(uint32_t h) : hash(h) {};
uint32_t hash { 0 };
bool success { false };
};
using MapOfAvatarEntityDataHashes = QMap<QUuid, AvatarEntityDataHash>;
MapOfAvatarEntityDataHashes _avatarEntityDataHashes;
uint64_t _lastRenderUpdateTime { 0 };
int _leftPointerGeometryID { 0 };
int _rightPointerGeometryID { 0 };

View file

@ -376,7 +376,9 @@ void MyAvatar::update(float deltaTime) {
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
Q_ARG(glm::vec3, (halfBoundingBoxDimensions*2.0f)));
if (_avatarEntityDataLocallyEdited) {
uint64_t now = usecTimestampNow();
if (now > _identityPacketExpiry || _avatarEntityDataLocallyEdited) {
_identityPacketExpiry = now + AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS;
sendIdentityPacket();
}
@ -1212,7 +1214,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
setSkeletonModelURL(fullAvatarURL);
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
}
sendIdentityPacket();
_identityPacketExpiry = 0; // triggers an identity packet next update()
}
void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData) {

View file

@ -507,6 +507,8 @@ private:
std::mutex _holdActionsMutex;
std::vector<AvatarActionHold*> _holdActions;
uint64_t _identityPacketExpiry { 0 };
float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f };
float AUDIO_ENERGY_CONSTANT { 0.000001f };
float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f };

View file

@ -63,7 +63,6 @@ AvatarData::AvatarData() :
_handState(0),
_keyState(NO_KEY_DOWN),
_forceFaceTrackerConnected(false),
_hasNewJointData(true),
_headData(NULL),
_displayNameTargetAlpha(1.0f),
_displayNameAlpha(1.0f),
@ -703,7 +702,6 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) {
bool hasFaceTrackerInfo = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_FACE_TRACKER_INFO);
bool hasJointData = HAS_FLAG(packetStateFlags, AvatarDataPacket::PACKET_HAS_JOINT_DATA);
quint64 now = usecTimestampNow();
if (hasAvatarGlobalPosition) {
@ -2202,14 +2200,24 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
setAttachmentData(newAttachments);
}
const int MAX_NUM_AVATAR_ENTITIES = 42;
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "updateAvatarEntity", Q_ARG(const QUuid&, entityID), Q_ARG(QByteArray, entityData));
return;
}
_avatarEntitiesLock.withWriteLock([&] {
_avatarEntityData.insert(entityID, entityData);
_avatarEntityDataLocallyEdited = true;
AvatarEntityMap::iterator itr = _avatarEntityData.find(entityID);
if (itr == _avatarEntityData.end()) {
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
_avatarEntityData.insert(entityID, entityData);
_avatarEntityDataLocallyEdited = true;
}
} else {
itr.value() = entityData;
_avatarEntityDataLocallyEdited = true;
}
});
}
@ -2240,6 +2248,11 @@ AvatarEntityMap AvatarData::getAvatarEntityData() const {
}
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) {
// the data is suspect
qCDebug(avatars) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size();
return;
}
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "setAvatarEntityData", Q_ARG(const AvatarEntityMap&, avatarEntityData));
return;

View file

@ -552,7 +552,7 @@ public:
int getJointCount() { return _jointData.size(); }
QVector<JointData> getLastSentJointData() {
QVector<JointData> getLastSentJointData() {
QReadLocker readLock(&_jointDataLock);
_lastSentJointData.resize(_jointData.size());
return _lastSentJointData;
@ -614,7 +614,7 @@ protected:
KeyState _keyState;
bool _forceFaceTrackerConnected;
bool _hasNewJointData; // set in AvatarData, cleared in Avatar
bool _hasNewJointData { true }; // set in AvatarData, cleared in Avatar
HeadData* _headData { nullptr };

View file

@ -38,19 +38,7 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type,
EntityTreePointer entityTree,
EntityItemID entityItemID,
const EntityItemProperties& properties) {
if (!_shouldSend) {
return; // bail early
}
if (properties.getOwningAvatarID() != _myAvatar->getID()) {
return; // don't send updates for someone else's avatarEntity
}
assert(properties.getClientOnly());
// this is an avatar-based entity. update our avatar-data rather than sending to the entity-server
assert(_myAvatar);
if (!entityTree) {
qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage null entityTree.";
return;
@ -93,7 +81,8 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
return; // bail early
}
if (properties.getClientOnly()) {
if (properties.getClientOnly() && properties.getOwningAvatarID() == _myAvatar->getID()) {
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties);
return;
}

View file

@ -27,10 +27,6 @@ public:
AvatarData* getMyAvatar() { return _myAvatar; }
void clearAvatarEntity(QUuid entityID) { assert(_myAvatar); _myAvatar->clearAvatarEntity(entityID); }
void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree,
EntityItemID entityItemID, const EntityItemProperties& properties);
/// 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 case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
@ -48,6 +44,10 @@ public:
public slots:
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
private:
void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree,
EntityItemID entityItemID, const EntityItemProperties& properties);
private:
AvatarData* _myAvatar { nullptr };
QScriptEngine _scriptEngine;