Merge branch 'master' of github.com:highfidelity/hifi into average-near-holds

This commit is contained in:
Seth Alves 2015-11-16 10:08:01 -08:00
commit b567a4846d
19 changed files with 348 additions and 254 deletions

View file

@ -63,7 +63,9 @@ AvatarMixer::~AvatarMixer() {
_broadcastThread.wait(); _broadcastThread.wait();
} }
const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 300.0f; // An 80% chance of sending a identity packet within a 5 second interval.
// assuming 60 htz update rate.
const float BILLBOARD_AND_IDENTITY_SEND_PROBABILITY = 1.0f / 187.0f;
// NOTE: some additional optimizations to consider. // NOTE: some additional optimizations to consider.
// 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present
@ -243,6 +245,47 @@ void AvatarMixer::broadcastAvatarData() {
return; return;
} }
// if an avatar has just connected make sure we send out the mesh and billboard
bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets()
|| !otherNodeData->checkAndSetHasReceivedFirstPacketsFrom(node->getUUID());
// we will also force a send of billboard or identity packet
// if either has changed in the last frame
if (otherNodeData->getBillboardChangeTimestamp() > 0
&& (forceSend
|| otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp
|| distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
QByteArray rfcUUID = otherNode->getUUID().toRfc4122();
QByteArray billboard = otherNodeData->getAvatar().getBillboard();
auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, rfcUUID.size() + billboard.size());
billboardPacket->write(rfcUUID);
billboardPacket->write(billboard);
nodeList->sendPacket(std::move(billboardPacket), *node);
++_sumBillboardPackets;
}
if (otherNodeData->getIdentityChangeTimestamp() > 0
&& (forceSend
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|| distribution(generator) < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size());
individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122());
identityPacket->write(individualData);
nodeList->sendPacket(std::move(identityPacket), *node);
++_sumIdentityPackets;
}
AvatarData& otherAvatar = otherNodeData->getAvatar(); AvatarData& otherAvatar = otherNodeData->getAvatar();
// Decide whether to send this avatar's data based on it's distance from us // Decide whether to send this avatar's data based on it's distance from us
@ -254,10 +297,10 @@ void AvatarMixer::broadcastAvatarData() {
// potentially update the max full rate distance for this frame // potentially update the max full rate distance for this frame
maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar); maxAvatarDistanceThisFrame = std::max(maxAvatarDistanceThisFrame, distanceToAvatar);
if (distanceToAvatar != 0.0f if (distanceToAvatar != 0.0f
&& distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) { && distribution(generator) > (nodeData->getFullRateDistance() / distanceToAvatar)) {
return; return;
} }
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID()); AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(otherNode->getUUID());
AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber(); AvatarDataSequenceNumber lastSeqFromSender = otherNodeData->getLastReceivedSequenceNumber();
@ -291,53 +334,11 @@ void AvatarMixer::broadcastAvatarData() {
numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122()); numAvatarDataBytes += avatarPacketList->write(otherNode->getUUID().toRfc4122());
numAvatarDataBytes += numAvatarDataBytes +=
avatarPacketList->write(otherAvatar.toByteArray(false, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO)); avatarPacketList->write(otherAvatar.toByteArray(false, distribution(generator) < AVATAR_SEND_FULL_UPDATE_RATIO));
avatarPacketList->endSegment(); avatarPacketList->endSegment();
// if the receiving avatar has just connected make sure we send out the mesh and billboard
// for this avatar (assuming they exist)
bool forceSend = !nodeData->checkAndSetHasReceivedFirstPackets();
// we will also force a send of billboard or identity packet
// if either has changed in the last frame
if (otherNodeData->getBillboardChangeTimestamp() > 0
&& (forceSend
|| otherNodeData->getBillboardChangeTimestamp() > _lastFrameTimestamp
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
QByteArray rfcUUID = otherNode->getUUID().toRfc4122();
QByteArray billboard = otherNodeData->getAvatar().getBillboard();
auto billboardPacket = NLPacket::create(PacketType::AvatarBillboard, rfcUUID.size() + billboard.size());
billboardPacket->write(rfcUUID);
billboardPacket->write(billboard);
nodeList->sendPacket(std::move(billboardPacket), *node);
++_sumBillboardPackets;
}
if (otherNodeData->getIdentityChangeTimestamp() > 0
&& (forceSend
|| otherNodeData->getIdentityChangeTimestamp() > _lastFrameTimestamp
|| randFloat() < BILLBOARD_AND_IDENTITY_SEND_PROBABILITY)) {
QByteArray individualData = otherNodeData->getAvatar().identityByteArray();
auto identityPacket = NLPacket::create(PacketType::AvatarIdentity, individualData.size());
individualData.replace(0, NUM_BYTES_RFC4122_UUID, otherNode->getUUID().toRfc4122());
identityPacket->write(individualData);
nodeList->sendPacket(std::move(identityPacket), *node);
++_sumIdentityPackets;
}
}); });
// close the current packet so that we're always sending something // close the current packet so that we're always sending something
avatarPacketList->closeCurrentPacket(true); avatarPacketList->closeCurrentPacket(true);
@ -484,7 +485,7 @@ void AvatarMixer::sendStatsPacket() {
// add the key to ask the domain-server for a username replacement, if it has it // add the key to ask the domain-server for a username replacement, if it has it
avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID()); avatarStats[USERNAME_UUID_REPLACEMENT_STATS_KEY] = uuidStringWithoutCurlyBraces(node->getUUID());
avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundBandwidth(); avatarStats[NODE_OUTBOUND_KBPS_STAT_KEY] = node->getOutboundBandwidth();
avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundBandwidth(); avatarStats[NODE_INBOUND_KBPS_STAT_KEY] = node->getInboundBandwidth();
@ -537,7 +538,7 @@ void AvatarMixer::run() {
qDebug() << "Waiting for domain settings from domain-server."; qDebug() << "Waiting for domain settings from domain-server.";
// block until we get the settingsRequestComplete signal // block until we get the settingsRequestComplete signal
QEventLoop loop; QEventLoop loop;
connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit); connect(&domainHandler, &DomainHandler::settingsReceived, &loop, &QEventLoop::quit);
connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit); connect(&domainHandler, &DomainHandler::settingsReceiveFail, &loop, &QEventLoop::quit);

