first cut at using partial property info in particle edits

This commit is contained in:
Brad Hefta-Gaub 2014-01-20 16:34:45 -08:00
parent b4b3720fcf
commit d831f9df5e
11 changed files with 439 additions and 293 deletions

View file

@ -21,14 +21,14 @@ EditPacketBuffer::EditPacketBuffer(PACKET_TYPE type, unsigned char* buffer, ssiz
_nodeUUID = nodeUUID; _nodeUUID = nodeUUID;
_currentType = type; _currentType = type;
_currentSize = length; _currentSize = length;
memcpy(_currentBuffer, buffer, length); memcpy(_currentBuffer, buffer, length);
}; };
const int OctreeEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::DEFAULT_PACKETS_PER_SECOND; const int OctreeEditPacketSender::DEFAULT_MAX_PENDING_MESSAGES = PacketSender::DEFAULT_PACKETS_PER_SECOND;
OctreeEditPacketSender::OctreeEditPacketSender(PacketSenderNotify* notify) : OctreeEditPacketSender::OctreeEditPacketSender(PacketSenderNotify* notify) :
PacketSender(notify), PacketSender(notify),
_shouldSend(true), _shouldSend(true),
_maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES), _maxPendingMessages(DEFAULT_MAX_PENDING_MESSAGES),
_releaseQueuedMessagesPending(false), _releaseQueuedMessagesPending(false),
@ -57,7 +57,7 @@ bool OctreeEditPacketSender::serversExist() const {
bool hasServers = false; bool hasServers = false;
bool atLeastOnJurisdictionMissing = false; // assume the best bool atLeastOnJurisdictionMissing = false; // assume the best
NodeList* nodeList = NodeList::getInstance(); NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
// only send to the NodeTypes that are getMyNodeType() // only send to the NodeTypes that are getMyNodeType()
if (node->getType() == getMyNodeType()) { if (node->getType() == getMyNodeType()) {
@ -78,15 +78,15 @@ bool OctreeEditPacketSender::serversExist() const {
break; // no point in looking further... break; // no point in looking further...
} }
} }
return (hasServers && !atLeastOnJurisdictionMissing); return (hasServers && !atLeastOnJurisdictionMissing);
} }
// This method is called when the edit packet layer has determined that it has a fully formed packet destined for // This method is called when the edit packet layer has determined that it has a fully formed packet destined for
// a known nodeID. // a known nodeID.
void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) { void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned char* buffer, ssize_t length) {
NodeList* nodeList = NodeList::getInstance(); NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
// only send to the NodeTypes that are getMyNodeType() // only send to the NodeTypes that are getMyNodeType()
if (node->getType() == getMyNodeType() && if (node->getType() == getMyNodeType() &&
@ -94,7 +94,7 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c
if (nodeList->getNodeActiveSocketOrPing(node.data())) { if (nodeList->getNodeActiveSocketOrPing(node.data())) {
const HifiSockAddr* nodeAddress = node->getActiveSocket(); const HifiSockAddr* nodeAddress = node->getActiveSocket();
queuePacketForSending(*nodeAddress, buffer, length); queuePacketForSending(*nodeAddress, buffer, length);
// debugging output... // debugging output...
bool wantDebugging = false; bool wantDebugging = false;
if (wantDebugging) { if (wantDebugging) {
@ -103,10 +103,10 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c
uint64_t createdAt = (*((uint64_t*)(buffer + numBytesPacketHeader + sizeof(sequence)))); uint64_t createdAt = (*((uint64_t*)(buffer + numBytesPacketHeader + sizeof(sequence))));
uint64_t queuedAt = usecTimestampNow(); uint64_t queuedAt = usecTimestampNow();
uint64_t transitTime = queuedAt - createdAt; uint64_t transitTime = queuedAt - createdAt;
qDebug() << "OctreeEditPacketSender::queuePacketToNode() queued " << buffer[0] << qDebug() << "OctreeEditPacketSender::queuePacketToNode() queued " << buffer[0] <<
" - command to node bytes=" << length << " - command to node bytes=" << length <<
" sequence=" << sequence << " sequence=" << sequence <<
" transitTimeSoFar=" << transitTime << " usecs"; " transitTimeSoFar=" << transitTime << " usecs";
} }
} }
@ -116,7 +116,7 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c
void OctreeEditPacketSender::processPreServerExistsPackets() { void OctreeEditPacketSender::processPreServerExistsPackets() {
assert(serversExist()); // we should only be here if we have jurisdictions assert(serversExist()); // we should only be here if we have jurisdictions
// First send out all the single message packets... // First send out all the single message packets...
while (!_preServerSingleMessagePackets.empty()) { while (!_preServerSingleMessagePackets.empty()) {
EditPacketBuffer* packet = _preServerSingleMessagePackets.front(); EditPacketBuffer* packet = _preServerSingleMessagePackets.front();
@ -133,7 +133,7 @@ void OctreeEditPacketSender::processPreServerExistsPackets() {
_preServerPackets.erase(_preServerPackets.begin()); _preServerPackets.erase(_preServerPackets.begin());
} }
// if while waiting for the jurisdictions the caller called releaseQueuedMessages() // if while waiting for the jurisdictions the caller called releaseQueuedMessages()
// then we want to honor that request now. // then we want to honor that request now.
if (_releaseQueuedMessagesPending) { if (_releaseQueuedMessagesPending) {
releaseQueuedMessages(); releaseQueuedMessages();
@ -160,17 +160,17 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l
if (!_shouldSend) { if (!_shouldSend) {
return; // bail early return; // bail early
} }
assert(serversExist()); // we must have jurisdictions to be here!! assert(serversExist()); // we must have jurisdictions to be here!!
int headerBytes = numBytesForPacketHeader(buffer) + sizeof(short) + sizeof(uint64_t); int headerBytes = numBytesForPacketHeader(buffer) + sizeof(short) + sizeof(uint64_t);
unsigned char* octCode = buffer + headerBytes; // skip the packet header to get to the octcode unsigned char* octCode = buffer + headerBytes; // skip the packet header to get to the octcode
// We want to filter out edit messages for servers based on the server's Jurisdiction // We want to filter out edit messages for servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined // But we can't really do that with a packed message, since each edit message could be destined
// for a different server... So we need to actually manage multiple queued packets... one // for a different server... So we need to actually manage multiple queued packets... one
// for each server // for each server
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
// only send to the NodeTypes that are getMyNodeType() // only send to the NodeTypes that are getMyNodeType()
if (node->getActiveSocket() != NULL && node->getType() == getMyNodeType()) { if (node->getActiveSocket() != NULL && node->getType() == getMyNodeType()) {
@ -181,7 +181,12 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l
const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID]; const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN); isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
if (isMyJurisdiction) { if (isMyJurisdiction) {
qDebug() << "calling queuePacketToNode(nodeUUID, buffer, length); nodeUUID=" << nodeUUID << " length=" << length;
queuePacketToNode(nodeUUID, buffer, length); queuePacketToNode(nodeUUID, buffer, length);
} else {
qDebug() << "not my jurisdiction skipping queuePacketToNode(nodeUUID, buffer, length); nodeUUID=" << nodeUUID << " length=" << length;
} }
} }
} }
@ -190,10 +195,14 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l
// NOTE: codeColorBuffer - is JUST the octcode/color and does not contain the packet header! // NOTE: codeColorBuffer - is JUST the octcode/color and does not contain the packet header!
void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length) { void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned char* codeColorBuffer, ssize_t length) {
qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
if (!_shouldSend) { if (!_shouldSend) {
return; // bail early return; // bail early
} }
qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
// If we don't have jurisdictions, then we will simply queue up all of these packets and wait till we have // If we don't have jurisdictions, then we will simply queue up all of these packets and wait till we have
// jurisdictions for processing // jurisdictions for processing
if (!serversExist()) { if (!serversExist()) {
@ -211,52 +220,61 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned c
} }
return; // bail early return; // bail early
} }
qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
// We want to filter out edit messages for servers based on the server's Jurisdiction // We want to filter out edit messages for servers based on the server's Jurisdiction
// But we can't really do that with a packed message, since each edit message could be destined // But we can't really do that with a packed message, since each edit message could be destined
// for a different server... So we need to actually manage multiple queued packets... one // for a different server... So we need to actually manage multiple queued packets... one
// for each server // for each server
foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
// only send to the NodeTypes that are getMyNodeType() // only send to the NodeTypes that are getMyNodeType()
if (node->getActiveSocket() != NULL && node->getType() == getMyNodeType()) { if (node->getActiveSocket() != NULL && node->getType() == getMyNodeType()) {
QUuid nodeUUID = node->getUUID(); QUuid nodeUUID = node->getUUID();
bool isMyJurisdiction = true; bool isMyJurisdiction = true;
if (_serverJurisdictions) { if (_serverJurisdictions) {
// we need to get the jurisdiction for this // we need to get the jurisdiction for this
// here we need to get the "pending packet" for this server // here we need to get the "pending packet" for this server
if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) { if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) {
const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID]; const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID];
isMyJurisdiction = (map.isMyJurisdiction(codeColorBuffer, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN); isMyJurisdiction = (map.isMyJurisdiction(codeColorBuffer, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN);
qDebug() << "queueOctreeEditMessage() line:" << __LINE__ << " isMyJurisdiction=" << isMyJurisdiction;
} else { } else {
isMyJurisdiction = false; isMyJurisdiction = false;
qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
} }
} }
if (isMyJurisdiction) { if (isMyJurisdiction) {
EditPacketBuffer& packetBuffer = _pendingEditPackets[nodeUUID]; EditPacketBuffer& packetBuffer = _pendingEditPackets[nodeUUID];
packetBuffer._nodeUUID = nodeUUID; packetBuffer._nodeUUID = nodeUUID;
qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
// If we're switching type, then we send the last one and start over // If we're switching type, then we send the last one and start over
if ((type != packetBuffer._currentType && packetBuffer._currentSize > 0) || if ((type != packetBuffer._currentType && packetBuffer._currentSize > 0) ||
(packetBuffer._currentSize + length >= _maxPacketSize)) { (packetBuffer._currentSize + length >= _maxPacketSize)) {
releaseQueuedPacket(packetBuffer); releaseQueuedPacket(packetBuffer);
initializePacket(packetBuffer, type); initializePacket(packetBuffer, type);
} }
// If the buffer is empty and not correctly initialized for our type... // If the buffer is empty and not correctly initialized for our type...
if (type != packetBuffer._currentType && packetBuffer._currentSize == 0) { if (type != packetBuffer._currentType && packetBuffer._currentSize == 0) {
initializePacket(packetBuffer, type); initializePacket(packetBuffer, type);
} }
// This is really the first time we know which server/node this particular edit message // This is really the first time we know which server/node this particular edit message
// is going to, so we couldn't adjust for clock skew till now. But here's our chance. // is going to, so we couldn't adjust for clock skew till now. But here's our chance.
// We call this virtual function that allows our specific type of EditPacketSender to // We call this virtual function that allows our specific type of EditPacketSender to
// fixup the buffer for any clock skew // fixup the buffer for any clock skew
if (node->getClockSkewUsec() != 0) { if (node->getClockSkewUsec() != 0) {
adjustEditPacketForClockSkew(codeColorBuffer, length, node->getClockSkewUsec()); adjustEditPacketForClockSkew(codeColorBuffer, length, node->getClockSkewUsec());
qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
} }
qDebug() << "queueOctreeEditMessage() line:" << __LINE__;
memcpy(&packetBuffer._currentBuffer[packetBuffer._currentSize], codeColorBuffer, length); memcpy(&packetBuffer._currentBuffer[packetBuffer._currentSize], codeColorBuffer, length);
packetBuffer._currentSize += length; packetBuffer._currentSize += length;
} }
@ -265,7 +283,7 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PACKET_TYPE type, unsigned c
} }
void OctreeEditPacketSender::releaseQueuedMessages() { void OctreeEditPacketSender::releaseQueuedMessages() {
// if we don't yet have jurisdictions then we can't actually release messages yet because we don't // if we don't yet have jurisdictions then we can't actually release messages yet because we don't
// know where to send them to. Instead, just remember this request and when we eventually get jurisdictions // know where to send them to. Instead, just remember this request and when we eventually get jurisdictions
// call release again at that time. // call release again at that time.
if (!serversExist()) { if (!serversExist()) {
@ -273,12 +291,14 @@ void OctreeEditPacketSender::releaseQueuedMessages() {
} else { } else {
for (std::map<QUuid, EditPacketBuffer>::iterator i = _pendingEditPackets.begin(); i != _pendingEditPackets.end(); i++) { for (std::map<QUuid, EditPacketBuffer>::iterator i = _pendingEditPackets.begin(); i != _pendingEditPackets.end(); i++) {
releaseQueuedPacket(i->second); releaseQueuedPacket(i->second);
qDebug() << "releaseQueuedMessages() line:" << __LINE__;
} }
} }
} }
void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) { void OctreeEditPacketSender::releaseQueuedPacket(EditPacketBuffer& packetBuffer) {
if (packetBuffer._currentSize > 0 && packetBuffer._currentType != PACKET_TYPE_UNKNOWN) { if (packetBuffer._currentSize > 0 && packetBuffer._currentType != PACKET_TYPE_UNKNOWN) {
qDebug() << "OctreeEditPacketSender::releaseQueuedPacket() line:" << __LINE__;
queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0], packetBuffer._currentSize); queuePacketToNode(packetBuffer._nodeUUID, &packetBuffer._currentBuffer[0], packetBuffer._currentSize);
} }
packetBuffer._currentSize = 0; packetBuffer._currentSize = 0;

