Merge branch 'master' of https://github.com/highfidelity/hifi into Case20377

This commit is contained in:
Roxanne Skelly 2019-01-11 14:56:05 -08:00
commit 2566f633f3
22 changed files with 1275 additions and 492 deletions

View file

@ -157,7 +157,7 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) { if (traitSize == AvatarTraits::DELETED_TRAIT_SIZE) {
_avatar->processDeletedTraitInstance(traitType, instanceID); _avatar->processDeletedTraitInstance(traitType, instanceID);
// Mixer doesn't need deleted IDs. // Mixer doesn't need deleted IDs.
_avatar->getAndClearRecentlyDetachedIDs(); _avatar->getAndClearRecentlyRemovedIDs();
// to track a deleted instance but keep version information // to track a deleted instance but keep version information
// the avatar mixer uses the negative value of the sent version // the avatar mixer uses the negative value of the sent version

View file

@ -21,6 +21,8 @@
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <ResourceRequestObserver.h> #include <ResourceRequestObserver.h>
#include <AvatarLogging.h> #include <AvatarLogging.h>
#include <EntityItem.h>
#include <EntityItemProperties.h>
ScriptableAvatar::ScriptableAvatar() { ScriptableAvatar::ScriptableAvatar() {
@ -249,3 +251,157 @@ void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFace
void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) { void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) {
_headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement); _headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement);
} }
AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const {
// DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive!
// Avoid calling this method if possible.
AvatarEntityMap data;
QUuid sessionID = getID();
_avatarEntitiesLock.withReadLock([&] {
for (const auto& itr : _entities) {
QUuid id = itr.first;
EntityItemPointer entity = itr.second;
EntityItemProperties properties = entity->getProperties();
QByteArray blob;
EntityItemProperties::propertiesToBlob(_scriptEngine, sessionID, properties, blob);
data[id] = blob;
}
});
return data;
}
void ScriptableAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
// Note: this is an invokable Script call
// avatarEntityData is expected to be a map of QByteArrays that represent EntityItemProperties objects from JavaScript
//
if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) {
// the data is suspect
qCDebug(avatars) << "discard suspect avatarEntityData with size =" << avatarEntityData.size();
return;
}
// convert binary data to EntityItemProperties
// NOTE: this operation is NOT efficient
std::map<QUuid, EntityItemProperties> newProperties;
AvatarEntityMap::const_iterator dataItr = avatarEntityData.begin();
while (dataItr != avatarEntityData.end()) {
EntityItemProperties properties;
const QByteArray& blob = dataItr.value();
if (!blob.isNull() && EntityItemProperties::blobToProperties(_scriptEngine, blob, properties)) {
newProperties[dataItr.key()] = properties;
}
++dataItr;
}
// delete existing entities not found in avatarEntityData
std::vector<QUuid> idsToClear;
_avatarEntitiesLock.withWriteLock([&] {
std::map<QUuid, EntityItemPointer>::iterator entityItr = _entities.begin();
while (entityItr != _entities.end()) {
QUuid id = entityItr->first;
std::map<QUuid, EntityItemProperties>::const_iterator propertiesItr = newProperties.find(id);
if (propertiesItr == newProperties.end()) {
idsToClear.push_back(id);
entityItr = _entities.erase(entityItr);
} else {
++entityItr;
}
}
});
// add or update entities
_avatarEntitiesLock.withWriteLock([&] {
std::map<QUuid, EntityItemProperties>::const_iterator propertiesItr = newProperties.begin();
while (propertiesItr != newProperties.end()) {
QUuid id = propertiesItr->first;
const EntityItemProperties& properties = propertiesItr->second;
std::map<QUuid, EntityItemPointer>::iterator entityItr = _entities.find(id);
EntityItemPointer entity;
if (entityItr != _entities.end()) {
entity = entityItr->second;
entity->setProperties(properties);
} else {
entity = EntityTypes::constructEntityItem(id, properties);
}
if (entity) {
// build outgoing payload
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
EncodeBitstreamParams params;
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra);
if (appendState == OctreeElement::COMPLETED) {
_entities[id] = entity;
QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize());
storeAvatarEntityDataPayload(id, tempArray);
} else {
// payload doesn't fit
entityItr = _entities.find(id);
if (entityItr != _entities.end()) {
_entities.erase(entityItr);
idsToClear.push_back(id);
}
}
}
++propertiesItr;
}
});
// clear deleted traits
for (const auto& id : idsToClear) {
clearAvatarEntity(id);
}
}
void ScriptableAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
if (entityData.isNull()) {
// interpret this as a DELETE
std::map<QUuid, EntityItemPointer>::iterator itr = _entities.find(entityID);
if (itr != _entities.end()) {
_entities.erase(itr);
clearAvatarEntity(entityID);
}
return;
}
EntityItemPointer entity;
EntityItemProperties properties;
if (!EntityItemProperties::blobToProperties(_scriptEngine, entityData, properties)) {
// entityData is corrupt
return;
}
std::map<QUuid, EntityItemPointer>::iterator itr = _entities.find(entityID);
if (itr == _entities.end()) {
// this is an ADD
entity = EntityTypes::constructEntityItem(entityID, properties);
if (entity) {
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
EncodeBitstreamParams params;
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra);
if (appendState == OctreeElement::COMPLETED) {
_entities[entityID] = entity;
QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize());
storeAvatarEntityDataPayload(entityID, tempArray);
}
}
} else {
// this is an UPDATE
entity = itr->second;
bool somethingChanged = entity->setProperties(properties);
if (somethingChanged) {
OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
EncodeBitstreamParams params;
EntityTreeElementExtraEncodeDataPointer extra { nullptr };
OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra);
if (appendState == OctreeElement::COMPLETED) {
QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize());
storeAvatarEntityDataPayload(entityID, tempArray);
}
}
}
}

View file

@ -16,6 +16,7 @@
#include <AnimSkeleton.h> #include <AnimSkeleton.h>
#include <AvatarData.h> #include <AvatarData.h>
#include <ScriptEngine.h> #include <ScriptEngine.h>
#include <EntityItem.h>
/**jsdoc /**jsdoc
* The <code>Avatar</code> API is used to manipulate scriptable avatars on the domain. This API is a subset of the * The <code>Avatar</code> API is used to manipulate scriptable avatars on the domain. This API is a subset of the
@ -185,6 +186,26 @@ public:
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement); void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); } bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
/**jsdoc
* Potentially Very Expensive. Do not use.
* @function Avatar.getAvatarEntityData
* @returns {object}
*/
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const override;
/**jsdoc
* @function MyAvatar.setAvatarEntityData
* @param {object} avatarEntityData
*/
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override;
/**jsdoc
* @function MyAvatar.updateAvatarEntity
* @param {Uuid} entityID
* @param {string} entityData
*/
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override;
public slots: public slots:
void update(float deltatime); void update(float deltatime);
@ -202,6 +223,8 @@ private:
QHash<QString, int> _fstJointIndices; ///< 1-based, since zero is returned for missing keys QHash<QString, int> _fstJointIndices; ///< 1-based, since zero is returned for missing keys
QStringList _fstJointNames; ///< in order of depth-first traversal QStringList _fstJointNames; ///< in order of depth-first traversal
QUrl _skeletonFBXURL; QUrl _skeletonFBXURL;
mutable QScriptEngine _scriptEngine;
std::map<QUuid, EntityItemPointer> _entities;
/// Loads the joint indices, names from the FST file (if any) /// Loads the joint indices, names from the FST file (if any)
void updateJointMappings(); void updateJointMappings();

View file