View file

@ -16,7 +16,7 @@
int AvatarMixerClientData::parseData(NLPacket& packet) { int AvatarMixerClientData::parseData(NLPacket& packet) {
// pull the sequence number from the data first // pull the sequence number from the data first
packet.readPrimitive(&_lastReceivedSequenceNumber); packet.readPrimitive(&_lastReceivedSequenceNumber);
// compute the offset to the data payload // compute the offset to the data payload
return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead())); return _avatar.parseDataFromBuffer(packet.readWithoutCopy(packet.bytesLeftToRead()));
} }
@ -27,6 +27,14 @@ bool AvatarMixerClientData::checkAndSetHasReceivedFirstPackets() {
return oldValue; return oldValue;
} }
bool AvatarMixerClientData::checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid) {
if (_hasReceivedFirstPacketsFrom.find(uuid) == _hasReceivedFirstPacketsFrom.end()) {
_hasReceivedFirstPacketsFrom.insert(uuid);
return false;
}
return true;
}
uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const { uint16_t AvatarMixerClientData::getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const {
// return the matching PacketSequenceNumber, or the default if we don't have it // return the matching PacketSequenceNumber, or the default if we don't have it
auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID); auto nodeMatch = _lastBroadcastSequenceNumbers.find(nodeUUID);
@ -45,9 +53,9 @@ void AvatarMixerClientData::loadJSONStats(QJsonObject& jsonObject) const {
jsonObject["avg_other_av_starves_per_second"] = getAvgNumOtherAvatarStarvesPerSecond(); jsonObject["avg_other_av_starves_per_second"] = getAvgNumOtherAvatarStarvesPerSecond();
jsonObject["avg_other_av_skips_per_second"] = getAvgNumOtherAvatarSkipsPerSecond(); jsonObject["avg_other_av_skips_per_second"] = getAvgNumOtherAvatarSkipsPerSecond();
jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends; jsonObject["total_num_out_of_order_sends"] = _numOutOfOrderSends;
jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps(); jsonObject[OUTBOUND_AVATAR_DATA_STATS_KEY] = getOutboundAvatarDataKbps();
jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar.getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT; jsonObject[INBOUND_AVATAR_DATA_STATS_KEY] = _avatar.getAverageBytesReceivedPerSecond() / (float) BYTES_PER_KILOBIT;
jsonObject["av_data_receive_rate"] = _avatar.getReceiveRate(); jsonObject["av_data_receive_rate"] = _avatar.getReceiveRate();
} }

View file

@ -15,6 +15,7 @@
#include <algorithm> #include <algorithm>
#include <cfloat> #include <cfloat>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <QtCore/QJsonObject> #include <QtCore/QJsonObject>
#include <QtCore/QUrl> #include <QtCore/QUrl>
@ -34,25 +35,26 @@ class AvatarMixerClientData : public NodeData {
public: public:
int parseData(NLPacket& packet); int parseData(NLPacket& packet);
AvatarData& getAvatar() { return _avatar; } AvatarData& getAvatar() { return _avatar; }
bool checkAndSetHasReceivedFirstPackets(); bool checkAndSetHasReceivedFirstPackets();
bool checkAndSetHasReceivedFirstPacketsFrom(const QUuid& uuid);
uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const; uint16_t getLastBroadcastSequenceNumber(const QUuid& nodeUUID) const;
void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber) void setLastBroadcastSequenceNumber(const QUuid& nodeUUID, uint16_t sequenceNumber)
{ _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; } { _lastBroadcastSequenceNumbers[nodeUUID] = sequenceNumber; }
Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); } Q_INVOKABLE void removeLastBroadcastSequenceNumber(const QUuid& nodeUUID) { _lastBroadcastSequenceNumbers.erase(nodeUUID); }
uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; } uint16_t getLastReceivedSequenceNumber() const { return _lastReceivedSequenceNumber; }
quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; } quint64 getBillboardChangeTimestamp() const { return _billboardChangeTimestamp; }
void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; } void setBillboardChangeTimestamp(quint64 billboardChangeTimestamp) { _billboardChangeTimestamp = billboardChangeTimestamp; }
quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; } quint64 getIdentityChangeTimestamp() const { return _identityChangeTimestamp; }
void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; } void setIdentityChangeTimestamp(quint64 identityChangeTimestamp) { _identityChangeTimestamp = identityChangeTimestamp; }
void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; } void setFullRateDistance(float fullRateDistance) { _fullRateDistance = fullRateDistance; }
float getFullRateDistance() const { return _fullRateDistance; } float getFullRateDistance() const { return _fullRateDistance; }
void setMaxAvatarDistance(float maxAvatarDistance) { _maxAvatarDistance = maxAvatarDistance; } void setMaxAvatarDistance(float maxAvatarDistance) { _maxAvatarDistance = maxAvatarDistance; }
float getMaxAvatarDistance() const { return _maxAvatarDistance; } float getMaxAvatarDistance() const { return _maxAvatarDistance; }
@ -73,31 +75,32 @@ public:
void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; } void resetNumFramesSinceFRDAdjustment() { _numFramesSinceAdjustment = 0; }
void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); } void recordSentAvatarData(int numBytes) { _avgOtherAvatarDataRate.updateAverage((float) numBytes); }
float getOutboundAvatarDataKbps() const float getOutboundAvatarDataKbps() const
{ return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; } { return _avgOtherAvatarDataRate.getAverageSampleValuePerSecond() / (float) BYTES_PER_KILOBIT; }
void loadJSONStats(QJsonObject& jsonObject) const; void loadJSONStats(QJsonObject& jsonObject) const;
private: private:
AvatarData _avatar; AvatarData _avatar;
uint16_t _lastReceivedSequenceNumber { 0 }; uint16_t _lastReceivedSequenceNumber { 0 };
std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers; std::unordered_map<QUuid, uint16_t> _lastBroadcastSequenceNumbers;
std::unordered_set<QUuid> _hasReceivedFirstPacketsFrom;
bool _hasReceivedFirstPackets = false; bool _hasReceivedFirstPackets = false;
quint64 _billboardChangeTimestamp = 0; quint64 _billboardChangeTimestamp = 0;
quint64 _identityChangeTimestamp = 0; quint64 _identityChangeTimestamp = 0;
float _fullRateDistance = FLT_MAX; float _fullRateDistance = FLT_MAX;
float _maxAvatarDistance = FLT_MAX; float _maxAvatarDistance = FLT_MAX;
int _numAvatarsSentLastFrame = 0; int _numAvatarsSentLastFrame = 0;
int _numFramesSinceAdjustment = 0; int _numFramesSinceAdjustment = 0;
SimpleMovingAverage _otherAvatarStarves; SimpleMovingAverage _otherAvatarStarves;
SimpleMovingAverage _otherAvatarSkips; SimpleMovingAverage _otherAvatarSkips;
int _numOutOfOrderSends = 0; int _numOutOfOrderSends = 0;
SimpleMovingAverage _avgOtherAvatarDataRate; SimpleMovingAverage _avgOtherAvatarDataRate;
}; };

