//// EntityItem.cpp// libraries/models/src//// Created by Brad Hefta-Gaub on 12/4/13.// Copyright 2013 High Fidelity, Inc.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include "EntityItem.h" #include <QtCore/QObject>#include <QtEndian>#include <QJsonDocument>#include <NetworkingConstants.h>#include <MetaverseAPI.h>#include <NetworkAccessManager.h>#include <QtNetwork/QNetworkReply>#include <QtNetwork/QNetworkRequest> #include <glm/gtx/transform.hpp>#include <glm/gtx/component_wise.hpp> #include <BufferParser.h>#include <ByteCountCoding.h>#include <GLMHelpers.h>#include <Octree.h>#include <PhysicsHelpers.h>#include <Profile.h>#include <RegisteredMetaTypes.h>#include <SharedUtil.h> // usecTimestampNow()#include <LogHandler.h>#include <Extents.h>#include <QVariantGLM.h>#include <Grab.h> #include "EntityScriptingInterface.h"#include "EntitiesLogging.h"#include "EntityTree.h"#include "EntitySimulation.h"#include "EntityDynamicFactoryInterface.h" //#define WANT_DEBUG Q_DECLARE_METATYPE(EntityItemPointer);int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>(); int EntityItem::_maxActionsDataSize = 800;quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND;std::function<void(const EntityItemID&, const QString&, const QString&)> EntityItem::_updateScriptUserDataOperator = nullptr; EntityItem::EntityItem(const EntityItemID& entityItemID) : SpatiallyNestable(NestableType::Entity, entityItemID){ setLocalVelocity(ENTITY_ITEM_DEFAULT_VELOCITY); setLocalAngularVelocity(ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY); setUnscaledDimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS); // explicitly set transform parts to set dirty flags used by batch rendering locationChanged(); dimensionsChanged(); quint64 now = usecTimestampNow(); _lastSimulated = now; _lastUpdated = now;} EntityItem::~EntityItem() { // these pointers MUST be correct at delete, else we probably have a dangling backpointer // to this EntityItem in the corresponding data structure. assert(!_simulated || (!_element && !_physicsInfo)); assert(!_element); assert(!_physicsInfo);} EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties; @Base_REQUESTED_PROPS@ return requestedProperties;} OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, const bool destinationNodeCanGetAndSetPrivateUserData) const { // ALL this fits... // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] // last edited [8 bytes] // ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes] // PropertyFlags<>( everything ) [1-2 bytes] // ~27-35 bytes... OctreeElement::AppendState appendState = OctreeElement::COMPLETED; // assume the best // encode our ID as a byte count coded byte stream QByteArray encodedID = getID().toRfc4122(); // encode our type as a byte count coded byte stream ByteCountCoded<quint32> typeCoder = getType(); QByteArray encodedType = typeCoder; // last updated (animations, non-physics changes) quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited(); ByteCountCoded<quint64> updateDeltaCoder = updateDelta; QByteArray encodedUpdateDelta = updateDeltaCoder; // last simulated (velocity, angular velocity, physics changes) quint64 simulatedDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited(); ByteCountCoded<quint64> simulatedDeltaCoder = simulatedDelta; QByteArray encodedSimulatedDelta = simulatedDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags requestedProperties = getEntityProperties(params); // these properties are not sent over the wire requestedProperties -= PROP_ENTITY_HOST_TYPE; requestedProperties -= PROP_OWNING_AVATAR_ID; requestedProperties -= PROP_VISIBLE_IN_SECONDARY_CAMERA; // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, // then our entityTreeElementExtraEncodeData should include data about which properties we need to append. if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) { requestedProperties = entityTreeElementExtraEncodeData->entities.value(getEntityItemID()); } QString privateUserData = ""; if (destinationNodeCanGetAndSetPrivateUserData) { privateUserData = getPrivateUserData(); } EntityPropertyFlags propertiesDidntFit = requestedProperties; LevelDetails entityLevel = packetData->startLevel(); quint64 lastEdited = getLastEdited(); #ifdef WANT_DEBUG float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); qCDebug(entities) << "Writing entity " << getEntityItemID() << " to buffer, lastEdited =" << lastEdited << " ago=" << editedAgo << "seconds - " << agoAsString; #endif bool successIDFits = false; bool successTypeFits = false; bool successCreatedFits = false; bool successLastEditedFits = false; bool successLastUpdatedFits = false; bool successLastSimulatedFits = false; bool successPropertyFlagsFits = false; int propertyFlagsOffset = 0; int oldPropertyFlagsLength = 0; QByteArray encodedPropertyFlags; int propertyCount = 0; successIDFits = packetData->appendRawData(encodedID); if (successIDFits) { successTypeFits = packetData->appendRawData(encodedType); } if (successTypeFits) { successCreatedFits = packetData->appendValue(_created); } if (successCreatedFits) { successLastEditedFits = packetData->appendValue(lastEdited); } if (successLastEditedFits) { successLastUpdatedFits = packetData->appendRawData(encodedUpdateDelta); } if (successLastUpdatedFits) { successLastSimulatedFits = packetData->appendRawData(encodedSimulatedDelta); } if (successLastSimulatedFits) { propertyFlagsOffset = packetData->getUncompressedByteOffset(); encodedPropertyFlags = propertyFlags; oldPropertyFlagsLength = encodedPropertyFlags.length(); successPropertyFlagsFits = packetData->appendRawData(encodedPropertyFlags); } bool headerFits = successIDFits && successTypeFits && successCreatedFits && successLastEditedFits && successLastUpdatedFits && successPropertyFlagsFits; int startOfEntityItemData = packetData->getUncompressedByteOffset(); if (headerFits) { bool successPropertyFits; propertyFlags -= PROP_LAST_ITEM; // clear the last item for now, we may or may not set it as the actual item // NOTE: When we enable partial packing of entity properties, we'll want to pack simulationOwner, transform, and velocity properties near each other // since they will commonly be transmitted together. simulationOwner must always go first, to avoid race conditions of simulation ownership bids // These items would go here once supported.... // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, // convert AVATAR_SELF_ID to actual sessionUUID. QUuid actualParentID = getParentID(); auto nodeList = DependencyManager::get<NodeList>(); if (actualParentID == AVATAR_SELF_ID) { actualParentID = nodeList->getSessionUUID(); } @Base_ENTITY_APPEND@ appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); encodedPropertyFlags = propertyFlags; int newPropertyFlagsLength = encodedPropertyFlags.length(); packetData->updatePriorBytes(propertyFlagsOffset, (const unsigned char*)encodedPropertyFlags.constData(), encodedPropertyFlags.length()); // if the size of the PropertyFlags shrunk, we need to shift everything down to front of packet. if (newPropertyFlagsLength < oldPropertyFlagsLength) { int oldSize = packetData->getUncompressedSize(); const unsigned char* modelItemData = packetData->getUncompressedData(propertyFlagsOffset + oldPropertyFlagsLength); int modelItemDataLength = endOfEntityItemData - startOfEntityItemData; int newEntityItemDataStart = propertyFlagsOffset + newPropertyFlagsLength; packetData->updatePriorBytes(newEntityItemDataStart, modelItemData, modelItemDataLength); int newSize = oldSize - (oldPropertyFlagsLength - newPropertyFlagsLength); packetData->setUncompressedSize(newSize); } else { assert(newPropertyFlagsLength == oldPropertyFlagsLength); // should not have grown } packetData->endLevel(entityLevel); } else { packetData->discardLevel(entityLevel); appendState = OctreeElement::NONE; // if we got here, then we didn't include the item } // If any part of the model items didn't fit, then the element is considered partial if (appendState != OctreeElement::COMPLETED) { // add this item into our list for the next appendElementData() pass entityTreeElementExtraEncodeData->entities.insert(getEntityItemID(), propertiesDidntFit); } // if any part of our entity was sent, call trackSend if (appendState != OctreeElement::NONE) { params.trackSend(getID(), getLastEdited()); } return appendState;} // TODO: My goal is to get rid of this concept completely. The old code (and some of the current code) used this// result to calculate if a packet being sent to it was potentially bad or corrupt. I've adjusted this to now// only consider the minimum header bytes as being required. But it would be preferable to completely eliminate// this logic from the callers.int EntityItem::expectedBytes() { // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] // last edited [8 bytes] // ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes] // PropertyFlags<>( everything ) [1-2 bytes] // ~27-35 bytes... const int MINIMUM_HEADER_BYTES = 27; return MINIMUM_HEADER_BYTES;} const uint8_t PENDING_STATE_NOTHING = 0;const uint8_t PENDING_STATE_TAKE = 1;const uint8_t PENDING_STATE_RELEASE = 2; // clients use this method to unpack FULL updates from entity-serverint EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { setSourceUUID(args.sourceUUID); args.entitiesPerPacket++; // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] // last edited [8 bytes] // ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes] // PropertyFlags<>( everything ) [1-2 bytes] // ~27-35 bytes... const int MINIMUM_HEADER_BYTES = 27; if (bytesLeftToRead < MINIMUM_HEADER_BYTES) { return 0; } int64_t clockSkew = 0; uint64_t maxPingRoundTrip = 33333; // two frames periods at 60 fps if (args.sourceNode) { clockSkew = args.sourceNode->getClockSkewUsec(); const float MSECS_PER_USEC = 1000; maxPingRoundTrip += args.sourceNode->getPingMs() * MSECS_PER_USEC; } BufferParser parser(data, bytesLeftToRead); #ifdef DEBUG#define VALIDATE_ENTITY_ITEM_PARSER 1#endif #ifdef VALIDATE_ENTITY_ITEM_PARSER int bytesRead = 0; int originalLength = bytesLeftToRead; // TODO: figure out a way to avoid the big deep copy below. QByteArray originalDataBuffer((const char*)data, originalLength); // big deep copy! const unsigned char* dataAt = data;#endif // id parser.readUuid(_id);#ifdef VALIDATE_ENTITY_ITEM_PARSER { QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size QUuid id = QUuid::fromRfc4122(encodedID); dataAt += encodedID.size(); bytesRead += encodedID.size(); Q_ASSERT(id == _id); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); }#endif // type parser.readCompressedCount<quint32>((quint32&)_type);#ifdef VALIDATE_ENTITY_ITEM_PARSER QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded<quint32> typeCoder = encodedType; encodedType = typeCoder; // determine true length dataAt += encodedType.size(); bytesRead += encodedType.size(); quint32 type = typeCoder; EntityTypes::EntityType oldType = (EntityTypes::EntityType)type; Q_ASSERT(oldType == _type); Q_ASSERT(parser.offset() == (unsigned int) bytesRead);#endif bool overwriteLocalData = true; // assume the new content overwrites our local data quint64 now = usecTimestampNow(); bool somethingChanged = false; // _created { quint64 createdFromBuffer = 0; parser.readValue(createdFromBuffer);#ifdef VALIDATE_ENTITY_ITEM_PARSER { quint64 createdFromBuffer2 = 0; memcpy(&createdFromBuffer2, dataAt, sizeof(createdFromBuffer2)); dataAt += sizeof(createdFromBuffer2); bytesRead += sizeof(createdFromBuffer2); Q_ASSERT(createdFromBuffer2 == createdFromBuffer); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); }#endif if (_created == UNKNOWN_CREATED_TIME) { // we don't yet have a _created timestamp, so we accept this one createdFromBuffer -= clockSkew; if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { createdFromBuffer = now; } _created = createdFromBuffer; } } #ifdef WANT_DEBUG { quint64 lastEdited = getLastEdited(); float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); QString ageAsString = formatSecondsElapsed(getAge()); qCDebug(entities) << "------------------------------------------"; qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; qCDebug(entities) << "------------------------------------------"; debugDump(); qCDebug(entities) << "------------------------------------------"; qCDebug(entities) << " _created =" << _created; qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; qCDebug(entities) << " lastEdited =" << lastEdited; qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; } #endif quint64 lastEditedFromBuffer = 0; // TODO: we could make this encoded as a delta from _created // _lastEdited parser.readValue(lastEditedFromBuffer);#ifdef VALIDATE_ENTITY_ITEM_PARSER { quint64 lastEditedFromBuffer2 = 0; memcpy(&lastEditedFromBuffer2, dataAt, sizeof(lastEditedFromBuffer2)); dataAt += sizeof(lastEditedFromBuffer2); bytesRead += sizeof(lastEditedFromBuffer2); Q_ASSERT(lastEditedFromBuffer2 == lastEditedFromBuffer); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); }#endif quint64 lastEditedFromBufferAdjusted = lastEditedFromBuffer == 0 ? 0 : lastEditedFromBuffer - clockSkew; if (lastEditedFromBufferAdjusted > now) { lastEditedFromBufferAdjusted = now; } bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); #ifdef WANT_DEBUG qCDebug(entities) << "data from server **************** "; qCDebug(entities) << " entityItemID:" << getEntityItemID(); qCDebug(entities) << " now:" << now; qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); qCDebug(entities) << " clockSkew:" << clockSkew; qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; #endif bool ignoreServerPacket = false; // assume we'll use this server packet // If this packet is from the same server edit as the last packet we accepted from the server // we probably want to use it. if (fromSameServerEdit) { // If this is from the same sever packet, then check against any local changes since we got // the most recent packet from this server time if (_lastEdited > _lastEditedFromRemote) { ignoreServerPacket = true; } } else { // If this isn't from the same sever packet, then honor our skew adjusted times... // If we've changed our local tree more recently than the new data from this packet // then we will not be changing our values, instead we just read and skip the data if (_lastEdited > lastEditedFromBufferAdjusted) { ignoreServerPacket = true; } } // before proceeding, check to see if this is an entity that we know has been deleted, which // might happen in the case of out-of-order and/or recorvered packets, if we've deleted the entity // we can confidently ignore this packet EntityTreePointer tree = getTree(); if (tree && tree->isDeletedEntity(_id)) { #ifdef WANT_DEBUG qCDebug(entities) << "Received packet for previously deleted entity [" << _id << "] ignoring. " "(inside " << __FUNCTION__ << ")"; #endif ignoreServerPacket = true; } if (ignoreServerPacket) { overwriteLocalData = false; #ifdef WANT_DEBUG qCDebug(entities) << "IGNORING old data from server!!! ****************"; debugDump(); #endif } else { #ifdef WANT_DEBUG qCDebug(entities) << "USING NEW data from server!!! ****************"; debugDump(); #endif // don't allow _lastEdited to be in the future _lastEdited = lastEditedFromBufferAdjusted; _lastEditedFromRemote = now; _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; } // last updated is stored as ByteCountCoded delta from lastEdited quint64 updateDelta; parser.readCompressedCount(updateDelta);#ifdef VALIDATE_ENTITY_ITEM_PARSER { QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded<quint64> updateDeltaCoder = encodedUpdateDelta; quint64 updateDelta2 = updateDeltaCoder; Q_ASSERT(updateDelta == updateDelta2); encodedUpdateDelta = updateDeltaCoder; // determine true length dataAt += encodedUpdateDelta.size(); bytesRead += encodedUpdateDelta.size(); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); }#endif if (overwriteLocalData) { _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that #ifdef WANT_DEBUG qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); #endif } Lines 1–500 of 2936. Load more
// // EntityItem.cpp // libraries/models/src // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "EntityItem.h" #include <QtCore/QObject> #include <QtEndian> #include <QJsonDocument> #include <NetworkingConstants.h> #include <MetaverseAPI.h> #include <NetworkAccessManager.h> #include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkRequest> #include <glm/gtx/transform.hpp> #include <glm/gtx/component_wise.hpp> #include <BufferParser.h> #include <ByteCountCoding.h> #include <GLMHelpers.h> #include <Octree.h> #include <PhysicsHelpers.h> #include <Profile.h> #include <RegisteredMetaTypes.h> #include <SharedUtil.h> // usecTimestampNow() #include <LogHandler.h> #include <Extents.h> #include <QVariantGLM.h> #include <Grab.h> #include "EntityScriptingInterface.h" #include "EntitiesLogging.h" #include "EntityTree.h" #include "EntitySimulation.h" #include "EntityDynamicFactoryInterface.h" //#define WANT_DEBUG Q_DECLARE_METATYPE(EntityItemPointer); int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>(); int EntityItem::_maxActionsDataSize = 800; quint64 EntityItem::_rememberDeletedActionTime = 20 * USECS_PER_SECOND; std::function<void(const EntityItemID&, const QString&, const QString&)> EntityItem::_updateScriptUserDataOperator = nullptr; EntityItem::EntityItem(const EntityItemID& entityItemID) : SpatiallyNestable(NestableType::Entity, entityItemID) { setLocalVelocity(ENTITY_ITEM_DEFAULT_VELOCITY); setLocalAngularVelocity(ENTITY_ITEM_DEFAULT_ANGULAR_VELOCITY); setUnscaledDimensions(ENTITY_ITEM_DEFAULT_DIMENSIONS); // explicitly set transform parts to set dirty flags used by batch rendering locationChanged(); dimensionsChanged(); quint64 now = usecTimestampNow(); _lastSimulated = now; _lastUpdated = now; } EntityItem::~EntityItem() { // these pointers MUST be correct at delete, else we probably have a dangling backpointer // to this EntityItem in the corresponding data structure. assert(!_simulated || (!_element && !_physicsInfo)); assert(!_element); assert(!_physicsInfo); } EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& params) const { EntityPropertyFlags requestedProperties; @Base_REQUESTED_PROPS@ return requestedProperties; } OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packetData, EncodeBitstreamParams& params, EntityTreeElementExtraEncodeDataPointer entityTreeElementExtraEncodeData, const bool destinationNodeCanGetAndSetPrivateUserData) const { // ALL this fits... // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] // last edited [8 bytes] // ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes] // PropertyFlags<>( everything ) [1-2 bytes] // ~27-35 bytes... OctreeElement::AppendState appendState = OctreeElement::COMPLETED; // assume the best // encode our ID as a byte count coded byte stream QByteArray encodedID = getID().toRfc4122(); // encode our type as a byte count coded byte stream ByteCountCoded<quint32> typeCoder = getType(); QByteArray encodedType = typeCoder; // last updated (animations, non-physics changes) quint64 updateDelta = getLastUpdated() <= getLastEdited() ? 0 : getLastUpdated() - getLastEdited(); ByteCountCoded<quint64> updateDeltaCoder = updateDelta; QByteArray encodedUpdateDelta = updateDeltaCoder; // last simulated (velocity, angular velocity, physics changes) quint64 simulatedDelta = getLastSimulated() <= getLastEdited() ? 0 : getLastSimulated() - getLastEdited(); ByteCountCoded<quint64> simulatedDeltaCoder = simulatedDelta; QByteArray encodedSimulatedDelta = simulatedDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); EntityPropertyFlags requestedProperties = getEntityProperties(params); // these properties are not sent over the wire requestedProperties -= PROP_ENTITY_HOST_TYPE; requestedProperties -= PROP_OWNING_AVATAR_ID; requestedProperties -= PROP_VISIBLE_IN_SECONDARY_CAMERA; // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, // then our entityTreeElementExtraEncodeData should include data about which properties we need to append. if (entityTreeElementExtraEncodeData && entityTreeElementExtraEncodeData->entities.contains(getEntityItemID())) { requestedProperties = entityTreeElementExtraEncodeData->entities.value(getEntityItemID()); } QString privateUserData = ""; if (destinationNodeCanGetAndSetPrivateUserData) { privateUserData = getPrivateUserData(); } EntityPropertyFlags propertiesDidntFit = requestedProperties; LevelDetails entityLevel = packetData->startLevel(); quint64 lastEdited = getLastEdited(); #ifdef WANT_DEBUG float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); qCDebug(entities) << "Writing entity " << getEntityItemID() << " to buffer, lastEdited =" << lastEdited << " ago=" << editedAgo << "seconds - " << agoAsString; #endif bool successIDFits = false; bool successTypeFits = false; bool successCreatedFits = false; bool successLastEditedFits = false; bool successLastUpdatedFits = false; bool successLastSimulatedFits = false; bool successPropertyFlagsFits = false; int propertyFlagsOffset = 0; int oldPropertyFlagsLength = 0; QByteArray encodedPropertyFlags; int propertyCount = 0; successIDFits = packetData->appendRawData(encodedID); if (successIDFits) { successTypeFits = packetData->appendRawData(encodedType); } if (successTypeFits) { successCreatedFits = packetData->appendValue(_created); } if (successCreatedFits) { successLastEditedFits = packetData->appendValue(lastEdited); } if (successLastEditedFits) { successLastUpdatedFits = packetData->appendRawData(encodedUpdateDelta); } if (successLastUpdatedFits) { successLastSimulatedFits = packetData->appendRawData(encodedSimulatedDelta); } if (successLastSimulatedFits) { propertyFlagsOffset = packetData->getUncompressedByteOffset(); encodedPropertyFlags = propertyFlags; oldPropertyFlagsLength = encodedPropertyFlags.length(); successPropertyFlagsFits = packetData->appendRawData(encodedPropertyFlags); } bool headerFits = successIDFits && successTypeFits && successCreatedFits && successLastEditedFits && successLastUpdatedFits && successPropertyFlagsFits; int startOfEntityItemData = packetData->getUncompressedByteOffset(); if (headerFits) { bool successPropertyFits; propertyFlags -= PROP_LAST_ITEM; // clear the last item for now, we may or may not set it as the actual item // NOTE: When we enable partial packing of entity properties, we'll want to pack simulationOwner, transform, and velocity properties near each other // since they will commonly be transmitted together. simulationOwner must always go first, to avoid race conditions of simulation ownership bids // These items would go here once supported.... // PROP_PAGED_PROPERTY, // PROP_CUSTOM_PROPERTIES_INCLUDED, // convert AVATAR_SELF_ID to actual sessionUUID. QUuid actualParentID = getParentID(); auto nodeList = DependencyManager::get<NodeList>(); if (actualParentID == AVATAR_SELF_ID) { actualParentID = nodeList->getSessionUUID(); } @Base_ENTITY_APPEND@ appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState); } if (propertyCount > 0) { int endOfEntityItemData = packetData->getUncompressedByteOffset(); encodedPropertyFlags = propertyFlags; int newPropertyFlagsLength = encodedPropertyFlags.length(); packetData->updatePriorBytes(propertyFlagsOffset, (const unsigned char*)encodedPropertyFlags.constData(), encodedPropertyFlags.length()); // if the size of the PropertyFlags shrunk, we need to shift everything down to front of packet. if (newPropertyFlagsLength < oldPropertyFlagsLength) { int oldSize = packetData->getUncompressedSize(); const unsigned char* modelItemData = packetData->getUncompressedData(propertyFlagsOffset + oldPropertyFlagsLength); int modelItemDataLength = endOfEntityItemData - startOfEntityItemData; int newEntityItemDataStart = propertyFlagsOffset + newPropertyFlagsLength; packetData->updatePriorBytes(newEntityItemDataStart, modelItemData, modelItemDataLength); int newSize = oldSize - (oldPropertyFlagsLength - newPropertyFlagsLength); packetData->setUncompressedSize(newSize); } else { assert(newPropertyFlagsLength == oldPropertyFlagsLength); // should not have grown } packetData->endLevel(entityLevel); } else { packetData->discardLevel(entityLevel); appendState = OctreeElement::NONE; // if we got here, then we didn't include the item } // If any part of the model items didn't fit, then the element is considered partial if (appendState != OctreeElement::COMPLETED) { // add this item into our list for the next appendElementData() pass entityTreeElementExtraEncodeData->entities.insert(getEntityItemID(), propertiesDidntFit); } // if any part of our entity was sent, call trackSend if (appendState != OctreeElement::NONE) { params.trackSend(getID(), getLastEdited()); } return appendState; } // TODO: My goal is to get rid of this concept completely. The old code (and some of the current code) used this // result to calculate if a packet being sent to it was potentially bad or corrupt. I've adjusted this to now // only consider the minimum header bytes as being required. But it would be preferable to completely eliminate // this logic from the callers. int EntityItem::expectedBytes() { // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] // last edited [8 bytes] // ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes] // PropertyFlags<>( everything ) [1-2 bytes] // ~27-35 bytes... const int MINIMUM_HEADER_BYTES = 27; return MINIMUM_HEADER_BYTES; } const uint8_t PENDING_STATE_NOTHING = 0; const uint8_t PENDING_STATE_TAKE = 1; const uint8_t PENDING_STATE_RELEASE = 2; // clients use this method to unpack FULL updates from entity-server int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) { setSourceUUID(args.sourceUUID); args.entitiesPerPacket++; // Header bytes // object ID [16 bytes] // ByteCountCoded(type code) [~1 byte] // last edited [8 bytes] // ByteCountCoded(last_edited to last_updated delta) [~1-8 bytes] // PropertyFlags<>( everything ) [1-2 bytes] // ~27-35 bytes... const int MINIMUM_HEADER_BYTES = 27; if (bytesLeftToRead < MINIMUM_HEADER_BYTES) { return 0; } int64_t clockSkew = 0; uint64_t maxPingRoundTrip = 33333; // two frames periods at 60 fps if (args.sourceNode) { clockSkew = args.sourceNode->getClockSkewUsec(); const float MSECS_PER_USEC = 1000; maxPingRoundTrip += args.sourceNode->getPingMs() * MSECS_PER_USEC; } BufferParser parser(data, bytesLeftToRead); #ifdef DEBUG #define VALIDATE_ENTITY_ITEM_PARSER 1 #endif #ifdef VALIDATE_ENTITY_ITEM_PARSER int bytesRead = 0; int originalLength = bytesLeftToRead; // TODO: figure out a way to avoid the big deep copy below. QByteArray originalDataBuffer((const char*)data, originalLength); // big deep copy! const unsigned char* dataAt = data; #endif // id parser.readUuid(_id); #ifdef VALIDATE_ENTITY_ITEM_PARSER { QByteArray encodedID = originalDataBuffer.mid(bytesRead, NUM_BYTES_RFC4122_UUID); // maximum possible size QUuid id = QUuid::fromRfc4122(encodedID); dataAt += encodedID.size(); bytesRead += encodedID.size(); Q_ASSERT(id == _id); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); } #endif // type parser.readCompressedCount<quint32>((quint32&)_type); #ifdef VALIDATE_ENTITY_ITEM_PARSER QByteArray encodedType = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded<quint32> typeCoder = encodedType; encodedType = typeCoder; // determine true length dataAt += encodedType.size(); bytesRead += encodedType.size(); quint32 type = typeCoder; EntityTypes::EntityType oldType = (EntityTypes::EntityType)type; Q_ASSERT(oldType == _type); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); #endif bool overwriteLocalData = true; // assume the new content overwrites our local data quint64 now = usecTimestampNow(); bool somethingChanged = false; // _created { quint64 createdFromBuffer = 0; parser.readValue(createdFromBuffer); #ifdef VALIDATE_ENTITY_ITEM_PARSER { quint64 createdFromBuffer2 = 0; memcpy(&createdFromBuffer2, dataAt, sizeof(createdFromBuffer2)); dataAt += sizeof(createdFromBuffer2); bytesRead += sizeof(createdFromBuffer2); Q_ASSERT(createdFromBuffer2 == createdFromBuffer); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); } #endif if (_created == UNKNOWN_CREATED_TIME) { // we don't yet have a _created timestamp, so we accept this one createdFromBuffer -= clockSkew; if (createdFromBuffer > now || createdFromBuffer == UNKNOWN_CREATED_TIME) { createdFromBuffer = now; } _created = createdFromBuffer; } } #ifdef WANT_DEBUG { quint64 lastEdited = getLastEdited(); float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); QString ageAsString = formatSecondsElapsed(getAge()); qCDebug(entities) << "------------------------------------------"; qCDebug(entities) << "Loading entity " << getEntityItemID() << " from buffer..."; qCDebug(entities) << "------------------------------------------"; debugDump(); qCDebug(entities) << "------------------------------------------"; qCDebug(entities) << " _created =" << _created; qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; qCDebug(entities) << " lastEdited =" << lastEdited; qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; } #endif quint64 lastEditedFromBuffer = 0; // TODO: we could make this encoded as a delta from _created // _lastEdited parser.readValue(lastEditedFromBuffer); #ifdef VALIDATE_ENTITY_ITEM_PARSER { quint64 lastEditedFromBuffer2 = 0; memcpy(&lastEditedFromBuffer2, dataAt, sizeof(lastEditedFromBuffer2)); dataAt += sizeof(lastEditedFromBuffer2); bytesRead += sizeof(lastEditedFromBuffer2); Q_ASSERT(lastEditedFromBuffer2 == lastEditedFromBuffer); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); } #endif quint64 lastEditedFromBufferAdjusted = lastEditedFromBuffer == 0 ? 0 : lastEditedFromBuffer - clockSkew; if (lastEditedFromBufferAdjusted > now) { lastEditedFromBufferAdjusted = now; } bool fromSameServerEdit = (lastEditedFromBuffer == _lastEditedFromRemoteInRemoteTime); #ifdef WANT_DEBUG qCDebug(entities) << "data from server **************** "; qCDebug(entities) << " entityItemID:" << getEntityItemID(); qCDebug(entities) << " now:" << now; qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now); qCDebug(entities) << " lastEditedFromBuffer:" << debugTime(lastEditedFromBuffer, now); qCDebug(entities) << " clockSkew:" << clockSkew; qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); qCDebug(entities) << " _lastEditedFromRemote:" << debugTime(_lastEditedFromRemote, now); qCDebug(entities) << " _lastEditedFromRemoteInRemoteTime:" << debugTime(_lastEditedFromRemoteInRemoteTime, now); qCDebug(entities) << " fromSameServerEdit:" << fromSameServerEdit; #endif bool ignoreServerPacket = false; // assume we'll use this server packet // If this packet is from the same server edit as the last packet we accepted from the server // we probably want to use it. if (fromSameServerEdit) { // If this is from the same sever packet, then check against any local changes since we got // the most recent packet from this server time if (_lastEdited > _lastEditedFromRemote) { ignoreServerPacket = true; } } else { // If this isn't from the same sever packet, then honor our skew adjusted times... // If we've changed our local tree more recently than the new data from this packet // then we will not be changing our values, instead we just read and skip the data if (_lastEdited > lastEditedFromBufferAdjusted) { ignoreServerPacket = true; } } // before proceeding, check to see if this is an entity that we know has been deleted, which // might happen in the case of out-of-order and/or recorvered packets, if we've deleted the entity // we can confidently ignore this packet EntityTreePointer tree = getTree(); if (tree && tree->isDeletedEntity(_id)) { #ifdef WANT_DEBUG qCDebug(entities) << "Received packet for previously deleted entity [" << _id << "] ignoring. " "(inside " << __FUNCTION__ << ")"; #endif ignoreServerPacket = true; } if (ignoreServerPacket) { overwriteLocalData = false; #ifdef WANT_DEBUG qCDebug(entities) << "IGNORING old data from server!!! ****************"; debugDump(); #endif } else { #ifdef WANT_DEBUG qCDebug(entities) << "USING NEW data from server!!! ****************"; debugDump(); #endif // don't allow _lastEdited to be in the future _lastEdited = lastEditedFromBufferAdjusted; _lastEditedFromRemote = now; _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; } // last updated is stored as ByteCountCoded delta from lastEdited quint64 updateDelta; parser.readCompressedCount(updateDelta); #ifdef VALIDATE_ENTITY_ITEM_PARSER { QByteArray encodedUpdateDelta = originalDataBuffer.mid(bytesRead); // maximum possible size ByteCountCoded<quint64> updateDeltaCoder = encodedUpdateDelta; quint64 updateDelta2 = updateDeltaCoder; Q_ASSERT(updateDelta == updateDelta2); encodedUpdateDelta = updateDeltaCoder; // determine true length dataAt += encodedUpdateDelta.size(); bytesRead += encodedUpdateDelta.size(); Q_ASSERT(parser.offset() == (unsigned int) bytesRead); } #endif if (overwriteLocalData) { _lastUpdated = lastEditedFromBufferAdjusted + updateDelta; // don't adjust for clock skew since we already did that #ifdef WANT_DEBUG qCDebug(entities) << " _lastUpdated:" << debugTime(_lastUpdated, now); qCDebug(entities) << " _lastEdited:" << debugTime(_lastEdited, now); qCDebug(entities) << " lastEditedFromBufferAdjusted:" << debugTime(lastEditedFromBufferAdjusted, now); #endif }