implemented support for PACKET_TYPE_PARTICLE_ERASE

This commit is contained in:
Brad Hefta-Gaub 2014-01-21 13:24:35 -08:00
parent 347933f1a5
commit 97b0ed2cc5
17 changed files with 427 additions and 113 deletions

View file

@ -4187,6 +4187,7 @@ void Application::processDatagrams() {
break;
case PACKET_TYPE_PARTICLE_DATA:
case PACKET_TYPE_PARTICLE_ERASE:
case PACKET_TYPE_VOXEL_DATA:
case PACKET_TYPE_VOXEL_ERASE:
case PACKET_TYPE_OCTREE_STATS:

View file

@ -11,7 +11,7 @@
#include "ParticleTreeRenderer.h"
ParticleTreeRenderer::ParticleTreeRenderer() :
ParticleTreeRenderer::ParticleTreeRenderer() :
OctreeRenderer() {
}
@ -31,13 +31,13 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
// actually render it here...
// we need to iterate the actual particles of the element
ParticleTreeElement* particleTreeElement = (ParticleTreeElement*)element;
const QList<Particle>& particles = particleTreeElement->getParticles();
uint16_t numberOfParticles = particles.size();
bool drawAsSphere = true;
for (uint16_t i = 0; i < numberOfParticles; i++) {
const Particle& particle = particles[i];
// render particle aspoints
@ -46,7 +46,7 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
float sphereRadius = particle.getRadius() * (float)TREE_SCALE;
args->_renderedItems++;
if (drawAsSphere) {
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
@ -60,3 +60,8 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
}
}
}
void ParticleTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr,
Node* sourceNode) {
static_cast<ParticleTree*>(_tree)->processEraseMessage(dataByteArray, senderSockAddr, sourceNode);
}

View file

@ -37,6 +37,8 @@ public:
ParticleTree* getTree() { return (ParticleTree*)_tree; }
void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode);
protected:
};

View file