View file

@ -96,29 +96,130 @@ bool EntityServer::hasSpecialPacketsToSend(const SharedNodePointer& node) {
return shouldSendDeletedEntities; return shouldSendDeletedEntities;
} }
// FIXME - most of the old code for this was encapsulated in EntityTree, I liked that design from a data
// hiding and object oriented perspective. But that didn't really allow us to handle the case of lots
// of entities being deleted at the same time. I'd like to look to move this back into EntityTree but
// for now this works and addresses the bug.
int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) { int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryNode* queryNode, int& packetsSent) {
int totalBytes = 0; int totalBytes = 0;
EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData()); EntityNodeData* nodeData = static_cast<EntityNodeData*>(node->getLinkedData());
if (nodeData) { if (nodeData) {
quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt(); quint64 deletedEntitiesSentAt = nodeData->getLastDeletedEntitiesSentAt();
quint64 considerEntitiesSince = EntityTree::getAdjustedConsiderSince(deletedEntitiesSentAt);
quint64 deletePacketSentAt = usecTimestampNow(); quint64 deletePacketSentAt = usecTimestampNow();
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
auto recentlyDeleted = tree->getRecentlyDeletedEntityIDs();
bool hasMoreToSend = true; bool hasMoreToSend = true;
packetsSent = 0; packetsSent = 0;
while (hasMoreToSend) { // create a new special packet
auto specialPacket = tree->encodeEntitiesDeletedSince(queryNode->getSequenceNumber(), deletedEntitiesSentAt, std::unique_ptr<NLPacket> deletesPacket = NLPacket::create(PacketType::EntityErase);
hasMoreToSend);
queryNode->packetSent(*specialPacket); // pack in flags
OCTREE_PACKET_FLAGS flags = 0;
deletesPacket->writePrimitive(flags);
totalBytes += specialPacket->getDataSize(); // pack in sequence number
packetsSent++; auto sequenceNumber = queryNode->getSequenceNumber();
deletesPacket->writePrimitive(sequenceNumber);
DependencyManager::get<NodeList>()->sendPacket(std::move(specialPacket), *node); // pack in timestamp
} OCTREE_PACKET_SENT_TIME now = usecTimestampNow();
deletesPacket->writePrimitive(now);
// figure out where we are now and pack a temporary number of IDs
uint16_t numberOfIDs = 0;
qint64 numberOfIDsPos = deletesPacket->pos();
deletesPacket->writePrimitive(numberOfIDs);
// we keep a multi map of entity IDs to timestamps, we only want to include the entity IDs that have been
// deleted since we last sent to this node
auto it = recentlyDeleted.constBegin();
while (it != recentlyDeleted.constEnd()) {
// if the timestamp is more recent then out last sent time, include it
if (it.key() > considerEntitiesSince) {
// get all the IDs for this timestamp
const auto& entityIDsFromTime = recentlyDeleted.values(it.key());
for (const auto& entityID : entityIDsFromTime) {
// check to make sure we have room for one more ID, if we don't have more
// room, then send out this packet and create another one
if (NUM_BYTES_RFC4122_UUID > deletesPacket->bytesAvailableForWrite()) {
// replace the count for the number of included IDs
deletesPacket->seek(numberOfIDsPos);
deletesPacket->writePrimitive(numberOfIDs);
// Send the current packet
queryNode->packetSent(*deletesPacket);
auto thisPacketSize = deletesPacket->getDataSize();
totalBytes += thisPacketSize;
packetsSent++;
DependencyManager::get<NodeList>()->sendPacket(std::move(deletesPacket), *node);
#ifdef EXTRA_ERASE_DEBUGGING
qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize;
#endif
// create another packet
deletesPacket = NLPacket::create(PacketType::EntityErase);
// pack in flags
deletesPacket->writePrimitive(flags);
// pack in sequence number
sequenceNumber = queryNode->getSequenceNumber();
deletesPacket->writePrimitive(sequenceNumber);
// pack in timestamp
deletesPacket->writePrimitive(now);
// figure out where we are now and pack a temporary number of IDs
numberOfIDs = 0;
numberOfIDsPos = deletesPacket->pos();
deletesPacket->writePrimitive(numberOfIDs);
}
// FIXME - we still seem to see cases where incorrect EntityIDs get sent from the server
// to the client. These were causing "lost" entities like flashlights and laser pointers
// now that we keep around some additional history of the erased entities and resend that
// history for a longer time window, these entities are not "lost". But we haven't yet
// found/fixed the underlying issue that caused bad UUIDs to be sent to some users.
deletesPacket->write(entityID.toRfc4122());
++numberOfIDs;
#ifdef EXTRA_ERASE_DEBUGGING
qDebug() << "EntityTree::encodeEntitiesDeletedSince() including:" << entityID;
#endif
} // end for (ids)
} // end if (it.val > sinceLast)
++it;
} // end while
// replace the count for the number of included IDs
deletesPacket->seek(numberOfIDsPos);
deletesPacket->writePrimitive(numberOfIDs);
// Send the current packet
queryNode->packetSent(*deletesPacket);
auto thisPacketSize = deletesPacket->getDataSize();
totalBytes += thisPacketSize;
packetsSent++;
DependencyManager::get<NodeList>()->sendPacket(std::move(deletesPacket), *node);
#ifdef EXTRA_ERASE_DEBUGGING
qDebug() << "EntityServer::sendSpecialPackets() sending packet packetsSent[" << packetsSent << "] size:" << thisPacketSize;
#endif
nodeData->setLastDeletedEntitiesSentAt(deletePacketSentAt); nodeData->setLastDeletedEntitiesSentAt(deletePacketSentAt);
} }
@ -134,6 +235,7 @@ int EntityServer::sendSpecialPackets(const SharedNodePointer& node, OctreeQueryN
return totalBytes; return totalBytes;
} }
void EntityServer::pruneDeletedEntities() { void EntityServer::pruneDeletedEntities() {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
if (tree->hasAnyDeletedEntities()) { if (tree->hasAnyDeletedEntities()) {

View file

@ -240,6 +240,7 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
int packetsSent = 0; int packetsSent = 0;
int totalBytesSent = 0;
NodeToSenderStatsMapIterator i = _singleSenderStats.begin(); NodeToSenderStatsMapIterator i = _singleSenderStats.begin();
while (i != _singleSenderStats.end()) { while (i != _singleSenderStats.end()) {
@ -291,12 +292,15 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
packetsSent += nackPacketList->getNumPackets(); packetsSent += nackPacketList->getNumPackets();
// send the list of nack packets // send the list of nack packets
nodeList->sendPacketList(std::move(nackPacketList), *destinationNode); totalBytesSent += nodeList->sendPacketList(std::move(nackPacketList), *destinationNode);
} }
++i; ++i;
} }
OctreeSendThread::_totalPackets += packetsSent;
OctreeSendThread::_totalBytes += totalBytesSent;
return packetsSent; return packetsSent;
} }

