diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp
index 6508f09f72..f37e8cf3f7 100644
--- a/assignment-client/src/entities/EntityServer.cpp
+++ b/assignment-client/src/entities/EntityServer.cpp
@@ -266,3 +266,72 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
tree->setWantEditLogging(wantEditLogging);
tree->setWantTerseEditLogging(wantTerseEditLogging);
}
+
+
+// FIXME - this stats tracking is somewhat temporary to debug the Whiteboard issues. It's not a bad
+// set of stats to have, but we'd probably want a different data structure if we keep it very long.
+// Since this version uses a single shared QMap for all senders, there could be some lock contention
+// on this QWriteLocker
+void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) {
+ QWriteLocker locker(&_viewerSendingStatsLock);
+ _viewerSendingStats[viewerNode][dataID] = { usecTimestampNow(), dataLastEdited };
+}
+
+void EntityServer::trackViewerGone(const QUuid& viewerNode) {
+ QWriteLocker locker(&_viewerSendingStatsLock);
+ _viewerSendingStats.remove(viewerNode);
+}
+
+QString EntityServer::serverSubclassStats() {
+ QLocale locale(QLocale::English);
+ QString statsString;
+
+ // display memory usage stats
+ statsString += "Entity Server Memory Statistics\r\n";
+ statsString += QString().sprintf("EntityTreeElement size... %ld bytes\r\n", sizeof(EntityTreeElement));
+ statsString += QString().sprintf(" EntityItem size... %ld bytes\r\n", sizeof(EntityItem));
+ statsString += "\r\n\r\n";
+
+ statsString += "Entity Server Sending to Viewer Statistics\r\n";
+ statsString += "----- Viewer Node ID ----------------- ----- Entity ID ---------------------- "
+ "---------- Last Sent To ---------- ---------- Last Edited -----------\r\n";
+
+ int viewers = 0;
+ const int COLUMN_WIDTH = 24;
+
+ {
+ QReadLocker locker(&_viewerSendingStatsLock);
+ quint64 now = usecTimestampNow();
+
+ for (auto viewerID : _viewerSendingStats.keys()) {
+ statsString += viewerID.toString() + "\r\n";
+
+ auto viewerData = _viewerSendingStats[viewerID];
+ for (auto entityID : viewerData.keys()) {
+ ViewerSendingStats stats = viewerData[entityID];
+
+ quint64 elapsedSinceSent = now - stats.lastSent;
+ double sentMsecsAgo = (double)(elapsedSinceSent / USECS_PER_MSEC);
+
+ quint64 elapsedSinceEdit = now - stats.lastEdited;
+ double editMsecsAgo = (double)(elapsedSinceEdit / USECS_PER_MSEC);
+
+ statsString += " "; // the viewerID spacing
+ statsString += entityID.toString();
+ statsString += " ";
+ statsString += QString("%1 msecs ago")
+ .arg(locale.toString((double)sentMsecsAgo).rightJustified(COLUMN_WIDTH, ' '));
+ statsString += QString("%1 msecs ago")
+ .arg(locale.toString((double)editMsecsAgo).rightJustified(COLUMN_WIDTH, ' '));
+ statsString += "\r\n";
+ }
+ viewers++;
+ }
+ }
+ if (viewers < 1) {
+ statsString += " no viewers... \r\n";
+ }
+ statsString += "\r\n\r\n";
+
+ return statsString;
+}
diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h
index d9795316c4..89b445c449 100644
--- a/assignment-client/src/entities/EntityServer.h
+++ b/assignment-client/src/entities/EntityServer.h
@@ -21,6 +21,12 @@
#include "EntityTree.h"
/// Handles assignments of type EntityServer - sending entities to various clients.
+
+struct ViewerSendingStats {
+ quint64 lastSent;
+ quint64 lastEdited;
+};
+
class EntityServer : public OctreeServer, public NewlyCreatedEntityHook {
Q_OBJECT
public:
@@ -44,6 +50,10 @@ public:
virtual void entityCreated(const EntityItem& newEntity, const SharedNodePointer& senderNode) override;
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override;
+ virtual QString serverSubclassStats();
+
+ virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode);
+ virtual void trackViewerGone(const QUuid& viewerNode);
public slots:
void pruneDeletedEntities();
@@ -57,6 +67,9 @@ private slots:
private:
EntitySimulation* _entitySimulation;
QTimer* _pruneDeletedEntitiesTimer = nullptr;
+
+ QReadWriteLock _viewerSendingStatsLock;
+ QMap> _viewerSendingStats;
};
#endif // hifi_EntityServer_h
diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp
index cff2c7ee2e..cafba8c083 100644
--- a/assignment-client/src/octree/OctreeQueryNode.cpp
+++ b/assignment-client/src/octree/OctreeQueryNode.cpp
@@ -179,15 +179,9 @@ void OctreeQueryNode::resetOctreePacket() {
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
// the clients requested color state.
- _currentPacketIsColor = getWantColor();
- _currentPacketIsCompressed = getWantCompression();
OCTREE_PACKET_FLAGS flags = 0;
- if (_currentPacketIsColor) {
- setAtBit(flags, PACKET_IS_COLOR_BIT);
- }
- if (_currentPacketIsCompressed) {
- setAtBit(flags, PACKET_IS_COMPRESSED_BIT);
- }
+ setAtBit(flags, PACKET_IS_COLOR_BIT);
+ setAtBit(flags, PACKET_IS_COMPRESSED_BIT);
_octreePacket->reset();
diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h
index 0c691a06a2..67298296e9 100644
--- a/assignment-client/src/octree/OctreeQueryNode.h
+++ b/assignment-client/src/octree/OctreeQueryNode.h
@@ -14,7 +14,6 @@
#include
-#include
#include
#include
#include
@@ -55,7 +54,6 @@ public:
void setMaxLevelReached(int maxLevelReached) { _maxLevelReachedInLastSearch = maxLevelReached; }
OctreeElementBag elementBag;
- CoverageMap map;
OctreeElementExtraEncodeData extraEncodeData;
ViewFrustum& getCurrentViewFrustum() { return _currentViewFrustum; }
@@ -79,9 +77,7 @@ public:
bool getCurrentPacketIsColor() const { return _currentPacketIsColor; }
bool getCurrentPacketIsCompressed() const { return _currentPacketIsCompressed; }
- bool getCurrentPacketFormatMatches() {
- return (getCurrentPacketIsColor() == getWantColor() && getCurrentPacketIsCompressed() == getWantCompression());
- }
+ bool getCurrentPacketFormatMatches() { return (getCurrentPacketIsCompressed() == true); } // FIXME
bool hasLodChanged() const { return _lodChanged; }
diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp
index 94d82b463e..d01117dff6 100644
--- a/assignment-client/src/octree/OctreeSendThread.cpp
+++ b/assignment-client/src/octree/OctreeSendThread.cpp
@@ -321,8 +321,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// If we're starting a fresh packet, then...
// If we're moving, and the client asked for low res, then we force monochrome, otherwise, use
// the clients requested color state.
- bool wantColor = nodeData->getWantColor();
- bool wantCompression = nodeData->getWantCompression();
// If we have a packet waiting, and our desired want color, doesn't match the current waiting packets color
// then let's just send that waiting packet.
@@ -333,10 +331,8 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
nodeData->resetOctreePacket();
}
int targetSize = MAX_OCTREE_PACKET_DATA_SIZE;
- if (wantCompression) {
- targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
- }
- _packetData.changeSettings(wantCompression, targetSize);
+ targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
+ _packetData.changeSettings(targetSize);
}
const ViewFrustum* lastViewFrustum = wantDelta ? &nodeData->getLastKnownViewFrustum() : NULL;
@@ -350,7 +346,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
if (nodeData->moveShouldDump() || nodeData->hasLodChanged()) {
nodeData->dumpOutOfView();
}
- nodeData->map.erase();
}
if (!viewFrustumChanged && !nodeData->getWantDelta()) {
@@ -451,22 +446,25 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
}
*/
- bool wantOcclusionCulling = nodeData->getWantOcclusionCulling();
- CoverageMap* coverageMap = wantOcclusionCulling ? &nodeData->map : IGNORE_COVERAGE_MAP;
-
float octreeSizeScale = nodeData->getOctreeSizeScale();
int boundaryLevelAdjustClient = nodeData->getBoundaryLevelAdjust();
int boundaryLevelAdjust = boundaryLevelAdjustClient + (viewFrustumChanged && nodeData->getWantLowResMoving()
? LOW_RES_MOVING_ADJUST : NO_BOUNDARY_ADJUST);
- EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(), wantColor,
+ EncodeBitstreamParams params(INT_MAX, &nodeData->getCurrentViewFrustum(),
WANT_EXISTS_BITS, DONT_CHOP, wantDelta, lastViewFrustum,
- wantOcclusionCulling, coverageMap, boundaryLevelAdjust, octreeSizeScale,
+ boundaryLevelAdjust, octreeSizeScale,
nodeData->getLastTimeBagEmpty(),
isFullScene, &nodeData->stats, _myServer->getJurisdiction(),
&nodeData->extraEncodeData);
+ // Our trackSend() function is implemented by the server subclass, and will be called back
+ // during the encodeTreeBitstream() as new entities/data elements are sent
+ params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
+ _myServer->trackSend(dataID, dataEdited, _nodeUUID);
+ };
+
// TODO: should this include the lock time or not? This stat is sent down to the client,
// it seems like it may be a good idea to include the lock time as part of the encode time
// are reported to client. Since you can encode without the lock
@@ -550,10 +548,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent);
quint64 packetSendingEnd = usecTimestampNow();
packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart);
-
- if (wantCompression) {
- targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
- }
+ targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE);
} else {
// If we're in compressed mode, then we want to see if we have room for more in this wire packet.
// but we've finalized the _packetData, so we want to start a new section, we will do that by
@@ -563,7 +558,7 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
// a larger compressed size then uncompressed size
targetSize = nodeData->getAvailable() - sizeof(OCTREE_PACKET_INTERNAL_SECTION_SIZE) - COMPRESS_PADDING;
}
- _packetData.changeSettings(nodeData->getWantCompression(), targetSize); // will do reset
+ _packetData.changeSettings(targetSize); // will do reset
}
OctreeServer::trackTreeWaitTime(lockWaitElapsedUsec);
@@ -628,7 +623,6 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus
if (nodeData->elementBag.isEmpty()) {
nodeData->updateLastKnownViewFrustum();
nodeData->setViewSent(true);
- nodeData->map.erase(); // It would be nice if we could save this, and only reset it when the view frustum changes
}
} // end if bag wasn't empty, and so we sent stuff...
diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp
index 7cd3e59edf..dead61d65a 100644
--- a/assignment-client/src/octree/OctreeServer.cpp
+++ b/assignment-client/src/octree/OctreeServer.cpp
@@ -821,6 +821,11 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
.arg(locale.toString((uint)checkSum).rightJustified(16, ' '));
statsString += "\r\n\r\n";
+
+ statsString += serverSubclassStats();
+
+ statsString += "\r\n\r\n";
+
statsString += "\r\n";
statsString += "