@ -2459,6 +2459,9 @@ void Application::updateHeartbeat() const {
} }
void Application::onAboutToQuit() { void Application::onAboutToQuit() {
// quickly save AvatarEntityData before the EntityTree is dismantled
getMyAvatar()->saveAvatarEntityDataToSettings();
emit beforeAboutToQuit(); emit beforeAboutToQuit();
if (getLoginDialogPoppedUp() && _firstRun.get()) { if (getLoginDialogPoppedUp() && _firstRun.get()) {
@ -6753,8 +6756,10 @@ void Application::updateWindowTitle() const {
} }
void Application::clearDomainOctreeDetails() { void Application::clearDomainOctreeDetails() {
// before we delete all entities get MyAvatar's AvatarEntityData ready
getMyAvatar()->prepareAvatarEntityDataForReload();
// if we're about to quit, we really don't need to do any of these things... // if we're about to quit, we really don't need to do the rest of these things...
if (_aboutToQuit) { if (_aboutToQuit) {
return; return;
} }
@ -6782,8 +6787,6 @@ void Application::clearDomainOctreeDetails() {
ShaderCache::instance().clearUnusedResources(); ShaderCache::instance().clearUnusedResources();
DependencyManager::get<TextureCache>()->clearUnusedResources(); DependencyManager::get<TextureCache>()->clearUnusedResources();
DependencyManager::get<recording::ClipCache>()->clearUnusedResources(); DependencyManager::get<recording::ClipCache>()->clearUnusedResources();
getMyAvatar()->setAvatarEntityDataChanged(true);
} }
void Application::domainURLChanged(QUrl domainURL) { void Application::domainURLChanged(QUrl domainURL) {

View file

@ -50,6 +50,7 @@
#include <RecordingScriptingInterface.h> #include <RecordingScriptingInterface.h>
#include <trackers/FaceTracker.h> #include <trackers/FaceTracker.h>
#include <RenderableModelEntityItem.h> #include <RenderableModelEntityItem.h>
#include <VariantMapToScriptValue.h>
#include "MyHead.h" #include "MyHead.h"
#include "MySkeletonModel.h" #include "MySkeletonModel.h"
@ -281,6 +282,8 @@ MyAvatar::MyAvatar(QThread* thread) :
MyAvatar::~MyAvatar() { MyAvatar::~MyAvatar() {
_lookAtTargetAvatar.reset(); _lookAtTargetAvatar.reset();
delete _myScriptEngine;
_myScriptEngine = nullptr;
} }
void MyAvatar::setDominantHand(const QString& hand) { void MyAvatar::setDominantHand(const QString& hand) {
@ -671,7 +674,7 @@ void MyAvatar::update(float deltaTime) {
_clientTraitsHandler->sendChangedTraitsToMixer(); _clientTraitsHandler->sendChangedTraitsToMixer();
simulate(deltaTime); simulate(deltaTime, true);
currentEnergy += energyChargeRate; currentEnergy += energyChargeRate;
currentEnergy -= getAccelerationEnergy(); currentEnergy -= getAccelerationEnergy();
@ -743,7 +746,7 @@ void MyAvatar::updateChildCauterization(SpatiallyNestablePointer object, bool ca
} }
} }
void MyAvatar::simulate(float deltaTime) { void MyAvatar::simulate(float deltaTime, bool inView) {
PerformanceTimer perfTimer("simulate"); PerformanceTimer perfTimer("simulate");
animateScaleChanges(deltaTime); animateScaleChanges(deltaTime);
@ -887,7 +890,7 @@ void MyAvatar::simulate(float deltaTime) {
_characterController.setCollisionlessAllowed(collisionlessAllowed); _characterController.setCollisionlessAllowed(collisionlessAllowed);
} }
updateAvatarEntities(); handleChangedAvatarEntityData();
updateFadingStatus(); updateFadingStatus();
} }
@ -1251,7 +1254,7 @@ void MyAvatar::saveAvatarUrl() {
} }
} }
void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex) { void MyAvatar::resizeAvatarEntitySettingHandles(uint32_t maxIndex) {
// The original Settings interface saved avatar-entity array data like this: // The original Settings interface saved avatar-entity array data like this:
// Avatar/avatarEntityData/size: 5 // Avatar/avatarEntityData/size: 5
// Avatar/avatarEntityData/1/id: ... // Avatar/avatarEntityData/1/id: ...
@ -1261,14 +1264,15 @@ void MyAvatar::resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex)
// Avatar/avatarEntityData/5/properties: ... // Avatar/avatarEntityData/5/properties: ...
// //
// Create Setting::Handles to mimic this. // Create Setting::Handles to mimic this.
uint32_t settingsIndex = (uint32_t)_avatarEntityIDSettings.size() + 1;
while (_avatarEntityIDSettings.size() <= avatarEntityIndex) { while (settingsIndex <= maxIndex) {
Setting::Handle<QUuid> idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" Setting::Handle<QUuid> idHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
<< QString::number(avatarEntityIndex + 1) << "id", QUuid()); << QString::number(settingsIndex) << "id", QUuid());
_avatarEntityIDSettings.push_back(idHandle); _avatarEntityIDSettings.push_back(idHandle);
Setting::Handle<QByteArray> dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData" Setting::Handle<QByteArray> dataHandle(QStringList() << AVATAR_SETTINGS_GROUP_NAME << "avatarEntityData"
<< QString::number(avatarEntityIndex + 1) << "properties", QByteArray()); << QString::number(settingsIndex) << "properties", QByteArray());
_avatarEntityDataSettings.push_back(dataHandle); _avatarEntityDataSettings.push_back(dataHandle);
settingsIndex++;
} }
} }
@ -1298,26 +1302,54 @@ void MyAvatar::saveData() {
_flyingHMDSetting.set(getFlyingHMDPref()); _flyingHMDSetting.set(getFlyingHMDPref());
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>(); auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
_avatarEntitiesLock.withReadLock([&] { saveAvatarEntityDataToSettings();
QList<QUuid> avatarEntityIDs = _avatarEntityData.keys(); }
unsigned int avatarEntityCount = avatarEntityIDs.size();
unsigned int previousAvatarEntityCount = _avatarEntityCountSetting.get(0);
resizeAvatarEntitySettingHandles(std::max<unsigned int>(avatarEntityCount, previousAvatarEntityCount));
_avatarEntityCountSetting.set(avatarEntityCount);
unsigned int avatarEntityIndex = 0; void MyAvatar::saveAvatarEntityDataToSettings() {
for (auto entityID : avatarEntityIDs) { if (!_needToSaveAvatarEntitySettings) {
_avatarEntityIDSettings[avatarEntityIndex].set(entityID); return;
_avatarEntityDataSettings[avatarEntityIndex].set(_avatarEntityData.value(entityID)); }
avatarEntityIndex++; bool success = updateStaleAvatarEntityBlobs();
} if (!success) {
return;
}
_needToSaveAvatarEntitySettings = false;
// clean up any left-over (due to the list shrinking) slots uint32_t numEntities = (uint32_t)_cachedAvatarEntityBlobs.size();
for (; avatarEntityIndex < previousAvatarEntityCount; avatarEntityIndex++) { uint32_t prevNumEntities = _avatarEntityCountSetting.get(0);
_avatarEntityIDSettings[avatarEntityIndex].remove(); resizeAvatarEntitySettingHandles(std::max<uint32_t>(numEntities, prevNumEntities));
_avatarEntityDataSettings[avatarEntityIndex].remove();
// save new Settings
if (numEntities > 0) {
// save all unfortunately-formatted-binary-blobs to Settings
_avatarEntitiesLock.withWriteLock([&] {
uint32_t i = 0;
AvatarEntityMap::const_iterator itr = _cachedAvatarEntityBlobs.begin();
while (itr != _cachedAvatarEntityBlobs.end()) {
_avatarEntityIDSettings[i].set(itr.key());
_avatarEntityDataSettings[i].set(itr.value());
++itr;
++i;
}
numEntities = i;
});
}
_avatarEntityCountSetting.set(numEntities);
// remove old Settings if any
if (numEntities < prevNumEntities) {
uint32_t numEntitiesToRemove = prevNumEntities - numEntities;
for (uint32_t i = 0; i < numEntitiesToRemove; ++i) {
if (_avatarEntityIDSettings.size() > numEntities) {
_avatarEntityIDSettings.back().remove();
_avatarEntityIDSettings.pop_back();
}
if (_avatarEntityDataSettings.size() > numEntities) {
_avatarEntityDataSettings.back().remove();
_avatarEntityDataSettings.pop_back();
}
} }
}); }
} }
float loadSetting(Settings& settings, const QString& name, float defaultValue) { float loadSetting(Settings& settings, const QString& name, float defaultValue) {
@ -1414,7 +1446,410 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
_skeletonModel->getRig().setEnableInverseKinematics(isEnabled); _skeletonModel->getRig().setEnableInverseKinematics(isEnabled);
} }
void MyAvatar::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) {
AvatarData::storeAvatarEntityDataPayload(entityID, payload);
_avatarEntitiesLock.withWriteLock([&] {
_cachedAvatarEntityBlobsToAddOrUpdate.push_back(entityID);
});
}
void MyAvatar::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) {
AvatarData::clearAvatarEntity(entityID, requiresRemovalFromTree);
_avatarEntitiesLock.withWriteLock([&] {
_cachedAvatarEntityBlobsToDelete.push_back(entityID);
});
}
void MyAvatar::sanitizeAvatarEntityProperties(EntityItemProperties& properties) const {
properties.setEntityHostType(entity::HostType::AVATAR);
properties.setOwningAvatarID(getID());
// there's no entity-server to tell us we're the simulation owner, so always set the
// simulationOwner to the owningAvatarID and a high priority.
properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY);
if (properties.getParentID() == AVATAR_SELF_ID) {
properties.setParentID(getID());
}
// When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed
// they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear.
// The thinking here is the local position was noticed as changing, but not the parentID (since it is now
// back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this,
// and seems safe (per Seth).
properties.markAllChanged();
}
void MyAvatar::handleChangedAvatarEntityData() {
// NOTE: this is a per-frame update
if (getID().isNull() ||
getID() == AVATAR_SELF_ID ||
DependencyManager::get<NodeList>()->getSessionUUID() == QUuid()) {
// wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong:
// things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent".
return;
}
if (_reloadAvatarEntityDataFromSettings) {
loadAvatarEntityDataFromSettings();
}
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return;
}
// We collect changes to AvatarEntities and then handle them all in one spot per frame: handleChangedAvatarEntityData().
// Basically this is a "transaction pattern" with an extra complication: these changes can come from two
// "directions" and the "authoritative source" of each direction is different, so we maintain two distinct sets
// of transaction lists:
//
// The _entitiesToDelete/Add/Update lists are for changes whose "authoritative sources" are already
// correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and
// setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to
// real EntityItems.
//
// The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are
// already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs
// and eventually to Settings.
//
// The DELETEs also need to be propagated to the traits, which will eventually propagate to
// AvatarData::_packedAvatarEntityData via deeper logic.
// move the lists to minimize lock time
std::vector<QUuid> cachedBlobsToDelete;
std::vector<QUuid> cachedBlobsToUpdate;
std::vector<QUuid> entitiesToDelete;
std::vector<QUuid> entitiesToAdd;
std::vector<QUuid> entitiesToUpdate;
_avatarEntitiesLock.withWriteLock([&] {
cachedBlobsToDelete = std::move(_cachedAvatarEntityBlobsToDelete);
cachedBlobsToUpdate = std::move(_cachedAvatarEntityBlobsToAddOrUpdate);
entitiesToDelete = std::move(_entitiesToDelete);
entitiesToAdd = std::move(_entitiesToAdd);
entitiesToUpdate = std::move(_entitiesToUpdate);
});
auto removeAllInstancesHelper = [] (const QUuid& id, std::vector<QUuid>& v) {
uint32_t i = 0;
while (i < v.size()) {
if (id == v[i]) {
v[i] = v.back();
v.pop_back();
} else {
++i;
}
}
};
// remove delete-add and delete-update overlap
for (const auto& id : entitiesToDelete) {
removeAllInstancesHelper(id, cachedBlobsToUpdate);
removeAllInstancesHelper(id, entitiesToAdd);
removeAllInstancesHelper(id, entitiesToUpdate);
}
for (const auto& id : cachedBlobsToDelete) {
removeAllInstancesHelper(id, entitiesToUpdate);
removeAllInstancesHelper(id, cachedBlobsToUpdate);
}
for (const auto& id : entitiesToAdd) {
removeAllInstancesHelper(id, entitiesToUpdate);
}
// DELETE real entities
for (const auto& id : entitiesToDelete) {
entityTree->withWriteLock([&] {
entityTree->deleteEntity(id);
});
}
// ADD real entities
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
for (const auto& id : entitiesToAdd) {
bool blobFailed = false;
EntityItemProperties properties;
_avatarEntitiesLock.withReadLock([&] {
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id);
if (itr == _cachedAvatarEntityBlobs.end()) {
blobFailed = true; // blob doesn't exist
return;
}
if (!EntityItemProperties::blobToProperties(*_myScriptEngine, itr.value(), properties)) {
blobFailed = true; // blob is corrupt
}
});
if (blobFailed) {
// remove from _cachedAvatarEntityBlobUpdatesToSkip just in case:
// avoids a resource leak when blob updates to be skipped are never actually skipped
// when the blob fails to result in a real EntityItem
_avatarEntitiesLock.withWriteLock([&] {
removeAllInstancesHelper(id, _cachedAvatarEntityBlobUpdatesToSkip);
});
continue;
}
sanitizeAvatarEntityProperties(properties);
entityTree->withWriteLock([&] {
EntityItemPointer entity = entityTree->addEntity(id, properties);
if (entity) {
packetSender->queueEditEntityMessage(PacketType::EntityAdd, entityTree, id, properties);
}
});
}
// CHANGE real entities
for (const auto& id : entitiesToUpdate) {
EntityItemProperties properties;
bool skip = false;
_avatarEntitiesLock.withReadLock([&] {
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id);
if (itr == _cachedAvatarEntityBlobs.end()) {
skip = true;
return;
}
if (!EntityItemProperties::blobToProperties(*_myScriptEngine, itr.value(), properties)) {
skip = true;
}
});
sanitizeAvatarEntityProperties(properties);
entityTree->withWriteLock([&] {
entityTree->updateEntity(id, properties);
});
}
// DELETE cached blobs
_avatarEntitiesLock.withWriteLock([&] {
for (const auto& id : cachedBlobsToDelete) {
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(id);
// remove blob and remember to remove from settings
if (itr != _cachedAvatarEntityBlobs.end()) {
_cachedAvatarEntityBlobs.erase(itr);
_needToSaveAvatarEntitySettings = true;
}
// also remove from list of stale blobs to avoid failed entity lookup later
std::set<QUuid>::iterator blobItr = _staleCachedAvatarEntityBlobs.find(id);
if (blobItr != _staleCachedAvatarEntityBlobs.end()) {
_staleCachedAvatarEntityBlobs.erase(blobItr);
}
// also remove from _cachedAvatarEntityBlobUpdatesToSkip just in case:
// avoids a resource leak when things are deleted before they could be skipped
removeAllInstancesHelper(id, _cachedAvatarEntityBlobUpdatesToSkip);
}
});
// ADD/UPDATE cached blobs
for (const auto& id : cachedBlobsToUpdate) {
// computing the blobs is expensive and we want to avoid it when possible
// so we add these ids to _staleCachedAvatarEntityBlobs for later
// and only build the blobs when absolutely necessary
bool skip = false;
uint32_t i = 0;
_avatarEntitiesLock.withWriteLock([&] {
while (i < _cachedAvatarEntityBlobUpdatesToSkip.size()) {
if (id == _cachedAvatarEntityBlobUpdatesToSkip[i]) {
_cachedAvatarEntityBlobUpdatesToSkip[i] = _cachedAvatarEntityBlobUpdatesToSkip.back();
_cachedAvatarEntityBlobUpdatesToSkip.pop_back();
skip = true;
break; // assume no duplicates
} else {
++i;
}
}
});
if (!skip) {
_staleCachedAvatarEntityBlobs.insert(id);
_needToSaveAvatarEntitySettings = true;
}
}
// DELETE traits
// (no need to worry about the ADDs and UPDATEs: each will be handled when the interface
// tries to send a real update packet (via AvatarData::storeAvatarEntityDataPayload()))
if (_clientTraitsHandler) {
// we have a client traits handler
// flag removed entities as deleted so that changes are sent next frame
_avatarEntitiesLock.withWriteLock([&] {
for (const auto& id : entitiesToDelete) {
if (_packedAvatarEntityData.find(id) != _packedAvatarEntityData.end()) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, id);
}
}
for (const auto& id : cachedBlobsToDelete) {
if (_packedAvatarEntityData.find(id) != _packedAvatarEntityData.end()) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, id);
}
}
});
}
}
bool MyAvatar::updateStaleAvatarEntityBlobs() const {
// call this right before you actually need to use the blobs
//
// Note: this method is const (and modifies mutable data members)
// so we can call it at the Last Minute inside other const methods
//
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return false;
}
std::set<QUuid> staleBlobs = std::move(_staleCachedAvatarEntityBlobs);
int32_t numFound = 0;
for (const auto& id : staleBlobs) {
bool found = false;
EntityItemProperties properties;
entityTree->withReadLock([&] {
EntityItemPointer entity = entityTree->findEntityByID(id);
if (entity) {
properties = entity->getProperties();
found = true;
}
});
if (found) {
++numFound;
QByteArray blob;
EntityItemProperties::propertiesToBlob(*_myScriptEngine, getID(), properties, blob);
_avatarEntitiesLock.withWriteLock([&] {
_cachedAvatarEntityBlobs[id] = blob;
});
}
}
return true;
}
void MyAvatar::prepareAvatarEntityDataForReload() {
saveAvatarEntityDataToSettings();
_avatarEntitiesLock.withWriteLock([&] {
_packedAvatarEntityData.clear();
_entitiesToDelete.clear();
_entitiesToAdd.clear();
_entitiesToUpdate.clear();
_cachedAvatarEntityBlobs.clear();
_cachedAvatarEntityBlobsToDelete.clear();
_cachedAvatarEntityBlobsToAddOrUpdate.clear();
_cachedAvatarEntityBlobUpdatesToSkip.clear();
});
_reloadAvatarEntityDataFromSettings = true;
}
AvatarEntityMap MyAvatar::getAvatarEntityData() const {
// NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs
updateStaleAvatarEntityBlobs();
AvatarEntityMap result;
_avatarEntitiesLock.withReadLock([&] {
result = _cachedAvatarEntityBlobs;
});
return result;
}
void MyAvatar::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
// Note: this is an invokable Script call
// avatarEntityData is expected to be a map of QByteArrays that represent EntityItemProperties objects from JavaScript,
// aka: unfortunately-formatted-binary-blobs because we store them in non-human-readable format in Settings.
//
if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) {
// the data is suspect
qCDebug(interfaceapp) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size();
return;
}
// this overwrites ALL AvatarEntityData so we clear pending operations
_avatarEntitiesLock.withWriteLock([&] {
_packedAvatarEntityData.clear();
_entitiesToDelete.clear();
_entitiesToAdd.clear();
_entitiesToUpdate.clear();
});
_needToSaveAvatarEntitySettings = true;
_avatarEntitiesLock.withWriteLock([&] {
// find new and updated IDs
AvatarEntityMap::const_iterator constItr = avatarEntityData.begin();
while (constItr != avatarEntityData.end()) {
QUuid id = constItr.key();
if (_cachedAvatarEntityBlobs.find(id) == _cachedAvatarEntityBlobs.end()) {
_entitiesToAdd.push_back(id);
} else {
_entitiesToUpdate.push_back(id);
}
++constItr;
}
// find and erase deleted IDs from _cachedAvatarEntityBlobs
std::vector<QUuid> deletedIDs;
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.begin();
while (itr != _cachedAvatarEntityBlobs.end()) {
QUuid id = itr.key();
if (std::find(_entitiesToUpdate.begin(), _entitiesToUpdate.end(), id) == _entitiesToUpdate.end()) {
deletedIDs.push_back(id);
itr = _cachedAvatarEntityBlobs.erase(itr);
} else {
++itr;
}
}
// copy new data
constItr = avatarEntityData.begin();
while (constItr != avatarEntityData.end()) {
_cachedAvatarEntityBlobs.insert(constItr.key(), constItr.value());
++constItr;
}
// erase deleted IDs from _packedAvatarEntityData
for (const auto& id : deletedIDs) {
itr = _packedAvatarEntityData.find(id);
if (itr != _packedAvatarEntityData.end()) {
_packedAvatarEntityData.erase(itr);
} else {
++itr;
}
_entitiesToDelete.push_back(id);
}
});
}
void MyAvatar::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
// NOTE: this is an invokable Script call
// TODO: we should handle the case where entityData is corrupt or invalid
// BEFORE we store into _cachedAvatarEntityBlobs
_needToSaveAvatarEntitySettings = true;
_avatarEntitiesLock.withWriteLock([&] {
AvatarEntityMap::iterator itr = _cachedAvatarEntityBlobs.find(entityID);
if (itr != _cachedAvatarEntityBlobs.end()) {
_entitiesToUpdate.push_back(entityID);
itr.value() = entityData;
} else {
_entitiesToAdd.push_back(entityID);
_cachedAvatarEntityBlobs.insert(entityID, entityData);
}
});
}
void MyAvatar::avatarEntityDataToJson(QJsonObject& root) const {
updateStaleAvatarEntityBlobs();
_avatarEntitiesLock.withReadLock([&] {
if (!_cachedAvatarEntityBlobs.empty()) {
QJsonArray avatarEntityJson;
int entityCount = 0;
AvatarEntityMap::const_iterator itr = _cachedAvatarEntityBlobs.begin();
while (itr != _cachedAvatarEntityBlobs.end()) {
QVariantMap entityData;
QUuid id = _avatarEntityForRecording.size() == _cachedAvatarEntityBlobs.size() ? _avatarEntityForRecording.values()[entityCount++] : itr.key();
entityData.insert("id", id);
entityData.insert("properties", itr.value().toBase64());
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
++itr;
}
const QString JSON_AVATAR_ENTITIES = QStringLiteral("attachedEntities");
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
}
});
}
void MyAvatar::loadData() { void MyAvatar::loadData() {
if (!_myScriptEngine) {
_myScriptEngine = new QScriptEngine();
}
getHead()->setBasePitch(_headPitchSetting.get()); getHead()->setBasePitch(_headPitchSetting.get());
_yawSpeed = _yawSpeedSetting.get(_yawSpeed); _yawSpeed = _yawSpeedSetting.get(_yawSpeed);
@ -1426,14 +1861,7 @@ void MyAvatar::loadData() {
useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName); useFullAvatarURL(_fullAvatarURLFromPreferences, _fullAvatarModelName);
int avatarEntityCount = _avatarEntityCountSetting.get(0); loadAvatarEntityDataFromSettings();
for (int i = 0; i < avatarEntityCount; i++) {
resizeAvatarEntitySettingHandles(i);
// QUuid entityID = QUuid::createUuid(); // generate a new ID
QUuid entityID = _avatarEntityIDSettings[i].get(QUuid());
QByteArray properties = _avatarEntityDataSettings[i].get();
updateAvatarEntity(entityID, properties);
}
// 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 };
@ -1455,6 +1883,38 @@ void MyAvatar::loadData() {
setEnableDebugDrawPosition(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawPosition)); setEnableDebugDrawPosition(Menu::getInstance()->isOptionChecked(MenuOption::AnimDebugDrawPosition));
} }
void MyAvatar::loadAvatarEntityDataFromSettings() {
// this overwrites ALL AvatarEntityData so we clear pending operations
_avatarEntitiesLock.withWriteLock([&] {
_packedAvatarEntityData.clear();
_entitiesToDelete.clear();
_entitiesToAdd.clear();
_entitiesToUpdate.clear();
});
_reloadAvatarEntityDataFromSettings = false;
_needToSaveAvatarEntitySettings = false;
int numEntities = _avatarEntityCountSetting.get(0);
if (numEntities == 0) {
return;
}
resizeAvatarEntitySettingHandles(numEntities);
_avatarEntitiesLock.withWriteLock([&] {
_entitiesToAdd.reserve(numEntities);
// TODO: build map between old and new IDs so we can restitch parent-child relationships
for (int i = 0; i < numEntities; i++) {
QUuid id = QUuid::createUuid(); // generate a new ID
_cachedAvatarEntityBlobs[id] = _avatarEntityDataSettings[i].get();
_entitiesToAdd.push_back(id);
// this blob is the "authoritative source" for this AvatarEntity and we want to avoid overwriting it
// (the outgoing update packet will flag it for save-back into the blob)
// which is why we remember its id: to skip its save-back later
_cachedAvatarEntityBlobUpdatesToSkip.push_back(id);
}
});
}
void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const { void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const {
Settings settings; Settings settings;
settings.beginGroup("savedAttachmentData"); settings.beginGroup("savedAttachmentData");
@ -1906,8 +2366,11 @@ void MyAvatar::clearAvatarEntities() {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
AvatarEntityMap avatarEntities = getAvatarEntityData(); QList<QUuid> avatarEntityIDs;
for (auto entityID : avatarEntities.keys()) { _avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
for (const auto& entityID : avatarEntityIDs) {
entityTree->withWriteLock([&entityID, &entityTree] { entityTree->withWriteLock([&entityID, &entityTree] {
// remove this entity first from the entity tree // remove this entity first from the entity tree
entityTree->deleteEntity(entityID, true, true); entityTree->deleteEntity(entityID, true, true);
@ -1922,10 +2385,12 @@ void MyAvatar::clearAvatarEntities() {
void MyAvatar::removeWearableAvatarEntities() { void MyAvatar::removeWearableAvatarEntities() {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) { if (entityTree) {
AvatarEntityMap avatarEntities = getAvatarEntityData(); QList<QUuid> avatarEntityIDs;
for (auto entityID : avatarEntities.keys()) { _avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
for (const auto& entityID : avatarEntityIDs) {
auto entity = entityTree->findEntityByID(entityID); auto entity = entityTree->findEntityByID(entityID);
if (entity && isWearableEntity(entity)) { if (entity && isWearableEntity(entity)) {
entityTree->withWriteLock([&entityID, &entityTree] { entityTree->withWriteLock([&entityID, &entityTree] {
@ -1942,13 +2407,16 @@ void MyAvatar::removeWearableAvatarEntities() {
} }
QVariantList MyAvatar::getAvatarEntitiesVariant() { QVariantList MyAvatar::getAvatarEntitiesVariant() {
// NOTE: this method is NOT efficient
QVariantList avatarEntitiesData; QVariantList avatarEntitiesData;
QScriptEngine scriptEngine;
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) { if (entityTree) {
AvatarEntityMap avatarEntities = getAvatarEntityData(); QList<QUuid> avatarEntityIDs;
for (auto entityID : avatarEntities.keys()) { _avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
for (const auto& entityID : avatarEntityIDs) {
auto entity = entityTree->findEntityByID(entityID); auto entity = entityTree->findEntityByID(entityID);
if (!entity) { if (!entity) {
continue; continue;
@ -1959,7 +2427,7 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() {
desiredProperties += PROP_LOCAL_POSITION; desiredProperties += PROP_LOCAL_POSITION;
desiredProperties += PROP_LOCAL_ROTATION; desiredProperties += PROP_LOCAL_ROTATION;
EntityItemProperties entityProperties = entity->getProperties(desiredProperties); EntityItemProperties entityProperties = entity->getProperties(desiredProperties);
QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(&scriptEngine, entityProperties); QScriptValue scriptProperties = EntityItemPropertiesToScriptValue(_myScriptEngine, entityProperties);
avatarEntityData["properties"] = scriptProperties.toVariant(); avatarEntityData["properties"] = scriptProperties.toVariant();
avatarEntitiesData.append(QVariant(avatarEntityData)); avatarEntitiesData.append(QVariant(avatarEntityData));
} }
@ -2348,17 +2816,17 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData)
} }
QVector<AttachmentData> MyAvatar::getAttachmentData() const { QVector<AttachmentData> MyAvatar::getAttachmentData() const {
QVector<AttachmentData> avatarData; QVector<AttachmentData> attachmentData;
auto avatarEntities = getAvatarEntityData(); QList<QUuid> avatarEntityIDs;
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); _avatarEntitiesLock.withReadLock([&] {
while (dataItr != avatarEntities.end()) { avatarEntityIDs = _packedAvatarEntityData.keys();
QUuid entityID = dataItr.key(); });
for (const auto& entityID : avatarEntityIDs) {
auto properties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID); auto properties = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
AttachmentData data = entityPropertiesToAttachmentData(properties); AttachmentData data = entityPropertiesToAttachmentData(properties);
avatarData.append(data); attachmentData.append(data);
dataItr++;
} }
return avatarData; return attachmentData;
} }
QVariantList MyAvatar::getAttachmentsVariant() const { QVariantList MyAvatar::getAttachmentsVariant() const {
@ -2387,16 +2855,16 @@ void MyAvatar::setAttachmentsVariant(const QVariantList& variant) {
} }
bool MyAvatar::findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID) { bool MyAvatar::findAvatarEntity(const QString& modelURL, const QString& jointName, QUuid& entityID) {
auto avatarEntities = getAvatarEntityData(); QList<QUuid> avatarEntityIDs;
AvatarEntityMap::const_iterator dataItr = avatarEntities.begin(); _avatarEntitiesLock.withReadLock([&] {
while (dataItr != avatarEntities.end()) { avatarEntityIDs = _packedAvatarEntityData.keys();
entityID = dataItr.key(); });
for (const auto& entityID : avatarEntityIDs) {
auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID); auto props = DependencyManager::get<EntityScriptingInterface>()->getEntityProperties(entityID);
if (props.getModelURL() == modelURL && if (props.getModelURL() == modelURL &&
(jointName.isEmpty() || props.getParentJointIndex() == getJointIndex(jointName))) { (jointName.isEmpty() || props.getParentJointIndex() == getJointIndex(jointName))) {
return true; return true;
} }
dataItr++;
} }
return false; return false;
} }

View file

@ -575,9 +575,11 @@ public:
float getHMDRollControlRate() const { return _hmdRollControlRate; } float getHMDRollControlRate() const { return _hmdRollControlRate; }
// get/set avatar data // get/set avatar data
void resizeAvatarEntitySettingHandles(unsigned int avatarEntityIndex); void resizeAvatarEntitySettingHandles(uint32_t maxIndex);
void saveData(); void saveData();
void saveAvatarEntityDataToSettings();
void loadData(); void loadData();
void loadAvatarEntityDataFromSettings();
void saveAttachmentData(const AttachmentData& attachment) const; void saveAttachmentData(const AttachmentData& attachment) const;
AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const; AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const;
@ -1184,6 +1186,7 @@ public:
virtual void setAttachmentsVariant(const QVariantList& variant) override; virtual void setAttachmentsVariant(const QVariantList& variant) override;
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); } glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
void prepareAvatarEntityDataForReload();
/**jsdoc /**jsdoc
* Create a new grab. * Create a new grab.
@ -1204,6 +1207,11 @@ public:
*/ */
Q_INVOKABLE void releaseGrab(const QUuid& grabID); Q_INVOKABLE void releaseGrab(const QUuid& grabID);
AvatarEntityMap getAvatarEntityData() const override;
void setAvatarEntityData(const AvatarEntityMap& avatarEntityData) override;
void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) override;
void avatarEntityDataToJson(QJsonObject& root) const override;
public slots: public slots:
/**jsdoc /**jsdoc
@ -1402,6 +1410,10 @@ public slots:
*/ */
bool getEnableMeshVisible() const override; bool getEnableMeshVisible() const override;
void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload) override;
void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true) override;
void sanitizeAvatarEntityProperties(EntityItemProperties& properties) const;
/**jsdoc /**jsdoc
* Set whether or not your avatar mesh is visible. * Set whether or not your avatar mesh is visible.
* @function MyAvatar.setEnableMeshVisible * @function MyAvatar.setEnableMeshVisible
@ -1601,23 +1613,24 @@ signals:
*/ */
void disableHandTouchForIDChanged(const QUuid& entityID, bool disable); void disableHandTouchForIDChanged(const QUuid& entityID, bool disable);
private slots: private slots:
void leaveDomain(); void leaveDomain();
void updateCollisionCapsuleCache(); void updateCollisionCapsuleCache();
protected: protected:
void handleChangedAvatarEntityData();
virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override; virtual void beParentOfChild(SpatiallyNestablePointer newChild) const override;
virtual void forgetChild(SpatiallyNestablePointer newChild) const override; virtual void forgetChild(SpatiallyNestablePointer newChild) const override;
virtual void recalculateChildCauterization() const override; virtual void recalculateChildCauterization() const override;
private: private:
bool updateStaleAvatarEntityBlobs() const;
bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut); bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut);
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override; virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override;
void simulate(float deltaTime); void simulate(float deltaTime, bool inView) override;
void updateFromTrackers(float deltaTime); void updateFromTrackers(float deltaTime);
void saveAvatarUrl(); void saveAvatarUrl();
virtual void render(RenderArgs* renderArgs) override; virtual void render(RenderArgs* renderArgs) override;
@ -1920,6 +1933,7 @@ private:
bool _haveReceivedHeightLimitsFromDomain { false }; bool _haveReceivedHeightLimitsFromDomain { false };
int _disableHandTouchCount { 0 }; int _disableHandTouchCount { 0 };
bool _skeletonModelLoaded { false }; bool _skeletonModelLoaded { false };
bool _reloadAvatarEntityDataFromSettings { true };
Setting::Handle<QString> _dominantHandSetting; Setting::Handle<QString> _dominantHandSetting;
Setting::Handle<float> _headPitchSetting; Setting::Handle<float> _headPitchSetting;
@ -1938,6 +1952,38 @@ private:
Setting::Handle<bool> _allowTeleportingSetting { "allowTeleporting", true }; Setting::Handle<bool> _allowTeleportingSetting { "allowTeleporting", true };
std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings; std::vector<Setting::Handle<QUuid>> _avatarEntityIDSettings;
std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings; std::vector<Setting::Handle<QByteArray>> _avatarEntityDataSettings;
// AvatarEntities stuff:
// We cache the "map of unfortunately-formatted-binary-blobs" because they are expensive to compute
// Do not confuse these with AvatarData::_packedAvatarEntityData which are in wire-format.
mutable AvatarEntityMap _cachedAvatarEntityBlobs;
// We collect changes to AvatarEntities and then handle them all in one spot per frame: updateAvatarEntities().
// Basically this is a "transaction pattern" with an extra complication: these changes can come from two
// "directions" and the "authoritative source" of each direction is different, so maintain two distinct sets of
// transaction lists;
//
// The _entitiesToDelete/Add/Update lists are for changes whose "authoritative sources" are already
// correctly stored in _cachedAvatarEntityBlobs. These come from loadAvatarEntityDataFromSettings() and
// setAvatarEntityData(). These changes need to be extracted from _cachedAvatarEntityBlobs and applied to
// real EntityItems.
std::vector<QUuid> _entitiesToDelete;
std::vector<QUuid> _entitiesToAdd;
std::vector<QUuid> _entitiesToUpdate;
//
// The _cachedAvatarEntityBlobsToDelete/Add/Update lists are for changes whose "authoritative sources" are
// already reflected in real EntityItems. These changes need to be propagated to _cachedAvatarEntityBlobs
// and eventually to settings.
std::vector<QUuid> _cachedAvatarEntityBlobsToDelete;
std::vector<QUuid> _cachedAvatarEntityBlobsToAddOrUpdate;
std::vector<QUuid> _cachedAvatarEntityBlobUpdatesToSkip;
//
// Also these lists for tracking delayed changes to blobs and Settings
mutable std::set<QUuid> _staleCachedAvatarEntityBlobs;
//
// keep a ScriptEngine around so we don't have to instantiate on the fly (these are very slow to create/delete)
QScriptEngine* _myScriptEngine { nullptr };
bool _needToSaveAvatarEntitySettings { false };
}; };
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);

View file

@ -7,10 +7,18 @@
// //
#include "OtherAvatar.h" #include "OtherAvatar.h"
#include "Application.h"
#include <glm/gtx/norm.hpp>
#include <glm/gtx/vector_angle.hpp>
#include <AvatarLogging.h>
#include "Application.h"
#include "AvatarMotionState.h" #include "AvatarMotionState.h"
const float DISPLAYNAME_FADE_TIME = 0.5f;
const float DISPLAYNAME_FADE_FACTOR = pow(0.01f, 1.0f / DISPLAYNAME_FADE_TIME);
static glm::u8vec3 getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) { static glm::u8vec3 getLoadingOrbColor(Avatar::LoadingStatus loadingStatus) {
const glm::u8vec3 NO_MODEL_COLOR(0xe3, 0xe3, 0xe3); const glm::u8vec3 NO_MODEL_COLOR(0xe3, 0xe3, 0xe3);
@ -142,4 +150,293 @@ void OtherAvatar::updateCollisionGroup(bool myAvatarCollide) {
_motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP); _motionState->addDirtyFlags(Simulation::DIRTY_COLLISION_GROUP);
} }
} }
} }
void OtherAvatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");
_globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition;
if (!hasParent()) {
setLocalPosition(_globalPosition);
}
_simulationRate.increment();
if (inView) {
_simulationInViewRate.increment();
}
PerformanceTimer perfTimer("simulate");
{
PROFILE_RANGE(simulation, "updateJoints");
if (inView) {
Head* head = getHead();
if (_hasNewJointData || _transit.isActive()) {
_skeletonModel->getRig().copyJointsFromJointData(_jointData);
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
_skeletonModel->getRig().computeExternalPoses(rootTransform);
_jointDataSimulationRate.increment();
_skeletonModel->simulate(deltaTime, true);
locationChanged(); // joints changed, so if there are any children, update them.
_hasNewJointData = false;
glm::vec3 headPosition = getWorldPosition();
if (!_skeletonModel->getHeadPosition(headPosition)) {
headPosition = getWorldPosition();
}
head->setPosition(headPosition);
}
head->setScale(getModelScale());
head->simulate(deltaTime);
relayJointDataToChildren();
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
_skeletonModel->simulate(deltaTime, false);
}
_skeletonModelSimulationRate.increment();
}
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
// the alpha function is
// Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt)
// Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt)
// factor^(dt) = coef
float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime);
if (_displayNameTargetAlpha < _displayNameAlpha) {
// Fading out
_displayNameAlpha *= coef;
} else {
// Fading in
_displayNameAlpha = 1.0f - (1.0f - _displayNameAlpha) * coef;
}
_displayNameAlpha = glm::abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;
}
{
PROFILE_RANGE(simulation, "misc");
measureMotionDerivatives(deltaTime);
simulateAttachments(deltaTime);
updatePalms();
}
{
PROFILE_RANGE(simulation, "entities");
handleChangedAvatarEntityData();
updateAttachedAvatarEntities();
}
{
PROFILE_RANGE(simulation, "grabs");
updateGrabs();
}
updateFadingStatus();
}
void OtherAvatar::handleChangedAvatarEntityData() {
PerformanceTimer perfTimer("attachments");
// AVATAR ENTITY UPDATE FLOW
// - if queueEditEntityMessage() sees "AvatarEntity" HostType it calls _myAvatar->storeAvatarEntityDataPayload()
// - storeAvatarEntityDataPayload() saves the payload and flags the trait instance for the entity as updated,
// - ClientTraitsHandler::sendChangedTraitsToMixea() sends the entity bytes to the mixer which relays them to other interfaces
// - AvatarHashMap::processBulkAvatarTraits() on other interfaces calls avatar->processTraitInstance()
// - AvatarData::processTraitInstance() calls storeAvatarEntityDataPayload(), which sets _avatarEntityDataChanged = true
// - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _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
// - (My)Avatar::simulate() calls handleChangedAvatarEntityData() every frame which checks _avatarEntityDataChanged
// and here we are...
if (!_avatarEntityDataChanged) {
return;
}
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return;
}
PackedAvatarEntityMap packedAvatarEntityData;
_avatarEntitiesLock.withReadLock([&] {
packedAvatarEntityData = _packedAvatarEntityData;
});
entityTree->withWriteLock([&] {
AvatarEntityMap::const_iterator dataItr = packedAvatarEntityData.begin();
while (dataItr != packedAvatarEntityData.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 {
// sanity check data
QUuid id;
EntityTypes::EntityType type;
EntityTypes::extractEntityTypeAndID((unsigned char*)(data.data()), data.size(), type, id);
if (id != entityID || !EntityTypes::typeIsValid(type)) {
// skip for corrupt
++dataItr;
continue;
}
// remember this hash for the future
stateItr = _avatarEntityDataHashes.insert(entityID, AvatarEntityDataHash(newHash));
}
++dataItr;
EntityItemProperties properties;
int32_t bytesLeftToRead = data.size();
unsigned char* dataAt = (unsigned char*)(data.data());
if (!properties.constructFromBuffer(dataAt, bytesLeftToRead)) {
// properties are corrupt
continue;
}
properties.setEntityHostType(entity::HostType::AVATAR);
properties.setOwningAvatarID(getID());
// there's no entity-server to tell us we're the simulation owner, so always set the
// simulationOwner to the owningAvatarID and a high priority.
properties.setSimulationOwner(getID(), AVATAR_ENTITY_SIMULATION_PRIORITY);
if (properties.getParentID() == AVATAR_SELF_ID) {
properties.setParentID(getID());
}
// NOTE: if this avatar entity is not attached to us, strip its entity script completely...
auto attachedScript = properties.getScript();
if (!isMyAvatar() && !attachedScript.isEmpty()) {
QString noScript;
properties.setScript(noScript);
}
auto specifiedHref = properties.getHref();
if (!isMyAvatar() && !specifiedHref.isEmpty()) {
qCDebug(avatars) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref;
QString noHref;
properties.setHref(noHref);
}
// When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed
// they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear.
// The thinking here is the local position was noticed as changing, but not the parentID (since it is now
// back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this,
// and seems safe (per Seth).
properties.markAllChanged();
// try to build the entity
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
bool success = true;
if (entity) {
QUuid oldParentID = entity->getParentID();
if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote();
} else {
success = false;
}
if (oldParentID != entity->getParentID()) {
if (entity->getParentID() == getID()) {
onAddAttachedAvatarEntity(entityID);
} else if (oldParentID == getID()) {
onRemoveAttachedAvatarEntity(entityID);
}
}
} else {
entity = entityTree->addEntity(entityID, properties);
if (!entity) {
success = false;
} else if (entity->getParentID() == getID()) {
onAddAttachedAvatarEntity(entityID);
}
}
stateItr.value().success = success;
}
AvatarEntityIDs recentlyRemovedAvatarEntities = getAndClearRecentlyRemovedIDs();
if (!recentlyRemovedAvatarEntities.empty()) {
// only lock this thread when absolutely necessary
AvatarEntityMap packedAvatarEntityData;
_avatarEntitiesLock.withReadLock([&] {
packedAvatarEntityData = _packedAvatarEntityData;
});
foreach (auto entityID, recentlyRemovedAvatarEntities) {
if (!packedAvatarEntityData.contains(entityID)) {
entityTree->deleteEntity(entityID, true, true);
}
}
// TODO: move this outside of tree lock
// remove stale data hashes
foreach (auto entityID, recentlyRemovedAvatarEntities) {
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
if (stateItr != _avatarEntityDataHashes.end()) {
_avatarEntityDataHashes.erase(stateItr);
}
onRemoveAttachedAvatarEntity(entityID);
}
}
if (packedAvatarEntityData.size() != _avatarEntityForRecording.size()) {
createRecordingIDs();
}
});
setAvatarEntityDataChanged(false);
}
void OtherAvatar::onAddAttachedAvatarEntity(const QUuid& id) {
for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) {
if (_attachedAvatarEntities[i] == id) {
return;
}
}
_attachedAvatarEntities.push_back(id);
}
void OtherAvatar::onRemoveAttachedAvatarEntity(const QUuid& id) {
for (uint32_t i = 0; i < _attachedAvatarEntities.size(); ++i) {
if (_attachedAvatarEntities[i] == id) {
if (i != _attachedAvatarEntities.size() - 1) {
_attachedAvatarEntities[i] = _attachedAvatarEntities.back();
}
_attachedAvatarEntities.pop_back();
break;
}
}
}
void OtherAvatar::updateAttachedAvatarEntities() {
if (!_attachedAvatarEntities.empty()) {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
if (!treeRenderer) {
return;
}
for (const QUuid& id : _attachedAvatarEntities) {
treeRenderer->onEntityChanged(id);
}
}
}