View file

@ -119,6 +119,10 @@ AtomicUIntStat OctreeSendThread::_totalBytes { 0 };
AtomicUIntStat OctreeSendThread::_totalWastedBytes { 0 }; AtomicUIntStat OctreeSendThread::_totalWastedBytes { 0 };
AtomicUIntStat OctreeSendThread::_totalPackets { 0 }; AtomicUIntStat OctreeSendThread::_totalPackets { 0 };
AtomicUIntStat OctreeSendThread::_totalSpecialBytes { 0 };
AtomicUIntStat OctreeSendThread::_totalSpecialPackets { 0 };
int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) {
OctreeServer::didHandlePacketSend(this); OctreeServer::didHandlePacketSend(this);
@ -579,11 +583,17 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// send the environment packet // send the environment packet
// TODO: should we turn this into a while loop to better handle sending multiple special packets // TODO: should we turn this into a while loop to better handle sending multiple special packets
if (_myServer->hasSpecialPacketsToSend(_node) && !nodeData->isShuttingDown()) { if (_myServer->hasSpecialPacketsToSend(_node) && !nodeData->isShuttingDown()) {
int specialPacketsSent; int specialPacketsSent = 0;
trueBytesSent += _myServer->sendSpecialPackets(_node, nodeData, specialPacketsSent); trueBytesSent += _myServer->sendSpecialPackets(_node, nodeData, specialPacketsSent);
nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed
truePacketsSent += specialPacketsSent; truePacketsSent += specialPacketsSent;
packetsSentThisInterval += specialPacketsSent; packetsSentThisInterval += specialPacketsSent;
_totalPackets += specialPacketsSent;
_totalBytes += trueBytesSent;
_totalSpecialPackets += specialPacketsSent;
_totalSpecialBytes += trueBytesSent;
} }
// Re-send packets that were nacked by the client // Re-send packets that were nacked by the client

View file

@ -38,6 +38,9 @@ public:
static AtomicUIntStat _totalWastedBytes; static AtomicUIntStat _totalWastedBytes;
static AtomicUIntStat _totalPackets; static AtomicUIntStat _totalPackets;
static AtomicUIntStat _totalSpecialBytes;
static AtomicUIntStat _totalSpecialPackets;
static AtomicUIntStat _usleepTime; static AtomicUIntStat _usleepTime;
static AtomicUIntStat _usleepCalls; static AtomicUIntStat _usleepCalls;

View file

@ -415,6 +415,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
quint64 totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks(); quint64 totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks();
quint64 totalBytesOfColor = OctreePacketData::getTotalBytesOfColor(); quint64 totalBytesOfColor = OctreePacketData::getTotalBytesOfColor();
quint64 totalOutboundSpecialPackets = OctreeSendThread::_totalSpecialPackets;
quint64 totalOutboundSpecialBytes = OctreeSendThread::_totalSpecialBytes;
statsString += QString(" Total Clients Connected: %1 clients\r\n") statsString += QString(" Total Clients Connected: %1 clients\r\n")
.arg(locale.toString((uint)getCurrentClientCount()).rightJustified(COLUMN_WIDTH, ' ')); .arg(locale.toString((uint)getCurrentClientCount()).rightJustified(COLUMN_WIDTH, ' '));
@ -606,6 +609,13 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
.arg(locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' ')); .arg(locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Outbound Bytes: %1 bytes\r\n") statsString += QString(" Total Outbound Bytes: %1 bytes\r\n")
.arg(locale.toString((uint)totalOutboundBytes).rightJustified(COLUMN_WIDTH, ' ')); .arg(locale.toString((uint)totalOutboundBytes).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Outbound Special Packets: %1 packets\r\n")
.arg(locale.toString((uint)totalOutboundSpecialPackets).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Outbound Special Bytes: %1 bytes\r\n")
.arg(locale.toString((uint)totalOutboundSpecialBytes).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Wasted Bytes: %1 bytes\r\n") statsString += QString(" Total Wasted Bytes: %1 bytes\r\n")
.arg(locale.toString((uint)totalWastedBytes).rightJustified(COLUMN_WIDTH, ' ')); .arg(locale.toString((uint)totalWastedBytes).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString().sprintf(" Total OctalCode Bytes: %s bytes (%5.2f%%)\r\n", statsString += QString().sprintf(" Total OctalCode Bytes: %s bytes (%5.2f%%)\r\n",

View file

@ -33,15 +33,6 @@ var SHOW = 4;
var HIDE = 5; var HIDE = 5;
var LOAD = 6; var LOAD = 6;
var COLORS = [];
COLORS[PLAY] = { red: PLAY, green: 0, blue: 0 };
COLORS[PLAY_LOOP] = { red: PLAY_LOOP, green: 0, blue: 0 };
COLORS[STOP] = { red: STOP, green: 0, blue: 0 };
COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 };
COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 };
COLORS[LOAD] = { red: LOAD, green: 0, blue: 0 };
var windowDimensions = Controller.getViewportDimensions(); var windowDimensions = Controller.getViewportDimensions();
var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/"; var TOOL_ICON_URL = HIFI_PUBLIC_BUCKET + "images/tools/";
@ -138,6 +129,7 @@ function setupToolBars() {
} }
function sendCommand(id, action) { function sendCommand(id, action) {
if (action === SHOW) { if (action === SHOW) {
toolBars[id].selectTool(onOffIcon[id], false); toolBars[id].selectTool(onOffIcon[id], false);
toolBars[id].setAlpha(ALPHA_ON, playIcon[id]); toolBars[id].setAlpha(ALPHA_ON, playIcon[id]);
@ -154,24 +146,29 @@ function sendCommand(id, action) {
return; return;
} }
if (id === (toolBars.length - 1)) { if (id === (toolBars.length - 1))
for (i = 0; i < NUM_AC; i++) { id = -1;
sendCommand(i, action);
}
return;
}
var position = { x: controlEntityPosition.x + id * controlEntitySize, var controlEntity = Entities.addEntity({
y: controlEntityPosition.y, z: controlEntityPosition.z }; name: 'New Actor Controller',
Entities.addEntity({
name: "Actor Controller",
userData: clip_url,
type: "Box", type: "Box",
position: position, color: { red: 0, green: 0, blue: 0 },
dimensions: { x: controlEntitySize, y: controlEntitySize, z: controlEntitySize }, position: controlEntityPosition,
color: COLORS[action], dimensions: { x: controlEntitySize, y: controlEntitySize, z: controlEntitySize },
lifetime: 5 visible: false,
}); lifetime: 10,
userData: JSON.stringify({
idKey: {
uD_id: id
},
actionKey: {
uD_action: action
},
clipKey: {
uD_url: clip_url
}
})
});
} }
function mousePressEvent(event) { function mousePressEvent(event) {
@ -191,8 +188,12 @@ function mousePressEvent(event) {
sendCommand(i, PLAY_LOOP); sendCommand(i, PLAY_LOOP);
} else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { } else if (stopIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
sendCommand(i, STOP); sendCommand(i, STOP);
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
sendCommand(i, LOAD); input_text = Window.prompt("Insert the url of the clip: ","");
if (!(input_text === "" || input_text === null)) {
clip_url = input_text;
sendCommand(i, LOAD);
}
} else { } else {
// Check individual controls // Check individual controls
for (i = 0; i < NUM_AC; i++) { for (i = 0; i < NUM_AC; i++) {
@ -210,7 +211,7 @@ function mousePressEvent(event) {
sendCommand(i, STOP); sendCommand(i, STOP);
} else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) { } else if (loadIcon[i] === toolBars[i].clicked(clickedOverlay, false)) {
input_text = Window.prompt("Insert the url of the clip: ",""); input_text = Window.prompt("Insert the url of the clip: ","");
if(!(input_text === "" || input_text === null)){ if (!(input_text === "" || input_text === null)) {
clip_url = input_text; clip_url = input_text;
sendCommand(i, LOAD); sendCommand(i, LOAD);
} }

View file

@ -38,18 +38,6 @@ var SHOW = 4;
var HIDE = 5; var HIDE = 5;
var LOAD = 6; var LOAD = 6;
var COLORS = [];
COLORS[PLAY] = { red: PLAY, green: 0, blue: 0 };
COLORS[PLAY_LOOP] = { red: PLAY_LOOP, green: 0, blue: 0 };
COLORS[STOP] = { red: STOP, green: 0, blue: 0 };
COLORS[SHOW] = { red: SHOW, green: 0, blue: 0 };
COLORS[HIDE] = { red: HIDE, green: 0, blue: 0 };
COLORS[LOAD] = { red: LOAD, green: 0, blue: 0 };
controlEntityPosition.x += id * controlEntitySize;
Avatar.loadRecording(clip_url);
Avatar.setPlayFromCurrentLocation(playFromCurrentLocation); Avatar.setPlayFromCurrentLocation(playFromCurrentLocation);
Avatar.setPlayerUseDisplayName(useDisplayName); Avatar.setPlayerUseDisplayName(useDisplayName);
Avatar.setPlayerUseAttachments(useAttachments); Avatar.setPlayerUseAttachments(useAttachments);
@ -67,27 +55,27 @@ function setupEntityViewer() {
EntityViewer.queryOctree(); EntityViewer.queryOctree();
} }
function getAction(controlEntity) { function getAction(controlEntity) {
clip_url = controlEntity.userData; if (controlEntity === null) {
return DO_NOTHING;
}
var userData = JSON.parse(Entities.getEntityProperties(controlEntity, ["userData"]).userData);
if (controlEntity === null || var uD_id = userData.idKey.uD_id;
controlEntity.position.x !== controlEntityPosition.x || var uD_action = userData.actionKey.uD_action;
controlEntity.position.y !== controlEntityPosition.y || var uD_url = userData.clipKey.uD_url;
controlEntity.position.z !== controlEntityPosition.z ||
controlEntity.dimensions.x !== controlEntitySize) { Entities.deleteEntity((Entities.getEntityProperties(controlEntity)).id);
if (uD_id === id || uD_id === -1) {
if (uD_action === 6)
clip_url = uD_url;
return uD_action;
} else {
return DO_NOTHING; return DO_NOTHING;
} }
for (i in COLORS) {
if (controlEntity.color.red === COLORS[i].red &&
controlEntity.color.green === COLORS[i].green &&
controlEntity.color.blue === COLORS[i].blue) {
Entities.deleteEntity(controlEntity.id);
return parseInt(i);
}
}
return DO_NOTHING;
} }
count = 100; // This is necessary to wait for the audio mixer to connect count = 100; // This is necessary to wait for the audio mixer to connect
@ -100,7 +88,7 @@ function update(event) {
var controlEntity = Entities.findClosestEntity(controlEntityPosition, controlEntitySize); var controlEntity = Entities.findClosestEntity(controlEntityPosition, controlEntitySize);
var action = getAction(Entities.getEntityProperties(controlEntity)); var action = getAction(controlEntity);
switch(action) { switch(action) {
case PLAY: case PLAY:

View file

@ -176,14 +176,13 @@ function formatTime(time) {
var SEC_PER_MIN = 60; var SEC_PER_MIN = 60;
var MSEC_PER_SEC = 1000; var MSEC_PER_SEC = 1000;
var hours = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR)); var hours = Math.floor(time / (SEC_PER_MIN * MIN_PER_HOUR));
time -= hours * (MSEC_PER_SEC * SEC_PER_MIN * MIN_PER_HOUR); time -= hours * (SEC_PER_MIN * MIN_PER_HOUR);
var minutes = Math.floor(time / (MSEC_PER_SEC * SEC_PER_MIN)); var minutes = Math.floor(time / (SEC_PER_MIN));
time -= minutes * (MSEC_PER_SEC * SEC_PER_MIN); time -= minutes * (SEC_PER_MIN);
var seconds = Math.floor(time / MSEC_PER_SEC); var seconds = time;
seconds = time / MSEC_PER_SEC;
var text = ""; var text = "";
text += (hours > 0) ? hours + ":" : text += (hours > 0) ? hours + ":" :

View file

@ -608,7 +608,7 @@ float MyAvatar::recorderElapsed() {
if (!_recorder) { if (!_recorder) {
return 0; return 0;
} }
return (float)_recorder->position() / MSECS_PER_SECOND; return (float)_recorder->position() / (float) MSECS_PER_SECOND;
} }
QMetaObject::Connection _audioClientRecorderConnection; QMetaObject::Connection _audioClientRecorderConnection;

View file

@ -804,12 +804,12 @@ float AvatarData::playerElapsed() {
return 0; return 0;
} }
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
qint64 result; float result;
QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(this, "playerElapsed", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result)); Q_RETURN_ARG(float, result));
return result; return result;
} }
return (float)_player->position() / MSECS_PER_SECOND; return (float)_player->position() / (float) MSECS_PER_SECOND;
} }
float AvatarData::playerLength() { float AvatarData::playerLength() {
@ -817,12 +817,12 @@ float AvatarData::playerLength() {
return 0; return 0;
} }
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
qint64 result; float result;
QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(this, "playerLength", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(qint64, result)); Q_RETURN_ARG(float, result));
return result; return result;
} }
return _player->length() / MSECS_PER_SECOND; return (float)_player->length() / (float) MSECS_PER_SECOND;
} }
void AvatarData::loadRecording(const QString& filename) { void AvatarData::loadRecording(const QString& filename) {
@ -1513,7 +1513,8 @@ void AvatarData::setRecordingBasis(std::shared_ptr<Transform> recordingBasis) {
recordingBasis = std::make_shared<Transform>(); recordingBasis = std::make_shared<Transform>();
recordingBasis->setRotation(getOrientation()); recordingBasis->setRotation(getOrientation());
recordingBasis->setTranslation(getPosition()); recordingBasis->setTranslation(getPosition());
recordingBasis->setScale(getTargetScale()); // TODO: find a different way to record/playback the Scale of the avatar
//recordingBasis->setScale(getTargetScale());
} }
_recordingBasis = recordingBasis; _recordingBasis = recordingBasis;
} }
@ -1532,7 +1533,7 @@ Transform AvatarData::getTransform() const {
static const QString JSON_AVATAR_BASIS = QStringLiteral("basisTransform"); static const QString JSON_AVATAR_BASIS = QStringLiteral("basisTransform");
static const QString JSON_AVATAR_RELATIVE = QStringLiteral("relativeTransform"); static const QString JSON_AVATAR_RELATIVE = QStringLiteral("relativeTransform");
static const QString JSON_AVATAR_JOINT_ROTATIONS = QStringLiteral("jointRotations"); static const QString JSON_AVATAR_JOINT_ARRAY = QStringLiteral("jointArray");
static const QString JSON_AVATAR_HEAD = QStringLiteral("head"); static const QString JSON_AVATAR_HEAD = QStringLiteral("head");
static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation"); static const QString JSON_AVATAR_HEAD_ROTATION = QStringLiteral("rotation");
static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes"); static const QString JSON_AVATAR_HEAD_BLENDSHAPE_COEFFICIENTS = QStringLiteral("blendShapes");
@ -1544,6 +1545,24 @@ static const QString JSON_AVATAR_BODY_MODEL = QStringLiteral("bodyModel");
static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName"); static const QString JSON_AVATAR_DISPLAY_NAME = QStringLiteral("displayName");
static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments"); static const QString JSON_AVATAR_ATTACHEMENTS = QStringLiteral("attachments");
QJsonValue toJsonValue(const JointData& joint) {
QJsonArray result;
result.push_back(toJsonValue(joint.rotation));
result.push_back(toJsonValue(joint.translation));
return result;
}
JointData jointDataFromJsonValue(const QJsonValue& json) {
JointData result;
if (json.isArray()) {
QJsonArray array = json.toArray();
result.rotation = quatFromJsonValue(array[0]);
result.rotationSet = true;
result.translation = vec3FromJsonValue(array[1]);
result.translationSet = false;
}
return result;
}
// Every frame will store both a basis for the recording and a relative transform // Every frame will store both a basis for the recording and a relative transform
// This allows the application to decide whether playback should be relative to an avatar's // This allows the application to decide whether playback should be relative to an avatar's
@ -1575,13 +1594,16 @@ QByteArray avatarStateToFrame(const AvatarData* _avatar) {
root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform); root[JSON_AVATAR_RELATIVE] = Transform::toJson(relativeTransform);
root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis); root[JSON_AVATAR_BASIS] = Transform::toJson(*recordingBasis);
} }
} else {
root[JSON_AVATAR_RELATIVE] = Transform::toJson(_avatar->getTransform());
} }
QJsonArray jointRotations; // Skeleton pose
for (const auto& jointRotation : _avatar->getJointRotations()) { QJsonArray jointArray;
jointRotations.push_back(toJsonValue(jointRotation)); for (const auto& joint : _avatar->getRawJointData()) {
jointArray.push_back(toJsonValue(joint));
} }
root[JSON_AVATAR_JOINT_ROTATIONS] = jointRotations; root[JSON_AVATAR_JOINT_ARRAY] = jointArray;
const HeadData* head = _avatar->getHeadData(); const HeadData* head = _avatar->getHeadData();
if (head) { if (head) {
@ -1643,24 +1665,34 @@ void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar) {
auto worldTransform = currentBasis->worldTransform(relativeTransform); auto worldTransform = currentBasis->worldTransform(relativeTransform);
_avatar->setPosition(worldTransform.getTranslation()); _avatar->setPosition(worldTransform.getTranslation());
_avatar->setOrientation(worldTransform.getRotation()); _avatar->setOrientation(worldTransform.getRotation());
_avatar->setTargetScale(worldTransform.getScale().x);
// TODO: find a way to record/playback the Scale of the avatar
//_avatar->setTargetScale(worldTransform.getScale().x);
} }
#if 0
if (root.contains(JSON_AVATAR_ATTACHEMENTS)) { if (root.contains(JSON_AVATAR_ATTACHEMENTS)) {
// FIXME de-serialize attachment data // FIXME de-serialize attachment data
} }
// Joint rotations are relative to the avatar, so they require no basis correction // Joint rotations are relative to the avatar, so they require no basis correction
if (root.contains(JSON_AVATAR_JOINT_ROTATIONS)) { if (root.contains(JSON_AVATAR_JOINT_ARRAY)) {
QVector<quat> jointRotations; QVector<JointData> jointArray;
QJsonArray jointRotationsJson = root[JSON_AVATAR_JOINT_ROTATIONS].toArray(); QJsonArray jointArrayJson = root[JSON_AVATAR_JOINT_ARRAY].toArray();
jointRotations.reserve(jointRotationsJson.size()); jointArray.reserve(jointArrayJson.size());
for (const auto& jointRotationJson : jointRotationsJson) { for (const auto& jointJson : jointArrayJson) {
jointRotations.push_back(quatFromJsonValue(jointRotationJson)); jointArray.push_back(jointDataFromJsonValue(jointJson));
} }
QVector<glm::quat> jointRotations;
jointRotations.reserve(jointArray.size());
for (const auto& joint : jointArray) {
jointRotations.push_back(joint.rotation);
}
_avatar->setJointRotations(jointRotations);
} }
#if 0
// Most head data is relative to the avatar, and needs no basis correction, // Most head data is relative to the avatar, and needs no basis correction,
// but the lookat vector does need correction // but the lookat vector does need correction
HeadData* head = _avatar->_headData; HeadData* head = _avatar->_headData;