View file

@ -22,6 +22,7 @@
#include "ParticlesScriptingInterface.h" #include "ParticlesScriptingInterface.h"
#include "Particle.h" #include "Particle.h"
#include "ParticleTree.h"
uint32_t Particle::_nextID = 0; uint32_t Particle::_nextID = 0;
VoxelEditPacketSender* Particle::_voxelEditSender = NULL; VoxelEditPacketSender* Particle::_voxelEditSender = NULL;
@ -57,6 +58,8 @@ void Particle::handleAddParticleResponse(unsigned char* packetData , int packetL
memcpy(&particleID, dataAt, sizeof(particleID)); memcpy(&particleID, dataAt, sizeof(particleID));
dataAt += sizeof(particleID); dataAt += sizeof(particleID);
qDebug() << "handleAddParticleResponse()... particleID=" << particleID << " creatorTokenID=" << creatorTokenID;
// add our token to id mapping // add our token to id mapping
_tokenIDsToIDs[creatorTokenID] = particleID; _tokenIDsToIDs[creatorTokenID] = particleID;
} }
@ -278,7 +281,7 @@ int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLef
} }
Particle Particle::fromEditPacket(unsigned char* data, int length, int& processedBytes) { Particle Particle::fromEditPacket(unsigned char* data, int length, int& processedBytes, ParticleTree* tree) {
Particle newParticle; // id and _lastUpdated will get set here... Particle newParticle; // id and _lastUpdated will get set here...
unsigned char* dataAt = data; unsigned char* dataAt = data;
processedBytes = 0; processedBytes = 0;
@ -309,10 +312,30 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
newParticle._newlyCreated = true; newParticle._newlyCreated = true;
newParticle.setAge(0); // this guy is new! newParticle.setAge(0); // this guy is new!
qDebug() << "fromEditPacket() NEW_PARTICLE";
} else { } else {
qDebug() << "fromEditPacket() existingParticle id=" << editID;
// look up the existing particle
qDebug() << "fromEditPacket() existingParticle id=" << editID << "calling tree->findParticleByID(editID)";
const Particle* existingParticle = tree->findParticleByID(editID, true);
qDebug() << "fromEditPacket() existingParticle id=" << editID << "DONE tree->findParticleByID(editID)";
// copy existing properties before over-writing with new properties
if (existingParticle) {
newParticle = *existingParticle;
qDebug() << "found it...";
existingParticle->debugDump();
} else {
qDebug() << "WHOA! Didn't find it...";
}
newParticle._id = editID; newParticle._id = editID;
newParticle._newlyCreated = false; newParticle._newlyCreated = false;
} }
// lastEdited // lastEdited
@ -320,55 +343,92 @@ Particle Particle::fromEditPacket(unsigned char* data, int length, int& processe
dataAt += sizeof(newParticle._lastEdited); dataAt += sizeof(newParticle._lastEdited);
processedBytes += sizeof(newParticle._lastEdited); processedBytes += sizeof(newParticle._lastEdited);
// All of the remaining items are optional, and may or may not be included based on their included values in the
// properties included bits
uint16_t packetContainsBits = 0;
memcpy(&packetContainsBits, dataAt, sizeof(packetContainsBits));
dataAt += sizeof(packetContainsBits);
processedBytes += sizeof(packetContainsBits);
qDebug() << "fromEditPacket() packetContainsBits=" << packetContainsBits;
// radius // radius
memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius)); if ((packetContainsBits & PACKET_CONTAINS_RADIUS) == PACKET_CONTAINS_RADIUS) {
dataAt += sizeof(newParticle._radius); qDebug() << "fromEditPacket() PACKET_CONTAINS_RADIUS";
processedBytes += sizeof(newParticle._radius);
memcpy(&newParticle._radius, dataAt, sizeof(newParticle._radius));
dataAt += sizeof(newParticle._radius);
processedBytes += sizeof(newParticle._radius);
}
// position // position
memcpy(&newParticle._position, dataAt, sizeof(newParticle._position)); if ((packetContainsBits & PACKET_CONTAINS_POSITION) == PACKET_CONTAINS_POSITION) {
dataAt += sizeof(newParticle._position); qDebug() << "fromEditPacket() PACKET_CONTAINS_POSITION";
processedBytes += sizeof(newParticle._position); memcpy(&newParticle._position, dataAt, sizeof(newParticle._position));
dataAt += sizeof(newParticle._position);
processedBytes += sizeof(newParticle._position);
}
// color // color
memcpy(newParticle._color, dataAt, sizeof(newParticle._color)); if ((packetContainsBits & PACKET_CONTAINS_COLOR) == PACKET_CONTAINS_COLOR) {
dataAt += sizeof(newParticle._color); qDebug() << "fromEditPacket() PACKET_CONTAINS_COLOR";
processedBytes += sizeof(newParticle._color); memcpy(newParticle._color, dataAt, sizeof(newParticle._color));
dataAt += sizeof(newParticle._color);
processedBytes += sizeof(newParticle._color);
}
// velocity // velocity
memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity)); if ((packetContainsBits & PACKET_CONTAINS_VELOCITY) == PACKET_CONTAINS_VELOCITY) {
dataAt += sizeof(newParticle._velocity); qDebug() << "fromEditPacket() PACKET_CONTAINS_VELOCITY";
processedBytes += sizeof(newParticle._velocity); memcpy(&newParticle._velocity, dataAt, sizeof(newParticle._velocity));
dataAt += sizeof(newParticle._velocity);
processedBytes += sizeof(newParticle._velocity);
}
// gravity // gravity
memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity)); if ((packetContainsBits & PACKET_CONTAINS_GRAVITY) == PACKET_CONTAINS_GRAVITY) {
dataAt += sizeof(newParticle._gravity); qDebug() << "fromEditPacket() PACKET_CONTAINS_GRAVITY";
processedBytes += sizeof(newParticle._gravity); memcpy(&newParticle._gravity, dataAt, sizeof(newParticle._gravity));
dataAt += sizeof(newParticle._gravity);
processedBytes += sizeof(newParticle._gravity);
}
// damping // damping
memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping)); if ((packetContainsBits & PACKET_CONTAINS_DAMPING) == PACKET_CONTAINS_DAMPING) {
dataAt += sizeof(newParticle._damping); qDebug() << "fromEditPacket() PACKET_CONTAINS_DAMPING";
processedBytes += sizeof(newParticle._damping); memcpy(&newParticle._damping, dataAt, sizeof(newParticle._damping));
dataAt += sizeof(newParticle._damping);
processedBytes += sizeof(newParticle._damping);
}
// lifetime // lifetime
memcpy(&newParticle._lifetime, dataAt, sizeof(newParticle._lifetime)); if ((packetContainsBits & PACKET_CONTAINS_LIFETIME) == PACKET_CONTAINS_LIFETIME) {
dataAt += sizeof(newParticle._lifetime); qDebug() << "fromEditPacket() PACKET_CONTAINS_LIFETIME";
processedBytes += sizeof(newParticle._lifetime); memcpy(&newParticle._lifetime, dataAt, sizeof(newParticle._lifetime));
dataAt += sizeof(newParticle._lifetime);
processedBytes += sizeof(newParticle._lifetime);
}
// inHand // inHand
memcpy(&newParticle._inHand, dataAt, sizeof(newParticle._inHand)); if ((packetContainsBits & PACKET_CONTAINS_INHAND) == PACKET_CONTAINS_INHAND) {
dataAt += sizeof(newParticle._inHand); qDebug() << "fromEditPacket() PACKET_CONTAINS_INHAND";
processedBytes += sizeof(newParticle._inHand); memcpy(&newParticle._inHand, dataAt, sizeof(newParticle._inHand));
dataAt += sizeof(newParticle._inHand);
processedBytes += sizeof(newParticle._inHand);
}
// script // script
uint16_t scriptLength; if ((packetContainsBits & PACKET_CONTAINS_SCRIPT) == PACKET_CONTAINS_SCRIPT) {
memcpy(&scriptLength, dataAt, sizeof(scriptLength)); qDebug() << "fromEditPacket() PACKET_CONTAINS_SCRIPT";
dataAt += sizeof(scriptLength); uint16_t scriptLength;
processedBytes += sizeof(scriptLength); memcpy(&scriptLength, dataAt, sizeof(scriptLength));
QString tempString((const char*)dataAt); dataAt += sizeof(scriptLength);
newParticle._script = tempString; processedBytes += sizeof(scriptLength);
dataAt += scriptLength; QString tempString((const char*)dataAt);
processedBytes += scriptLength; newParticle._script = tempString;
dataAt += scriptLength;
processedBytes += scriptLength;
}
const bool wantDebugging = true; const bool wantDebugging = true;
if (wantDebugging) { if (wantDebugging) {
@ -386,116 +446,168 @@ void Particle::debugDump() const {
printf(" age:%f\n", getAge()); printf(" age:%f\n", getAge());
printf(" edited ago:%f\n", getEditedAgo()); printf(" edited ago:%f\n", getEditedAgo());
printf(" position:%f,%f,%f\n", _position.x, _position.y, _position.z); printf(" position:%f,%f,%f\n", _position.x, _position.y, _position.z);
printf(" radius:%f\n", getRadius());
printf(" velocity:%f,%f,%f\n", _velocity.x, _velocity.y, _velocity.z); printf(" velocity:%f,%f,%f\n", _velocity.x, _velocity.y, _velocity.z);
printf(" gravity:%f,%f,%f\n", _gravity.x, _gravity.y, _gravity.z); printf(" gravity:%f,%f,%f\n", _gravity.x, _gravity.y, _gravity.z);
printf(" color:%d,%d,%d\n", _color[0], _color[1], _color[2]); printf(" color:%d,%d,%d\n", _color[0], _color[1], _color[2]);
} }
bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details, bool Particle::encodeParticleEditMessageDetails(PACKET_TYPE command, ParticleID id, const ParticleProperties& properties,
unsigned char* bufferOut, int sizeIn, int& sizeOut) { unsigned char* bufferOut, int sizeIn, int& sizeOut) {
bool success = true; // assume the best bool success = true; // assume the best
unsigned char* copyAt = bufferOut; unsigned char* copyAt = bufferOut;
sizeOut = 0; sizeOut = 0;
for (int i = 0; i < count && success; i++) { // get the octal code for the particle
// get the octal code for the particle
unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y,
details[i].position.z, details[i].radius);
int octets = numberOfThreeBitSectionsInCode(octcode); // this could be a problem if the caller doesn't include position....
int lengthOfOctcode = bytesRequiredForCodeLength(octets); glm::vec3 rootPosition(0);
int lenfthOfEditData = lengthOfOctcode + expectedEditMessageBytes(); float rootScale = 0.5f;
unsigned char* octcode = pointToOctalCode(rootPosition.x, rootPosition.y, rootPosition.z, rootScale);
// make sure we have room to copy this particle // old code... this matters for particle servers with different jurisdictions, but for now, we'll send
if (sizeOut + lenfthOfEditData > sizeIn) { // everything to the root, since the tree does the right thing...
success = false; //
} else { //unsigned char* octcode = pointToOctalCode(details[i].position.x, details[i].position.y,
// add it to our message // details[i].position.z, details[i].radius);
memcpy(copyAt, octcode, lengthOfOctcode);
copyAt += lengthOfOctcode;
sizeOut += lengthOfOctcode;
// Now add our edit content details... int octets = numberOfThreeBitSectionsInCode(octcode);
int lengthOfOctcode = bytesRequiredForCodeLength(octets);
int lenfthOfEditData = lengthOfOctcode + expectedEditMessageBytes();
// id // make sure we have room to copy this particle
memcpy(copyAt, &details[i].id, sizeof(details[i].id)); if (sizeOut + lenfthOfEditData > sizeIn) {
copyAt += sizeof(details[i].id); success = false;
sizeOut += sizeof(details[i].id); } else {
// special case for handling "new" particles // add it to our message
if (details[i].id == NEW_PARTICLE) { memcpy(copyAt, octcode, lengthOfOctcode);
// If this is a NEW_PARTICLE, then we assume that there's an additional uint32_t creatorToken, that copyAt += lengthOfOctcode;
// we want to send back to the creator as an map to the actual id sizeOut += lengthOfOctcode;
memcpy(copyAt, &details[i].creatorTokenID, sizeof(details[i].creatorTokenID));
copyAt += sizeof(details[i].creatorTokenID);
sizeOut += sizeof(details[i].creatorTokenID);
}
// lastEdited // Now add our edit content details...
memcpy(copyAt, &details[i].lastEdited, sizeof(details[i].lastEdited));
copyAt += sizeof(details[i].lastEdited);
sizeOut += sizeof(details[i].lastEdited);
// radius // id
memcpy(copyAt, &details[i].radius, sizeof(details[i].radius)); memcpy(copyAt, &id.id, sizeof(id.id));
copyAt += sizeof(details[i].radius); copyAt += sizeof(id.id);
sizeOut += sizeof(details[i].radius); sizeOut += sizeof(id.id);
// special case for handling "new" particles
if (id.id == NEW_PARTICLE) {
// If this is a NEW_PARTICLE, then we assume that there's an additional uint32_t creatorToken, that
// we want to send back to the creator as an map to the actual id
memcpy(copyAt, &id.creatorTokenID, sizeof(id.creatorTokenID));
copyAt += sizeof(id.creatorTokenID);
sizeOut += sizeof(id.creatorTokenID);
}
// position // lastEdited
memcpy(copyAt, &details[i].position, sizeof(details[i].position)); uint64_t lastEdited = properties.getLastEdited();
copyAt += sizeof(details[i].position); memcpy(copyAt, &lastEdited, sizeof(lastEdited));
sizeOut += sizeof(details[i].position); copyAt += sizeof(lastEdited);
sizeOut += sizeof(lastEdited);
// color // All of the remaining items are optional, and may or may not be included based on their included values in the
memcpy(copyAt, details[i].color, sizeof(details[i].color)); // properties included bits
copyAt += sizeof(details[i].color); uint16_t packetContainsBits = properties.getChangedBits();
sizeOut += sizeof(details[i].color); memcpy(copyAt, &packetContainsBits, sizeof(packetContainsBits));
copyAt += sizeof(packetContainsBits);
sizeOut += sizeof(packetContainsBits);
// velocity qDebug() << " packetContainsBits = " << packetContainsBits;
memcpy(copyAt, &details[i].velocity, sizeof(details[i].velocity));
copyAt += sizeof(details[i].velocity);
sizeOut += sizeof(details[i].velocity);
// gravity // radius
memcpy(copyAt, &details[i].gravity, sizeof(details[i].gravity)); if ((packetContainsBits & PACKET_CONTAINS_RADIUS) == PACKET_CONTAINS_RADIUS) {
copyAt += sizeof(details[i].gravity); printf("encodeParticleEditMessageDetails() including PACKET_CONTAINS_RADIUS\n");
sizeOut += sizeof(details[i].gravity); float radius = properties.getRadius();
memcpy(copyAt, &radius, sizeof(radius));
copyAt += sizeof(radius);
sizeOut += sizeof(radius);
}
// damping // position
memcpy(copyAt, &details[i].damping, sizeof(details[i].damping)); if ((packetContainsBits & PACKET_CONTAINS_POSITION) == PACKET_CONTAINS_POSITION) {
copyAt += sizeof(details[i].damping); printf("encodeParticleEditMessageDetails() including PACKET_CONTAINS_POSITION\n");
sizeOut += sizeof(details[i].damping); memcpy(copyAt, &properties.getPosition(), sizeof(properties.getPosition()));
copyAt += sizeof(properties.getPosition());
sizeOut += sizeof(properties.getPosition());
}
// damping // color
memcpy(copyAt, &details[i].lifetime, sizeof(details[i].lifetime)); if ((packetContainsBits & PACKET_CONTAINS_COLOR) == PACKET_CONTAINS_COLOR) {
copyAt += sizeof(details[i].lifetime); printf("encodeParticleEditMessageDetails() including PACKET_CONTAINS_COLOR\n");
sizeOut += sizeof(details[i].lifetime); rgbColor color = { properties.getColor().red, properties.getColor().green, properties.getColor().blue };
memcpy(copyAt, color, sizeof(color));
copyAt += sizeof(color);
sizeOut += sizeof(color);
}
// inHand // velocity
memcpy(copyAt, &details[i].inHand, sizeof(details[i].inHand)); if ((packetContainsBits & PACKET_CONTAINS_VELOCITY) == PACKET_CONTAINS_VELOCITY) {
copyAt += sizeof(details[i].inHand); printf("encodeParticleEditMessageDetails() including PACKET_CONTAINS_VELOCITY\n");
sizeOut += sizeof(details[i].inHand); memcpy(copyAt, &properties.getVelocity(), sizeof(properties.getVelocity()));
copyAt += sizeof(properties.getVelocity());
sizeOut += sizeof(properties.getVelocity());
}
// script // gravity
uint16_t scriptLength = details[i].updateScript.size() + 1; if ((packetContainsBits & PACKET_CONTAINS_GRAVITY) == PACKET_CONTAINS_GRAVITY) {
printf("encodeParticleEditMessageDetails() including PACKET_CONTAINS_GRAVITY\n");
memcpy(copyAt, &properties.getGravity(), sizeof(properties.getGravity()));
copyAt += sizeof(properties.getGravity());
sizeOut += sizeof(properties.getGravity());
}
// damping
if ((packetContainsBits & PACKET_CONTAINS_DAMPING) == PACKET_CONTAINS_DAMPING) {
printf("encodeParticleEditMessageDetails() including PACKET_CONTAINS_DAMPING\n");
float damping = properties.getDamping();
memcpy(copyAt, &damping, sizeof(damping));
copyAt += sizeof(damping);
sizeOut += sizeof(damping);
}
// lifetime
if ((packetContainsBits & PACKET_CONTAINS_LIFETIME) == PACKET_CONTAINS_LIFETIME) {
printf("encodeParticleEditMessageDetails() including PACKET_CONTAINS_LIFETIME\n");
float lifetime = properties.getLifetime();
memcpy(copyAt, &lifetime, sizeof(lifetime));
copyAt += sizeof(lifetime);
sizeOut += sizeof(lifetime);
}
// inHand
if ((packetContainsBits & PACKET_CONTAINS_INHAND) == PACKET_CONTAINS_INHAND) {
printf("encodeParticleEditMessageDetails() including PACKET_CONTAINS_INHAND\n");
bool inHand = properties.getInHand();
memcpy(copyAt, &inHand, sizeof(inHand));
copyAt += sizeof(inHand);
sizeOut += sizeof(inHand);
}
// script
if ((packetContainsBits & PACKET_CONTAINS_SCRIPT) == PACKET_CONTAINS_SCRIPT) {
printf("encodeParticleEditMessageDetails() including SCRIPT\n");
uint16_t scriptLength = properties.getScript().size() + 1;
memcpy(copyAt, &scriptLength, sizeof(scriptLength)); memcpy(copyAt, &scriptLength, sizeof(scriptLength));
copyAt += sizeof(scriptLength); copyAt += sizeof(scriptLength);
sizeOut += sizeof(scriptLength); sizeOut += sizeof(scriptLength);
memcpy(copyAt, qPrintable(details[i].updateScript), scriptLength); memcpy(copyAt, qPrintable(properties.getScript()), scriptLength);
copyAt += scriptLength; copyAt += scriptLength;
sizeOut += scriptLength; sizeOut += scriptLength;
bool wantDebugging = false;
if (wantDebugging) {
printf("encodeParticleEditMessageDetails()....\n");
printf("Particle id :%u\n", details[i].id);
printf(" nextID:%u\n", _nextID);
}
} }
// cleanup
delete[] octcode; bool wantDebugging = false;
if (wantDebugging) {
printf("encodeParticleEditMessageDetails()....\n");
printf("Particle id :%u\n", id.id);
printf(" nextID:%u\n", _nextID);
}
} }
// cleanup
delete[] octcode;
return success; return success;
} }
@ -696,7 +808,7 @@ void Particle::setProperties(const ParticleProperties& properties) {
ParticleProperties::ParticleProperties() : ParticleProperties::ParticleProperties() :
_position(0), _position(0),
_color(), _color(),
_radius(DEFAULT_RADIUS), _radius(0),
_velocity(0), _velocity(0),
_gravity(DEFAULT_GRAVITY), _gravity(DEFAULT_GRAVITY),
_damping(DEFAULT_DAMPING), _damping(DEFAULT_DAMPING),
@ -705,6 +817,7 @@ ParticleProperties::ParticleProperties() :
_inHand(false), _inHand(false),
_shouldDie(false), _shouldDie(false),
_lastEdited(usecTimestampNow()),
_positionChanged(false), _positionChanged(false),
_colorChanged(false), _colorChanged(false),
_radiusChanged(false), _radiusChanged(false),
@ -718,6 +831,55 @@ ParticleProperties::ParticleProperties() :
{ {
} }
uint16_t ParticleProperties::getChangedBits() const {
uint16_t changedBits = 0;
if (_radiusChanged) {
changedBits += PACKET_CONTAINS_RADIUS;
}
if (_positionChanged) {
changedBits += PACKET_CONTAINS_POSITION;
}
if (_colorChanged) {
changedBits += PACKET_CONTAINS_COLOR;
}
if (_velocityChanged) {
changedBits += PACKET_CONTAINS_VELOCITY;
}
if (_gravityChanged) {
changedBits += PACKET_CONTAINS_GRAVITY;
}
if (_dampingChanged) {
changedBits += PACKET_CONTAINS_DAMPING;
}
if (_lifetimeChanged) {
changedBits += PACKET_CONTAINS_LIFETIME;
}
if (_inHandChanged) {
changedBits += PACKET_CONTAINS_INHAND;
}
if (_scriptChanged) {
changedBits += PACKET_CONTAINS_SCRIPT;
}
// how do we want to handle this?
//if (_shouldDieChanged) {
// changedBits += PACKET_CONTAINS_SHOULDDIE;
//}
return changedBits;
}
QScriptValue ParticleProperties::copyToScriptValue(QScriptEngine* engine) const { QScriptValue ParticleProperties::copyToScriptValue(QScriptEngine* engine) const {
QScriptValue properties = engine->newObject(); QScriptValue properties = engine->newObject();
@ -783,13 +945,18 @@ void ParticleProperties::copyFromScriptValue(const QScriptValue &object) {
} }
QScriptValue radius = object.property("radius"); QScriptValue radius = object.property("radius");
qDebug() << "copyFromScriptValue() line:" << __LINE__;
if (radius.isValid()) { if (radius.isValid()) {
qDebug() << "copyFromScriptValue() line:" << __LINE__;
float newRadius; float newRadius;
newRadius = radius.toVariant().toFloat(); newRadius = radius.toVariant().toFloat();
qDebug() << "copyFromScriptValue() line:" << __LINE__ << " newRadius=" << newRadius;
qDebug() << "copyFromScriptValue() line:" << __LINE__ << " _radius=" << _radius;
if (newRadius != _radius) { if (newRadius != _radius) {
_radius = newRadius; _radius = newRadius;
_radiusChanged = true; _radiusChanged = true;
} }
qDebug() << "copyFromScriptValue() line:" << __LINE__ << " _radiusChanged=" << _radiusChanged;
} }
QScriptValue velocity = object.property("velocity"); QScriptValue velocity = object.property("velocity");
@ -875,6 +1042,8 @@ void ParticleProperties::copyFromScriptValue(const QScriptValue &object) {
_shouldDieChanged = true; _shouldDieChanged = true;
} }
} }
_lastEdited = usecTimestampNow();
} }
void ParticleProperties::copyToParticle(Particle& particle) const { void ParticleProperties::copyToParticle(Particle& particle) const {

View file

@ -25,26 +25,21 @@ class VoxelEditPacketSender;
class ParticleEditPacketSender; class ParticleEditPacketSender;
class ParticleProperties; class ParticleProperties;
class Particle; class Particle;
class ParticleTree;
const uint32_t NEW_PARTICLE = 0xFFFFFFFF; const uint32_t NEW_PARTICLE = 0xFFFFFFFF;
const uint32_t UNKNOWN_TOKEN = 0xFFFFFFFF; const uint32_t UNKNOWN_TOKEN = 0xFFFFFFFF;
const uint32_t UNKNOWN_PARTICLE_ID = 0xFFFFFFFF; const uint32_t UNKNOWN_PARTICLE_ID = 0xFFFFFFFF;
class ParticleDetail { const uint16_t PACKET_CONTAINS_RADIUS = 1;
public: const uint16_t PACKET_CONTAINS_POSITION = 2;
uint32_t id; const uint16_t PACKET_CONTAINS_COLOR = 4;
uint64_t lastEdited; const uint16_t PACKET_CONTAINS_VELOCITY = 8;
glm::vec3 position; const uint16_t PACKET_CONTAINS_GRAVITY = 16;
float radius; const uint16_t PACKET_CONTAINS_DAMPING = 32;
rgbColor color; const uint16_t PACKET_CONTAINS_LIFETIME = 64;
glm::vec3 velocity; const uint16_t PACKET_CONTAINS_INHAND = 128;
glm::vec3 gravity; const uint16_t PACKET_CONTAINS_SCRIPT = 256;
float damping;
float lifetime;
bool inHand;
QString updateScript;
uint32_t creatorTokenID;
};
const float DEFAULT_LIFETIME = 60.0f * 60.0f * 24.0f; // particles live for 1 day by default const float DEFAULT_LIFETIME = 60.0f * 60.0f * 24.0f; // particles live for 1 day by default
const float DEFAULT_DAMPING = 0.99f; const float DEFAULT_DAMPING = 0.99f;
@ -74,10 +69,13 @@ public:
const glm::vec3& getGravity() const { return _gravity; } const glm::vec3& getGravity() const { return _gravity; }
float getDamping() const { return _damping; } float getDamping() const { return _damping; }
float getLifetime() const { return _lifetime; } float getLifetime() const { return _lifetime; }
QString getScript() const { return _script; } const QString& getScript() const { return _script; }
bool getInHand() const { return _inHand; } bool getInHand() const { return _inHand; }
bool getShouldDie() const { return _shouldDie; } bool getShouldDie() const { return _shouldDie; }
uint64_t getLastEdited() const { return _lastEdited; }
uint16_t getChangedBits() const;
private: private:
glm::vec3 _position; glm::vec3 _position;
xColor _color; xColor _color;
@ -90,6 +88,7 @@ private:
bool _inHand; bool _inHand;
bool _shouldDie; bool _shouldDie;
uint64_t _lastEdited;
bool _positionChanged; bool _positionChanged;
bool _colorChanged; bool _colorChanged;
bool _radiusChanged; bool _radiusChanged;
@ -138,7 +137,7 @@ public:
bool inHand = NOT_IN_HAND, QString updateScript = DEFAULT_SCRIPT, uint32_t id = NEW_PARTICLE); bool inHand = NOT_IN_HAND, QString updateScript = DEFAULT_SCRIPT, uint32_t id = NEW_PARTICLE);
/// creates an NEW particle from an PACKET_TYPE_PARTICLE_ADD_OR_EDIT edit data buffer /// creates an NEW particle from an PACKET_TYPE_PARTICLE_ADD_OR_EDIT edit data buffer
static Particle fromEditPacket(unsigned char* data, int length, int& processedBytes); static Particle fromEditPacket(unsigned char* data, int length, int& processedBytes, ParticleTree* tree);
virtual ~Particle(); virtual ~Particle();
virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity,
@ -196,7 +195,7 @@ public:
static int expectedBytes(); static int expectedBytes();
static int expectedEditMessageBytes(); static int expectedEditMessageBytes();
static bool encodeParticleEditMessageDetails(PACKET_TYPE command, int count, const ParticleDetail* details, static bool encodeParticleEditMessageDetails(PACKET_TYPE command, ParticleID id, const ParticleProperties& details,
unsigned char* bufferOut, int sizeIn, int& sizeOut); unsigned char* bufferOut, int sizeIn, int& sizeOut);
static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); static void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew);