@ -17,7 +17,7 @@
void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, unsigned char* packetData, ssize_t packetLength) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"VoxelPacketProcessor::processPacket()");
const int WAY_BEHIND = 300;
if (packetsToProcessCount() > WAY_BEHIND && Application::getInstance()->getLogger()->extraDebugging()) {
@ -27,19 +27,19 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
Application* app = Application::getInstance();
bool wasStatsPacket = false;
// check to see if the UI thread asked us to kill the voxel tree. since we're the only thread allowed to do that
if (app->_wantToKillLocalVoxels) {
app->_voxels.killLocalVoxels();
app->_wantToKillLocalVoxels = false;
}
// note: PACKET_TYPE_OCTREE_STATS can have PACKET_TYPE_VOXEL_DATA
// immediately following them inside the same packet. So, we process the PACKET_TYPE_OCTREE_STATS first
// then process any remaining bytes as if it was another packet
if (packetData[0] == PACKET_TYPE_OCTREE_STATS) {
int statsMessageLength = app->parseOctreeStats(packetData, messageLength, senderSockAddr);
wasStatsPacket = true;
if (messageLength > statsMessageLength) {
@ -56,20 +56,25 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, uns
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
app->trackIncomingVoxelPacket(packetData, messageLength, senderSockAddr, wasStatsPacket);
SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
if (serverNode && serverNode->getActiveSocket() && *serverNode->getActiveSocket() == senderSockAddr) {
switch(packetData[0]) {
case PACKET_TYPE_PARTICLE_ERASE: {
app->_particles.processEraseMessage(QByteArray((char*) packetData, messageLength),
senderSockAddr, serverNode.data());
} break;
case PACKET_TYPE_PARTICLE_DATA: {
app->_particles.processDatagram(QByteArray((char*) packetData, messageLength),
senderSockAddr, serverNode.data());
} break;
case PACKET_TYPE_ENVIRONMENT_DATA: {
app->_environment.parseData(senderSockAddr, packetData, messageLength);
} break;
default : {
app->_voxels.setDataSourceUUID(serverNode->getUUID());
app->_voxels.parseData(packetData, messageLength);

View file

@ -525,7 +525,8 @@ int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, b
// Here's where we can/should allow the server to send other data...
// send the environment packet
if (_myServer->hasSpecialPacketToSend()) {
// TODO: should we turn this into a while loop to better handle sending multiple special packets
if (_myServer->hasSpecialPacketToSend(node)) {
trueBytesSent += _myServer->sendSpecialPacket(node);
truePacketsSent++;
packetsSentThisInterval++;

View file

@ -100,27 +100,27 @@ void OctreeServer::initHTTPManager(int port) {
}
bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) {
#ifdef FORCE_CRASH
if (connection->requestOperation() == QNetworkAccessManager::GetOperation
&& path == "/force_crash") {
qDebug() << "About to force a crash!";
int foo;
int* forceCrash = &foo;
QString responseString("forcing a crash...");
connection->respond(HTTPConnection::StatusCode200, qPrintable(responseString));
delete[] forceCrash;
return true;
}
#endif
bool showStats = false;
if (connection->requestOperation() == QNetworkAccessManager::GetOperation) {
if (path == "/") {
showStats = true;
@ -129,28 +129,28 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
showStats = true;
}
}
if (showStats) {
uint64_t checkSum;
// return a 200
QString statsString("<html><doc>\r\n<pre>\r\n");
statsString += QString("<b>Your %1 Server is running... <a href='/'>[RELOAD]</a></b>\r\n").arg(getMyServerName());
tm* localtm = localtime(&_started);
const int MAX_TIME_LENGTH = 128;
char buffer[MAX_TIME_LENGTH];
strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", localtm);
statsString += QString("Running since: %1").arg(buffer);
// Convert now to tm struct for UTC
tm* gmtm = gmtime(&_started);
if (gmtm) {
strftime(buffer, MAX_TIME_LENGTH, "%m/%d/%Y %X", gmtm);
statsString += (QString(" [%1 UTM] ").arg(buffer));
}
statsString += "\r\n";
uint64_t now = usecTimestampNow();
const int USECS_PER_MSEC = 1000;
uint64_t msecsElapsed = (now - _startedUSecs) / USECS_PER_MSEC;
@ -158,13 +158,13 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
const int SECS_PER_MIN = 60;
const int MIN_PER_HOUR = 60;
const int MSECS_PER_MIN = MSECS_PER_SEC * SECS_PER_MIN;
float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC;
int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR;
int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR));
statsString += "Uptime: ";
if (hours > 0) {
statsString += QString("%1 hour%2").arg(hours).arg((hours > 1) ? "s" : "");
}
@ -175,7 +175,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
statsString += QString().sprintf("%.3f seconds", seconds);
}
statsString += "\r\n\r\n";
// display voxel file load time
if (isInitialLoadComplete()) {
if (isPersistEnabled()) {
@ -183,14 +183,14 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
} else {
statsString += QString("%1 File Persist Disabled...\r\n").arg(getMyServerName());
}
statsString += "\r\n";
uint64_t msecsElapsed = getLoadElapsedTime() / USECS_PER_MSEC;;
float seconds = (msecsElapsed % MSECS_PER_MIN)/(float)MSECS_PER_SEC;
int minutes = (msecsElapsed/(MSECS_PER_MIN)) % MIN_PER_HOUR;
int hours = (msecsElapsed/(MSECS_PER_MIN * MIN_PER_HOUR));
statsString += QString("%1 File Load Took").arg(getMyServerName());
if (hours > 0) {
statsString += QString("%1 hour%2").arg(hours).arg((hours > 1) ? "s" : "");
@ -202,25 +202,25 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
statsString += QString().sprintf("%.3f seconds", seconds);
}
statsString += "\r\n";
} else {
statsString += "Voxels not yet loaded...\r\n";
}
statsString += "\r\n\r\n";
statsString += "<b>Configuration:</b>\r\n";
for (int i = 1; i < _argc; i++) {
statsString += _argv[i];
}
statsString += "\r\n"; //one to end the config line
statsString += "\r\n\r\n"; // two more for spacing
// display scene stats
unsigned long nodeCount = OctreeElement::getNodeCount();
unsigned long internalNodeCount = OctreeElement::getInternalNodeCount();
unsigned long leafNodeCount = OctreeElement::getLeafNodeCount();
QLocale locale(QLocale::English);
const float AS_PERCENT = 100.0;
statsString += "<b>Current Nodes in scene:</b>\r\n";
@ -234,7 +234,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
((float)leafNodeCount / (float)nodeCount) * AS_PERCENT);
statsString += "\r\n";
statsString += "\r\n";
// display outbound packet stats
statsString += QString("<b>%1 Outbound Packet Statistics...</b>\r\n").arg(getMyServerName());
uint64_t totalOutboundPackets = OctreeSendThread::_totalPackets;
@ -243,7 +243,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
uint64_t totalBytesOfOctalCodes = OctreePacketData::getTotalBytesOfOctalCodes();
uint64_t totalBytesOfBitMasks = OctreePacketData::getTotalBytesOfBitMasks();
uint64_t totalBytesOfColor = OctreePacketData::getTotalBytesOfColor();
const int COLUMN_WIDTH = 10;
statsString += QString(" Total Outbound Packets: %1 packets\r\n")
.arg(locale.toString((uint)totalOutboundPackets).rightJustified(COLUMN_WIDTH, ' '));
@ -260,10 +260,10 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
statsString += QString().sprintf(" Total Color Bytes: %s bytes (%5.2f%%)\r\n",
locale.toString((uint)totalBytesOfColor).rightJustified(COLUMN_WIDTH, ' ').toLocal8Bit().constData(),
((float)totalBytesOfColor / (float)totalOutboundBytes) * AS_PERCENT);
statsString += "\r\n";
statsString += "\r\n";
// display inbound packet stats
statsString += QString().sprintf("<b>%s Edit Statistics... <a href='/resetStats'>[RESET]</a></b>\r\n",
getMyServerName());
@ -274,9 +274,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
uint64_t averageLockWaitTimePerElement = _octreeInboundPacketProcessor->getAverageLockWaitTimePerElement();
uint64_t totalElementsProcessed = _octreeInboundPacketProcessor->getTotalElementsProcessed();
uint64_t totalPacketsProcessed = _octreeInboundPacketProcessor->getTotalPacketsProcessed();
float averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed;
statsString += QString(" Total Inbound Packets: %1 packets\r\n")
.arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Inbound Elements: %1 elements\r\n")
@ -292,18 +292,18 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
.arg(locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Wait Lock Time/Element: %1 usecs\r\n")
.arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
int senderNumber = 0;
NodeToSenderStatsMap& allSenderStats = _octreeInboundPacketProcessor->getSingleSenderStats();
for (NodeToSenderStatsMapIterator i = allSenderStats.begin(); i != allSenderStats.end(); i++) {
senderNumber++;
QUuid senderID = i->first;
SingleSenderStats& senderStats = i->second;
statsString += QString("\r\n Stats for sender %1 uuid: %2\r\n")
.arg(senderNumber).arg(senderID.toString());
averageTransitTimePerPacket = senderStats.getAverageTransitTimePerPacket();
averageProcessTimePerPacket = senderStats.getAverageProcessTimePerPacket();
averageLockWaitTimePerPacket = senderStats.getAverageLockWaitTimePerPacket();
@ -311,9 +311,9 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
averageLockWaitTimePerElement = senderStats.getAverageLockWaitTimePerElement();
totalElementsProcessed = senderStats.getTotalElementsProcessed();
totalPacketsProcessed = senderStats.getTotalPacketsProcessed();
averageElementsPerPacket = totalPacketsProcessed == 0 ? 0 : totalElementsProcessed / totalPacketsProcessed;
statsString += QString(" Total Inbound Packets: %1 packets\r\n")
.arg(locale.toString((uint)totalPacketsProcessed).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Total Inbound Elements: %1 elements\r\n")
@ -330,16 +330,16 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
.arg(locale.toString((uint)averageProcessTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
statsString += QString(" Average Wait Lock Time/Element: %1 usecs\r\n")
.arg(locale.toString((uint)averageLockWaitTimePerElement).rightJustified(COLUMN_WIDTH, ' '));
}
statsString += "\r\n\r\n";
// display memory usage stats
statsString += "<b>Current Memory Usage Statistics</b>\r\n";
statsString += QString().sprintf("\r\nOctreeElement size... %ld bytes\r\n", sizeof(OctreeElement));
statsString += "\r\n";
const char* memoryScaleLabel;
const float MEGABYTES = 1000000.f;
const float GIGABYTES = 1000000000.f;
@ -351,7 +351,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
memoryScaleLabel = "GB";
memoryScale = GIGABYTES;
}
statsString += QString().sprintf("Element Node Memory Usage: %8.2f %s\r\n",
OctreeElement::getVoxelMemoryUsage() / memoryScale, memoryScaleLabel);
statsString += QString().sprintf("Octcode Memory Usage: %8.2f %s\r\n",
@ -362,7 +362,7 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
statsString += QString().sprintf(" Total: %8.2f %s\r\n",
OctreeElement::getTotalMemoryUsage() / memoryScale, memoryScaleLabel);
statsString += "\r\n";
statsString += "OctreeElement Children Population Statistics...\r\n";
checkSum = 0;
for (int i=0; i <= NUMBER_OF_CHILDREN; i++) {
@ -374,11 +374,11 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
statsString += " ----------------------\r\n";
statsString += QString(" Total: %1 nodes\r\n")
.arg(locale.toString((uint)checkSum).rightJustified(16, ' '));
#ifdef BLENDED_UNION_CHILDREN
statsString += "\r\n";
statsString += "OctreeElement Children Encoding Statistics...\r\n";
statsString += QString().sprintf(" Single or No Children: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getSingleChildrenCount(),
((float)OctreeElement::getSingleChildrenCount() / (float)nodeCount) * AS_PERCENT));
@ -397,31 +397,31 @@ bool OctreeServer::handleHTTPRequest(HTTPConnection* connection, const QString&
statsString += QString().sprintf(" Children as External Array: %10.llu nodes (%5.2f%%)\r\n",
OctreeElement::getExternalChildrenCount(),
((float)OctreeElement::getExternalChildrenCount() / (float)nodeCount) * AS_PERCENT);
checkSum = OctreeElement::getSingleChildrenCount() +
OctreeElement::getTwoChildrenOffsetCount() + OctreeElement::getTwoChildrenExternalCount() +
OctreeElement::getThreeChildrenOffsetCount() + OctreeElement::getThreeChildrenExternalCount() +
OctreeElement::getExternalChildrenCount();
statsString += " ----------------\r\n";
statsString += QString().sprintf(" Total: %10.llu nodes\r\n", checkSum);
statsString += QString().sprintf(" Expected: %10.lu nodes\r\n", nodeCount);
statsString += "\r\n";
statsString += "In other news....\r\n";
statsString += QString().sprintf("could store 4 children internally: %10.llu nodes\r\n",
OctreeElement::getCouldStoreFourChildrenInternally());
statsString += QString().sprintf("could NOT store 4 children internally: %10.llu nodes\r\n",
OctreeElement::getCouldNotStoreFourChildrenInternally());
#endif
statsString += "\r\n\r\n";
statsString += "</pre>\r\n";
statsString += "</doc></html>";
connection->respond(HTTPConnection::StatusCode200, qPrintable(statsString), "text/html");
return true;
} else {
// have HTTPManager attempt to process this request from the document_root

View file

@ -27,7 +27,7 @@
/// Handles assignments of type OctreeServer - sending octrees to various clients.
class OctreeServer : public ThreadedAssignment, public HTTPRequestHandler {
Q_OBJECT
public:
public:
OctreeServer(const unsigned char* dataBuffer, int numBytes);
~OctreeServer();
@ -58,27 +58,27 @@ public:
// subclass may implement these method
virtual void beforeRun() { };
virtual bool hasSpecialPacketToSend() { return false; }
virtual bool hasSpecialPacketToSend(Node* node) { return false; }
virtual int sendSpecialPacket(Node* node) { return 0; }
static void attachQueryNodeToNode(Node* newNode);
bool handleHTTPRequest(HTTPConnection* connection, const QString& path);
public slots:
/// runs the voxel server assignment
void run();
void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr);
void nodeKilled(SharedNodePointer node);
protected:
void parsePayload();
void initHTTPManager(int port);
int _argc;
const char** _argv;
char** _parsedArgV;
HTTPManager* _httpManager;
char _persistFilename[MAX_FILENAME_LENGTH];
@ -94,9 +94,9 @@ protected:
OctreePersistThread* _persistThread;
static OctreeServer* _instance;
time_t _started;
uint64_t _startedUSecs;
uint64_t _startedUSecs;
};
#endif // __octree_server__OctreeServer__

View file

@ -1367,7 +1367,7 @@ void Octree::writeToSVOFile(const char* fileName, OctreeElement* node) {
PACKET_TYPE expectedType = expectedDataPacketType();
PACKET_VERSION expectedVersion = versionForPacketType(expectedType);
file.write(&expectedType, sizeof(expectedType));
file.write(&expectedVersion, sizeof(expectedType));
file.write(&expectedVersion, sizeof(expectedVersion));
}
OctreeElementBag nodeBag;

View file

@ -15,8 +15,17 @@
class ParticleNodeData : public OctreeQueryNode {
public:
ParticleNodeData(Node* owningNode) : OctreeQueryNode(owningNode) { };
ParticleNodeData(Node* owningNode) :
OctreeQueryNode(owningNode),
_lastDeletedParticlesSentAt(0) { };
virtual PACKET_TYPE getMyPacketType() const { return PACKET_TYPE_PARTICLE_DATA; }
uint64_t getLastDeletedParticlesSentAt() const { return _lastDeletedParticlesSentAt; }
void setLastDeletedParticlesSentAt(uint64_t sentAt) { _lastDeletedParticlesSentAt = sentAt; }
private:
uint64_t _lastDeletedParticlesSentAt;
};
#endif /* defined(__hifi__ParticleNodeData__) */

View file

@ -6,6 +6,7 @@
// Copyright (c) 2013 HighFidelity, Inc. All rights reserved.
//
#include <QTimer>
#include <ParticleTree.h>
#include "ParticleServer.h"
@ -36,7 +37,10 @@ Octree* ParticleServer::createTree() {
}
void ParticleServer::beforeRun() {
// nothing special to do...
QTimer* pruneDeletedParticlesTimer = new QTimer(this);
connect(pruneDeletedParticlesTimer, SIGNAL(timeout()), this, SLOT(pruneDeletedParticles()));
const int PRUNE_DELETED_PARTICLES_INTERVAL_MSECS = 1 * 1000; // once every second
pruneDeletedParticlesTimer->start(PRUNE_DELETED_PARTICLES_INTERVAL_MSECS);
}
void ParticleServer::particleCreated(const Particle& newParticle, Node* node) {
@ -46,20 +50,89 @@ void ParticleServer::particleCreated(const Particle& newParticle, Node* node) {
int numBytesPacketHeader = populateTypeAndVersion(outputBuffer, PACKET_TYPE_PARTICLE_ADD_RESPONSE);
int packetLength = numBytesPacketHeader;
copyAt += numBytesPacketHeader;
// encode the creatorTokenID
uint32_t creatorTokenID = newParticle.getCreatorTokenID();
memcpy(copyAt, &creatorTokenID, sizeof(creatorTokenID));
copyAt += sizeof(creatorTokenID);
packetLength += sizeof(creatorTokenID);
// encode the particle ID
uint32_t particleID = newParticle.getID();
memcpy(copyAt, &particleID, sizeof(particleID));
copyAt += sizeof(particleID);
packetLength += sizeof(particleID);
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) outputBuffer, packetLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
}
// ParticleServer will use the "special packets" to send list of recently deleted particles
bool ParticleServer::hasSpecialPacketToSend(Node* node) {
bool shouldSendDeletedParticles = false;
// check to see if any new particles have been added since we last sent to this node...
ParticleNodeData* nodeData = static_cast<ParticleNodeData*>(node->getLinkedData());
if (nodeData) {
uint64_t deletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt();
ParticleTree* tree = static_cast<ParticleTree*>(_tree);
shouldSendDeletedParticles = tree->hasParitclesDeletedSince(deletedParticlesSentAt);
}
return shouldSendDeletedParticles;
}
int ParticleServer::sendSpecialPacket(Node* node) {
unsigned char outputBuffer[MAX_PACKET_SIZE];
size_t packetLength = 0;
ParticleNodeData* nodeData = static_cast<ParticleNodeData*>(node->getLinkedData());
if (nodeData) {
uint64_t deletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt();
uint64_t deletePacketSentAt = usecTimestampNow();
ParticleTree* tree = static_cast<ParticleTree*>(_tree);
bool hasMoreToSend = true;
// TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 particles?
while (hasMoreToSend) {
hasMoreToSend = tree->encodeParitclesDeletedSince(deletedParticlesSentAt,
outputBuffer, MAX_PACKET_SIZE, packetLength);
//qDebug() << "sending PACKET_TYPE_PARTICLE_ERASE packetLength:" << packetLength;
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) outputBuffer, packetLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
}
nodeData->setLastDeletedParticlesSentAt(deletePacketSentAt);
}
// TODO: caller is expecting a packetLength, what if we send more than one packet??
return packetLength;
}
void ParticleServer::pruneDeletedParticles() {
ParticleTree* tree = static_cast<ParticleTree*>(_tree);
if (tree->hasAnyDeletedParitcles()) {
//qDebug() << "there are some deleted particles to consider...";
uint64_t earliestLastDeletedParticlesSent = usecTimestampNow() + 1; // in the future
foreach (const SharedNodePointer& otherNode, NodeList::getInstance()->getNodeHash()) {
if (otherNode->getLinkedData()) {
ParticleNodeData* nodeData = static_cast<ParticleNodeData*>(otherNode->getLinkedData());
uint64_t nodeLastDeletedParticlesSentAt = nodeData->getLastDeletedParticlesSentAt();
if (nodeLastDeletedParticlesSentAt < earliestLastDeletedParticlesSent) {
earliestLastDeletedParticlesSent = nodeLastDeletedParticlesSentAt;
}
}
}
//qDebug() << "earliestLastDeletedParticlesSent=" << earliestLastDeletedParticlesSent;
tree->forgetParitclesDeletedBefore(earliestLastDeletedParticlesSent);
}
}

View file

@ -18,11 +18,12 @@
/// Handles assignments of type ParticleServer - sending particles to various clients.
class ParticleServer : public OctreeServer, public NewlyCreatedParticleHook {
public:
Q_OBJECT
public:
ParticleServer(const unsigned char* dataBuffer, int numBytes);
~ParticleServer();
// Subclasses must implement these methods
// Subclasses must implement these methods
virtual OctreeQueryNode* createOctreeQueryNode(Node* newNode);
virtual Octree* createTree();
virtual unsigned char getMyNodeType() const { return NODE_TYPE_PARTICLE_SERVER; }
@ -30,12 +31,17 @@ public:
virtual const char* getMyServerName() const { return PARTICLE_SERVER_NAME; }
virtual const char* getMyLoggingServerTargetName() const { return PARTICLE_SERVER_LOGGING_TARGET_NAME; }
virtual const char* getMyDefaultPersistFilename() const { return LOCAL_PARTICLES_PERSIST_FILE; }
// subclass may implement these method
virtual void beforeRun();
virtual bool hasSpecialPacketToSend(Node* node);
virtual int sendSpecialPacket(Node* node);
virtual void particleCreated(const Particle& newParticle, Node* senderNode);
public slots:
void pruneDeletedParticles();
private:
};

View file

@ -29,6 +29,48 @@ bool ParticleTree::handlesEditPacketType(PACKET_TYPE packetType) const {
return false;
}
class FindAndDeleteParticlesArgs {
public:
QList<uint32_t> _idsToDelete;
};
bool ParticleTree::findAndDeleteOperation(OctreeElement* element, void* extraData) {
//qDebug() << "findAndDeleteOperation()";
FindAndDeleteParticlesArgs* args = static_cast< FindAndDeleteParticlesArgs*>(extraData);
// if we've found and deleted all our target particles, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
//qDebug() << "findAndDeleteOperation() args->_idsToDelete.size():" << args->_idsToDelete.size();
for (QList<uint32_t>::iterator it = args->_idsToDelete.begin(); it != args->_idsToDelete.end(); it++) {
uint32_t particleID = *it;
//qDebug() << "findAndDeleteOperation() particleID:" << particleID;
if (particleTreeElement->removeParticleWithID(particleID)) {
// if the particle was in this element, then remove it from our search list.
//qDebug() << "findAndDeleteOperation() it = args->_idsToDelete.erase(it)";
it = args->_idsToDelete.erase(it);
}
if (it == args->_idsToDelete.end()) {
//qDebug() << "findAndDeleteOperation() breaking";
break;
}
}
// if we've found and deleted all our target particles, then we can stop looking
if (args->_idsToDelete.size() <= 0) {
return false;
}
return true;
}
class FindAndUpdateParticleArgs {
public:
@ -172,21 +214,17 @@ int ParticleTree::processEditPacketData(PACKET_TYPE packetType, unsigned char* p
// we handle these types of "edit" packets
switch (packetType) {
case PACKET_TYPE_PARTICLE_ADD_OR_EDIT: {
qDebug() << " got PACKET_TYPE_PARTICLE_ADD_OR_EDIT... ";
//qDebug() << " got PACKET_TYPE_PARTICLE_ADD_OR_EDIT... ";
Particle newParticle = Particle::fromEditPacket(editData, maxLength, processedBytes, this);
qDebug() << "PACKET_TYPE_PARTICLE_ADD_OR_EDIT... calling storeParticle() ";
storeParticle(newParticle, senderNode);
qDebug() << "PACKET_TYPE_PARTICLE_ADD_OR_EDIT... AFTER storeParticle() newParticle.getShouldDie()=" << newParticle.getShouldDie();
if (newParticle.isNewlyCreated()) {
qDebug() << "PACKET_TYPE_PARTICLE_ADD_OR_EDIT... calling notifyNewlyCreatedParticle() ";
notifyNewlyCreatedParticle(newParticle, senderNode);
qDebug() << "PACKET_TYPE_PARTICLE_ADD_OR_EDIT... AFTER notifyNewlyCreatedParticle() ";
}
qDebug() << " DONE... PACKET_TYPE_PARTICLE_ADD_OR_EDIT... ";
//qDebug() << " DONE... PACKET_TYPE_PARTICLE_ADD_OR_EDIT... ";
} break;
// TODO: wire in support here for server to get PACKET_TYPE_PARTICLE_ERASE messages
// instead of using PACKET_TYPE_PARTICLE_ADD_OR_EDIT messages to delete particles
case PACKET_TYPE_PARTICLE_ERASE: {
processedBytes = 0;
} break;
@ -254,6 +292,12 @@ void ParticleTree::update() {
if (!shouldDie && treeBounds.contains(args._movingParticles[i].getPosition())) {
storeParticle(args._movingParticles[i]);
} else {
uint32_t particleID = args._movingParticles[i].getID();
uint64_t deletedAt = usecTimestampNow();
_recentlyDeletedParticlesLock.lockForWrite();
_recentlyDeletedParticleIDs.insert(deletedAt, particleID);
_recentlyDeletedParticlesLock.unlock();
}
}
@ -262,3 +306,143 @@ void ParticleTree::update() {
}
bool ParticleTree::hasParitclesDeletedSince(uint64_t sinceTime) {
// we can probably leverage the ordered nature of QMultiMap to do this quickly...
bool hasSomethingNewer = false;
_recentlyDeletedParticlesLock.lockForRead();
QMultiMap<uint64_t, uint32_t>::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin();
while (iterator != _recentlyDeletedParticleIDs.constEnd()) {
//qDebug() << "considering... time/key:" << iterator.key();
if (iterator.key() > sinceTime) {
//qDebug() << "YES newer... time/key:" << iterator.key();
hasSomethingNewer = true;
}
++iterator;
}
_recentlyDeletedParticlesLock.unlock();
return hasSomethingNewer;
}
// sinceTime is an in/out parameter - it will be side effected with the last time sent out
bool ParticleTree::encodeParitclesDeletedSince(uint64_t& sinceTime, unsigned char* outputBuffer, size_t maxLength,
size_t& outputLength) {
bool hasMoreToSend = true;
unsigned char* copyAt = outputBuffer;
size_t numBytesPacketHeader = populateTypeAndVersion(outputBuffer, PACKET_TYPE_PARTICLE_ERASE);
copyAt += numBytesPacketHeader;
outputLength = numBytesPacketHeader;
uint16_t numberOfIds = 0; // placeholder for now
unsigned char* numberOfIDsAt = copyAt;
memcpy(copyAt, &numberOfIds, sizeof(numberOfIds));
copyAt += sizeof(numberOfIds);
outputLength += sizeof(numberOfIds);
// we keep a multi map of particle IDs to timestamps, we only want to include the particle IDs that have been
// deleted since we last sent to this node
_recentlyDeletedParticlesLock.lockForRead();
QMultiMap<uint64_t, uint32_t>::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin();
while (iterator != _recentlyDeletedParticleIDs.constEnd()) {
QList<uint32_t> values = _recentlyDeletedParticleIDs.values(iterator.key());
for (int valueItem = 0; valueItem < values.size(); ++valueItem) {
//qDebug() << "considering... " << iterator.key() << ": " << values.at(valueItem);
// if the timestamp is more recent then out last sent time, include it
if (iterator.key() > sinceTime) {
//qDebug() << "including... " << iterator.key() << ": " << values.at(valueItem);
uint32_t particleID = values.at(valueItem);
memcpy(copyAt, &particleID, sizeof(particleID));
copyAt += sizeof(particleID);
outputLength += sizeof(particleID);
numberOfIds++;
// check to make sure we have room for one more id...
if (outputLength + sizeof(uint32_t) > maxLength) {
break;
}
}
}
// check to make sure we have room for one more id...
if (outputLength + sizeof(uint32_t) > maxLength) {
// let our caller know how far we got
sinceTime = iterator.key();
break;
}
++iterator;
}
// if we got to the end, then we're done sending
if (iterator == _recentlyDeletedParticleIDs.constEnd()) {
hasMoreToSend = false;
}
_recentlyDeletedParticlesLock.unlock();
// replace the correct count for ids included
memcpy(numberOfIDsAt, &numberOfIds, sizeof(numberOfIds));
return hasMoreToSend;
}
// called by the server when it knows all nodes have been sent deleted packets
void ParticleTree::forgetParitclesDeletedBefore(uint64_t sinceTime) {
_recentlyDeletedParticlesLock.lockForWrite();
QMultiMap<uint64_t, uint32_t>::const_iterator iterator = _recentlyDeletedParticleIDs.constBegin();
while (iterator != _recentlyDeletedParticleIDs.constEnd()) {
//qDebug() << "considering... time/key:" << iterator.key();
if (iterator.key() <= sinceTime) {
//qDebug() << "YES older... time/key:" << iterator.key();
_recentlyDeletedParticleIDs.remove(iterator.key());
}
++iterator;
}
_recentlyDeletedParticlesLock.unlock();
}
void ParticleTree::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr,
Node* sourceNode) {
//qDebug() << "ParticleTree::processEraseMessage()...";
const unsigned char* packetData = (const unsigned char*)dataByteArray.constData();
const unsigned char* dataAt = packetData;
size_t packetLength = dataByteArray.size();
size_t numBytesPacketHeader = numBytesForPacketHeader(packetData);
size_t processedBytes = numBytesPacketHeader;
dataAt += numBytesPacketHeader;
uint16_t numberOfIds = 0; // placeholder for now
memcpy(&numberOfIds, dataAt, sizeof(numberOfIds));
dataAt += sizeof(numberOfIds);
processedBytes += sizeof(numberOfIds);
//qDebug() << "got erase message for numberOfIds:" << numberOfIds;
if (numberOfIds > 0) {
FindAndDeleteParticlesArgs args;
for (size_t i = 0; i < numberOfIds; i++) {
if (processedBytes + sizeof(uint32_t) > packetLength) {
//qDebug() << "bailing?? processedBytes:" << processedBytes << " packetLength:" << packetLength;
break; // bail to prevent buffer overflow
}
uint32_t particleID = 0; // placeholder for now
memcpy(&particleID, dataAt, sizeof(particleID));
dataAt += sizeof(particleID);
processedBytes += sizeof(particleID);
//qDebug() << "got erase message for particleID:" << particleID;
args._idsToDelete.push_back(particleID);
}
// calling recurse to actually delete the particles
//qDebug() << "calling recurse to actually delete the particles";
recurseTreeWithOperation(findAndDeleteOperation, &args);
}
}

View file

@ -46,6 +46,13 @@ public:
void addNewlyCreatedHook(NewlyCreatedParticleHook* hook);
void removeNewlyCreatedHook(NewlyCreatedParticleHook* hook);
bool hasAnyDeletedParitcles() const { return _recentlyDeletedParticleIDs.size() > 0; }
bool hasParitclesDeletedSince(uint64_t sinceTime);
bool encodeParitclesDeletedSince(uint64_t& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength);
void forgetParitclesDeletedBefore(uint64_t sinceTime);
void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode);
private:
static bool updateOperation(OctreeElement* element, void* extraData);
@ -53,11 +60,16 @@ private:
static bool findNearPointOperation(OctreeElement* element, void* extraData);
static bool pruneOperation(OctreeElement* element, void* extraData);
static bool findByIDOperation(OctreeElement* element, void* extraData);
static bool findAndDeleteOperation(OctreeElement* element, void* extraData);
void notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode);
QReadWriteLock _newlyCreatedHooksLock;
std::vector<NewlyCreatedParticleHook*> _newlyCreatedHooks;
QReadWriteLock _recentlyDeletedParticlesLock;
QMultiMap<uint64_t, uint32_t> _recentlyDeletedParticleIDs;
};
#endif /* defined(__hifi__ParticleTree__) */

View file

@ -200,6 +200,20 @@ const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const {
return foundParticle;
}
bool ParticleTreeElement::removeParticleWithID(uint32_t id) {
bool foundParticle = false;
uint16_t numberOfParticles = _particles->size();
for (uint16_t i = 0; i < numberOfParticles; i++) {
if ((*_particles)[i].getID() == id) {
foundParticle = true;
_particles->removeAt(i);
break;
}
}
return foundParticle;
}
int ParticleTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
ReadBitstreamToTreeParams& args) {

View file

@ -28,11 +28,11 @@ public:
class ParticleTreeElement : public OctreeElement {
friend class ParticleTree; // to allow createElement to new us...
ParticleTreeElement(unsigned char* octalCode = NULL);
virtual OctreeElement* createNewElement(unsigned char* octalCode = NULL) const;
public:
virtual ~ParticleTreeElement();
@ -40,31 +40,31 @@ public:
ParticleTreeElement* getChildAtIndex(int index) { return (ParticleTreeElement*)OctreeElement::getChildAtIndex(index); }
// methods you can and should override to implement your tree functionality
/// Adds a child to the current element. Override this if there is additional child initialization your class needs.
virtual ParticleTreeElement* addChildAtIndex(int index);
/// Override this to implement LOD averaging on changes to the tree.
/// Override this to implement LOD averaging on changes to the tree.
virtual void calculateAverageFromChildren();
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
/// Override this to implement LOD collapsing and identical child pruning on changes to the tree.
virtual bool collapseChildren();
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
/// By default we assume that only leaves are actual content, but some octrees may have different semantics.
virtual bool hasContent() const { return isLeaf(); }
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
/// meaningful split would be to break the larger cube into smaller cubes of the same color/texture.
virtual void splitChildren() { }
/// Override to indicate that this element requires a split before editing lower elements in the octree
virtual bool requiresSplit() const { return false; }
/// Override to serialize the state of this element. This is used for persistance and for transmission across the network.
virtual bool appendElementData(OctreePacketData* packetData) const;
/// Override to deserialize the state of this element. This is used for loading from a persisted file or from reading
/// from the network.
virtual int readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args);
@ -76,21 +76,23 @@ public:
virtual bool isRendered() const { return getShouldRender(); }
virtual bool deleteApproved() const { return !hasParticles(); }
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
virtual bool findSpherePenetration(const glm::vec3& center, float radius,
glm::vec3& penetration, void** penetratedObject) const;
const QList<Particle>& getParticles() const { return *_particles; }
QList<Particle>& getParticles() { return *_particles; }
bool hasParticles() const { return _particles->size() > 0; }
void update(ParticleTreeUpdateArgs& args);
void setTree(ParticleTree* tree) { _myTree = tree; }
bool containsParticle(const Particle& particle) const;
bool updateParticle(const Particle& particle);
const Particle* getClosestParticle(glm::vec3 position) const;
const Particle* getParticleWithID(uint32_t id) const;
bool removeParticleWithID(uint32_t id);
protected:
virtual void init(unsigned char * octalCode);

View file

@ -32,7 +32,7 @@ Octree* VoxelServer::createTree() {
return new VoxelTree(true);
}
bool VoxelServer::hasSpecialPacketToSend() {
bool VoxelServer::hasSpecialPacketToSend(Node* node) {
bool shouldSendEnvironments = _sendEnvironments && shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, OCTREE_SEND_INTERVAL_USECS);
return shouldSendEnvironments;
}
@ -41,11 +41,11 @@ int VoxelServer::sendSpecialPacket(Node* node) {
int numBytesPacketHeader = populateTypeAndVersion(_tempOutputBuffer, PACKET_TYPE_ENVIRONMENT_DATA);
int envPacketLength = numBytesPacketHeader;
int environmentsToSend = getSendMinimalEnvironment() ? 1 : getEnvironmentDataCount();
for (int i = 0; i < environmentsToSend; i++) {
envPacketLength += getEnvironmentData(i)->getBroadcastData(_tempOutputBuffer + envPacketLength);
}
NodeList::getInstance()->getNodeSocket().writeDatagram((char*) _tempOutputBuffer, envPacketLength,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());

View file

@ -24,7 +24,7 @@
/// Handles assignments of type VoxelServer - sending voxels to various clients.
class VoxelServer : public OctreeServer {
public:
public:
VoxelServer(const unsigned char* dataBuffer, int numBytes);
~VoxelServer();
@ -33,7 +33,7 @@ public:
EnvironmentData* getEnvironmentData(int i) { return &_environmentData[i]; }
int getEnvironmentDataCount() const { return sizeof(_environmentData)/sizeof(EnvironmentData); }
// Subclasses must implement these methods
// Subclasses must implement these methods
virtual OctreeQueryNode* createOctreeQueryNode(Node* newNode);
virtual Octree* createTree();
virtual unsigned char getMyNodeType() const { return NODE_TYPE_VOXEL_SERVER; }
@ -41,10 +41,10 @@ public:
virtual const char* getMyServerName() const { return VOXEL_SERVER_NAME; }
virtual const char* getMyLoggingServerTargetName() const { return VOXEL_SERVER_LOGGING_TARGET_NAME; }
virtual const char* getMyDefaultPersistFilename() const { return LOCAL_VOXELS_PERSIST_FILE; }
// subclass may implement these method
virtual void beforeRun();
virtual bool hasSpecialPacketToSend();
virtual bool hasSpecialPacketToSend(Node* node);
virtual int sendSpecialPacket(Node* node);