View file

@ -457,6 +457,9 @@ public:
bool translationSet = false; bool translationSet = false;
}; };
QJsonValue toJsonValue(const JointData& joint);
JointData jointDataFromJsonValue(const QJsonValue& q);
class AttachmentData { class AttachmentData {
public: public:
QUrl modelURL; QUrl modelURL;

View file

@ -887,8 +887,13 @@ void EntityTree::update() {
} }
} }
quint64 EntityTree::getAdjustedConsiderSince(quint64 sinceTime) {
return (sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER);
}
bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) { bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) {
quint64 considerEntitiesSince = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER; quint64 considerEntitiesSince = getAdjustedConsiderSince(sinceTime);
// we can probably leverage the ordered nature of QMultiMap to do this quickly... // we can probably leverage the ordered nature of QMultiMap to do this quickly...
bool hasSomethingNewer = false; bool hasSomethingNewer = false;
@ -915,88 +920,6 @@ bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) {
return hasSomethingNewer; return hasSomethingNewer;
} }
// sinceTime is an in/out parameter - it will be side effected with the last time sent out
std::unique_ptr<NLPacket> EntityTree::encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime,
bool& hasMore) {
quint64 considerEntitiesSince = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER;
auto deletesPacket = NLPacket::create(PacketType::EntityErase);
// pack in flags
OCTREE_PACKET_FLAGS flags = 0;
deletesPacket->writePrimitive(flags);
// pack in sequence number
deletesPacket->writePrimitive(sequenceNumber);
// pack in timestamp
OCTREE_PACKET_SENT_TIME now = usecTimestampNow();
deletesPacket->writePrimitive(now);
// figure out where we are now and pack a temporary number of IDs
uint16_t numberOfIDs = 0;
qint64 numberOfIDsPos = deletesPacket->pos();
deletesPacket->writePrimitive(numberOfIDs);
// we keep a multi map of entity IDs to timestamps, we only want to include the entity IDs that have been
// deleted since we last sent to this node
{
QReadLocker locker(&_recentlyDeletedEntitiesLock);
bool hasFilledPacket = false;
auto it = _recentlyDeletedEntityItemIDs.constBegin();
while (it != _recentlyDeletedEntityItemIDs.constEnd()) {
QList<QUuid> values = _recentlyDeletedEntityItemIDs.values(it.key());
for (int valueItem = 0; valueItem < values.size(); ++valueItem) {
// if the timestamp is more recent then out last sent time, include it
if (it.key() > considerEntitiesSince) {
QUuid entityID = values.at(valueItem);
// FIXME - we still seem to see cases where incorrect EntityIDs get sent from the server
// to the client. These were causing "lost" entities like flashlights and laser pointers
// now that we keep around some additional history of the erased entities and resend that
// history for a longer time window, these entities are not "lost". But we haven't yet
// found/fixed the underlying issue that caused bad UUIDs to be sent to some users.
deletesPacket->write(entityID.toRfc4122());
++numberOfIDs;
#ifdef EXTRA_ERASE_DEBUGGING
qDebug() << "EntityTree::encodeEntitiesDeletedSince() including:" << entityID;
#endif
// check to make sure we have room for one more ID
if (NUM_BYTES_RFC4122_UUID > deletesPacket->bytesAvailableForWrite()) {
hasFilledPacket = true;
break;
}
}
}
// check to see if we're about to return
if (hasFilledPacket) {
// let our caller know how far we got
sinceTime = it.key();
break;
}
++it;
}
// if we got to the end, then we're done sending
if (it == _recentlyDeletedEntityItemIDs.constEnd()) {
hasMore = false;
}
}
// replace the count for the number of included IDs
deletesPacket->seek(numberOfIDsPos);
deletesPacket->writePrimitive(numberOfIDs);
return deletesPacket;
}
// called by the server when it knows all nodes have been sent deleted packets // called by the server when it knows all nodes have been sent deleted packets
void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) { void EntityTree::forgetEntitiesDeletedBefore(quint64 sinceTime) {
quint64 considerSinceTime = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER; quint64 considerSinceTime = sinceTime - DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER;

View file

@ -147,10 +147,19 @@ public:
void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void addNewlyCreatedHook(NewlyCreatedEntityHook* hook);
void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook);
bool hasAnyDeletedEntities() const { return _recentlyDeletedEntityItemIDs.size() > 0; } bool hasAnyDeletedEntities() const {
QReadLocker locker(&_recentlyDeletedEntitiesLock);
return _recentlyDeletedEntityItemIDs.size() > 0;
}
bool hasEntitiesDeletedSince(quint64 sinceTime); bool hasEntitiesDeletedSince(quint64 sinceTime);
std::unique_ptr<NLPacket> encodeEntitiesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, static quint64 getAdjustedConsiderSince(quint64 sinceTime);
bool& hasMore);
QMultiMap<quint64, QUuid> getRecentlyDeletedEntityIDs() const {
QReadLocker locker(&_recentlyDeletedEntitiesLock);
return _recentlyDeletedEntityItemIDs;
}
void forgetEntitiesDeletedBefore(quint64 sinceTime); void forgetEntitiesDeletedBefore(quint64 sinceTime);
int processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode); int processEraseMessage(NLPacket& packet, const SharedNodePointer& sourceNode);
@ -243,7 +252,7 @@ private:
QReadWriteLock _newlyCreatedHooksLock; QReadWriteLock _newlyCreatedHooksLock;
QVector<NewlyCreatedEntityHook*> _newlyCreatedHooks; QVector<NewlyCreatedEntityHook*> _newlyCreatedHooks;
QReadWriteLock _recentlyDeletedEntitiesLock; mutable QReadWriteLock _recentlyDeletedEntitiesLock;
QMultiMap<quint64, QUuid> _recentlyDeletedEntityItemIDs; QMultiMap<quint64, QUuid> _recentlyDeletedEntityItemIDs;
EntityItemFBXService* _fbxService; EntityItemFBXService* _fbxService;