View file

@ -10,6 +10,7 @@
#define hifi_OtherAvatar_h #define hifi_OtherAvatar_h
#include <memory> #include <memory>
#include <vector>
#include <avatars-renderer/Avatar.h> #include <avatars-renderer/Avatar.h>
#include <workload/Space.h> #include <workload/Space.h>
@ -47,9 +48,17 @@ public:
void updateCollisionGroup(bool myAvatarCollide); void updateCollisionGroup(bool myAvatarCollide);
void simulate(float deltaTime, bool inView) override;
friend AvatarManager; friend AvatarManager;
protected: protected:
void handleChangedAvatarEntityData();
void updateAttachedAvatarEntities();
void onAddAttachedAvatarEntity(const QUuid& id);
void onRemoveAttachedAvatarEntity(const QUuid& id);
std::vector<QUuid> _attachedAvatarEntities;
std::shared_ptr<Sphere3DOverlay> _otherAvatarOrbMeshPlaceholder { nullptr }; std::shared_ptr<Sphere3DOverlay> _otherAvatarOrbMeshPlaceholder { nullptr };
OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID }; OverlayID _otherAvatarOrbMeshPlaceholderID { UNKNOWN_OVERLAY_ID };
AvatarMotionState* _motionState { nullptr }; AvatarMotionState* _motionState { nullptr };