View file

@ -13,7 +13,6 @@
#include "ParticleTree.h" #include "ParticleTree.h"
std::map<uint32_t,ParticleEditHandle*> ParticleEditHandle::_allHandles; std::map<uint32_t,ParticleEditHandle*> ParticleEditHandle::_allHandles;
uint32_t ParticleEditHandle::_nextCreatorTokenID = 0;
ParticleEditHandle::ParticleEditHandle(ParticleEditPacketSender* packetSender, ParticleTree* localTree, uint32_t id) { ParticleEditHandle::ParticleEditHandle(ParticleEditPacketSender* packetSender, ParticleTree* localTree, uint32_t id) {
if (id == NEW_PARTICLE) { if (id == NEW_PARTICLE) {
@ -43,13 +42,14 @@ void ParticleEditHandle::createParticle(glm::vec3 position, float radius, xColor
glm::vec3 gravity, float damping, float lifetime, bool inHand, QString updateScript) { glm::vec3 gravity, float damping, float lifetime, bool inHand, QString updateScript) {
// setup a ParticleDetail struct with the data // setup a ParticleDetail struct with the data
/****
uint64_t now = usecTimestampNow(); uint64_t now = usecTimestampNow();
ParticleDetail addParticleDetail = { NEW_PARTICLE, now, ParticleDetail addParticleDetail = { NEW_PARTICLE, now,
position, radius, {color.red, color.green, color.blue }, position, radius, {color.red, color.green, color.blue },
velocity, gravity, damping, lifetime, inHand, updateScript, _creatorTokenID }; velocity, gravity, damping, lifetime, inHand, updateScript, _creatorTokenID };
// queue the packet // queue the packet
_packetSender->queueParticleEditMessages(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, 1, &addParticleDetail); _packetSender->queueParticleEditMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, 1, &addParticleDetail);
// release them // release them
_packetSender->releaseQueuedMessages(); _packetSender->releaseQueuedMessages();
@ -59,6 +59,8 @@ void ParticleEditHandle::createParticle(glm::vec3 position, float radius, xColor
// we can't really do this here, because if we create a particle locally, we'll get a ghost particle // we can't really do this here, because if we create a particle locally, we'll get a ghost particle
// because we can't really handle updating/deleting it locally // because we can't really handle updating/deleting it locally
} }
****/
} }
bool ParticleEditHandle::updateParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity, bool ParticleEditHandle::updateParticle(glm::vec3 position, float radius, xColor color, glm::vec3 velocity,
@ -69,6 +71,7 @@ bool ParticleEditHandle::updateParticle(glm::vec3 position, float radius, xColor
} }
// setup a ParticleDetail struct with the data // setup a ParticleDetail struct with the data
/****
uint64_t now = usecTimestampNow(); uint64_t now = usecTimestampNow();
ParticleDetail newParticleDetail = { _id, now, ParticleDetail newParticleDetail = { _id, now,
position, radius, {color.red, color.green, color.blue }, position, radius, {color.red, color.green, color.blue },
@ -86,6 +89,7 @@ bool ParticleEditHandle::updateParticle(glm::vec3 position, float radius, xColor
Particle tempParticle(position, radius, rcolor, velocity, gravity, damping, lifetime, inHand, updateScript, _id); Particle tempParticle(position, radius, rcolor, velocity, gravity, damping, lifetime, inHand, updateScript, _id);
_localTree->storeParticle(tempParticle); _localTree->storeParticle(tempParticle);
} }
***/
return true; return true;
} }

View file

@ -45,7 +45,6 @@ private:
uint32_t _creatorTokenID; uint32_t _creatorTokenID;
uint32_t _id; uint32_t _id;
bool _isKnownID; bool _isKnownID;
static uint32_t _nextCreatorTokenID;
static std::map<uint32_t,ParticleEditHandle*> _allHandles; static std::map<uint32_t,ParticleEditHandle*> _allHandles;
ParticleEditPacketSender* _packetSender; ParticleEditPacketSender* _packetSender;
ParticleTree* _localTree; ParticleTree* _localTree;

View file

@ -16,7 +16,7 @@
#include "Particle.h" #include "Particle.h"
void ParticleEditPacketSender::sendEditParticleMessage(PACKET_TYPE type, const ParticleDetail& detail) { void ParticleEditPacketSender::sendEditParticleMessage(PACKET_TYPE type, ParticleID particleID, const ParticleProperties& properties) {
// allows app to disable sending if for example voxels have been disabled // allows app to disable sending if for example voxels have been disabled
if (!_shouldSend) { if (!_shouldSend) {
return; // bail early return; // bail early
@ -26,35 +26,42 @@ void ParticleEditPacketSender::sendEditParticleMessage(PACKET_TYPE type, const P
int sizeOut = 0; int sizeOut = 0;
// This encodes the voxel edit message into a buffer... // This encodes the voxel edit message into a buffer...
if (Particle::encodeParticleEditMessageDetails(type, 1, &detail, &bufferOut[0], _maxPacketSize, sizeOut)){ if (Particle::encodeParticleEditMessageDetails(type, particleID, properties, &bufferOut[0], _maxPacketSize, sizeOut)){
// If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have // If we don't have voxel jurisdictions, then we will simply queue up these packets and wait till we have
// jurisdictions for processing // jurisdictions for processing
if (!serversExist()) { if (!serversExist()) {
queuePendingPacketToNodes(type, bufferOut, sizeOut); queuePendingPacketToNodes(type, bufferOut, sizeOut);
} else { } else {
qDebug("calling queuePacketToNodes(bufferOut, sizeOut=%d)... ", sizeOut);
queuePacketToNodes(bufferOut, sizeOut); queuePacketToNodes(bufferOut, sizeOut);
} }
} }
} }
void ParticleEditPacketSender::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) { void ParticleEditPacketSender::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew) {
Particle::adjustEditPacketForClockSkew(codeColorBuffer, length, clockSkew); Particle::adjustEditPacketForClockSkew(codeColorBuffer, length, clockSkew);
} }
void ParticleEditPacketSender::queueParticleEditMessages(PACKET_TYPE type, int numberOfDetails, ParticleDetail* details) { void ParticleEditPacketSender::queueParticleEditMessage(PACKET_TYPE type, ParticleID particleID, const ParticleProperties& properties) {
qDebug() << "ParticleEditPacketSender::queueParticleEditMessage() id.id=" << particleID.id << " id.creatorTokenID=" << particleID.creatorTokenID;
if (!_shouldSend) { if (!_shouldSend) {
return; // bail early return; // bail early
} }
for (int i = 0; i < numberOfDetails; i++) { // use MAX_PACKET_SIZE since it's static and guaranteed to be larger than _maxPacketSize
// use MAX_PACKET_SIZE since it's static and guaranteed to be larger than _maxPacketSize static unsigned char bufferOut[MAX_PACKET_SIZE];
static unsigned char bufferOut[MAX_PACKET_SIZE]; int sizeOut = 0;
int sizeOut = 0;
if (Particle::encodeParticleEditMessageDetails(type, particleID, properties, &bufferOut[0], _maxPacketSize, sizeOut)) {
if (Particle::encodeParticleEditMessageDetails(type, 1, &details[i], &bufferOut[0], _maxPacketSize, sizeOut)) {
queueOctreeEditMessage(type, bufferOut, sizeOut); qDebug("calling queueOctreeEditMessage(bufferOut, sizeOut=%d)... ", sizeOut);
}
} queueOctreeEditMessage(type, bufferOut, sizeOut);
}
} }

View file

@ -19,14 +19,14 @@ class ParticleEditPacketSender : public OctreeEditPacketSender {
public: public:
ParticleEditPacketSender(PacketSenderNotify* notify = NULL) : OctreeEditPacketSender(notify) { } ParticleEditPacketSender(PacketSenderNotify* notify = NULL) : OctreeEditPacketSender(notify) { }
~ParticleEditPacketSender() { } ~ParticleEditPacketSender() { }
/// Send particle add message immediately
void sendEditParticleMessage(PACKET_TYPE type, const ParticleDetail& detail);
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines /// Send particle add message immediately
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in void sendEditParticleMessage(PACKET_TYPE type, ParticleID particleID, const ParticleProperties& properties);
/// Queues an array of several voxel edit messages. Will potentially send a pending multi-command packet. Determines
/// which voxel-server node or nodes the packet should be sent to. Can be called even before voxel servers are known, in
/// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known. /// which case up to MaxPendingMessages will be buffered and processed when voxel servers are known.
void queueParticleEditMessages(PACKET_TYPE type, int numberOfDetails, ParticleDetail* details); void queueParticleEditMessage(PACKET_TYPE type, ParticleID particleID, const ParticleProperties& properties);
// My server type is the particle server // My server type is the particle server
virtual unsigned char getMyNodeType() const { return NODE_TYPE_PARTICLE_SERVER; } virtual unsigned char getMyNodeType() const { return NODE_TYPE_PARTICLE_SERVER; }

View file

@ -15,7 +15,7 @@ ParticleTree::ParticleTree(bool shouldReaverage) : Octree(shouldReaverage) {
} }
ParticleTreeElement* ParticleTree::createNewElement(unsigned char * octalCode) const { ParticleTreeElement* ParticleTree::createNewElement(unsigned char * octalCode) const {
ParticleTreeElement* newElement = new ParticleTreeElement(octalCode); ParticleTreeElement* newElement = new ParticleTreeElement(octalCode);
return newElement; return newElement;
} }
@ -35,7 +35,7 @@ public:
const Particle& searchParticle; const Particle& searchParticle;
bool found; bool found;
}; };
bool ParticleTree::findAndUpdateOperation(OctreeElement* element, void* extraData) { bool ParticleTree::findAndUpdateOperation(OctreeElement* element, void* extraData) {
FindAndUpdateParticleArgs* args = static_cast<FindAndUpdateParticleArgs*>(extraData); FindAndUpdateParticleArgs* args = static_cast<FindAndUpdateParticleArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element); ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
@ -51,7 +51,7 @@ void ParticleTree::storeParticle(const Particle& particle, Node* senderNode) {
// First, look for the existing particle in the tree.. // First, look for the existing particle in the tree..
FindAndUpdateParticleArgs args = { particle, false }; FindAndUpdateParticleArgs args = { particle, false };
recurseTreeWithOperation(findAndUpdateOperation, &args); recurseTreeWithOperation(findAndUpdateOperation, &args);
// if we didn't find it in the tree, then store it... // if we didn't find it in the tree, then store it...
if (!args.found) { if (!args.found) {
glm::vec3 position = particle.getPosition(); glm::vec3 position = particle.getPosition();
@ -59,7 +59,7 @@ void ParticleTree::storeParticle(const Particle& particle, Node* senderNode) {
ParticleTreeElement* element = (ParticleTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); ParticleTreeElement* element = (ParticleTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size);
element->storeParticle(particle, senderNode); element->storeParticle(particle, senderNode);
} }
// what else do we need to do here to get reaveraging to work // what else do we need to do here to get reaveraging to work
_isDirty = true; _isDirty = true;
} }
@ -72,25 +72,25 @@ public:
const Particle* closestParticle; const Particle* closestParticle;
float closestParticleDistance; float closestParticleDistance;
}; };
bool ParticleTree::findNearPointOperation(OctreeElement* element, void* extraData) { bool ParticleTree::findNearPointOperation(OctreeElement* element, void* extraData) {
FindNearPointArgs* args = static_cast<FindNearPointArgs*>(extraData); FindNearPointArgs* args = static_cast<FindNearPointArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element); ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
glm::vec3 penetration; glm::vec3 penetration;
bool sphereIntersection = particleTreeElement->getAABox().findSpherePenetration(args->position, bool sphereIntersection = particleTreeElement->getAABox().findSpherePenetration(args->position,
args->targetRadius, penetration); args->targetRadius, penetration);
// If this particleTreeElement contains the point, then search it... // If this particleTreeElement contains the point, then search it...
if (sphereIntersection) { if (sphereIntersection) {
const Particle* thisClosestParticle = particleTreeElement->getClosestParticle(args->position); const Particle* thisClosestParticle = particleTreeElement->getClosestParticle(args->position);
// we may have gotten NULL back, meaning no particle was available // we may have gotten NULL back, meaning no particle was available
if (thisClosestParticle) { if (thisClosestParticle) {
glm::vec3 particlePosition = thisClosestParticle->getPosition(); glm::vec3 particlePosition = thisClosestParticle->getPosition();
float distanceFromPointToParticle = glm::distance(particlePosition, args->position); float distanceFromPointToParticle = glm::distance(particlePosition, args->position);
// If we're within our target radius // If we're within our target radius
if (distanceFromPointToParticle <= args->targetRadius) { if (distanceFromPointToParticle <= args->targetRadius) {
// we are closer than anything else we've found // we are closer than anything else we've found
@ -101,11 +101,11 @@ bool ParticleTree::findNearPointOperation(OctreeElement* element, void* extraDat
} }
} }
} }
// we should be able to optimize this... // we should be able to optimize this...
return true; // keep searching in case children have closer particles return true; // keep searching in case children have closer particles
} }
// if this element doesn't contain the point, then none of it's children can contain the point, so stop searching // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching
return false; return false;
} }
@ -124,9 +124,11 @@ public:
bool found; bool found;
const Particle* foundParticle; const Particle* foundParticle;
}; };
bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) { bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) {
qDebug() << "ParticleTree::findByIDOperation()....";
FindByIDArgs* args = static_cast<FindByIDArgs*>(extraData); FindByIDArgs* args = static_cast<FindByIDArgs*>(extraData);
ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element); ParticleTreeElement* particleTreeElement = static_cast<ParticleTreeElement*>(element);
@ -134,7 +136,7 @@ bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) {
if (args->found) { if (args->found) {
return false; return false;
} }
// as the tree element if it has this particle // as the tree element if it has this particle
const Particle* foundParticle = particleTreeElement->getParticleWithID(args->id); const Particle* foundParticle = particleTreeElement->getParticleWithID(args->id);
if (foundParticle) { if (foundParticle) {
@ -142,17 +144,23 @@ bool ParticleTree::findByIDOperation(OctreeElement* element, void* extraData) {
args->found = true; args->found = true;
return false; return false;
} }
// keep looking // keep looking
return true; return true;
} }
const Particle* ParticleTree::findParticleByID(uint32_t id) { const Particle* ParticleTree::findParticleByID(uint32_t id, bool alreadyLocked) {
FindByIDArgs args = { id, false, NULL }; FindByIDArgs args = { id, false, NULL };
lockForRead(); if (!alreadyLocked) {
qDebug() << "ParticleTree::findParticleByID().... about to call lockForRead()....";
lockForRead();
qDebug() << "ParticleTree::findParticleByID().... after call lockForRead()....";
}
recurseTreeWithOperation(findByIDOperation, &args); recurseTreeWithOperation(findByIDOperation, &args);
unlock(); if (!alreadyLocked) {
unlock();
}
return args.foundParticle; return args.foundParticle;
} }
@ -164,13 +172,21 @@ int ParticleTree::processEditPacketData(PACKET_TYPE packetType, unsigned char* p
// we handle these types of "edit" packets // we handle these types of "edit" packets
switch (packetType) { switch (packetType) {
case PACKET_TYPE_PARTICLE_ADD_OR_EDIT: { case PACKET_TYPE_PARTICLE_ADD_OR_EDIT: {
Particle newParticle = Particle::fromEditPacket(editData, maxLength, processedBytes);
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); storeParticle(newParticle, senderNode);
qDebug() << "PACKET_TYPE_PARTICLE_ADD_OR_EDIT... AFTER storeParticle() ";
if (newParticle.isNewlyCreated()) { if (newParticle.isNewlyCreated()) {
qDebug() << "PACKET_TYPE_PARTICLE_ADD_OR_EDIT... calling notifyNewlyCreatedParticle() ";
notifyNewlyCreatedParticle(newParticle, senderNode); notifyNewlyCreatedParticle(newParticle, senderNode);
qDebug() << "PACKET_TYPE_PARTICLE_ADD_OR_EDIT... AFTER notifyNewlyCreatedParticle() ";
} }
qDebug() << " DONE... PACKET_TYPE_PARTICLE_ADD_OR_EDIT... ";
} break; } break;
case PACKET_TYPE_PARTICLE_ERASE: { case PACKET_TYPE_PARTICLE_ERASE: {
processedBytes = 0; processedBytes = 0;
} break; } break;
@ -227,7 +243,7 @@ void ParticleTree::update() {
ParticleTreeUpdateArgs args = { }; ParticleTreeUpdateArgs args = { };
recurseTreeWithOperation(updateOperation, &args); recurseTreeWithOperation(updateOperation, &args);
// now add back any of the particles that moved elements.... // now add back any of the particles that moved elements....
int movingParticles = args._movingParticles.size(); int movingParticles = args._movingParticles.size();
for (int i = 0; i < movingParticles; i++) { for (int i = 0; i < movingParticles; i++) {
@ -235,12 +251,12 @@ void ParticleTree::update() {
// if the particle is still inside our total bounds, then re-add it // if the particle is still inside our total bounds, then re-add it
AABox treeBounds = getRoot()->getAABox(); AABox treeBounds = getRoot()->getAABox();
if (!shouldDie && treeBounds.contains(args._movingParticles[i].getPosition())) { if (!shouldDie && treeBounds.contains(args._movingParticles[i].getPosition())) {
storeParticle(args._movingParticles[i]); storeParticle(args._movingParticles[i]);
} }
} }
// prune the tree... // prune the tree...
recurseTreeWithOperation(pruneOperation, NULL); recurseTreeWithOperation(pruneOperation, NULL);
} }

View file

@ -21,14 +21,14 @@ class ParticleTree : public Octree {
Q_OBJECT Q_OBJECT
public: public:
ParticleTree(bool shouldReaverage = false); ParticleTree(bool shouldReaverage = false);
/// Implements our type specific root element factory /// Implements our type specific root element factory
virtual ParticleTreeElement* createNewElement(unsigned char * octalCode = NULL) const; virtual ParticleTreeElement* createNewElement(unsigned char * octalCode = NULL) const;
/// Type safe version of getRoot() /// Type safe version of getRoot()
ParticleTreeElement* getRoot() { return (ParticleTreeElement*)_rootNode; } ParticleTreeElement* getRoot() { return (ParticleTreeElement*)_rootNode; }
// These methods will allow the OctreeServer to send your tree inbound edit packets of your // These methods will allow the OctreeServer to send your tree inbound edit packets of your
// own definition. Implement these to allow your octree based server to support editing // own definition. Implement these to allow your octree based server to support editing
virtual bool getWantSVOfileVersions() const { return true; } virtual bool getWantSVOfileVersions() const { return true; }
@ -37,11 +37,11 @@ public:
virtual int processEditPacketData(PACKET_TYPE packetType, unsigned char* packetData, int packetLength, virtual int processEditPacketData(PACKET_TYPE packetType, unsigned char* packetData, int packetLength,
unsigned char* editData, int maxLength, Node* senderNode); unsigned char* editData, int maxLength, Node* senderNode);
virtual void update(); virtual void update();
void storeParticle(const Particle& particle, Node* senderNode = NULL); void storeParticle(const Particle& particle, Node* senderNode = NULL);
const Particle* findClosestParticle(glm::vec3 position, float targetRadius); const Particle* findClosestParticle(glm::vec3 position, float targetRadius);
const Particle* findParticleByID(uint32_t id); const Particle* findParticleByID(uint32_t id, bool alreadyLocked = false);
void addNewlyCreatedHook(NewlyCreatedParticleHook* hook); void addNewlyCreatedHook(NewlyCreatedParticleHook* hook);
void removeNewlyCreatedHook(NewlyCreatedParticleHook* hook); void removeNewlyCreatedHook(NewlyCreatedParticleHook* hook);
@ -53,9 +53,9 @@ private:
static bool findNearPointOperation(OctreeElement* element, void* extraData); static bool findNearPointOperation(OctreeElement* element, void* extraData);
static bool pruneOperation(OctreeElement* element, void* extraData); static bool pruneOperation(OctreeElement* element, void* extraData);
static bool findByIDOperation(OctreeElement* element, void* extraData); static bool findByIDOperation(OctreeElement* element, void* extraData);
void notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode); void notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode);
QReadWriteLock _newlyCreatedHooksLock; QReadWriteLock _newlyCreatedHooksLock;
std::vector<NewlyCreatedParticleHook*> _newlyCreatedHooks; std::vector<NewlyCreatedParticleHook*> _newlyCreatedHooks;
}; };