View file

@ -25,6 +25,8 @@ void Deck::queueClip(ClipPointer clip, Time timeOffset) {
// FIXME if the time offset is not zero, wrap the clip in a OffsetClip wrapper // FIXME if the time offset is not zero, wrap the clip in a OffsetClip wrapper
_clips.push_back(clip); _clips.push_back(clip);
_length = std::max(_length, clip->duration());
} }
void Deck::play() { void Deck::play() {

View file

@ -27,7 +27,7 @@ public:
static const FrameType TYPE_INVALID = 0xFFFF; static const FrameType TYPE_INVALID = 0xFFFF;
static const FrameType TYPE_HEADER = 0x0; static const FrameType TYPE_HEADER = 0x0;
FrameType type { TYPE_INVALID }; FrameType type { TYPE_INVALID };
Time timeOffset { 0 }; Time timeOffset { 0 }; // milliseconds
QByteArray data; QByteArray data;
Frame() {} Frame() {}

View file

@ -89,11 +89,7 @@ void Model::setScale(const glm::vec3& scale) {
} }
void Model::setScaleInternal(const glm::vec3& scale) { void Model::setScaleInternal(const glm::vec3& scale) {
float scaleLength = glm::length(_scale); if (glm::distance(_scale, scale) > METERS_PER_MILLIMETER) {
float relativeDeltaScale = glm::length(_scale - scale) / scaleLength;
const float ONE_PERCENT = 0.01f;
if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) {
_scale = scale; _scale = scale;
initJointTransforms(); initJointTransforms();
} }