View file

@ -308,171 +308,16 @@ void Avatar::setAvatarEntityDataChanged(bool value) {
_avatarEntityDataHashes.clear(); _avatarEntityDataHashes.clear();
} }
void Avatar::updateAvatarEntities() {
PerformanceTimer perfTimer("attachments");
// AVATAR ENTITY UPDATE FLOW
// - if queueEditEntityMessage sees avatarEntity flag it does _myAvatar->updateAvatarEntity()
// - updateAvatarEntity saves the bytes and flags the trait instance for the entity as updated
// - ClientTraitsHandler::sendChangedTraitsToMixer sends the entity bytes to the mixer which relays them to other interfaces
// - AvatarHashMap::processBulkAvatarTraits on other interfaces calls avatar->processTraitInstace
// - AvatarData::processTraitInstance calls updateAvatarEntity, which sets _avatarEntityDataChanged = true
// - (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) {
return;
}
if (getID().isNull() ||
getID() == AVATAR_SELF_ID ||
DependencyManager::get<NodeList>()->getSessionUUID() == QUuid()) {
// wait until MyAvatar and this Node gets an ID before doing this. Otherwise, various things go wrong --
// things get their parent fixed up from AVATAR_SELF_ID to a null uuid which means "no parent".
return;
}
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (!entityTree) {
return;
}
QScriptEngine scriptEngine;
entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData();
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.
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(data);
if (!jsonProperties.isObject()) {
qCDebug(avatars_renderer) << "got bad avatarEntity json" << QString(data.toHex());
continue;
}
QVariant variantProperties = jsonProperties.toVariant();
QVariantMap asMap = variantProperties.toMap();
QScriptValue scriptProperties = variantMapToScriptValue(asMap, scriptEngine);
EntityItemProperties properties;
EntityItemPropertiesFromScriptValueIgnoreReadOnly(scriptProperties, properties);
properties.setEntityHostType(entity::HostType::AVATAR);
properties.setOwningAvatarID(getID());
if (properties.getParentID() == AVATAR_SELF_ID) {
properties.setParentID(getID());
}
// NOTE: if this avatar entity is not attached to us, strip its entity script completely...
auto attachedScript = properties.getScript();
if (!isMyAvatar() && !attachedScript.isEmpty()) {
QString noScript;
properties.setScript(noScript);
}
auto specifiedHref = properties.getHref();
if (!isMyAvatar() && !specifiedHref.isEmpty()) {
qCDebug(avatars_renderer) << "removing entity href from avatar attached entity:" << entityID << "old href:" << specifiedHref;
QString noHref;
properties.setHref(noHref);
}
// When grabbing avatar entities, they are parented to the joint moving them, then when un-grabbed
// they go back to the default parent (null uuid). When un-gripped, others saw the entity disappear.
// The thinking here is the local position was noticed as changing, but not the parentID (since it is now
// back to the default), and the entity flew off somewhere. Marking all changed definitely fixes this,
// and seems safe (per Seth).
properties.markAllChanged();
// try to build the entity
EntityItemPointer entity = entityTree->findEntityByEntityItemID(EntityItemID(entityID));
bool success = true;
if (entity) {
if (entityTree->updateEntity(entityID, properties)) {
entity->updateLastEditedFromRemote();
} else {
success = false;
}
} else {
entity = entityTree->addEntity(entityID, properties);
if (!entity) {
success = false;
}
}
stateItr.value().success = success;
}
AvatarEntityIDs recentlyDetachedAvatarEntities = getAndClearRecentlyDetachedIDs();
if (!recentlyDetachedAvatarEntities.empty()) {
// only lock this thread when absolutely necessary
AvatarEntityMap avatarEntityData;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityData = _avatarEntityData;
});
foreach (auto entityID, recentlyDetachedAvatarEntities) {
if (!avatarEntityData.contains(entityID)) {
entityTree->deleteEntity(entityID, true, true);
}
}
// remove stale data hashes
foreach (auto entityID, recentlyDetachedAvatarEntities) {
MapOfAvatarEntityDataHashes::iterator stateItr = _avatarEntityDataHashes.find(entityID);
if (stateItr != _avatarEntityDataHashes.end()) {
_avatarEntityDataHashes.erase(stateItr);
}
}
}
if (avatarEntities.size() != _avatarEntityForRecording.size()) {
createRecordingIDs();
}
});
setAvatarEntityDataChanged(false);
}
void Avatar::removeAvatarEntitiesFromTree() { void Avatar::removeAvatarEntitiesFromTree() {
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>(); auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr; EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
if (entityTree) { if (entityTree) {
QList<QUuid> avatarEntityIDs;
_avatarEntitiesLock.withReadLock([&] {
avatarEntityIDs = _packedAvatarEntityData.keys();
});
entityTree->withWriteLock([&] { entityTree->withWriteLock([&] {
AvatarEntityMap avatarEntities = getAvatarEntityData(); for (const auto& entityID : avatarEntityIDs) {
for (auto entityID : avatarEntities.keys()) {
entityTree->deleteEntity(entityID, true, true); entityTree->deleteEntity(entityID, true, true);
} }
}); });
@ -647,87 +492,6 @@ void Avatar::relayJointDataToChildren() {
_reconstructSoftEntitiesJointMap = false; _reconstructSoftEntitiesJointMap = false;
} }
void Avatar::simulate(float deltaTime, bool inView) {
PROFILE_RANGE(simulation, "simulate");
_globalPosition = _transit.isActive() ? _transit.getCurrentPosition() : _serverPosition;
if (!hasParent()) {
setLocalPosition(_globalPosition);
}
_simulationRate.increment();
if (inView) {
_simulationInViewRate.increment();
}
PerformanceTimer perfTimer("simulate");
{
PROFILE_RANGE(simulation, "updateJoints");
if (inView) {
Head* head = getHead();
if (_hasNewJointData || _transit.isActive()) {
_skeletonModel->getRig().copyJointsFromJointData(_jointData);
glm::mat4 rootTransform = glm::scale(_skeletonModel->getScale()) * glm::translate(_skeletonModel->getOffset());
_skeletonModel->getRig().computeExternalPoses(rootTransform);
_jointDataSimulationRate.increment();
_skeletonModel->simulate(deltaTime, true);
locationChanged(); // joints changed, so if there are any children, update them.
_hasNewJointData = false;
glm::vec3 headPosition = getWorldPosition();
if (!_skeletonModel->getHeadPosition(headPosition)) {
headPosition = getWorldPosition();
}
head->setPosition(headPosition);
}
head->setScale(getModelScale());
head->simulate(deltaTime);
relayJointDataToChildren();
} else {
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
_skeletonModel->simulate(deltaTime, false);
}
_skeletonModelSimulationRate.increment();
}
// update animation for display name fade in/out
if ( _displayNameTargetAlpha != _displayNameAlpha) {
// the alpha function is
// Fade out => alpha(t) = factor ^ t => alpha(t+dt) = alpha(t) * factor^(dt)
// Fade in => alpha(t) = 1 - factor^t => alpha(t+dt) = 1-(1-alpha(t))*coef^(dt)
// factor^(dt) = coef
float coef = pow(DISPLAYNAME_FADE_FACTOR, deltaTime);
if (_displayNameTargetAlpha < _displayNameAlpha) {
// Fading out
_displayNameAlpha *= coef;
} else {
// Fading in
_displayNameAlpha = 1 - (1 - _displayNameAlpha) * coef;
}
_displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;
}
{
PROFILE_RANGE(simulation, "misc");
measureMotionDerivatives(deltaTime);
simulateAttachments(deltaTime);
updatePalms();
}
{
PROFILE_RANGE(simulation, "entities");
updateAvatarEntities();
}
{
PROFILE_RANGE(simulation, "grabs");
updateGrabs();
}
updateFadingStatus();
}
float Avatar::getSimulationRate(const QString& rateName) const { float Avatar::getSimulationRate(const QString& rateName) const {
if (rateName == "") { if (rateName == "") {
return _simulationRate.rate(); return _simulationRate.rate();
@ -1042,7 +806,6 @@ void Avatar::render(RenderArgs* renderArgs) {
} }
} }
void Avatar::setEnableMeshVisible(bool isEnabled) { void Avatar::setEnableMeshVisible(bool isEnabled) {
if (_isMeshVisible != isEnabled) { if (_isMeshVisible != isEnabled) {
_isMeshVisible = isEnabled; _isMeshVisible = isEnabled;

View file

@ -139,9 +139,8 @@ public:
typedef render::Payload<AvatarData> Payload; typedef render::Payload<AvatarData> Payload;
void init(); void init();
void updateAvatarEntities();
void removeAvatarEntitiesFromTree(); void removeAvatarEntitiesFromTree();
void simulate(float deltaTime, bool inView); virtual void simulate(float deltaTime, bool inView) = 0;
virtual void simulateAttachments(float deltaTime); virtual void simulateAttachments(float deltaTime);
virtual void render(RenderArgs* renderArgs); virtual void render(RenderArgs* renderArgs);
@ -240,8 +239,6 @@ public:
static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2, static void renderJointConnectingCone(gpu::Batch& batch, glm::vec3 position1, glm::vec3 position2,
float radius1, float radius2, const glm::vec4& color); float radius1, float radius2, const glm::vec4& color);
virtual void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) { }
/**jsdoc /**jsdoc
* Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example, * Set the offset applied to the current avatar. The offset adjusts the position that the avatar is rendered. For example,
* with an offset of <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly. * with an offset of <code>{ x: 0, y: 0.1, z: 0 }</code>, your avatar will appear to be raised off the ground slightly.

View file

@ -1908,10 +1908,9 @@ qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitTy
// grab a read lock on the avatar entities and check for entity data for the given ID // grab a read lock on the avatar entities and check for entity data for the given ID
QByteArray entityBinaryData; QByteArray entityBinaryData;
_avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] { _avatarEntitiesLock.withReadLock([this, &entityBinaryData, &traitInstanceID] {
if (_avatarEntityData.contains(traitInstanceID)) { if (_packedAvatarEntityData.contains(traitInstanceID)) {
entityBinaryData = _avatarEntityData[traitInstanceID]; entityBinaryData = _packedAvatarEntityData[traitInstanceID];
} }
}); });
@ -1998,7 +1997,7 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr
void AvatarData::prepareResetTraitInstances() { void AvatarData::prepareResetTraitInstances() {
if (_clientTraitsHandler) { if (_clientTraitsHandler) {
_avatarEntitiesLock.withReadLock([this]{ _avatarEntitiesLock.withReadLock([this]{
foreach (auto entityID, _avatarEntityData.keys()) { foreach (auto entityID, _packedAvatarEntityData.keys()) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID); _clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
} }
foreach (auto grabID, _avatarGrabData.keys()) { foreach (auto grabID, _avatarGrabData.keys()) {
@ -2019,7 +2018,7 @@ void AvatarData::processTrait(AvatarTraits::TraitType traitType, QByteArray trai
void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType, void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType,
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) { AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) {
if (traitType == AvatarTraits::AvatarEntity) { if (traitType == AvatarTraits::AvatarEntity) {
updateAvatarEntity(instanceID, traitBinaryData); storeAvatarEntityDataPayload(instanceID, traitBinaryData);
} else if (traitType == AvatarTraits::Grab) { } else if (traitType == AvatarTraits::Grab) {
updateAvatarGrabData(instanceID, traitBinaryData); updateAvatarGrabData(instanceID, traitBinaryData);
} }
@ -2367,7 +2366,7 @@ void AvatarData::setRecordingBasis(std::shared_ptr<Transform> recordingBasis) {
void AvatarData::createRecordingIDs() { void AvatarData::createRecordingIDs() {
_avatarEntitiesLock.withReadLock([&] { _avatarEntitiesLock.withReadLock([&] {
_avatarEntityForRecording.clear(); _avatarEntityForRecording.clear();
for (int i = 0; i < _avatarEntityData.size(); i++) { for (int i = 0; i < _packedAvatarEntityData.size(); i++) {
_avatarEntityForRecording.insert(QUuid::createUuid()); _avatarEntityForRecording.insert(QUuid::createUuid());
} }
}); });
@ -2422,6 +2421,10 @@ JointData jointDataFromJsonValue(int version, const QJsonValue& json) {
return result; return result;
} }
void AvatarData::avatarEntityDataToJson(QJsonObject& root) const {
// overridden where needed
}
QJsonObject AvatarData::toJson() const { QJsonObject AvatarData::toJson() const {
QJsonObject root; QJsonObject root;
@ -2433,20 +2436,8 @@ QJsonObject AvatarData::toJson() const {
if (!getDisplayName().isEmpty()) { if (!getDisplayName().isEmpty()) {
root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName(); root[JSON_AVATAR_DISPLAY_NAME] = getDisplayName();
} }
_avatarEntitiesLock.withReadLock([&] {
if (!_avatarEntityData.empty()) { avatarEntityDataToJson(root);
QJsonArray avatarEntityJson;
int entityCount = 0;
for (auto entityID : _avatarEntityData.keys()) {
QVariantMap entityData;
QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID;
entityData.insert("id", newId);
entityData.insert("properties", _avatarEntityData.value(entityID).toBase64());
avatarEntityJson.push_back(QVariant(entityData).toJsonObject());
}
root[JSON_AVATAR_ENTITIES] = avatarEntityJson;
}
});
auto recordingBasis = getRecordingBasis(); auto recordingBasis = getRecordingBasis();
bool success; bool success;
@ -2568,9 +2559,9 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
for (auto attachmentJson : attachmentsJson) { for (auto attachmentJson : attachmentsJson) {
if (attachmentJson.isObject()) { if (attachmentJson.isObject()) {
QVariantMap entityData = attachmentJson.toObject().toVariantMap(); QVariantMap entityData = attachmentJson.toObject().toVariantMap();
QUuid entityID = entityData.value("id").toUuid(); QUuid id = entityData.value("id").toUuid();
QByteArray properties = QByteArray::fromBase64(entityData.value("properties").toByteArray()); QByteArray data = QByteArray::fromBase64(entityData.value("properties").toByteArray());
updateAvatarEntity(entityID, properties); updateAvatarEntity(id, data);
} }
} }
} }
@ -2752,17 +2743,15 @@ void AvatarData::setAttachmentsVariant(const QVariantList& variant) {
setAttachmentData(newAttachments); setAttachmentData(newAttachments);
} }
const int MAX_NUM_AVATAR_ENTITIES = 42; void AvatarData::storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& data) {
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
_avatarEntitiesLock.withWriteLock([&] { _avatarEntitiesLock.withWriteLock([&] {
AvatarEntityMap::iterator itr = _avatarEntityData.find(entityID); PackedAvatarEntityMap::iterator itr = _packedAvatarEntityData.find(entityID);
if (itr == _avatarEntityData.end()) { if (itr == _packedAvatarEntityData.end()) {
if (_avatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) { if (_packedAvatarEntityData.size() < MAX_NUM_AVATAR_ENTITIES) {
_avatarEntityData.insert(entityID, entityData); _packedAvatarEntityData.insert(entityID, data);
} }
} else { } else {
itr.value() = entityData; itr.value() = data;
} }
}); });
@ -2775,15 +2764,20 @@ void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& ent
} }
} }
void AvatarData::updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData) {
// overridden where needed
// expects 'entityData' to be a JavaScript EntityItemProperties Object in QByteArray form
}
void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) { void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree) {
bool removedEntity = false; bool removedEntity = false;
_avatarEntitiesLock.withWriteLock([this, &removedEntity, &entityID] { _avatarEntitiesLock.withWriteLock([this, &removedEntity, &entityID] {
removedEntity = _avatarEntityData.remove(entityID); removedEntity = _packedAvatarEntityData.remove(entityID);
}); });
insertDetachedEntityID(entityID); insertRemovedEntityID(entityID);
if (removedEntity && _clientTraitsHandler) { if (removedEntity && _clientTraitsHandler) {
// we have a client traits handler, so we need to mark this removed instance trait as deleted // we have a client traits handler, so we need to mark this removed instance trait as deleted
@ -2793,75 +2787,29 @@ void AvatarData::clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFr
} }
AvatarEntityMap AvatarData::getAvatarEntityData() const { AvatarEntityMap AvatarData::getAvatarEntityData() const {
AvatarEntityMap result; // overridden where needed
_avatarEntitiesLock.withReadLock([&] { // NOTE: the return value is expected to be a map of unfortunately-formatted-binary-blobs
result = _avatarEntityData; return AvatarEntityMap();
});
return result;
}
void AvatarData::insertDetachedEntityID(const QUuid entityID) {
_avatarEntitiesLock.withWriteLock([&] {
_avatarEntityDetached.insert(entityID);
});
_avatarEntityDataChanged = true;
} }
void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) { void AvatarData::setAvatarEntityData(const AvatarEntityMap& avatarEntityData) {
if (avatarEntityData.size() > MAX_NUM_AVATAR_ENTITIES) { // overridden where needed
// the data is suspect // avatarEntityData is expected to be a map of QByteArrays
qCDebug(avatars) << "discard suspect AvatarEntityData with size =" << avatarEntityData.size(); // each QByteArray represents an EntityItemProperties object from JavaScript
return;
}
std::vector<QUuid> deletedEntityIDs;
QList<QUuid> updatedEntityIDs;
_avatarEntitiesLock.withWriteLock([&] {
if (_avatarEntityData != avatarEntityData) {
// keep track of entities that were attached to this avatar but no longer are
AvatarEntityIDs previousAvatarEntityIDs = QSet<QUuid>::fromList(_avatarEntityData.keys());
_avatarEntityData = avatarEntityData;
setAvatarEntityDataChanged(true);
deletedEntityIDs.reserve(previousAvatarEntityIDs.size());
foreach (auto entityID, previousAvatarEntityIDs) {
if (!_avatarEntityData.contains(entityID)) {
_avatarEntityDetached.insert(entityID);
deletedEntityIDs.push_back(entityID);
}
}
updatedEntityIDs = _avatarEntityData.keys();
}
});
if (_clientTraitsHandler) {
// we have a client traits handler
// flag removed entities as deleted so that changes are sent next frame
for (auto& deletedEntityID : deletedEntityIDs) {
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::AvatarEntity, deletedEntityID);
}
// flag any updated or created entities so that we send changes for them next frame
for (auto& entityID : updatedEntityIDs) {
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
}
}
} }
AvatarEntityIDs AvatarData::getAndClearRecentlyDetachedIDs() { void AvatarData::insertRemovedEntityID(const QUuid entityID) {
_avatarEntitiesLock.withWriteLock([&] {
_avatarEntityRemoved.insert(entityID);
});
_avatarEntityDataChanged = true;
}
AvatarEntityIDs AvatarData::getAndClearRecentlyRemovedIDs() {
AvatarEntityIDs result; AvatarEntityIDs result;
_avatarEntitiesLock.withWriteLock([&] { _avatarEntitiesLock.withWriteLock([&] {
result = _avatarEntityDetached; result = _avatarEntityRemoved;
_avatarEntityDetached.clear(); _avatarEntityRemoved.clear();
}); });
return result; return result;
} }

View file

@ -63,6 +63,7 @@ using AvatarWeakPointer = std::weak_ptr<AvatarData>;
using AvatarHash = QHash<QUuid, AvatarSharedPointer>; using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
using AvatarEntityMap = QMap<QUuid, QByteArray>; using AvatarEntityMap = QMap<QUuid, QByteArray>;
using PackedAvatarEntityMap = QMap<QUuid, QByteArray>; // similar to AvatarEntityMap, but different internal format
using AvatarEntityIDs = QSet<QUuid>; using AvatarEntityIDs = QSet<QUuid>;
using AvatarGrabDataMap = QMap<QUuid, QByteArray>; using AvatarGrabDataMap = QMap<QUuid, QByteArray>;
@ -71,6 +72,8 @@ using AvatarGrabMap = QMap<QUuid, GrabPointer>;
using AvatarDataSequenceNumber = uint16_t; using AvatarDataSequenceNumber = uint16_t;
const int MAX_NUM_AVATAR_ENTITIES = 42;
// avatar motion behaviors // avatar motion behaviors
const quint32 AVATAR_MOTION_ACTION_MOTOR_ENABLED = 1U << 0; const quint32 AVATAR_MOTION_ACTION_MOTOR_ENABLED = 1U << 0;
const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1; const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1;
@ -952,19 +955,20 @@ public:
// FIXME: Can this name be improved? Can it be deprecated? // FIXME: Can this name be improved? Can it be deprecated?
Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant); Q_INVOKABLE virtual void setAttachmentsVariant(const QVariantList& variant);
virtual void storeAvatarEntityDataPayload(const QUuid& entityID, const QByteArray& payload);
/**jsdoc /**jsdoc
* @function MyAvatar.updateAvatarEntity * @function MyAvatar.updateAvatarEntity
* @param {Uuid} entityID * @param {Uuid} entityID
* @param {string} entityData * @param {string} entityData
*/ */
Q_INVOKABLE void updateAvatarEntity(const QUuid& entityID, const QByteArray& entityData); Q_INVOKABLE virtual 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, bool requiresRemovalFromTree = true); Q_INVOKABLE virtual void clearAvatarEntity(const QUuid& entityID, bool requiresRemovalFromTree = true);
/**jsdoc /**jsdoc
@ -1125,6 +1129,7 @@ public:
TransformPointer getRecordingBasis() const; TransformPointer getRecordingBasis() const;
void setRecordingBasis(TransformPointer recordingBasis = TransformPointer()); void setRecordingBasis(TransformPointer recordingBasis = TransformPointer());
void createRecordingIDs(); void createRecordingIDs();
virtual void avatarEntityDataToJson(QJsonObject& root) const;
QJsonObject toJson() const; QJsonObject toJson() const;
void fromJson(const QJsonObject& json, bool useFrameSkeleton = true); void fromJson(const QJsonObject& json, bool useFrameSkeleton = true);
@ -1136,17 +1141,16 @@ public:
* @function MyAvatar.getAvatarEntityData * @function MyAvatar.getAvatarEntityData
* @returns {object} * @returns {object}
*/ */
Q_INVOKABLE AvatarEntityMap getAvatarEntityData() const; Q_INVOKABLE virtual AvatarEntityMap getAvatarEntityData() const;
/**jsdoc /**jsdoc
* @function MyAvatar.setAvatarEntityData * @function MyAvatar.setAvatarEntityData
* @param {object} avatarEntityData * @param {object} avatarEntityData
*/ */
Q_INVOKABLE void setAvatarEntityData(const AvatarEntityMap& avatarEntityData); Q_INVOKABLE virtual void setAvatarEntityData(const AvatarEntityMap& avatarEntityData);
virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; } virtual void setAvatarEntityDataChanged(bool value) { _avatarEntityDataChanged = value; }
void insertDetachedEntityID(const QUuid entityID); AvatarEntityIDs getAndClearRecentlyRemovedIDs();
AvatarEntityIDs getAndClearRecentlyDetachedIDs();
/**jsdoc /**jsdoc
* @function MyAvatar.getSensorToWorldMatrix * @function MyAvatar.getSensorToWorldMatrix
@ -1333,6 +1337,7 @@ public slots:
void resetLastSent() { _lastToByteArray = 0; } void resetLastSent() { _lastToByteArray = 0; }
protected: protected:
void insertRemovedEntityID(const QUuid entityID);
void lazyInitHeadData() const; void lazyInitHeadData() const;
float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const; float getDistanceBasedMinRotationDOT(glm::vec3 viewerPosition) const;
@ -1461,9 +1466,9 @@ protected:
AABox _defaultBubbleBox; AABox _defaultBubbleBox;
mutable ReadWriteLockable _avatarEntitiesLock; mutable ReadWriteLockable _avatarEntitiesLock;
AvatarEntityIDs _avatarEntityDetached; // recently detached from this avatar AvatarEntityIDs _avatarEntityRemoved; // recently removed AvatarEntity ids
AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording AvatarEntityIDs _avatarEntityForRecording; // create new entities id for avatar recording
AvatarEntityMap _avatarEntityData; PackedAvatarEntityMap _packedAvatarEntityData;
bool _avatarEntityDataChanged { false }; bool _avatarEntityDataChanged { false };
mutable ReadWriteLockable _avatarGrabsLock; mutable ReadWriteLockable _avatarGrabsLock;

View file

@ -39,13 +39,12 @@ void EntityEditPacketSender::adjustEditPacketForClockSkew(PacketType type, QByte
} }
} }
void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type, void EntityEditPacketSender::queueEditAvatarEntityMessage(EntityTreePointer entityTree,
EntityTreePointer entityTree,
EntityItemID entityItemID, EntityItemID entityItemID,
const EntityItemProperties& properties) { const EntityItemProperties& properties) {
assert(_myAvatar); assert(_myAvatar);
if (!entityTree) { if (!entityTree) {
qCDebug(entities) << "EntityEditPacketSender::queueEditEntityMessage null entityTree."; qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage null entityTree.";
return; return;
} }
EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID); EntityItemPointer entity = entityTree->findEntityByEntityItemID(entityItemID);
@ -53,33 +52,27 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type,
qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage can't find entity: " << entityItemID; qCDebug(entities) << "EntityEditPacketSender::queueEditAvatarEntityMessage can't find entity: " << entityItemID;
return; return;
} }
entity->setLastBroadcast(usecTimestampNow());
// the properties that get serialized into the avatar identity packet should be the entire set // serialize ALL properties in an "AvatarEntity" packet
// rather than just the ones being edited. // rather than just the ones being edited.
EntityItemProperties entityProperties = entity->getProperties(); EntityItemProperties entityProperties = entity->getProperties();
entityProperties.merge(properties); entityProperties.merge(properties);
std::lock_guard<std::mutex> lock(_mutex); OctreePacketData packetData(false, AvatarTraits::MAXIMUM_TRAIT_SIZE);
QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties); EncodeBitstreamParams params;
QVariant variantProperties = scriptProperties.toVariant(); EntityTreeElementExtraEncodeDataPointer extra { nullptr };
QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties); OctreeElement::AppendState appendState = entity->appendEntityData(&packetData, params, extra);
// the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar if (appendState != OctreeElement::COMPLETED) {
QJsonObject jsonObject = jsonProperties.object(); // this entity's payload is too big
if (jsonObject.contains("parentID")) { return;
if (QUuid(jsonObject["parentID"].toString()) == _myAvatar->getID()) {
jsonObject["parentID"] = AVATAR_SELF_ID.toString();
}
} }
jsonProperties = QJsonDocument(jsonObject);
QByteArray binaryProperties = jsonProperties.toBinaryData(); QByteArray tempArray((const char*)packetData.getUncompressedData(), packetData.getUncompressedSize());
_myAvatar->updateAvatarEntity(entityItemID, binaryProperties); _myAvatar->storeAvatarEntityDataPayload(entityItemID, tempArray);
entity->setLastBroadcast(usecTimestampNow());
} }
void EntityEditPacketSender::queueEditEntityMessage(PacketType type, void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
EntityTreePointer entityTree, EntityTreePointer entityTree,
EntityItemID entityItemID, EntityItemID entityItemID,
@ -89,7 +82,7 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type,
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar"; qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit with no myAvatar";
} else if (properties.getOwningAvatarID() == _myAvatar->getID()) { } else if (properties.getOwningAvatarID() == _myAvatar->getID()) {
// this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server // this is an avatar-based entity --> update our avatar-data rather than sending to the entity-server
queueEditAvatarEntityMessage(type, entityTree, entityItemID, properties); queueEditAvatarEntityMessage(entityTree, entityItemID, properties);
} else { } else {
qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar"; qCWarning(entities) << "Suppressing entity edit message: cannot send avatar entity edit for another avatar";
} }

View file

@ -50,7 +50,7 @@ public slots:
void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode); void processEntityEditNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
private: private:
void queueEditAvatarEntityMessage(PacketType type, EntityTreePointer entityTree, void queueEditAvatarEntityMessage(EntityTreePointer entityTree,
EntityItemID entityItemID, const EntityItemProperties& properties); EntityItemID entityItemID, const EntityItemProperties& properties);
private: private:

View file

@ -28,6 +28,7 @@
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <RegisteredMetaTypes.h> #include <RegisteredMetaTypes.h>
#include <Extents.h> #include <Extents.h>
#include <VariantMapToScriptValue.h>
#include "EntitiesLogging.h" #include "EntitiesLogging.h"
#include "EntityItem.h" #include "EntityItem.h"
@ -90,6 +91,16 @@ void EntityItemProperties::setLastEdited(quint64 usecTime) {
_lastEdited = usecTime > _created ? usecTime : _created; _lastEdited = usecTime > _created ? usecTime : _created;
} }
bool EntityItemProperties::constructFromBuffer(const unsigned char* data, int dataLength) {
ReadBitstreamToTreeParams args;
EntityItemPointer tempEntity = EntityTypes::constructEntityItem(data, dataLength);
if (!tempEntity) {
return false;
}
tempEntity->readEntityDataFromBuffer(data, dataLength, args);
(*this) = tempEntity->getProperties();
return true;
}
QHash<QString, ShapeType> stringToShapeTypeLookup; QHash<QString, ShapeType> stringToShapeTypeLookup;
@ -2023,6 +2034,18 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
_lastEdited = usecTimestampNow(); _lastEdited = usecTimestampNow();
} }
void EntityItemProperties::copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString) {
// DANGER: this method is expensive
QJsonDocument propertiesDoc = QJsonDocument::fromJson(jsonString.toUtf8());
QJsonObject propertiesObj = propertiesDoc.object();
QVariant propertiesVariant(propertiesObj);
QVariantMap propertiesMap = propertiesVariant.toMap();
QScriptValue propertiesScriptValue = variantMapToScriptValue(propertiesMap, scriptEngine);
bool honorReadOnly = true;
copyFromScriptValue(propertiesScriptValue, honorReadOnly);
}
void EntityItemProperties::merge(const EntityItemProperties& other) { void EntityItemProperties::merge(const EntityItemProperties& other) {
// Core // Core
COPY_PROPERTY_IF_CHANGED(simulationOwner); COPY_PROPERTY_IF_CHANGED(simulationOwner);
@ -2252,7 +2275,6 @@ void EntityItemPropertiesFromScriptValueHonorReadOnly(const QScriptValue &object
properties.copyFromScriptValue(object, true); properties.copyFromScriptValue(object, true);
} }
QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) { QScriptValue EntityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags) {
return EntityItemProperties::entityPropertyFlagsToScriptValue(engine, flags); return EntityItemProperties::entityPropertyFlagsToScriptValue(engine, flags);
} }
@ -4590,6 +4612,40 @@ void EntityItemProperties::convertToCloneProperties(const EntityItemID& entityID
setCloneAvatarEntity(ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY); setCloneAvatarEntity(ENTITY_ITEM_DEFAULT_CLONE_AVATAR_ENTITY);
} }
bool EntityItemProperties::blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties) {
// DANGER: this method is NOT efficient.
// begin recipe for converting unfortunately-formatted-binary-blob to EntityItemProperties
QJsonDocument jsonProperties = QJsonDocument::fromBinaryData(blob);
if (!jsonProperties.isObject()) {
qCDebug(entities) << "bad avatarEntityData json" << QString(blob.toHex());
return false;
}
QVariant variant = jsonProperties.toVariant();
QVariantMap variantMap = variant.toMap();
QScriptValue scriptValue = variantMapToScriptValue(variantMap, scriptEngine);
EntityItemPropertiesFromScriptValueHonorReadOnly(scriptValue, properties);
// end recipe
return true;
}
void EntityItemProperties::propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob) {
// DANGER: this method is NOT efficient.
// begin recipe for extracting unfortunately-formatted-binary-blob from EntityItem
QScriptValue scriptValue = EntityItemNonDefaultPropertiesToScriptValue(&scriptEngine, properties);
QVariant variantProperties = scriptValue.toVariant();
QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties);
// the ID of the parent/avatar changes from session to session. use a special UUID to indicate the avatar
QJsonObject jsonObject = jsonProperties.object();
if (jsonObject.contains("parentID")) {
if (QUuid(jsonObject["parentID"].toString()) == myAvatarID) {
jsonObject["parentID"] = AVATAR_SELF_ID.toString();
}
}
jsonProperties = QJsonDocument(jsonObject);
blob = jsonProperties.toBinaryData();
// end recipe
}
QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) { QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) {
QString result = "[ "; QString result = "[ ";

View file

@ -98,6 +98,9 @@ class EntityItemProperties {
friend class ZoneEntityItem; friend class ZoneEntityItem;
friend class MaterialEntityItem; friend class MaterialEntityItem;
public: public:
static bool blobToProperties(QScriptEngine& scriptEngine, const QByteArray& blob, EntityItemProperties& properties);
static void propertiesToBlob(QScriptEngine& scriptEngine, const QUuid& myAvatarID, const EntityItemProperties& properties, QByteArray& blob);
EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags()); EntityItemProperties(EntityPropertyFlags desiredProperties = EntityPropertyFlags());
virtual ~EntityItemProperties() = default; virtual ~EntityItemProperties() = default;
@ -109,6 +112,7 @@ public:
virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false, virtual QScriptValue copyToScriptValue(QScriptEngine* engine, bool skipDefaults, bool allowUnknownCreateTime = false,
bool strictSemantics = false, EntityPsuedoPropertyFlags psueudoPropertyFlags = EntityPsuedoPropertyFlags()) const; bool strictSemantics = false, EntityPsuedoPropertyFlags psueudoPropertyFlags = EntityPsuedoPropertyFlags()) const;
virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly); virtual void copyFromScriptValue(const QScriptValue& object, bool honorReadOnly);
void copyFromJSONString(QScriptEngine& scriptEngine, const QString& jsonString);
static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags); static QScriptValue entityPropertyFlagsToScriptValue(QScriptEngine* engine, const EntityPropertyFlags& flags);
static void entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags); static void entityPropertyFlagsFromScriptValue(const QScriptValue& object, EntityPropertyFlags& flags);
@ -135,6 +139,8 @@ public:
EntityPropertyFlags getDesiredProperties() { return _desiredProperties; } EntityPropertyFlags getDesiredProperties() { return _desiredProperties; }
void setDesiredProperties(EntityPropertyFlags properties) { _desiredProperties = properties; } void setDesiredProperties(EntityPropertyFlags properties) { _desiredProperties = properties; }
bool constructFromBuffer(const unsigned char* data, int dataLength);
// Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type, value) creates the following methods and variables: // Note: DEFINE_PROPERTY(PROP_FOO, Foo, foo, type, value) creates the following methods and variables:
// type getFoo() const; // type getFoo() const;
// void setFoo(type); // void setFoo(type);

View file

@ -174,7 +174,7 @@ int EntityTree::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
addToNeedsParentFixupList(entity); addToNeedsParentFixupList(entity);
} }
} else { } else {
entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead, args); entity = EntityTypes::constructEntityItem(dataAt, bytesLeftToRead);
if (entity) { if (entity) {
bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args); bytesForThisEntity = entity->readEntityDataFromBuffer(dataAt, bytesLeftToRead, args);
@ -490,7 +490,6 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
} }
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone) { EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone) {
EntityItemPointer result = NULL;
EntityItemProperties props = properties; EntityItemProperties props = properties;
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -517,12 +516,12 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
if (containingElement) { if (containingElement) {
qCWarning(entities) << "EntityTree::addEntity() on existing entity item with entityID=" << entityID qCWarning(entities) << "EntityTree::addEntity() on existing entity item with entityID=" << entityID
<< "containingElement=" << containingElement.get(); << "containingElement=" << containingElement.get();
return result; return nullptr;
} }
// construct the instance of the entity // construct the instance of the entity
EntityTypes::EntityType type = props.getType(); EntityTypes::EntityType type = props.getType();
result = EntityTypes::constructEntityItem(type, entityID, props); EntityItemPointer result = EntityTypes::constructEntityItem(type, entityID, props);
if (result) { if (result) {
if (recordCreationTime) { if (recordCreationTime) {
@ -531,10 +530,6 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
// Recurse the tree and store the entity in the correct tree element // Recurse the tree and store the entity in the correct tree element
AddEntityOperator theOperator(getThisPointer(), result); AddEntityOperator theOperator(getThisPointer(), result);
recurseTreeWithOperator(&theOperator); recurseTreeWithOperator(&theOperator);
if (!result->getParentID().isNull()) {
addToNeedsParentFixupList(result);
}
postAddEntity(result); postAddEntity(result);
} }
return result; return result;
@ -2969,27 +2964,30 @@ void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object,
MovingEntitiesOperator& moveOperator, bool force, bool tellServer) { MovingEntitiesOperator& moveOperator, bool force, bool tellServer) {
// if the queryBox has changed, tell the entity-server // if the queryBox has changed, tell the entity-server
EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(object); EntityItemPointer entity = std::dynamic_pointer_cast<EntityItem>(object);
if (entity && (entity->updateQueryAACube() || force)) { if (entity) {
bool success; bool tellServerThis = tellServer && (entity->getEntityHostType() != entity::HostType::AVATAR);
AACube newCube = entity->getQueryAACube(success); if ((entity->updateQueryAACube() || force)) {
if (success) { bool success;
moveOperator.addEntityToMoveList(entity, newCube); AACube newCube = entity->getQueryAACube(success);
} if (success) {
// send an edit packet to update the entity-server about the queryAABox. We do this for domain-hosted moveOperator.addEntityToMoveList(entity, newCube);
// entities as well as for avatar-entities; the packet-sender will route the update accordingly }
if (tellServer && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) { // send an edit packet to update the entity-server about the queryAABox. We do this for domain-hosted
quint64 now = usecTimestampNow(); // entities as well as for avatar-entities; the packet-sender will route the update accordingly
EntityItemProperties properties = entity->getProperties(); if (tellServerThis && packetSender && (entity->isDomainEntity() || entity->isAvatarEntity())) {
properties.setQueryAACubeDirty(); quint64 now = usecTimestampNow();
properties.setLocationDirty(); EntityItemProperties properties = entity->getProperties();
properties.setLastEdited(now); properties.setQueryAACubeDirty();
properties.setLocationDirty();
properties.setLastEdited(now);
packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties); packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties);
entity->setLastBroadcast(now); // for debug/physics status icons entity->setLastBroadcast(now); // for debug/physics status icons
} }
entity->markDirtyFlags(Simulation::DIRTY_POSITION); entity->markDirtyFlags(Simulation::DIRTY_POSITION);
entityChanged(entity); entityChanged(entity);
}
} }
object->forEachDescendant([&](SpatiallyNestablePointer descendant) { object->forEachDescendant([&](SpatiallyNestablePointer descendant) {

View file

@ -58,6 +58,10 @@ REGISTER_ENTITY_TYPE(Light)
REGISTER_ENTITY_TYPE(Zone) REGISTER_ENTITY_TYPE(Zone)
REGISTER_ENTITY_TYPE(Material) REGISTER_ENTITY_TYPE(Material)
bool EntityTypes::typeIsValid(EntityType type) {
return type > EntityType::Unknown && type <= EntityType::NUM_TYPES;
}
const QString& EntityTypes::getEntityTypeName(EntityType entityType) { const QString& EntityTypes::getEntityTypeName(EntityType entityType) {
QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType); QMap<EntityType, QString>::iterator matchedTypeName = _typeToNameMap.find(entityType);
if (matchedTypeName != _typeToNameMap.end()) { if (matchedTypeName != _typeToNameMap.end()) {
@ -107,8 +111,7 @@ EntityItemPointer EntityTypes::constructEntityItem(EntityType entityType, const
return newEntityItem; return newEntityItem;
} }
EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead, void EntityTypes::extractEntityTypeAndID(const unsigned char* data, int dataLength, EntityTypes::EntityType& typeOut, QUuid& idOut) {
ReadBitstreamToTreeParams& args) {
// Header bytes // Header bytes
// object ID [16 bytes] // object ID [16 bytes]
@ -119,28 +122,36 @@ EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, in
// ~27-35 bytes... // ~27-35 bytes...
const int MINIMUM_HEADER_BYTES = 27; const int MINIMUM_HEADER_BYTES = 27;
int bytesRead = 0; if (dataLength >= MINIMUM_HEADER_BYTES) {
if (bytesToRead >= MINIMUM_HEADER_BYTES) { int bytesRead = 0;
int originalLength = bytesToRead; QByteArray originalDataBuffer = QByteArray::fromRawData((const char*)data, dataLength);
QByteArray originalDataBuffer((const char*)data, originalLength);
// id // id
QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size
QUuid actualID = QUuid::fromRfc4122(encodedID); idOut = QUuid::fromRfc4122(encodedID);
bytesRead += encodedID.size(); bytesRead += encodedID.size();
// type // type
QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size
ByteCountCoded<quint32> typeCoder = encodedType; ByteCountCoded<quint32> typeCoder = encodedType;
encodedType = typeCoder; // determine true length encodedType = typeCoder; // determine true length
bytesRead += encodedType.size();
quint32 type = typeCoder; quint32 type = typeCoder;
EntityTypes::EntityType entityType = (EntityTypes::EntityType)type; typeOut = (EntityTypes::EntityType)type;
EntityItemID tempEntityID(actualID);
EntityItemProperties tempProperties;
return constructEntityItem(entityType, tempEntityID, tempProperties);
} }
}
return NULL;
EntityItemPointer EntityTypes::constructEntityItem(const unsigned char* data, int bytesToRead) {
QUuid id;
EntityTypes::EntityType type = EntityTypes::Unknown;
extractEntityTypeAndID(data, bytesToRead, type, id);
if (type > EntityTypes::Unknown && type <= EntityTypes::NUM_TYPES) {
EntityItemID tempEntityID(id);
EntityItemProperties tempProperties;
return constructEntityItem(type, tempEntityID, tempProperties);
}
return nullptr;
}
EntityItemPointer EntityTypes::constructEntityItem(const QUuid& id, const EntityItemProperties& properties) {
return constructEntityItem(properties.getType(), id, properties);
} }

View file

@ -109,11 +109,14 @@ public:
NUM_TYPES NUM_TYPES
} EntityType; } EntityType;
static bool typeIsValid(EntityType type);
static const QString& getEntityTypeName(EntityType entityType); static const QString& getEntityTypeName(EntityType entityType);
static EntityTypes::EntityType getEntityTypeFromName(const QString& name); static EntityTypes::EntityType getEntityTypeFromName(const QString& name);
static bool registerEntityType(EntityType entityType, const char* name, EntityTypeFactory factoryMethod); static bool registerEntityType(EntityType entityType, const char* name, EntityTypeFactory factoryMethod);
static void extractEntityTypeAndID(const unsigned char* data, int dataLength, EntityTypes::EntityType& typeOut, QUuid& idOut);
static EntityItemPointer constructEntityItem(EntityType entityType, const EntityItemID& entityID, const EntityItemProperties& properties); static EntityItemPointer constructEntityItem(EntityType entityType, const EntityItemID& entityID, const EntityItemProperties& properties);
static EntityItemPointer constructEntityItem(const unsigned char* data, int bytesToRead, ReadBitstreamToTreeParams& args); static EntityItemPointer constructEntityItem(const unsigned char* data, int bytesToRead);
static EntityItemPointer constructEntityItem(const QUuid& id, const EntityItemProperties& properties);
private: private:
static QMap<EntityType, QString> _typeToNameMap; static QMap<EntityType, QString> _typeToNameMap;

View file

@ -41,7 +41,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
return static_cast<PacketVersion>(AvatarMixerPacketVersion::CollisionFlag); return static_cast<PacketVersion>(AvatarMixerPacketVersion::CollisionFlag);
case PacketType::BulkAvatarData: case PacketType::BulkAvatarData:
case PacketType::KillAvatar: case PacketType::KillAvatar:
return static_cast<PacketVersion>(AvatarMixerPacketVersion::GrabTraits); return static_cast<PacketVersion>(AvatarMixerPacketVersion::FasterAvatarEntities);
case PacketType::MessagesData: case PacketType::MessagesData:
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData); return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
// ICE packets // ICE packets

View file

@ -311,7 +311,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
JointTransScaled, JointTransScaled,
GrabTraits, GrabTraits,
CollisionFlag, CollisionFlag,
AvatarTraitsAck AvatarTraitsAck,
FasterAvatarEntities
}; };
enum class DomainConnectRequestVersion : PacketVersion { enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -38,7 +38,11 @@ void OctreePacketData::changeSettings(bool enableCompression, unsigned int targe
_enableCompression = enableCompression; _enableCompression = enableCompression;
_targetSize = targetSize; _targetSize = targetSize;
_uncompressedByteArray.resize(_targetSize); _uncompressedByteArray.resize(_targetSize);
_compressedByteArray.resize(_targetSize); if (_enableCompression) {
_compressedByteArray.resize(_targetSize);
} else {
_compressedByteArray.resize(0);
}
_uncompressed = (unsigned char*)_uncompressedByteArray.data(); _uncompressed = (unsigned char*)_uncompressedByteArray.data();
_compressed = (unsigned char*)_compressedByteArray.data(); _compressed = (unsigned char*)_compressedByteArray.data();
@ -586,13 +590,10 @@ bool OctreePacketData::appendRawData(QByteArray data) {
AtomicUIntStat OctreePacketData::_compressContentTime { 0 }; AtomicUIntStat OctreePacketData::_compressContentTime { 0 };
AtomicUIntStat OctreePacketData::_compressContentCalls { 0 }; AtomicUIntStat OctreePacketData::_compressContentCalls { 0 };
bool OctreePacketData::compressContent() { bool OctreePacketData::compressContent() {
PerformanceWarning warn(false, "OctreePacketData::compressContent()", false, &_compressContentTime, &_compressContentCalls); PerformanceWarning warn(false, "OctreePacketData::compressContent()", false, &_compressContentTime, &_compressContentCalls);
assert(_dirty);
// without compression, we always pass... assert(_enableCompression);
if (!_enableCompression) {
return true;
}
_bytesInUseLastCheck = _bytesInUse; _bytesInUseLastCheck = _bytesInUse;
@ -605,13 +606,13 @@ bool OctreePacketData::compressContent() {
QByteArray compressedData = qCompress(uncompressedData, uncompressedSize, MAX_COMPRESSION); QByteArray compressedData = qCompress(uncompressedData, uncompressedSize, MAX_COMPRESSION);
if (compressedData.size() < (int)MAX_OCTREE_PACKET_DATA_SIZE) { if (compressedData.size() < _compressedByteArray.size()) {
_compressedBytes = compressedData.size(); _compressedBytes = compressedData.size();
memcpy(_compressed, compressedData.constData(), _compressedBytes); memcpy(_compressed, compressedData.constData(), _compressedBytes);
_dirty = false; _dirty = false;
success = true; success = true;
} else { } else {
qCWarning(octree) << "OctreePacketData::compressContent -- compressedData.size >= MAX_OCTREE_PACKET_DATA_SIZE"; qCWarning(octree) << "OctreePacketData::compressContent -- compressedData.size >= " << _compressedByteArray.size();
assert(false); assert(false);
} }
return success; return success;
@ -644,8 +645,7 @@ void OctreePacketData::loadFinalizedContent(const unsigned char* data, int lengt
memcpy(_uncompressed, uncompressedData.constData(), _bytesInUse); memcpy(_uncompressed, uncompressedData.constData(), _bytesInUse);
} else { } else {
memcpy(_uncompressed, data, length); memcpy(_uncompressed, data, length);
memcpy(_compressed, data, length); _bytesInUse = length;
_bytesInUse = _compressedBytes = length;
} }
} else { } else {
if (_debug) { if (_debug) {