View file

@ -10,53 +10,12 @@
void ParticlesScriptingInterface::queueParticleMessage(PACKET_TYPE packetType, ParticleDetail& particleDetails) { void ParticlesScriptingInterface::queueParticleMessage(PACKET_TYPE packetType,
getParticlePacketSender()->queueParticleEditMessages(packetType, 1, &particleDetails); ParticleID particleID, const ParticleProperties& properties) {
}
ParticleID ParticlesScriptingInterface::addParticle(glm::vec3 position, float radius, qDebug() << "ParticlesScriptingInterface::queueParticleMessage()...";
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, float lifetime, bool inHand, QString script) {
// The application will keep track of creatorTokenID getParticlePacketSender()->queueParticleEditMessage(packetType, particleID, properties);
uint32_t creatorTokenID = Particle::getNextCreatorTokenID();
// setup a ParticleDetail struct with the data
uint64_t now = usecTimestampNow();
ParticleDetail addParticleDetail = { NEW_PARTICLE, now,
position, radius, {color.red, color.green, color.blue }, velocity,
gravity, damping, lifetime, inHand, script, creatorTokenID };
// queue the packet
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, addParticleDetail);
ParticleID id(NEW_PARTICLE, creatorTokenID, false);
return id;
}
void ParticlesScriptingInterface::editParticle(ParticleID particleID, glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, float lifetime, bool inHand, QString script) {
uint32_t actualID = particleID.id; // may not be valid... will check below..
// if we don't know the actual id, look it up
if (!particleID.isKnownID) {
actualID = Particle::getIDfromCreatorTokenID(particleID.creatorTokenID);
// if we couldn't fine it, then bail without changing anything...
if (actualID == UNKNOWN_PARTICLE_ID) {
return; // no changes...
}
}
// setup a ParticleDetail struct with the data
uint64_t now = usecTimestampNow();
ParticleDetail editParticleDetail = { actualID , now,
position, radius, {color.red, color.green, color.blue }, velocity,
gravity, damping, lifetime, inHand, script, UNKNOWN_TOKEN };
// queue the packet
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, editParticleDetail);
} }
ParticleID ParticlesScriptingInterface::addParticle(const ParticleProperties& properties) { ParticleID ParticlesScriptingInterface::addParticle(const ParticleProperties& properties) {
@ -64,53 +23,33 @@ ParticleID ParticlesScriptingInterface::addParticle(const ParticleProperties& pr
// The application will keep track of creatorTokenID // The application will keep track of creatorTokenID
uint32_t creatorTokenID = Particle::getNextCreatorTokenID(); uint32_t creatorTokenID = Particle::getNextCreatorTokenID();
// setup a ParticleDetail struct with the data ParticleID id(NEW_PARTICLE, creatorTokenID, false );
uint64_t now = usecTimestampNow();
xColor color = properties.getColor();
ParticleDetail addParticleDetail = { NEW_PARTICLE, now,
properties.getPosition(), properties.getRadius(),
{color.red, color.green, color.blue }, properties.getVelocity(),
properties.getGravity(), properties.getDamping(), properties.getLifetime(),
properties.getInHand(), properties.getScript(),
creatorTokenID };
// queue the packet // queue the packet
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, addParticleDetail); queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, id, properties);
ParticleID id(NEW_PARTICLE, creatorTokenID, false );
return id; return id;
} }
void ParticlesScriptingInterface::editParticle(ParticleID particleID, const ParticleProperties& properties) { void ParticlesScriptingInterface::editParticle(ParticleID particleID, const ParticleProperties& properties) {
uint32_t actualID = particleID.id; // may not be valid... will check below.. qDebug() << "ParticlesScriptingInterface::editParticle() id.id=" << particleID.id << " id.creatorTokenID=" << particleID.creatorTokenID;
// if we don't know the actual id, look it up uint32_t actualID = particleID.id;
if (!particleID.isKnownID) { if (!particleID.isKnownID) {
actualID = Particle::getIDfromCreatorTokenID(particleID.creatorTokenID); actualID = Particle::getIDfromCreatorTokenID(particleID.creatorTokenID);
qDebug() << "ParticlesScriptingInterface::editParticle()... actualID: " << actualID; // hmmm... we kind of want to bail if someone attempts to edit an unknown
// if we couldn't fine it, then bail without changing anything...
if (actualID == UNKNOWN_PARTICLE_ID) { if (actualID == UNKNOWN_PARTICLE_ID) {
return; // no changes... return; // bailing early
} }
qDebug() << "ParticlesScriptingInterface::editParticle() actualID=" << actualID;
} }
// setup a ParticleDetail struct with the data particleID.id = actualID;
uint64_t now = usecTimestampNow(); particleID.isKnownID = true;
xColor color = properties.getColor(); queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, particleID, properties);
ParticleDetail editParticleDetail = { actualID, now,
properties.getPosition(), properties.getRadius(),
{color.red, color.green, color.blue }, properties.getVelocity(),
properties.getGravity(), properties.getDamping(), properties.getLifetime(),
properties.getInHand(), properties.getScript(),
UNKNOWN_TOKEN };
// queue the packet
queueParticleMessage(PACKET_TYPE_PARTICLE_ADD_OR_EDIT, editParticleDetail);
} }

View file

@ -24,19 +24,12 @@ public:
virtual OctreeEditPacketSender* createPacketSender() { return new ParticleEditPacketSender(); } virtual OctreeEditPacketSender* createPacketSender() { return new ParticleEditPacketSender(); }
public slots: public slots:
ParticleID addParticle(glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, float lifetime, bool inHand, QString script);
void editParticle(ParticleID particleID, glm::vec3 position, float radius,
xColor color, glm::vec3 velocity, glm::vec3 gravity, float damping, float lifetime, bool inHand, QString script);
ParticleID addParticle(const ParticleProperties& properties); ParticleID addParticle(const ParticleProperties& properties);
void editParticle(ParticleID particleID, const ParticleProperties& properties); void editParticle(ParticleID particleID, const ParticleProperties& properties);
void deleteParticle(ParticleID particleID); void deleteParticle(ParticleID particleID);
private: private:
void queueParticleMessage(PACKET_TYPE packetType, ParticleDetail& particleDetails); void queueParticleMessage(PACKET_TYPE packetType, ParticleID particleID, const ParticleProperties& properties);
uint32_t _nextCreatorTokenID; uint32_t _nextCreatorTokenID;
}; };