diff --git a/README.md b/README.md index a42bbd1626..5678312f6f 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,18 @@ send your resume to hiring@highfidelity.io Building Interface & other High Fidelity Components ========= -Interface is our OS X and Linux build-able client for accessing our virtual +Interface is our Windows, OS X, and Linux build-able client for accessing our virtual world. +For detailed notes on building for Windows, please refer to the following wiki page: +https://github.com/highfidelity/hifi/wiki/Building-on-Windows + +For detailed notes on building for Ubuntu, please refer to the following wiki page: +https://github.com/highfidelity/hifi/wiki/Building-on-Ubuntu-13.04 + +Building on Mac OS X and Linux: +-------------------------------- + CMake ----- Hifi uses CMake to generate build files and project files @@ -45,9 +54,9 @@ If Cmake throws you an error related to Qt5 it likely cannot find your Qt5 cmake You can solve this by setting an environment variable, QT_CMAKE_PREFIX_PATH, to the location of the folder distributed with Qt5 that contains them. -For example, a Qt5 5.1.1 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). +For example, a Qt5 5.2.0 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment). - export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.1.1/clang_64/lib/cmake/ + export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.2.0/clang_64/lib/cmake/ The path it needs to be set to will depend on where and how Qt5 was installed. @@ -64,7 +73,7 @@ components located in the build/target_name/Debug directories. Other dependencies & information ---- -In addition to CMake, Qt 5.1 is required to build all components. +In addition to CMake, Qt 5.2 is required to build all components. What can I build on? We have successfully built on OS X 10.8, Ubuntu and a few other modern Linux diff --git a/animation-server/src/AnimationServer.cpp b/animation-server/src/AnimationServer.cpp index 24ea7ede9c..3151445794 100644 --- a/animation-server/src/AnimationServer.cpp +++ b/animation-server/src/AnimationServer.cpp @@ -830,12 +830,16 @@ void AnimationServer::readPendingDatagrams() { receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize()); nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(), nodeSockAddr.getAddressPointer(), nodeSockAddr.getPortPointer()); - if (packetVersionMatch(receivedPacket)) { + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { if (packetTypeForPacket(receivedPacket) == PacketTypeJurisdiction) { int headerBytes = numBytesForPacketHeader(receivedPacket); // PacketType_JURISDICTION, first byte is the node type... if (receivedPacket.data()[headerBytes] == NodeType::VoxelServer && ::jurisdictionListener) { - ::jurisdictionListener->queueReceivedPacket(nodeSockAddr, receivedPacket); + + SharedNodePointer matchedNode = NodeList::getInstance()->sendingNodeForPacket(receivedPacket); + if (matchedNode) { + ::jurisdictionListener->queueReceivedPacket(matchedNode, receivedPacket); + } } } NodeList::getInstance()->processNodeData(nodeSockAddr, receivedPacket); diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 51a91621da..60b9d75338 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -30,9 +30,6 @@ link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(metavoxels ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(particle-server ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(voxel-server ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(script-engine ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR}) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 2694bf83e2..cc0ddd59f2 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -23,33 +23,51 @@ #include "Agent.h" Agent::Agent(const QByteArray& packet) : - ThreadedAssignment(packet) + ThreadedAssignment(packet), + _voxelEditSender(), + _particleEditSender() { + _scriptEngine.getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender); + _scriptEngine.getParticlesScriptingInterface()->setPacketSender(&_particleEditSender); } -void Agent::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) { - PacketType datagramPacketType = packetTypeForPacket(dataByteArray); - if (datagramPacketType == PacketTypeJurisdiction) { - int headerBytes = numBytesForPacketHeader(dataByteArray); - // PacketType_JURISDICTION, first byte is the node type... - switch (dataByteArray[headerBytes]) { - case NodeType::VoxelServer: - _scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr, - dataByteArray); - break; - case NodeType::ParticleServer: - _scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr, - dataByteArray); - break; +void Agent::readPendingDatagrams() { + QByteArray receivedPacket; + HifiSockAddr senderSockAddr; + NodeList* nodeList = NodeList::getInstance(); + + while (readAvailableDatagram(receivedPacket, senderSockAddr)) { + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + PacketType datagramPacketType = packetTypeForPacket(receivedPacket); + if (datagramPacketType == PacketTypeJurisdiction) { + int headerBytes = numBytesForPacketHeader(receivedPacket); + + SharedNodePointer matchedNode = nodeList->sendingNodeForPacket(receivedPacket); + + if (matchedNode) { + // PacketType_JURISDICTION, first byte is the node type... + switch (receivedPacket[headerBytes]) { + case NodeType::VoxelServer: + _scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(matchedNode, + receivedPacket); + break; + case NodeType::ParticleServer: + _scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(matchedNode, + receivedPacket); + break; + } + } + + } else if (datagramPacketType == PacketTypeParticleAddResponse) { + // this will keep creatorTokenIDs to IDs mapped correctly + Particle::handleAddParticleResponse(receivedPacket); + + // also give our local particle tree a chance to remap any internal locally created particles + _particleTree.handleAddParticleResponse(receivedPacket); + } else { + NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); + } } - } else if (datagramPacketType == PacketTypeParticleAddResponse) { - // this will keep creatorTokenIDs to IDs mapped correctly - Particle::handleAddParticleResponse(dataByteArray); - - // also give our local particle tree a chance to remap any internal locally created particles - _particleTree.handleAddParticleResponse(dataByteArray); - } else { - NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray); } } @@ -89,10 +107,6 @@ void Agent::run() { connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); - QTimer* pingNodesTimer = new QTimer(this); - connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes())); - pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000); - // tell our script engine about our local particle tree _scriptEngine.getParticlesScriptingInterface()->setParticleTree(&_particleTree); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 8b2038a8b0..bc6b4f65fe 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -15,9 +15,12 @@ #include #include +#include #include #include #include +#include + class Agent : public ThreadedAssignment { Q_OBJECT @@ -32,13 +35,15 @@ public: public slots: void run(); - void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr); + void readPendingDatagrams(); signals: void willSendAudioDataCallback(); void willSendVisualDataCallback(); private: ScriptEngine _scriptEngine; ParticleTree _particleTree; + VoxelEditPacketSender _voxelEditSender; + ParticleEditPacketSender _particleEditSender; }; #endif /* defined(__hifi__Agent__) */ diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index b2a5555e36..978882f358 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -90,8 +90,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : timer->start(ASSIGNMENT_REQUEST_INTERVAL_MSECS); // connect our readPendingDatagrams method to the readyRead() signal of the socket - connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams, - Qt::QueuedConnection); + connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams); } void AssignmentClient::sendAssignmentRequest() { @@ -111,50 +110,45 @@ void AssignmentClient::readPendingDatagrams() { nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(), senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); - if (packetVersionMatch(receivedPacket)) { - if (_currentAssignment) { - // have the threaded current assignment handle this datagram - QMetaObject::invokeMethod(_currentAssignment, "processDatagram", Qt::QueuedConnection, - Q_ARG(QByteArray, receivedPacket), - Q_ARG(HifiSockAddr, senderSockAddr)); - } else if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) { + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) { + // construct the deployed assignment from the packet data + _currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket); if (_currentAssignment) { - qDebug() << "Dropping received assignment since we are currently running one."; - } else { - // construct the deployed assignment from the packet data - _currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket); + qDebug() << "Received an assignment -" << *_currentAssignment; - if (_currentAssignment) { - qDebug() << "Received an assignment -" << *_currentAssignment; - - // switch our nodelist domain IP and port to whoever sent us the assignment - - nodeList->setDomainSockAddr(senderSockAddr); - nodeList->setOwnerUUID(_currentAssignment->getUUID()); - - qDebug() << "Destination IP for assignment is" << nodeList->getDomainIP().toString(); - - // start the deployed assignment - QThread* workerThread = new QThread(this); - - connect(workerThread, SIGNAL(started()), _currentAssignment, SLOT(run())); - - connect(_currentAssignment, SIGNAL(finished()), this, SLOT(assignmentCompleted())); - connect(_currentAssignment, SIGNAL(finished()), workerThread, SLOT(quit())); - connect(_currentAssignment, SIGNAL(finished()), _currentAssignment, SLOT(deleteLater())); - connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); - - _currentAssignment->moveToThread(workerThread); - - // move the NodeList to the thread used for the _current assignment - nodeList->moveToThread(workerThread); - - // Starts an event loop, and emits workerThread->started() - workerThread->start(); - } else { - qDebug() << "Received an assignment that could not be unpacked. Re-requesting."; - } + // switch our nodelist domain IP and port to whoever sent us the assignment + + nodeList->setDomainSockAddr(senderSockAddr); + nodeList->setSessionUUID(_currentAssignment->getUUID()); + + qDebug() << "Destination IP for assignment is" << nodeList->getDomainIP().toString(); + + // start the deployed assignment + QThread* workerThread = new QThread(this); + + connect(workerThread, SIGNAL(started()), _currentAssignment, SLOT(run())); + + connect(_currentAssignment, SIGNAL(finished()), this, SLOT(assignmentCompleted())); + connect(_currentAssignment, SIGNAL(finished()), workerThread, SLOT(quit())); + connect(_currentAssignment, SIGNAL(finished()), _currentAssignment, SLOT(deleteLater())); + connect(workerThread, SIGNAL(finished()), workerThread, SLOT(deleteLater())); + + _currentAssignment->moveToThread(workerThread); + + // move the NodeList to the thread used for the _current assignment + nodeList->moveToThread(workerThread); + + // let the assignment handle the incoming datagrams for its duration + disconnect(&nodeList->getNodeSocket(), 0, this, 0); + connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, _currentAssignment, + &ThreadedAssignment::readPendingDatagrams); + + // Starts an event loop, and emits workerThread->started() + workerThread->start(); + } else { + qDebug() << "Received an assignment that could not be unpacked. Re-requesting."; } } else { // have the NodeList attempt to handle it @@ -170,10 +164,14 @@ void AssignmentClient::assignmentCompleted() { qDebug("Assignment finished or never started - waiting for new assignment."); - _currentAssignment = NULL; - NodeList* nodeList = NodeList::getInstance(); + // have us handle incoming NodeList datagrams again + disconnect(&nodeList->getNodeSocket(), 0, _currentAssignment, 0); + connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams); + + _currentAssignment = NULL; + // reset our NodeList by switching back to unassigned and clearing the list nodeList->setOwnerType(NodeType::Unassigned); nodeList->reset(); diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp index 5bf0417f22..cde5666ab9 100644 --- a/assignment-client/src/AssignmentFactory.cpp +++ b/assignment-client/src/AssignmentFactory.cpp @@ -8,15 +8,13 @@ #include -#include - -#include - #include "Agent.h" #include "AssignmentFactory.h" #include "audio/AudioMixer.h" #include "avatars/AvatarMixer.h" #include "metavoxels/MetavoxelServer.h" +#include "particles/ParticleServer.h" +#include "voxels/VoxelServer.h" ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet) { QDataStream packetStream(packet); diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 90e2810f27..30359f85c4 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -207,31 +207,25 @@ void AudioMixer::prepareMixForListeningNode(Node* node) { } -void AudioMixer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) { - // pull any new audio data from nodes off of the network stack - PacketType mixerPacketType = packetTypeForPacket(dataByteArray); - if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho - || mixerPacketType == PacketTypeMicrophoneAudioWithEcho - || mixerPacketType == PacketTypeInjectAudio) { - QUuid nodeUUID; - deconstructPacketHeader(dataByteArray, nodeUUID); - - NodeList* nodeList = NodeList::getInstance(); - - SharedNodePointer matchingNode = nodeList->nodeWithUUID(nodeUUID); - - if (matchingNode) { - nodeList->updateNodeWithData(matchingNode.data(), senderSockAddr, dataByteArray); - - if (!matchingNode->getActiveSocket()) { - // we don't have an active socket for this node, but they're talking to us - // this means they've heard from us and can reply, let's assume public is active - matchingNode->activatePublicSocket(); +void AudioMixer::readPendingDatagrams() { + QByteArray receivedPacket; + HifiSockAddr senderSockAddr; + NodeList* nodeList = NodeList::getInstance(); + + while (readAvailableDatagram(receivedPacket, senderSockAddr)) { + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + // pull any new audio data from nodes off of the network stack + PacketType mixerPacketType = packetTypeForPacket(receivedPacket); + if (mixerPacketType == PacketTypeMicrophoneAudioNoEcho + || mixerPacketType == PacketTypeMicrophoneAudioWithEcho + || mixerPacketType == PacketTypeInjectAudio) { + + nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket); + } else { + // let processNodeData handle it. + nodeList->processNodeData(senderSockAddr, receivedPacket); } } - } else { - // let processNodeData handle it. - NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray); } } @@ -279,9 +273,7 @@ void AudioMixer::run() { prepareMixForListeningNode(node.data()); memcpy(clientPacket + numBytesPacketHeader, _clientSamples, sizeof(_clientSamples)); - nodeList->getNodeSocket().writeDatagram((char*) clientPacket, sizeof(clientPacket), - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + nodeList->writeDatagram((char*) clientPacket, sizeof(clientPacket), node); } } diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 2d42a6b629..e120d19d32 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -25,7 +25,7 @@ public slots: /// threaded run of assignment void run(); - void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr); + void readPendingDatagrams(); private: /// adds one buffer to the mix for a listening node void addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuffer* bufferToAdd, diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 358e507fc4..c7989bc9e9 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -11,6 +11,7 @@ // nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms. #include +#include #include #include @@ -19,7 +20,7 @@ #include #include -#include "AvatarData.h" +#include "AvatarMixerClientData.h" #include "AvatarMixer.h" @@ -36,7 +37,7 @@ AvatarMixer::AvatarMixer(const QByteArray& packet) : void attachAvatarDataToNode(Node* newNode) { if (newNode->getLinkedData() == NULL) { - newNode->setLinkedData(new AvatarData()); + newNode->setLinkedData(new AvatarMixerClientData()); } } @@ -52,8 +53,6 @@ void broadcastAvatarData() { int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData); - int packetsSent = 0; - NodeList* nodeList = NodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { @@ -70,15 +69,11 @@ void broadcastAvatarData() { QByteArray avatarByteArray; avatarByteArray.append(otherNode->getUUID().toRfc4122()); - AvatarData* nodeData = (AvatarData*) otherNode->getLinkedData(); + AvatarMixerClientData* nodeData = reinterpret_cast(otherNode->getLinkedData()); avatarByteArray.append(nodeData->toByteArray()); if (avatarByteArray.size() + mixedAvatarByteArray.size() > MAX_PACKET_SIZE) { - packetsSent++; - //printf("packetsSent=%d packetLength=%d\n", packetsSent, packetLength); - nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray, - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + nodeList->writeDatagram(mixedAvatarByteArray, node); // reset the packet mixedAvatarByteArray.resize(numPacketHeaderBytes); @@ -89,15 +84,45 @@ void broadcastAvatarData() { } } - packetsSent++; - //printf("packetsSent=%d packetLength=%d\n", packetsSent, packetLength); - nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray, - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + nodeList->writeDatagram(mixedAvatarByteArray, node); } } } +void broadcastIdentityPacket() { + + NodeList* nodeList = NodeList::getInstance(); + + QByteArray avatarIdentityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity); + int numPacketHeaderBytes = avatarIdentityPacket.size(); + + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + if (node->getLinkedData() && node->getType() == NodeType::Agent) { + + AvatarMixerClientData* nodeData = reinterpret_cast(node->getLinkedData()); + QByteArray individualData = nodeData->identityByteArray(); + individualData.replace(0, NUM_BYTES_RFC4122_UUID, node->getUUID().toRfc4122()); + + if (avatarIdentityPacket.size() + individualData.size() > MAX_PACKET_SIZE) { + // we've hit MTU, send out the current packet before appending + nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent); + avatarIdentityPacket.resize(numPacketHeaderBytes); + } + + // append the individual data to the current the avatarIdentityPacket + avatarIdentityPacket.append(individualData); + + // re-set the bool in AvatarMixerClientData so a change between key frames gets sent out + nodeData->setHasSentIdentityBetweenKeyFrames(false); + } + } + + // send out the final packet + if (avatarIdentityPacket.size() > numPacketHeaderBytes) { + nodeList->broadcastToNodes(avatarIdentityPacket, NodeSet() << NodeType::Agent); + } +} + void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { if (killedNode->getType() == NodeType::Agent && killedNode->getLinkedData()) { @@ -111,37 +136,56 @@ void AvatarMixer::nodeKilled(SharedNodePointer killedNode) { } } -void AvatarMixer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) { +void AvatarMixer::readPendingDatagrams() { + QByteArray receivedPacket; + HifiSockAddr senderSockAddr; NodeList* nodeList = NodeList::getInstance(); - switch (packetTypeForPacket(dataByteArray)) { - case PacketTypeAvatarData: { - QUuid nodeUUID; - deconstructPacketHeader(dataByteArray, nodeUUID); - - // add or update the node in our list - SharedNodePointer avatarNode = nodeList->nodeWithUUID(nodeUUID); - - if (avatarNode) { - // parse positional data from an node - nodeList->updateNodeWithData(avatarNode.data(), senderSockAddr, dataByteArray); - + while (readAvailableDatagram(receivedPacket, senderSockAddr)) { + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + switch (packetTypeForPacket(receivedPacket)) { + case PacketTypeAvatarData: { + nodeList->findNodeAndUpdateWithDataFromPacket(receivedPacket); + break; + } + case PacketTypeAvatarIdentity: { + + // check if we have a matching node in our list + SharedNodePointer avatarNode = nodeList->sendingNodeForPacket(receivedPacket); + + if (avatarNode && avatarNode->getLinkedData()) { + AvatarMixerClientData* nodeData = reinterpret_cast(avatarNode->getLinkedData()); + if (nodeData->hasIdentityChangedAfterParsing(receivedPacket) + && !nodeData->hasSentIdentityBetweenKeyFrames()) { + // this avatar changed their identity in some way and we haven't sent a packet in this keyframe + QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity); + + QByteArray individualByteArray = nodeData->identityByteArray(); + individualByteArray.replace(0, NUM_BYTES_RFC4122_UUID, avatarNode->getUUID().toRfc4122()); + + identityPacket.append(individualByteArray); + + nodeData->setHasSentIdentityBetweenKeyFrames(true); + nodeList->broadcastToNodes(identityPacket, NodeSet() << NodeType::Agent); + } + } + } + case PacketTypeKillAvatar: { + nodeList->processKillNode(receivedPacket); + break; + } + default: + // hand this off to the NodeList + nodeList->processNodeData(senderSockAddr, receivedPacket); + break; } - break; } - case PacketTypeKillAvatar: { - nodeList->processKillNode(dataByteArray); - break; - } - default: - // hand this off to the NodeList - nodeList->processNodeData(senderSockAddr, dataByteArray); - break; } - } +const qint64 AVATAR_IDENTITY_KEYFRAME_MSECS = 5000; + void AvatarMixer::run() { commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer); @@ -155,6 +199,9 @@ void AvatarMixer::run() { gettimeofday(&startTime, NULL); + QElapsedTimer identityTimer; + identityTimer.start(); + while (!_isFinished) { QCoreApplication::processEvents(); @@ -165,6 +212,14 @@ void AvatarMixer::run() { broadcastAvatarData(); + if (identityTimer.elapsed() >= AVATAR_IDENTITY_KEYFRAME_MSECS) { + // it's time to broadcast the keyframe identity packets + broadcastIdentityPacket(); + + // restart the timer so we do it again in AVATAR_IDENTITY_KEYFRAME_MSECS + identityTimer.restart(); + } + int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * AVATAR_DATA_SEND_INTERVAL_USECS) - usecTimestampNow(); if (usecToSleep > 0) { diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 92ab1a5c53..acc5a178aa 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -20,9 +20,10 @@ public slots: /// runs the avatar mixer void run(); + void nodeAdded(SharedNodePointer nodeAdded); void nodeKilled(SharedNodePointer killedNode); - void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr); + void readPendingDatagrams(); }; #endif /* defined(__hifi__AvatarMixer__) */ diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp new file mode 100644 index 0000000000..0261613532 --- /dev/null +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -0,0 +1,15 @@ +// +// AvatarMixerClientData.cpp +// hifi +// +// Created by Stephen Birarda on 2/4/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#include "AvatarMixerClientData.h" + +AvatarMixerClientData::AvatarMixerClientData() : + _hasSentIdentityBetweenKeyFrames(false) +{ + +} diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h new file mode 100644 index 0000000000..8e046d9212 --- /dev/null +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -0,0 +1,29 @@ +// +// AvatarMixerClientData.h +// hifi +// +// Created by Stephen Birarda on 2/4/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__AvatarMixerClientData__ +#define __hifi__AvatarMixerClientData__ + +#include + +#include + +class AvatarMixerClientData : public AvatarData { + Q_OBJECT +public: + AvatarMixerClientData(); + + bool hasSentIdentityBetweenKeyFrames() const { return _hasSentIdentityBetweenKeyFrames; } + void setHasSentIdentityBetweenKeyFrames(bool hasSentIdentityBetweenKeyFrames) + { _hasSentIdentityBetweenKeyFrames = hasSentIdentityBetweenKeyFrames; } +private: + + bool _hasSentIdentityBetweenKeyFrames; +}; + +#endif /* defined(__hifi__AvatarMixerClientData__) */ diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 3cd81d8ac7..fa934142d3 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -41,15 +41,27 @@ void MetavoxelServer::run() { _sendTimer.start(SEND_INTERVAL); } -void MetavoxelServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) { - switch (dataByteArray.at(0)) { - case PacketTypeMetavoxelData: - processData(dataByteArray, senderSockAddr); - break; - - default: - NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray); - break; +void MetavoxelServer::readPendingDatagrams() { + QByteArray receivedPacket; + HifiSockAddr senderSockAddr; + + NodeList* nodeList = NodeList::getInstance(); + + while (readAvailableDatagram(receivedPacket, senderSockAddr)) { + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + switch (packetTypeForPacket(receivedPacket)) { + case PacketTypeMetavoxelData: { + SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); + if (matchingNode) { + processData(receivedPacket, matchingNode); + } + break; + } + default: + NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); + break; + } + } } } @@ -67,10 +79,10 @@ void MetavoxelServer::sendDeltas() { _sendTimer.start(qMax(0, 2 * SEND_INTERVAL - elapsed)); } -void MetavoxelServer::processData(const QByteArray& data, const HifiSockAddr& sender) { +void MetavoxelServer::processData(const QByteArray& data, const SharedNodePointer& sendingNode) { // read the session id int headerPlusIDSize; - QUuid sessionID = readSessionID(data, sender, headerPlusIDSize); + QUuid sessionID = readSessionID(data, sendingNode, headerPlusIDSize); if (sessionID.isNull()) { return; } @@ -78,18 +90,19 @@ void MetavoxelServer::processData(const QByteArray& data, const HifiSockAddr& se // forward to session, creating if necessary MetavoxelSession*& session = _sessions[sessionID]; if (!session) { - session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize), sender); + session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize), + sendingNode); } - session->receivedData(data, sender); + session->receivedData(data, sendingNode); } MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, - const QByteArray& datagramHeader, const HifiSockAddr& sender) : + const QByteArray& datagramHeader, const SharedNodePointer& sendingNode) : QObject(server), _server(server), _sessionId(sessionId), _sequencer(datagramHeader), - _sender(sender) { + _sendingNode(sendingNode) { const int TIMEOUT_INTERVAL = 30 * 1000; _timeoutTimer.setInterval(TIMEOUT_INTERVAL); @@ -105,15 +118,15 @@ MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& session SendRecord record = { 0 }; _sendRecords.append(record); - qDebug() << "Opened session [sessionId=" << _sessionId << ", sender=" << _sender << "]"; + qDebug() << "Opened session [sessionId=" << _sessionId << ", sendingNode=" << sendingNode << "]"; } -void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr& sender) { +void MetavoxelSession::receivedData(const QByteArray& data, const SharedNodePointer& sendingNode) { // reset the timeout timer _timeoutTimer.start(); // save the most recent sender - _sender = sender; + _sendingNode = sendingNode; // process through sequencer _sequencer.receivedDatagram(data); @@ -131,12 +144,12 @@ void MetavoxelSession::sendDelta() { } void MetavoxelSession::timedOut() { - qDebug() << "Session timed out [sessionId=" << _sessionId << ", sender=" << _sender << "]"; + qDebug() << "Session timed out [sessionId=" << _sessionId << ", sendingNode=" << _sendingNode << "]"; _server->removeSession(_sessionId); } void MetavoxelSession::sendData(const QByteArray& data) { - NodeList::getInstance()->getNodeSocket().writeDatagram(data, _sender.getAddress(), _sender.getPort()); + NodeList::getInstance()->writeDatagram(data, _sendingNode); } void MetavoxelSession::readPacket(Bitstream& in) { @@ -152,7 +165,7 @@ void MetavoxelSession::clearSendRecordsBefore(int index) { void MetavoxelSession::handleMessage(const QVariant& message) { int userType = message.userType(); if (userType == CloseSessionMessage::Type) { - qDebug() << "Session closed [sessionId=" << _sessionId << ", sender=" << _sender << "]"; + qDebug() << "Session closed [sessionId=" << _sessionId << ", sendingNode=" << _sendingNode << "]"; _server->removeSession(_sessionId); } else if (userType == ClientStateMessage::Type) { diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h index f106bd3494..60fdeca5a5 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.h +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -39,7 +39,7 @@ public: virtual void run(); - virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr); + virtual void readPendingDatagrams(); private slots: @@ -47,7 +47,7 @@ private slots: private: - void processData(const QByteArray& data, const HifiSockAddr& sender); + void processData(const QByteArray& data, const SharedNodePointer& sendingNode); QTimer _sendTimer; qint64 _lastSend; @@ -64,9 +64,9 @@ class MetavoxelSession : public QObject { public: MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, - const QByteArray& datagramHeader, const HifiSockAddr& sender); + const QByteArray& datagramHeader, const SharedNodePointer& sendingNode); - void receivedData(const QByteArray& data, const HifiSockAddr& sender); + void receivedData(const QByteArray& data, const SharedNodePointer& sendingNode); void sendDelta(); @@ -96,7 +96,7 @@ private: QTimer _timeoutTimer; DatagramSequencer _sequencer; - HifiSockAddr _sender; + SharedNodePointer _sendingNode; glm::vec3 _position; diff --git a/libraries/octree-server/src/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp similarity index 93% rename from libraries/octree-server/src/OctreeInboundPacketProcessor.cpp rename to assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index f9523851b4..5e8de3a1b3 100644 --- a/libraries/octree-server/src/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -39,7 +39,7 @@ void OctreeInboundPacketProcessor::resetStats() { } -void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { +void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) { bool debugProcessPacket = _myServer->wantsVerboseDebug(); @@ -55,8 +55,6 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE",debugProcessPacket); _receivedPacketCount++; - - SharedNodePointer senderNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr); const unsigned char* packetData = reinterpret_cast(packet.data()); @@ -90,7 +88,7 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA int editDataBytesRead = _myServer->getOctree()->processEditPacketData(packetType, reinterpret_cast(packet.data()), packet.size(), - editData, maxSize, senderNode.data()); + editData, maxSize, sendingNode); _myServer->getOctree()->unlock(); quint64 endProcess = usecTimestampNow(); @@ -113,9 +111,9 @@ void OctreeInboundPacketProcessor::processPacket(const HifiSockAddr& senderSockA // Make sure our Node and NodeList knows we've heard from this node. QUuid& nodeUUID = DEFAULT_NODE_ID_REF; - if (senderNode) { - senderNode->setLastHeardMicrostamp(usecTimestampNow()); - nodeUUID = senderNode->getUUID(); + if (sendingNode) { + sendingNode->setLastHeardMicrostamp(usecTimestampNow()); + nodeUUID = sendingNode->getUUID(); if (debugProcessPacket) { qDebug() << "sender has uuid=" << nodeUUID; } diff --git a/libraries/octree-server/src/OctreeInboundPacketProcessor.h b/assignment-client/src/octree/OctreeInboundPacketProcessor.h similarity index 97% rename from libraries/octree-server/src/OctreeInboundPacketProcessor.h rename to assignment-client/src/octree/OctreeInboundPacketProcessor.h index 3d9b12f484..237a22d954 100644 --- a/libraries/octree-server/src/OctreeInboundPacketProcessor.h +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.h @@ -63,7 +63,7 @@ public: NodeToSenderStatsMap& getSingleSenderStats() { return _singleSenderStats; } protected: - virtual void processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet); + virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet); private: void trackInboundPackets(const QUuid& nodeUUID, int sequence, quint64 transitTime, diff --git a/libraries/octree-server/src/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp similarity index 100% rename from libraries/octree-server/src/OctreeQueryNode.cpp rename to assignment-client/src/octree/OctreeQueryNode.cpp diff --git a/libraries/octree-server/src/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h similarity index 100% rename from libraries/octree-server/src/OctreeQueryNode.h rename to assignment-client/src/octree/OctreeQueryNode.h diff --git a/libraries/octree-server/src/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp similarity index 94% rename from libraries/octree-server/src/OctreeSendThread.cpp rename to assignment-client/src/octree/OctreeSendThread.cpp index 26df0ffb0b..121e80b808 100644 --- a/libraries/octree-server/src/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -48,7 +48,7 @@ bool OctreeSendThread::process() { if (_myServer->wantsDebugSending() && _myServer->wantsVerboseDebug()) { printf("nodeData->updateCurrentViewFrustum() changed=%s\n", debug::valueOf(viewFrustumChanged)); } - packetsSent = packetDistributor(node.data(), nodeData, viewFrustumChanged); + packetsSent = packetDistributor(node, nodeData, viewFrustumChanged); } node->getMutex().unlock(); // we're done with this node for now. @@ -86,12 +86,19 @@ quint64 OctreeSendThread::_totalBytes = 0; quint64 OctreeSendThread::_totalWastedBytes = 0; quint64 OctreeSendThread::_totalPackets = 0; -int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { +int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { bool debug = _myServer->wantsDebugSending(); quint64 now = usecTimestampNow(); bool packetSent = false; // did we send a packet? int packetsSent = 0; + + // double check that the node has an active socket, otherwise, don't send... + const HifiSockAddr* nodeAddress = node->getActiveSocket(); + if (!nodeAddress) { + return packetsSent; // without sending... + } + // Here's where we check to see if this packet is a duplicate of the last packet. If it is, we will silently // obscure the packet and not send it. This allows the callers and upper level logic to not need to know about // this rate control savings. @@ -135,15 +142,11 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in } // actually send it - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) statsMessage, statsMessageLength, - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, SharedNodePointer(node)); packetSent = true; } else { // not enough room in the packet, send two packets - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) statsMessage, statsMessageLength, - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, SharedNodePointer(node)); // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since // there was nothing else to send. @@ -161,9 +164,8 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in truePacketsSent++; packetsSent++; - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), + SharedNodePointer(node)); packetSent = true; @@ -182,9 +184,8 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in // If there's actually a packet waiting, then send it. if (nodeData->isPacketWaiting()) { // just send the voxel packet - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), + SharedNodePointer(node)); packetSent = true; int thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength(); @@ -211,7 +212,7 @@ int OctreeSendThread::handlePacketSend(Node* node, OctreeQueryNode* nodeData, in } /// Version of voxel distributor that sends the deepest LOD level at once -int OctreeSendThread::packetDistributor(Node* node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { +int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { bool forceDebugging = false; int truePacketsSent = 0; diff --git a/libraries/octree-server/src/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h similarity index 81% rename from libraries/octree-server/src/OctreeSendThread.h rename to assignment-client/src/octree/OctreeSendThread.h index 6f60f0eb41..ab7a7231ab 100644 --- a/libraries/octree-server/src/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -36,8 +36,8 @@ private: QUuid _nodeUUID; OctreeServer* _myServer; - int handlePacketSend(Node* node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent); - int packetDistributor(Node* node, OctreeQueryNode* nodeData, bool viewFrustumChanged); + int handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent); + int packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged); OctreePacketData _packetData; }; diff --git a/libraries/octree-server/src/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp similarity index 93% rename from libraries/octree-server/src/OctreeServer.cpp rename to assignment-client/src/octree/OctreeServer.cpp index 6e58a0a987..1fb858c884 100644 --- a/libraries/octree-server/src/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -458,43 +458,43 @@ void OctreeServer::parsePayload() { } } -void OctreeServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) { +void OctreeServer::readPendingDatagrams() { + QByteArray receivedPacket; + HifiSockAddr senderSockAddr; + NodeList* nodeList = NodeList::getInstance(); - - PacketType packetType = packetTypeForPacket(dataByteArray); - - if (packetType == getMyQueryMessageType()) { - bool debug = false; - if (debug) { - qDebug() << "Got PacketType_VOXEL_QUERY at" << usecTimestampNow(); - } - - // If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we - // need to make sure we have it in our nodeList. - QUuid nodeUUID; - deconstructPacketHeader(dataByteArray, nodeUUID); - - SharedNodePointer node = nodeList->nodeWithUUID(nodeUUID); - - if (node) { - nodeList->updateNodeWithData(node.data(), senderSockAddr, dataByteArray); - if (!node->getActiveSocket()) { - // we don't have an active socket for this node, but they're talking to us - // this means they've heard from us and can reply, let's assume public is active - node->activatePublicSocket(); - } - OctreeQueryNode* nodeData = (OctreeQueryNode*) node->getLinkedData(); - if (nodeData && !nodeData->isOctreeSendThreadInitalized()) { - nodeData->initializeOctreeSendThread(this, nodeUUID); + + while (readAvailableDatagram(receivedPacket, senderSockAddr)) { + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + PacketType packetType = packetTypeForPacket(receivedPacket); + + SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); + + if (packetType == getMyQueryMessageType()) { + bool debug = false; + if (debug) { + qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow(); + } + + // If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we + // need to make sure we have it in our nodeList. + if (matchingNode) { + nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket); + + OctreeQueryNode* nodeData = (OctreeQueryNode*) matchingNode->getLinkedData(); + if (nodeData && !nodeData->isOctreeSendThreadInitalized()) { + nodeData->initializeOctreeSendThread(this, matchingNode->getUUID()); + } + } + } else if (packetType == PacketTypeJurisdictionRequest) { + _jurisdictionSender->queueReceivedPacket(matchingNode, receivedPacket); + } else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) { + _octreeInboundPacketProcessor->queueReceivedPacket(matchingNode, receivedPacket); + } else { + // let processNodeData handle it. + NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); } } - } else if (packetType == PacketTypeJurisdictionRequest) { - _jurisdictionSender->queueReceivedPacket(senderSockAddr, dataByteArray); - } else if (_octreeInboundPacketProcessor && getOctree()->handlesEditPacketType(packetType)) { - _octreeInboundPacketProcessor->queueReceivedPacket(senderSockAddr, dataByteArray); - } else { - // let processNodeData handle it. - NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray); } } @@ -655,8 +655,4 @@ void OctreeServer::run() { QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); - - QTimer* pingNodesTimer = new QTimer(this); - connect(pingNodesTimer, SIGNAL(timeout()), nodeList, SLOT(pingInactiveNodes())); - pingNodesTimer->start(PING_INACTIVE_NODE_INTERVAL_USECS / 1000); } diff --git a/libraries/octree-server/src/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h similarity index 93% rename from libraries/octree-server/src/OctreeServer.h rename to assignment-client/src/octree/OctreeServer.h index 48a74a5e9e..e221011b76 100644 --- a/libraries/octree-server/src/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -58,8 +58,8 @@ public: // subclass may implement these method virtual void beforeRun() { }; - virtual bool hasSpecialPacketToSend(Node* node) { return false; } - virtual int sendSpecialPacket(Node* node) { return 0; } + virtual bool hasSpecialPacketToSend(const SharedNodePointer& node) { return false; } + virtual int sendSpecialPacket(const SharedNodePointer& node) { return 0; } static void attachQueryNodeToNode(Node* newNode); @@ -67,7 +67,7 @@ public: public slots: /// runs the voxel server assignment void run(); - void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr); + void readPendingDatagrams(); protected: void parsePayload(); diff --git a/libraries/octree-server/src/OctreeServerConsts.h b/assignment-client/src/octree/OctreeServerConsts.h similarity index 100% rename from libraries/octree-server/src/OctreeServerConsts.h rename to assignment-client/src/octree/OctreeServerConsts.h diff --git a/libraries/particle-server/src/ParticleNodeData.h b/assignment-client/src/particles/ParticleNodeData.h similarity index 95% rename from libraries/particle-server/src/ParticleNodeData.h rename to assignment-client/src/particles/ParticleNodeData.h index 4ab16cb33a..450767613e 100644 --- a/libraries/particle-server/src/ParticleNodeData.h +++ b/assignment-client/src/particles/ParticleNodeData.h @@ -10,9 +10,10 @@ #ifndef __hifi__ParticleNodeData__ #define __hifi__ParticleNodeData__ -#include #include +#include "../octree/OctreeQueryNode.h" + class ParticleNodeData : public OctreeQueryNode { public: ParticleNodeData() : diff --git a/libraries/particle-server/src/ParticleServer.cpp b/assignment-client/src/particles/ParticleServer.cpp similarity index 86% rename from libraries/particle-server/src/ParticleServer.cpp rename to assignment-client/src/particles/ParticleServer.cpp index 9344d8c4ae..1785ef46d6 100644 --- a/libraries/particle-server/src/ParticleServer.cpp +++ b/assignment-client/src/particles/ParticleServer.cpp @@ -43,7 +43,7 @@ void ParticleServer::beforeRun() { pruneDeletedParticlesTimer->start(PRUNE_DELETED_PARTICLES_INTERVAL_MSECS); } -void ParticleServer::particleCreated(const Particle& newParticle, Node* node) { +void ParticleServer::particleCreated(const Particle& newParticle, const SharedNodePointer& senderNode) { unsigned char outputBuffer[MAX_PACKET_SIZE]; unsigned char* copyAt = outputBuffer; @@ -63,14 +63,12 @@ void ParticleServer::particleCreated(const Particle& newParticle, Node* node) { copyAt += sizeof(particleID); packetLength += sizeof(particleID); - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) outputBuffer, packetLength, - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, senderNode); } // ParticleServer will use the "special packets" to send list of recently deleted particles -bool ParticleServer::hasSpecialPacketToSend(Node* node) { +bool ParticleServer::hasSpecialPacketToSend(const SharedNodePointer& node) { bool shouldSendDeletedParticles = false; // check to see if any new particles have been added since we last sent to this node... @@ -85,7 +83,7 @@ bool ParticleServer::hasSpecialPacketToSend(Node* node) { return shouldSendDeletedParticles; } -int ParticleServer::sendSpecialPacket(Node* node) { +int ParticleServer::sendSpecialPacket(const SharedNodePointer& node) { unsigned char outputBuffer[MAX_PACKET_SIZE]; size_t packetLength = 0; @@ -104,9 +102,7 @@ int ParticleServer::sendSpecialPacket(Node* node) { //qDebug() << "sending PacketType_PARTICLE_ERASE packetLength:" << packetLength; - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) outputBuffer, packetLength, - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node)); } nodeData->setLastDeletedParticlesSentAt(deletePacketSentAt); diff --git a/libraries/particle-server/src/ParticleServer.h b/assignment-client/src/particles/ParticleServer.h similarity index 83% rename from libraries/particle-server/src/ParticleServer.h rename to assignment-client/src/particles/ParticleServer.h index 1c1e3b5be9..4e6a896475 100644 --- a/libraries/particle-server/src/ParticleServer.h +++ b/assignment-client/src/particles/ParticleServer.h @@ -10,7 +10,7 @@ #ifndef __particle_server__ParticleServer__ #define __particle_server__ParticleServer__ -#include +#include "../octree/OctreeServer.h" #include "Particle.h" #include "ParticleServerConsts.h" @@ -34,10 +34,10 @@ public: // subclass may implement these method virtual void beforeRun(); - virtual bool hasSpecialPacketToSend(Node* node); - virtual int sendSpecialPacket(Node* node); + virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); + virtual int sendSpecialPacket(const SharedNodePointer& node); - virtual void particleCreated(const Particle& newParticle, Node* senderNode); + virtual void particleCreated(const Particle& newParticle, const SharedNodePointer& senderNode); public slots: void pruneDeletedParticles(); diff --git a/libraries/particle-server/src/ParticleServerConsts.h b/assignment-client/src/particles/ParticleServerConsts.h similarity index 100% rename from libraries/particle-server/src/ParticleServerConsts.h rename to assignment-client/src/particles/ParticleServerConsts.h diff --git a/libraries/voxel-server/src/VoxelNodeData.h b/assignment-client/src/voxels/VoxelNodeData.h similarity index 79% rename from libraries/voxel-server/src/VoxelNodeData.h rename to assignment-client/src/voxels/VoxelNodeData.h index 54ef9da5b6..c24bfad6ce 100644 --- a/libraries/voxel-server/src/VoxelNodeData.h +++ b/assignment-client/src/voxels/VoxelNodeData.h @@ -3,15 +3,16 @@ // hifi // // Created by Stephen Birarda on 3/21/13. -// +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. // #ifndef __hifi__VoxelNodeData__ #define __hifi__VoxelNodeData__ -#include #include +#include "../octree/OctreeQueryNode.h" + class VoxelNodeData : public OctreeQueryNode { public: VoxelNodeData() : OctreeQueryNode() { }; diff --git a/libraries/voxel-server/src/VoxelServer.cpp b/assignment-client/src/voxels/VoxelServer.cpp similarity index 85% rename from libraries/voxel-server/src/VoxelServer.cpp rename to assignment-client/src/voxels/VoxelServer.cpp index b71bcd3dc2..771c91570f 100644 --- a/libraries/voxel-server/src/VoxelServer.cpp +++ b/assignment-client/src/voxels/VoxelServer.cpp @@ -32,12 +32,12 @@ Octree* VoxelServer::createTree() { return new VoxelTree(true); } -bool VoxelServer::hasSpecialPacketToSend(Node* node) { +bool VoxelServer::hasSpecialPacketToSend(const SharedNodePointer& node) { bool shouldSendEnvironments = _sendEnvironments && shouldDo(ENVIRONMENT_SEND_INTERVAL_USECS, OCTREE_SEND_INTERVAL_USECS); return shouldSendEnvironments; } -int VoxelServer::sendSpecialPacket(Node* node) { +int VoxelServer::sendSpecialPacket(const SharedNodePointer& node) { int numBytesPacketHeader = populatePacketHeader(reinterpret_cast(_tempOutputBuffer), PacketTypeEnvironmentData); int envPacketLength = numBytesPacketHeader; int environmentsToSend = getSendMinimalEnvironment() ? 1 : getEnvironmentDataCount(); @@ -46,9 +46,7 @@ int VoxelServer::sendSpecialPacket(Node* node) { envPacketLength += getEnvironmentData(i)->getBroadcastData(_tempOutputBuffer + envPacketLength); } - NodeList::getInstance()->getNodeSocket().writeDatagram((char*) _tempOutputBuffer, envPacketLength, - node->getActiveSocket()->getAddress(), - node->getActiveSocket()->getPort()); + NodeList::getInstance()->writeDatagram((char*) _tempOutputBuffer, envPacketLength, SharedNodePointer(node)); return envPacketLength; } diff --git a/libraries/voxel-server/src/VoxelServer.h b/assignment-client/src/voxels/VoxelServer.h similarity index 91% rename from libraries/voxel-server/src/VoxelServer.h rename to assignment-client/src/voxels/VoxelServer.h index 183224b2a7..509d838fff 100644 --- a/libraries/voxel-server/src/VoxelServer.h +++ b/assignment-client/src/voxels/VoxelServer.h @@ -17,8 +17,7 @@ #include #include -#include - +#include "../octree/OctreeServer.h" #include "VoxelServerConsts.h" @@ -44,8 +43,8 @@ public: // subclass may implement these method virtual void beforeRun(); - virtual bool hasSpecialPacketToSend(Node* node); - virtual int sendSpecialPacket(Node* node); + virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); + virtual int sendSpecialPacket(const SharedNodePointer& node); private: diff --git a/libraries/voxel-server/src/VoxelServerConsts.h b/assignment-client/src/voxels/VoxelServerConsts.h similarity index 100% rename from libraries/voxel-server/src/VoxelServerConsts.h rename to assignment-client/src/voxels/VoxelServerConsts.h diff --git a/data-server/src/DataServer.cpp b/data-server/src/DataServer.cpp index 258afb755f..429a2ec331 100644 --- a/data-server/src/DataServer.cpp +++ b/data-server/src/DataServer.cpp @@ -136,7 +136,7 @@ void DataServer::readPendingDatagrams() { } if ((requestType == PacketTypeDataServerPut || requestType == PacketTypeDataServerGet) && - packetVersionMatch(receivedPacket)) { + receivedPacket[numBytesArithmeticCodingFromBuffer(receivedPacket.data())] == versionForPacketType(requestType)) { QDataStream packetStream(receivedPacket); int numReceivedHeaderBytes = numBytesForPacketHeader(receivedPacket); diff --git a/domain-server/resources/web/assignment/placeholder.js b/domain-server/resources/web/assignment/placeholder.js index ee8f89cdd6..7c84767f31 100644 --- a/domain-server/resources/web/assignment/placeholder.js +++ b/domain-server/resources/web/assignment/placeholder.js @@ -8,11 +8,9 @@ var NUMBER_OF_CELLS = NUMBER_OF_CELLS_EACH_DIMENSION * NUMBER_OF_CELLS_EACH_DIME var currentCells = []; var nextCells = []; -var METER_LENGTH = 1 / TREE_SCALE; +var METER_LENGTH = 1; var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION; -print("TREE_SCALE = " + TREE_SCALE + "\n"); - // randomly populate the cell start values for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) { // create the array to hold this row @@ -108,7 +106,7 @@ function sendNextCells() { // queue a packet to add a voxel for the new cell var color = (nextCells[i][j] == 1) ? 255 : 1; - Voxels.queueDestructiveVoxelAdd(x, y, 0, cellScale, color, color, color); + Voxels.setVoxel(x, y, 0, cellScale, color, color, color); } } } @@ -128,4 +126,6 @@ function step() { sendNextCells(); } -Agent.willSendVisualDataCallback.connect(step); \ No newline at end of file + +Script.willSendVisualDataCallback.connect(step); +Voxels.setPacketsPerSecond(200); \ No newline at end of file diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index 390beaa89a..d0855d7967 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -23,8 +23,8 @@ $(document).ready(function(){ $.each(json.queued, function (uuid, data) { queuedTableBody += ""; - queuedTableBody += "" + uuid + ""; queuedTableBody += "" + data.type + ""; + queuedTableBody += "" + uuid + ""; queuedTableBody += "" + (data.pool ? data.pool : "") + ""; queuedTableBody += ""; }); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 37626e8a43..05a1b4ea65 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -21,6 +21,8 @@ #include #include +#include "DomainServerNodeData.h" + #include "DomainServer.h" const int RESTART_HOLD_TIME_MSECS = 5 * 1000; @@ -57,8 +59,9 @@ DomainServer::DomainServer(int argc, char* argv[]) : populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); NodeList* nodeList = NodeList::createInstance(NodeType::DomainServer, domainServerPort); - - connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), this, SLOT(nodeKilled(SharedNodePointer))); + + connect(nodeList, &NodeList::nodeAdded, this, &DomainServer::nodeAdded); + connect(nodeList, &NodeList::nodeKilled, this, &DomainServer::nodeKilled); QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); @@ -209,6 +212,10 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSetgetNodeSocket().hasPendingDatagrams()) { receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize()); nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(), senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); - if (packetVersionMatch(receivedPacket)) { + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { PacketType requestType = packetTypeForPacket(receivedPacket); if (requestType == PacketTypeDomainListRequest) { @@ -237,7 +244,7 @@ void DomainServer::readAvailableDatagrams() { QDataStream packetStream(receivedPacket); packetStream.skipRawData(numBytesForPacketHeader(receivedPacket)); - deconstructPacketHeader(receivedPacket, nodeUUID); + QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); packetStream >> nodeType; packetStream >> nodePublicAddress >> nodeLocalAddress; @@ -255,18 +262,20 @@ void DomainServer::readAvailableDatagrams() { } } - const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer - << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer - << NodeType::MetavoxelServer; - SharedAssignmentPointer matchingStaticAssignment; // check if this is a non-statically assigned node, a node that is assigned and checking in for the first time // or a node that has already checked in and is continuing to report for duty if (!STATICALLY_ASSIGNED_NODES.contains(nodeType) || (matchingStaticAssignment = matchingStaticAssignmentForCheckIn(nodeUUID, nodeType)) - || nodeList->getInstance()->nodeWithUUID(nodeUUID)) - { + || nodeList->getInstance()->nodeWithUUID(nodeUUID)) { + + if (nodeUUID.isNull()) { + // this is a check in from an unidentified node + // we need to generate a session UUID for this node + nodeUUID = QUuid::createUuid(); + } + SharedNodePointer checkInNode = nodeList->addOrUpdateNode(nodeUUID, nodeType, nodePublicAddress, @@ -291,16 +300,37 @@ void DomainServer::readAvailableDatagrams() { NodeType_t* nodeTypesOfInterest = reinterpret_cast(receivedPacket.data() + packetStream.device()->pos()); + // always send the node their own UUID back + QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append); + broadcastDataStream << checkInNode->getUUID(); + + DomainServerNodeData* nodeData = reinterpret_cast(checkInNode->getLinkedData()); + if (numInterestTypes > 0) { - QDataStream broadcastDataStream(&broadcastPacket, QIODevice::Append); - - // if the node has sent no types of interest, assume they want nothing but their own ID back - foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (node->getUUID() != nodeUUID && - memchr(nodeTypesOfInterest, node->getType(), numInterestTypes)) { + // if the node has any interest types, send back those nodes as well + foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { + if (otherNode->getUUID() != nodeUUID && + memchr(nodeTypesOfInterest, otherNode->getType(), numInterestTypes)) { // don't send avatar nodes to other avatars, that will come from avatar mixer - broadcastDataStream << *node.data(); + broadcastDataStream << *otherNode.data(); + + // pack the secret that these two nodes will use to communicate with each other + QUuid secretUUID = nodeData->getSessionSecretHash().value(otherNode->getUUID()); + if (secretUUID.isNull()) { + // generate a new secret UUID these two nodes can use + secretUUID = QUuid::createUuid(); + + // set that on the current Node's sessionSecretHash + nodeData->getSessionSecretHash().insert(otherNode->getUUID(), secretUUID); + + // set it on the other Node's sessionSecretHash + reinterpret_cast(otherNode->getLinkedData()) + ->getSessionSecretHash().insert(nodeUUID, secretUUID); + + } + + broadcastDataStream << secretUUID; } } } @@ -344,7 +374,7 @@ void DomainServer::readAvailableDatagrams() { } } -QJsonObject jsonForSocket(const HifiSockAddr& socket) { +QJsonObject DomainServer::jsonForSocket(const HifiSockAddr& socket) { QJsonObject socketJSON; socketJSON["ip"] = socket.getAddress().toString(); @@ -358,7 +388,7 @@ const char JSON_KEY_PUBLIC_SOCKET[] = "public"; const char JSON_KEY_LOCAL_SOCKET[] = "local"; const char JSON_KEY_POOL[] = "pool"; -QJsonObject jsonObjectForNode(Node* node) { +QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { QJsonObject nodeJson; // re-format the type name so it matches the target name @@ -372,12 +402,13 @@ QJsonObject jsonObjectForNode(Node* node) { // add the node socket information nodeJson[JSON_KEY_PUBLIC_SOCKET] = jsonForSocket(node->getPublicSocket()); nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket()); - + // if the node has pool information, add it - if (node->getLinkedData() && !((Assignment*) node->getLinkedData())->getPool().isEmpty()) { - nodeJson[JSON_KEY_POOL] = ((Assignment*) node->getLinkedData())->getPool(); + SharedAssignmentPointer matchingAssignment = _staticAssignmentHash.value(node->getUUID()); + if (matchingAssignment) { + nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool(); } - + return nodeJson; } @@ -397,10 +428,10 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& // enumerate the NodeList to find the assigned nodes foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { - if (node->getLinkedData()) { + if (_staticAssignmentHash.value(node->getUUID())) { // add the node using the UUID as the key QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); - assignedNodesJSON[uuidString] = jsonObjectForNode(node.data()); + assignedNodesJSON[uuidString] = jsonObjectForNode(node); } } @@ -443,7 +474,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { // add the node using the UUID as the key QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); - nodesJSON[uuidString] = jsonObjectForNode(node.data()); + nodesJSON[uuidString] = jsonObjectForNode(node); } rootJSON["nodes"] = nodesJSON; @@ -546,6 +577,11 @@ void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& _staticAssignmentHash.remove(oldUUID); } +void DomainServer::nodeAdded(SharedNodePointer node) { + // we don't use updateNodeWithData, so add the DomainServerNodeData to the node here + node->setLinkedData(new DomainServerNodeData()); +} + void DomainServer::nodeKilled(SharedNodePointer node) { // if this node's UUID matches a static assignment we need to throw it back in the assignment queue SharedAssignmentPointer matchedAssignment = _staticAssignmentHash.value(node->getUUID()); @@ -553,6 +589,15 @@ void DomainServer::nodeKilled(SharedNodePointer node) { if (matchedAssignment) { refreshStaticAssignmentAndAddToQueue(matchedAssignment); } + + // cleanup the connection secrets that we set up for this node (on the other nodes) + DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) { + SharedNodePointer otherNode = NodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID); + if (otherNode) { + reinterpret_cast(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID()); + } + } } SharedAssignmentPointer DomainServer::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) { diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 60251b3bb4..0646653d02 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -30,7 +30,9 @@ public: void exit(int retCode = 0); public slots: - /// Called by NodeList to inform us that a node has been killed. + /// Called by NodeList to inform us a node has been added + void nodeAdded(SharedNodePointer node); + /// Called by NodeList to inform us a node has been killed void nodeKilled(SharedNodePointer node); private: @@ -46,6 +48,9 @@ private: void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment); void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment); + QJsonObject jsonForSocket(const HifiSockAddr& socket); + QJsonObject jsonObjectForNode(const SharedNodePointer& node); + HTTPManager _HTTPManager; QHash _staticAssignmentHash; diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h new file mode 100644 index 0000000000..ea56e31f1c --- /dev/null +++ b/domain-server/src/DomainServerNodeData.h @@ -0,0 +1,26 @@ +// +// DomainServerNodeData.h +// hifi +// +// Created by Stephen Birarda on 2/6/2014. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__DomainServerNodeData__ +#define __hifi__DomainServerNodeData__ + +#include + +#include + +class DomainServerNodeData : public NodeData { +public: + DomainServerNodeData() : _sessionSecretHash() {}; + int parseData(const QByteArray& packet) { return 0; } + + QHash& getSessionSecretHash() { return _sessionSecretHash; } +private: + QHash _sessionSecretHash; +}; + +#endif /* defined(__hifi__DomainServerNodeData__) */ diff --git a/examples/cameraExample.js b/examples/cameraExample.js new file mode 100644 index 0000000000..d42c3c1b0e --- /dev/null +++ b/examples/cameraExample.js @@ -0,0 +1,135 @@ +// +// cameraExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 2/6/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates use of the Camera class +// +// + +var damping = 0.9; +var yaw = 0.0; +var pitch = 0.0; +var roll = 0.0; +var thrust = { x: 0, y: 0, z: 0 }; +var velocity = { x: 0, y: 0, z: 0 }; +var position = { x: MyAvatar.position.x, y: MyAvatar.position.y + 1, z: MyAvatar.position.z }; +var joysticksCaptured = false; +var THRUST_CONTROLLER = 0; +var VIEW_CONTROLLER = 1; + +function checkCamera() { + if (Camera.getMode() == "independent") { + var deltaTime = 1/60; // approximately our FPS - maybe better to be elapsed time since last call + var THRUST_MAG_UP = 800.0; + var THRUST_MAG_DOWN = 300.0; + var THRUST_MAG_FWD = 500.0; + var THRUST_MAG_BACK = 300.0; + var THRUST_MAG_LATERAL = 250.0; + var THRUST_JUMP = 120.0; + var scale = 1.0; + var thrustMultiplier = 1.0; // maybe increase this as you hold it down? + + var YAW_MAG = 500.0; + var PITCH_MAG = 100.0; + var THRUST_MAG_HAND_JETS = THRUST_MAG_FWD; + var JOYSTICK_YAW_MAG = YAW_MAG; + var JOYSTICK_PITCH_MAG = PITCH_MAG * 0.5; + + var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER); + + var currentOrientation = Camera.getOrientation(); + + var front = Quat.getFront(currentOrientation); + var right = Quat.getRight(currentOrientation); + var up = Quat.getUp(currentOrientation); + + var thrustFront = Vec3.multiply(front, scale * THRUST_MAG_HAND_JETS * thrustJoystickPosition.y * thrustMultiplier * deltaTime); + var thrustRight = Vec3.multiply(right, scale * THRUST_MAG_HAND_JETS * thrustJoystickPosition.x * thrustMultiplier * deltaTime); + + thrust = Vec3.sum(thrust, thrustFront); + thrust = Vec3.sum(thrust, thrustRight); + + // add thrust to velocity + velocity = Vec3.sum(velocity, Vec3.multiply(thrust, deltaTime)); + + // add velocity to position + position = Vec3.sum(position, Vec3.multiply(velocity, deltaTime)); + Camera.setPosition(position); + + // reset thrust + thrust = { x: 0, y: 0, z: 0 }; + + // damp velocity + velocity = Vec3.multiply(velocity, damping); + + // View Controller + var viewJoystickPosition = Controller.getJoystickPosition(VIEW_CONTROLLER); + yaw -= viewJoystickPosition.x * JOYSTICK_YAW_MAG * deltaTime; + pitch += viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime; + var orientation = Quat.fromPitchYawRoll(pitch, yaw, roll); + Camera.setOrientation(orientation); + } +} + +Script.willSendVisualDataCallback.connect(checkCamera); + +function mouseMoveEvent(event) { + print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y); + var pickRay = Camera.computePickRay(event.x, event.y); + print("called Camera.computePickRay()"); + print("computePickRay origin=" + pickRay.origin.x + ", " + pickRay.origin.y + ", " + pickRay.origin.z); + print("computePickRay direction=" + pickRay.direction.x + ", " + pickRay.direction.y + ", " + pickRay.direction.z); +} + +Controller.mouseMoveEvent.connect(mouseMoveEvent); + + +function keyPressEvent(event) { + if (joysticksCaptured) { + Controller.releaseJoystick(THRUST_CONTROLLER); + Controller.releaseJoystick(VIEW_CONTROLLER); + joysticksCaptured = false; + } + + if (event.text == "1") { + Camera.setMode("first person"); + } + + if (event.text == "2") { + Camera.setMode("mirror"); + } + + if (event.text == "3") { + Camera.setMode("third person"); + } + + if (event.text == "4") { + Camera.setMode("independent"); + joysticksCaptured = true; + Controller.captureJoystick(THRUST_CONTROLLER); + Controller.captureJoystick(VIEW_CONTROLLER); + position = { x: MyAvatar.position.x, y: MyAvatar.position.y + 1, z: MyAvatar.position.z }; + } +} + + +// Map keyPress and mouse move events to our callbacks +Controller.keyPressEvent.connect(keyPressEvent); +Controller.captureKeyEvents({ text: "1" }); +Controller.captureKeyEvents({ text: "2" }); +Controller.captureKeyEvents({ text: "3" }); +Controller.captureKeyEvents({ text: "4" }); +function scriptEnding() { + // re-enabled the standard application for touch events + Controller.releaseKeyEvents({ text: "1" }); + Controller.releaseKeyEvents({ text: "2" }); + Controller.releaseKeyEvents({ text: "3" }); + Controller.releaseKeyEvents({ text: "4" }); + Controller.releaseJoystick(THRUST_CONTROLLER); + Controller.releaseJoystick(VIEW_CONTROLLER); +} +Script.scriptEnding.connect(scriptEnding); + diff --git a/examples/spaceInvadersExample.js b/examples/spaceInvadersExample.js index 79e6837f21..c817afcdd4 100644 --- a/examples/spaceInvadersExample.js +++ b/examples/spaceInvadersExample.js @@ -275,6 +275,9 @@ function cleanupGame() { if (missileFired) { Particles.deleteParticle(myMissile); } + + Controller.releaseKeyEvents({text: " "}); + Script.stop(); } Script.scriptEnding.connect(cleanupGame); diff --git a/interface/resources/scripts/sphere.js b/interface/resources/scripts/sphere.js deleted file mode 100644 index b696021fe8..0000000000 --- a/interface/resources/scripts/sphere.js +++ /dev/null @@ -1,171 +0,0 @@ -// -// sphere.js -// interface -// -// Created by Andrzej Kapolka on 12/17/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// - -function strictIndexOf(array, element) { - for (var i = 0; i < array.length; i++) { - if (array[i] == element) { - return i; - } - } - return -1; -} - -var colorIndex; -var normalIndex; -var visitor; -var info; - -var MAX_DEPTH = 4; - -var sphereCenter = [ 0.5, 0.5, 0.5 ]; -var sphereColor = 0xFFFF00FF; -var sphereRadius = 0.25; -var sphereRadiusSquared = sphereRadius * sphereRadius; - -function lengthSquared(x, y, z) { - return x*x + y*y + z*z; -} - -function setNormal(vector) { - if (normalIndex != -1) { - var length = Math.sqrt(lengthSquared(vector[0], vector[1], vector[2])); - if (length == 0.0) { - info.inputValues[normalIndex] = 0x007F00; - - } else { - var scale = 127.0 / length; - info.inputValues[normalIndex] = - (Math.floor(vector[0] * scale) & 0xFF) << 16 | - (Math.floor(vector[1] * scale) & 0xFF) << 8 | - Math.floor(vector[2] * scale) & 0xFF; - } - } -} - -function guide(minimum, size, depth) { - info.minimum = minimum; - info.size = size; - - // start with a relative fast bounding volume test to find most non-intersecting states - var maximum = [ minimum[0] + size, minimum[1] + size, minimum[2] + size ]; - if (minimum[0] >= sphereCenter[0] + sphereRadius || - minimum[1] >= sphereCenter[1] + sphereRadius || - minimum[2] >= sphereCenter[2] + sphereRadius || - maximum[0] <= sphereCenter[0] - sphereRadius || - maximum[1] <= sphereCenter[1] - sphereRadius || - maximum[2] <= sphereCenter[2] - sphereRadius) { - info.isLeaf = true; - if (colorIndex != -1) { - info.inputValues[colorIndex] = 0x0; - } - visitor.visit(info); - return; - } - - var halfSize = size / 2; - var center = [ minimum[0] + halfSize, minimum[1] + halfSize, minimum[2] + halfSize ]; - var vector = [ center[0] - sphereCenter[0], center[1] - sphereCenter[1], center[2] - sphereCenter[2] ]; - - // count the number of points inside the sphere - var inside = 0; - if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - minimum[2]) <= - sphereRadiusSquared) { - inside++; - } - if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - minimum[2]) <= - sphereRadiusSquared) { - inside++; - } - if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - minimum[2]) <= - sphereRadiusSquared) { - inside++; - } - if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - minimum[2]) <= - sphereRadiusSquared) { - inside++; - } - if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - maximum[2]) <= - sphereRadiusSquared) { - inside++; - } - if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - minimum[1], sphereCenter[2] - maximum[2]) <= - sphereRadiusSquared) { - inside++; - } - if (lengthSquared(sphereCenter[0] - minimum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - maximum[2]) <= - sphereRadiusSquared) { - inside++; - } - if (lengthSquared(sphereCenter[0] - maximum[0], sphereCenter[1] - maximum[1], sphereCenter[2] - maximum[2]) <= - sphereRadiusSquared) { - inside++; - } - - // see if all points are in the sphere - if (inside == 8) { - info.isLeaf = true; - if (colorIndex != -1) { - info.inputValues[colorIndex] = sphereColor; - } - setNormal(vector); - visitor.visit(info); - return; - } - - // if we've reached max depth, compute alpha using a volume estimate - if (depth == MAX_DEPTH) { - info.isLeaf = true; - if (inside >= 3) { - if (colorIndex != -1) { - info.inputValues[colorIndex] = sphereColor; - } - setNormal(vector); - - } else { - if (colorIndex != -1) { - info.inputValues[colorIndex] = 0x0; - } - } - visitor.visit(info); - return; - } - - // recurse - info.isLeaf = false; - if (!visitor.visit(info)) { - return; - } - depth += 1; - guide(minimum, halfSize, depth); - guide([ center[0], minimum[1], minimum[2] ], halfSize, depth); - guide([ minimum[0], center[1], minimum[2] ], halfSize, depth); - guide([ center[0], center[1], minimum[2] ], halfSize, depth); - guide([ minimum[0], minimum[1], center[2] ], halfSize, depth); - guide([ center[0], minimum[1], center[2] ], halfSize, depth); - guide([ minimum[0], center[1], center[2] ], halfSize, depth); - guide([ center[0], center[1], center[2] ], halfSize, depth); -} - -(function(visitation) { - var inputs = visitation.visitor.getInputs(); - colorIndex = strictIndexOf(inputs, AttributeRegistry.colorAttribute); - normalIndex = strictIndexOf(inputs, AttributeRegistry.normalAttribute); - visitor = visitation.visitor; - info = { inputValues: new Array(inputs.length) }; - - // have the sphere orbit the center and pulse in size - var time = new Date().getTime(); - var ROTATE_PERIOD = 400.0; - sphereCenter[0] = 0.5 + 0.25 * Math.cos(time / ROTATE_PERIOD); - sphereCenter[2] = 0.5 + 0.25 * Math.sin(time / ROTATE_PERIOD); - var PULSE_PERIOD = 300.0; - sphereRadius = 0.25 + 0.0625 * Math.cos(time / PULSE_PERIOD); - sphereRadiusSquared = sphereRadius * sphereRadius; - - guide(visitation.info.minimum, visitation.info.size, 0); -}) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d0d071a141..39e1868dd0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -154,8 +154,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _resetRecentMaxPacketsSoon(true), _swatch(NULL), _pasteMode(false), - _logger(new FileLogger(this)), - _persistThread(NULL) + _logger(new FileLogger(this)) { _myAvatar = _avatarManager.getMyAvatar(); @@ -201,10 +200,12 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : audioThread->start(); connect(nodeList, SIGNAL(domainChanged(const QString&)), SLOT(domainChanged(const QString&))); - + connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); + connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer))); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer))); + connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo("resources/info/ApplicationInfo.ini", QSettings::IniFormat); @@ -244,6 +245,11 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->moveToThread(_nodeThread); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + + // send the identity packet for our avatar each second to our avatar mixer + QTimer* identityPacketTimer = new QTimer(); + connect(identityPacketTimer, &QTimer::timeout, _myAvatar, &MyAvatar::sendIdentityPacket); + identityPacketTimer->start(1000); QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); @@ -316,12 +322,7 @@ Application::~Application() { _voxelHideShowThread.terminate(); _voxelEditSender.terminate(); _particleEditSender.terminate(); - if (_persistThread) { - _persistThread->terminate(); - _persistThread->deleteLater(); - _persistThread = NULL; - } - + storeSizeAndPosition(); saveScripts(); _sharedVoxelSystem.changeTree(new VoxelTree); @@ -440,9 +441,9 @@ void Application::paintGL() { glEnable(GL_LINE_SMOOTH); if (OculusManager::isConnected()) { - _myCamera.setUpShift (0.0f); - _myCamera.setDistance (0.0f); - _myCamera.setTightness (0.0f); // Camera is directly connected to head without smoothing + _myCamera.setUpShift(0.0f); + _myCamera.setDistance(0.0f); + _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getHead().calculateAverageEyePosition()); _myCamera.setTargetRotation(_myAvatar->getHead().getOrientation()); @@ -452,7 +453,7 @@ void Application::paintGL() { _myCamera.setTargetRotation(_myAvatar->getHead().getCameraOrientation()); } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { - _myCamera.setTightness (0.0f); // Camera is directly connected to head without smoothing + _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition()); _myCamera.setTargetRotation(_myAvatar->getHead().getCameraOrientation()); @@ -1262,7 +1263,8 @@ void Application::mousePressEvent(QMouseEvent* event) { pasteVoxels(); } - if (MAKE_SOUND_ON_VOXEL_CLICK && _isHoverVoxel && !_isHoverVoxelSounding) { + if (!Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode) && + MAKE_SOUND_ON_VOXEL_CLICK && _isHoverVoxel && !_isHoverVoxelSounding) { _hoverVoxelOriginalColor[0] = _hoverVoxel.red; _hoverVoxelOriginalColor[1] = _hoverVoxel.green; _hoverVoxelOriginalColor[2] = _hoverVoxel.blue; @@ -1857,12 +1859,6 @@ void Application::init() { } qDebug("Loaded settings"); - if (!_profile.getUsername().isEmpty()) { - // we have a username for this avatar, ask the data-server for the mesh URL for this avatar - DataServerClient::getValueForKeyAndUserString(DataServerKey::FaceMeshURL, _profile.getUserString(), &_profile); - DataServerClient::getValueForKeyAndUserString(DataServerKey::SkeletonURL, _profile.getUserString(), &_profile); - } - // Set up VoxelSystem after loading preferences so we can get the desired max voxel count _voxels.setMaxVoxels(Menu::getInstance()->getMaxVoxels()); _voxels.setUseVoxelShader(Menu::getInstance()->isOptionChecked(MenuOption::UseVoxelShader)); @@ -1907,9 +1903,6 @@ void Application::init() { connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView())); connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView())); connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors())); - - - updateLocalOctreeCache(true); } void Application::closeMirrorView() { @@ -1971,8 +1964,13 @@ void Application::updateMouseRay() { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMouseRay()"); - _viewFrustum.computePickRay(_mouseX / (float)_glWidget->width(), _mouseY / (float)_glWidget->height(), - _mouseRayOrigin, _mouseRayDirection); + // if the mouse pointer isn't visible, act like it's at the center of the screen + float x = 0.5f, y = 0.5f; + if (!_mouseHidden) { + x = _mouseX / (float)_glWidget->width(); + y = _mouseY / (float)_glWidget->height(); + } + _viewFrustum.computePickRay(x, y, _mouseRayOrigin, _mouseRayDirection); // adjust for mirroring if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { @@ -2009,18 +2007,20 @@ void Application::updateMyAvatarLookAtPosition(glm::vec3& lookAtSpot) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); - const float FAR_AWAY_STARE = TREE_SCALE; if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { lookAtSpot = _myCamera.getPosition(); - } else if (_mouseHidden) { - // if the mouse cursor is hidden, just look straight ahead - glm::vec3 rayOrigin, rayDirection; - _viewFrustum.computePickRay(0.5f, 0.5f, rayOrigin, rayDirection); - lookAtSpot = rayOrigin + rayDirection * FAR_AWAY_STARE; } else { - // just look in direction of the mouse ray - lookAtSpot = _mouseRayOrigin + _mouseRayDirection * FAR_AWAY_STARE; + // look in direction of the mouse ray, but use distance from intersection, if any + float distance = TREE_SCALE; + if (_myAvatar->getLookAtTargetAvatar()) { + distance = glm::distance(_mouseRayOrigin, + static_cast(_myAvatar->getLookAtTargetAvatar())->getHead().calculateAverageEyePosition()); + + } else if (_isHoverVoxel) { + distance = glm::distance(_mouseRayOrigin, getMouseVoxelWorldCoordinates(_hoverVoxel)); + } + lookAtSpot = _mouseRayOrigin + _mouseRayDirection * distance; } if (_faceshift.isActive()) { // deflect using Faceshift gaze data @@ -2062,7 +2062,7 @@ void Application::updateHoverVoxels(float deltaTime, float& distance, BoxFace& f glm::vec4 oldVoxel(_hoverVoxel.x, _hoverVoxel.y, _hoverVoxel.z, _hoverVoxel.s); // only do this work if MAKE_SOUND_ON_VOXEL_HOVER or MAKE_SOUND_ON_VOXEL_CLICK is enabled, // and make sure the tree is not already busy... because otherwise you'll have to wait. - if (!(_voxels.treeIsBusy() || _mousePressed)) { + if (!_mousePressed) { { PerformanceWarning warn(showWarnings, "Application::updateHoverVoxels() _voxels.findRayIntersection()"); _isHoverVoxel = _voxels.findRayIntersection(_mouseRayOrigin, _mouseRayDirection, _hoverVoxel, distance, face); @@ -2207,9 +2207,6 @@ void Application::updateThreads(float deltaTime) { _voxelHideShowThread.threadRoutine(); _voxelEditSender.threadRoutine(); _particleEditSender.threadRoutine(); - if (_persistThread) { - _persistThread->threadRoutine(); - } } } @@ -2231,28 +2228,30 @@ void Application::updateMetavoxels(float deltaTime) { } } +void Application::cameraMenuChanged() { + if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { + if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { + _myCamera.setMode(CAMERA_MODE_MIRROR); + _myCamera.setModeShiftRate(100.0f); + } + } else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { + if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { + _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); + _myCamera.setModeShiftRate(1.0f); + } + } else { + if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { + _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); + _myCamera.setModeShiftRate(1.0f); + } + } +} + void Application::updateCamera(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateCamera()"); if (!OculusManager::isConnected() && !TV3DManager::isConnected()) { - if (Menu::getInstance()->isOptionChecked(MenuOption::FullscreenMirror)) { - if (_myCamera.getMode() != CAMERA_MODE_MIRROR) { - _myCamera.setMode(CAMERA_MODE_MIRROR); - _myCamera.setModeShiftRate(100.0f); - } - } else if (Menu::getInstance()->isOptionChecked(MenuOption::FirstPerson)) { - if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) { - _myCamera.setMode(CAMERA_MODE_FIRST_PERSON); - _myCamera.setModeShiftRate(1.0f); - } - } else { - if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) { - _myCamera.setMode(CAMERA_MODE_THIRD_PERSON); - _myCamera.setModeShiftRate(1.0f); - } - } - if (Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) { float xSign = _myCamera.getMode() == CAMERA_MODE_MIRROR ? 1.0f : -1.0f; if (_faceshift.isActive()) { @@ -2553,10 +2552,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node int packetLength = endOfVoxelQueryPacket - voxelQueryPacket; // make sure we still have an active socket - if (node->getActiveSocket()) { - nodeList->getNodeSocket().writeDatagram((char*) voxelQueryPacket, packetLength, - node->getActiveSocket()->getAddress(), node->getActiveSocket()->getPort()); - } + nodeList->writeDatagram(reinterpret_cast(voxelQueryPacket), packetLength, node); // Feed number of bytes to corresponding channel of the bandwidth meter _bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(packetLength); @@ -2884,7 +2880,8 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } } - _avatarManager.renderAvatars(whichCamera.getMode() == CAMERA_MODE_MIRROR, selfAvatarOnly); + bool renderMyHead = (whichCamera.getInterpolatedMode() != CAMERA_MODE_FIRST_PERSON); + _avatarManager.renderAvatars(renderMyHead, selfAvatarOnly); if (!selfAvatarOnly) { // Render the world box @@ -3859,7 +3856,7 @@ void Application::updateWindowTitle(){ QString buildVersion = " (build " + applicationVersion() + ")"; NodeList* nodeList = NodeList::getInstance(); - QString title = QString() + _profile.getUsername() + " " + nodeList->getOwnerUUID().toString() + QString title = QString() + _profile.getUsername() + " " + nodeList->getSessionUUID().toString() + " @ " + nodeList->getDomainHostname() + buildVersion; qDebug("Application title set to: %s", title.toStdString().c_str()); @@ -3882,10 +3879,13 @@ void Application::domainChanged(const QString& domainHostname) { // reset the particle renderer _particles.clear(); +} - // reset our persist thread - qDebug() << "Domain changed to" << domainHostname << ". Swapping persist cache."; - updateLocalOctreeCache(); +void Application::nodeAdded(SharedNodePointer node) { + if (node->getType() == NodeType::AvatarMixer) { + // new avatar mixer, send off our identity packet right away + _myAvatar->sendIdentityPacket(); + } } void Application::nodeKilled(SharedNodePointer node) { @@ -3957,27 +3957,25 @@ void Application::nodeKilled(SharedNodePointer node) { } } -void Application::trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket) { +void Application::trackIncomingVoxelPacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket) { // Attempt to identify the sender from it's address. - SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr); - if (serverNode) { - QUuid nodeUUID = serverNode->getUUID(); + if (sendingNode) { + QUuid nodeUUID = sendingNode->getUUID(); // now that we know the node ID, let's add these stats to the stats for that node... _voxelSceneStatsLock.lockForWrite(); if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) { VoxelSceneStats& stats = _octreeServerSceneStats[nodeUUID]; - stats.trackIncomingOctreePacket(packet, wasStatsPacket, serverNode->getClockSkewUsec()); + stats.trackIncomingOctreePacket(packet, wasStatsPacket, sendingNode->getClockSkewUsec()); } _voxelSceneStatsLock.unlock(); } } -int Application::parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { +int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePointer& sendingNode) { // But, also identify the sender, and keep track of the contained jurisdiction root for this server - SharedNodePointer server = NodeList::getInstance()->nodeWithAddress(senderSockAddr); // parse the incoming stats datas stick it in a temporary object for now, while we // determine which server it belongs to @@ -3985,8 +3983,8 @@ int Application::parseOctreeStats(const QByteArray& packet, const HifiSockAddr& int statsMessageLength = temp.unpackFromMessage(reinterpret_cast(packet.data()), packet.size()); // quick fix for crash... why would voxelServer be NULL? - if (server) { - QUuid nodeUUID = server->getUUID(); + if (sendingNode) { + QUuid nodeUUID = sendingNode->getUUID(); // now that we know the node ID, let's add these stats to the stats for that node... _voxelSceneStatsLock.lockForWrite(); @@ -4003,7 +4001,7 @@ int Application::parseOctreeStats(const QByteArray& packet, const HifiSockAddr& // see if this is the first we've heard of this node... NodeToJurisdictionMap* jurisdiction = NULL; - if (server->getType() == NodeType::VoxelServer) { + if (sendingNode->getType() == NodeType::VoxelServer) { jurisdiction = &_voxelServerJurisdictions; } else { jurisdiction = &_particleServerJurisdictions; @@ -4109,6 +4107,10 @@ void Application::loadScript(const QString& fileNameString) { // hook our avatar object into this script engine scriptEngine->setAvatarData( static_cast(_myAvatar), "MyAvatar"); + CameraScriptableObject* cameraScriptable = new CameraScriptableObject(&_myCamera, &_viewFrustum); + scriptEngine->registerGlobalObject("Camera", cameraScriptable); + connect(scriptEngine, SIGNAL(finished(const QString&)), cameraScriptable, SLOT(deleteLater())); + QThread* workerThread = new QThread(this); // when the worker thread is started, call our engine's run.. @@ -4155,49 +4157,6 @@ void Application::initAvatarAndViewFrustum() { updateMyAvatar(0.f); } -QString Application::getLocalVoxelCacheFileName() { - QString fileName = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - QDir logDir(fileName); - if (!logDir.exists(fileName)) { - logDir.mkdir(fileName); - } - - fileName.append(QString("/hifi.voxelscache.")); - fileName.append(_profile.getLastDomain()); - fileName.append(QString(".svo")); - - return fileName; -} - - -void Application::updateLocalOctreeCache(bool firstTime) { - // only do this if we've already got a persistThread or we're told this is the first time - if (firstTime || _persistThread) { - - if (_persistThread) { - _persistThread->terminate(); - _persistThread->deleteLater(); - _persistThread = NULL; - } - - QString localVoxelCacheFileName = getLocalVoxelCacheFileName(); - const int LOCAL_CACHE_PERSIST_INTERVAL = 1000 * 10; // every 10 seconds - - if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableLocalVoxelCache)) { - _persistThread = new OctreePersistThread(_voxels.getTree(), - localVoxelCacheFileName.toLocal8Bit().constData(),LOCAL_CACHE_PERSIST_INTERVAL); - - qDebug() << "updateLocalOctreeCache()... localVoxelCacheFileName=" << localVoxelCacheFileName; - } - - if (_persistThread) { - _voxels.beginLoadingLocalVoxelCache(); // while local voxels are importing, don't do individual node VBO updates - connect(_persistThread, SIGNAL(loadCompleted()), &_voxels, SLOT(localVoxelCacheLoaded())); - _persistThread->initialize(true); - } - } -} - void Application::checkVersion() { QNetworkRequest latestVersionRequest((QUrl(CHECK_VERSION_URL))); latestVersionRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); diff --git a/interface/src/Application.h b/interface/src/Application.h index bf9981a160..94b5601797 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -126,8 +126,6 @@ public: void touchEndEvent(QTouchEvent* event); void touchUpdateEvent(QTouchEvent* event); - void updateWindowTitle(); - void wheelEvent(QWheelEvent* event); void makeVoxel(glm::vec3 position, @@ -170,6 +168,7 @@ public: GeometryCache* getGeometryCache() { return &_geometryCache; } TextureCache* getTextureCache() { return &_textureCache; } GlowEffect* getGlowEffect() { return &_glowEffect; } + ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; } AvatarManager& getAvatarManager() { return _avatarManager; } Profile* getProfile() { return &_profile; } @@ -213,6 +212,8 @@ signals: public slots: void domainChanged(const QString& domainHostname); + void updateWindowTitle(); + void nodeAdded(SharedNodePointer node); void nodeKilled(SharedNodePointer node); void packetSent(quint64 length); @@ -238,6 +239,7 @@ private slots: void setFullscreen(bool fullscreen); void setEnable3DTVMode(bool enable3DTVMode); + void cameraMenuChanged(); void renderThrustAtVoxel(const glm::vec3& thrust); @@ -470,8 +472,8 @@ private: PieMenu _pieMenu; - int parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderAddress); - void trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket); + int parseOctreeStats(const QByteArray& packet, const SharedNodePointer& sendingNode); + void trackIncomingVoxelPacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket); NodeToJurisdictionMap _voxelServerJurisdictions; NodeToJurisdictionMap _particleServerJurisdictions; @@ -484,11 +486,6 @@ private: FileLogger* _logger; - OctreePersistThread* _persistThread; - - QString getLocalVoxelCacheFileName(); - void updateLocalOctreeCache(bool firstTime = false); - void checkVersion(); void displayUpdateDialog(); bool shouldSkipVersion(QString latestVersion); diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 9fede84a93..9dd3c0377f 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -56,6 +56,8 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p _numOutputCallbackBytes(0), _loopbackAudioOutput(NULL), _loopbackOutputDevice(NULL), + _proceduralAudioOutput(NULL), + _proceduralOutputDevice(NULL), _inputRingBuffer(0), _ringBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL), _scope(scope), @@ -75,7 +77,7 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p _muted(false) { // clear the array of locally injected samples - memset(_localInjectedSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); + memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); } void Audio::init(QGLWidget *parent) { @@ -272,6 +274,9 @@ void Audio::start() { // setup a loopback audio output device _loopbackAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); + + // setup a procedural audio output device + _proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); gettimeofday(&_lastReceiveTime, NULL); } @@ -332,7 +337,7 @@ void Audio::handleAudioInput() { memset(monoAudioSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); // zero out the locally injected audio in preparation for audio procedural sounds - memset(_localInjectedSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); + memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); if (!_muted) { // we aren't muted, downsample the input audio @@ -363,11 +368,27 @@ void Audio::handleAudioInput() { // add procedural effects to the appropriate input samples addProceduralSounds(monoAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); + + if (!_proceduralOutputDevice) { + _proceduralOutputDevice = _proceduralAudioOutput->start(); + } + + // send whatever procedural sounds we want to locally loop back to the _proceduralOutputDevice + QByteArray proceduralOutput; + proceduralOutput.resize(NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 4 * sizeof(int16_t)); + + linearResampling(_localProceduralSamples, + reinterpret_cast(proceduralOutput.data()), + NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL, + NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 4, + _desiredInputFormat, _outputFormat); + + _proceduralOutputDevice->write(proceduralOutput); NodeList* nodeList = NodeList::getInstance(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - if (audioMixer && nodeList->getNodeActiveSocketOrPing(audioMixer.data())) { + if (audioMixer && audioMixer->getActiveSocket()) { MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar(); glm::vec3 headPosition = interfaceAvatar->getHead().getPosition(); glm::quat headOrientation = interfaceAvatar->getHead().getOrientation(); @@ -388,15 +409,14 @@ void Audio::handleAudioInput() { memcpy(currentPacketPtr, &headOrientation, sizeof(headOrientation)); currentPacketPtr += sizeof(headOrientation); - nodeList->getNodeSocket().writeDatagram(monoAudioDataPacket, - NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes, - audioMixer->getActiveSocket()->getAddress(), - audioMixer->getActiveSocket()->getPort()); + nodeList->writeDatagram(monoAudioDataPacket, + NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes, + audioMixer); Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::AUDIO) .updateValue(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL + leadingBytes); } - delete[] inputAudioSamples; + delete[] inputAudioSamples; } } @@ -431,12 +451,6 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate()) * (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount()); - - static int numRequiredOutputSamples = NETWORK_BUFFER_LENGTH_SAMPLES_STEREO / networkOutputToOutputRatio; - - QByteArray outputBuffer; - outputBuffer.resize(numRequiredOutputSamples * sizeof(int16_t)); - if (!_ringBuffer.isStarved() && _audioOutput->bytesFree() == _audioOutput->bufferSize()) { // we don't have any audio data left in the output buffer @@ -448,6 +462,14 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { // if there is anything in the ring buffer, decide what to do if (_ringBuffer.samplesAvailable() > 0) { + + + int numNetworkOutputSamples = _ringBuffer.samplesAvailable(); + int numDeviceOutputSamples = numNetworkOutputSamples / networkOutputToOutputRatio; + + QByteArray outputBuffer; + outputBuffer.resize(numDeviceOutputSamples * sizeof(int16_t)); + if (!_ringBuffer.isNotStarvedOrHasMinimumSamples(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO + (_jitterBufferSamples * 2))) { // starved and we don't have enough to start, keep waiting @@ -458,64 +480,28 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { // copy the samples we'll resample from the ring buffer - this also // pushes the read pointer of the ring buffer forwards - int16_t ringBufferSamples[NETWORK_BUFFER_LENGTH_SAMPLES_STEREO]; - _ringBuffer.readSamples(ringBufferSamples, NETWORK_BUFFER_LENGTH_SAMPLES_STEREO); - - // add the next NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL from each QByteArray - // in our _localInjectionByteArrays QVector to the _localInjectedSamples - - // add to the output samples whatever is in the _localAudioOutput byte array - // that lets this user hear sound effects and loopback (if enabled) - - for (int b = 0; b < _localInjectionByteArrays.size(); b++) { - QByteArray audioByteArray = _localInjectionByteArrays.at(b); - - int16_t* byteArraySamples = (int16_t*) audioByteArray.data(); - - int samplesToRead = qMin((int)(audioByteArray.size() / sizeof(int16_t)), - NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - - for (int i = 0; i < samplesToRead; i++) { - _localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + byteArraySamples[i], - MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); - } - - if (samplesToRead < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) { - // there isn't anything left to inject from this byte array, remove it from the vector - _localInjectionByteArrays.remove(b); - } else { - // pull out the bytes we just read for outputs - audioByteArray.remove(0, samplesToRead * sizeof(int16_t)); - - // still data left to read - replace the byte array in the QVector with the smaller one - _localInjectionByteArrays.replace(b, audioByteArray); - } - } - - for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) { - ringBufferSamples[i * 2] = glm::clamp(ringBufferSamples[i * 2] + _localInjectedSamples[i], - MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); - ringBufferSamples[(i * 2) + 1] = glm::clamp(ringBufferSamples[(i * 2) + 1] + _localInjectedSamples[i], - MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); - } + int16_t* ringBufferSamples= new int16_t[numNetworkOutputSamples]; + _ringBuffer.readSamples(ringBufferSamples, numNetworkOutputSamples); + + // add the next numNetworkOutputSamples from each QByteArray + // in our _localInjectionByteArrays QVector to the localInjectedSamples // copy the packet from the RB to the output linearResampling(ringBufferSamples, (int16_t*) outputBuffer.data(), - NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, - numRequiredOutputSamples, + numNetworkOutputSamples, + numDeviceOutputSamples, _desiredOutputFormat, _outputFormat); if (_outputDevice) { - _outputDevice->write(outputBuffer); // add output (@speakers) data just written to the scope QMetaObject::invokeMethod(_scope, "addSamples", Qt::QueuedConnection, - Q_ARG(QByteArray, QByteArray((char*) ringBufferSamples, - NETWORK_BUFFER_LENGTH_BYTES_STEREO)), + Q_ARG(QByteArray, QByteArray((char*) ringBufferSamples, numNetworkOutputSamples)), Q_ARG(bool, true), Q_ARG(bool, false)); } + delete[] ringBufferSamples; } } @@ -672,7 +658,7 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) { int16_t collisionSample = (int16_t) sample; monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); - _localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + collisionSample, + _localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); _collisionSoundMagnitude *= _collisionSoundDuration; @@ -696,7 +682,7 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) { int16_t collisionSample = (int16_t) sample; monoInput[i] = glm::clamp(monoInput[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); - _localInjectedSamples[i] = glm::clamp(_localInjectedSamples[i] + collisionSample, + _localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); _drumSoundVolume *= (1.f - _drumSoundDecay); @@ -727,8 +713,8 @@ void Audio::startDrumSound(float volume, float frequency, float duration, float } void Audio::handleAudioByteArray(const QByteArray& audioByteArray) { - // add this byte array to our QVector - _localInjectionByteArrays.append(audioByteArray); + // TODO: either create a new audio device (up to the limit of the sound card or a hard limit) + // or send to the mixer and use delayed loopback } void Audio::renderToolIcon(int screenHeight) { diff --git a/interface/src/Audio.h b/interface/src/Audio.h index cc5a43d8e0..fa4fb62adf 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -86,8 +86,7 @@ private: QAudioFormat _inputFormat; QIODevice* _inputDevice; int _numInputCallbackBytes; - int16_t _localInjectedSamples[NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL]; - QVector _localInjectionByteArrays; + int16_t _localProceduralSamples[NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL]; QAudioOutput* _audioOutput; QAudioFormat _desiredOutputFormat; QAudioFormat _outputFormat; @@ -95,6 +94,8 @@ private: int _numOutputCallbackBytes; QAudioOutput* _loopbackAudioOutput; QIODevice* _loopbackOutputDevice; + QAudioOutput* _proceduralAudioOutput; + QIODevice* _proceduralOutputDevice; AudioRingBuffer _inputRingBuffer; AudioRingBuffer _ringBuffer; diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 94bc693c2b..87725967b9 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -8,8 +8,11 @@ #include #include +#include +#include "Application.h" #include "Camera.h" +#include "Menu.h" #include "Util.h" const float CAMERA_MINIMUM_MODE_SHIFT_RATE = 0.5f; @@ -18,6 +21,10 @@ const float CAMERA_FIRST_PERSON_MODE_UP_SHIFT = 0.0f; const float CAMERA_FIRST_PERSON_MODE_DISTANCE = 0.0f; const float CAMERA_FIRST_PERSON_MODE_TIGHTNESS = 100.0f; +const float CAMERA_INDEPENDENT_MODE_UP_SHIFT = 0.0f; +const float CAMERA_INDEPENDENT_MODE_DISTANCE = 0.0f; +const float CAMERA_INDEPENDENT_MODE_TIGHTNESS = 100.0f; + const float CAMERA_THIRD_PERSON_MODE_UP_SHIFT = -0.2f; const float CAMERA_THIRD_PERSON_MODE_DISTANCE = 1.5f; const float CAMERA_THIRD_PERSON_MODE_TIGHTNESS = 8.0f; @@ -27,32 +34,32 @@ const float CAMERA_MIRROR_MODE_DISTANCE = 0.17f; const float CAMERA_MIRROR_MODE_TIGHTNESS = 100.0f; -Camera::Camera() { - - _needsToInitialize = true; - _frustumNeedsReshape = true; - - _modeShift = 1.0f; - _modeShiftRate = 1.0f; - _linearModeShift = 0.0f; - _mode = CAMERA_MODE_THIRD_PERSON; - _tightness = 10.0f; // default - _fieldOfView = DEFAULT_FIELD_OF_VIEW_DEGREES; - _aspectRatio = 16.f/9.f; - _nearClip = 0.08f; // default - _farClip = 50.0f * TREE_SCALE; // default - _upShift = 0.0f; - _distance = 0.0f; - _previousUpShift = 0.0f; - _previousDistance = 0.0f; - _previousTightness = 0.0f; - _newUpShift = 0.0f; - _newDistance = 0.0f; - _newTightness = 0.0f; - _targetPosition = glm::vec3(0.0f, 0.0f, 0.0f); - _position = glm::vec3(0.0f, 0.0f, 0.0f); - _idealPosition = glm::vec3(0.0f, 0.0f, 0.0f); - _scale = 1.0f; +Camera::Camera() : + _needsToInitialize(true), + _mode(CAMERA_MODE_THIRD_PERSON), + _prevMode(CAMERA_MODE_THIRD_PERSON), + _frustumNeedsReshape(true), + _position(0.0f, 0.0f, 0.0f), + _idealPosition(0.0f, 0.0f, 0.0f), + _targetPosition(0.0f, 0.0f, 0.0f), + _fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES), + _aspectRatio(16.f/9.f), + _nearClip(0.08f), // default + _farClip(50.0f * TREE_SCALE), // default + _upShift(0.0f), + _distance(0.0f), + _tightness(10.0f), // default + _previousUpShift(0.0f), + _previousDistance(0.0f), + _previousTightness(0.0f), + _newUpShift(0.0f), + _newDistance(0.0f), + _newTightness(0.0f), + _modeShift(1.0f), + _linearModeShift(0.0f), + _modeShiftRate(1.0f), + _scale(1.0f) +{ } void Camera::update(float deltaTime) { @@ -65,12 +72,9 @@ void Camera::update(float deltaTime) { // use iterative forces to keep the camera at the desired position and angle void Camera::updateFollowMode(float deltaTime) { - if (_linearModeShift < 1.0f) { _linearModeShift += _modeShiftRate * deltaTime; - _modeShift = ONE_HALF - ONE_HALF * cosf(_linearModeShift * PIE ); - _upShift = _previousUpShift * (1.0f - _modeShift) + _newUpShift * _modeShift; _distance = _previousDistance * (1.0f - _modeShift) + _newDistance * _modeShift; _tightness = _previousTightness * (1.0f - _modeShift) + _newTightness * _modeShift; @@ -97,7 +101,6 @@ void Camera::updateFollowMode(float deltaTime) { _idealPosition = _targetPosition + _scale * (_rotation * glm::vec3(0.0f, _upShift, _distance)); _position = _idealPosition; _needsToInitialize = false; - } else { // pull rotation towards ideal _rotation = safeMix(_rotation, _targetRotation, t); @@ -123,6 +126,7 @@ void Camera::setModeShiftRate ( float rate ) { void Camera::setMode(CameraMode m) { + _prevMode = _mode; _mode = m; _modeShift = 0.0; _linearModeShift = 0.0; @@ -135,16 +139,19 @@ void Camera::setMode(CameraMode m) { _newUpShift = CAMERA_THIRD_PERSON_MODE_UP_SHIFT; _newDistance = CAMERA_THIRD_PERSON_MODE_DISTANCE; _newTightness = CAMERA_THIRD_PERSON_MODE_TIGHTNESS; - } else if (_mode == CAMERA_MODE_FIRST_PERSON) { _newUpShift = CAMERA_FIRST_PERSON_MODE_UP_SHIFT; _newDistance = CAMERA_FIRST_PERSON_MODE_DISTANCE; _newTightness = CAMERA_FIRST_PERSON_MODE_TIGHTNESS; - } else if (_mode == CAMERA_MODE_MIRROR) { _newUpShift = CAMERA_MIRROR_MODE_UP_SHIFT; _newDistance = CAMERA_MIRROR_MODE_DISTANCE; _newTightness = CAMERA_MIRROR_MODE_TIGHTNESS; + } else if (_mode == CAMERA_MODE_INDEPENDENT) { + _newUpShift = CAMERA_INDEPENDENT_MODE_UP_SHIFT; + _newDistance = CAMERA_INDEPENDENT_MODE_DISTANCE; + _newTightness = CAMERA_INDEPENDENT_MODE_TIGHTNESS; + } } @@ -163,22 +170,22 @@ void Camera::setAspectRatio(float a) { _frustumNeedsReshape = true; } -void Camera::setNearClip (float n) { +void Camera::setNearClip(float n) { _nearClip = n; _frustumNeedsReshape = true; } -void Camera::setFarClip (float f) { +void Camera::setFarClip(float f) { _farClip = f; _frustumNeedsReshape = true; } -void Camera::setEyeOffsetPosition (const glm::vec3& p) { +void Camera::setEyeOffsetPosition(const glm::vec3& p) { _eyeOffsetPosition = p; _frustumNeedsReshape = true; } -void Camera::setEyeOffsetOrientation (const glm::quat& o) { +void Camera::setEyeOffsetOrientation(const glm::quat& o) { _eyeOffsetOrientation = o; _frustumNeedsReshape = true; } @@ -199,10 +206,82 @@ bool Camera::getFrustumNeedsReshape() const { return _frustumNeedsReshape; } +// call this when deciding whether to render the head or not +CameraMode Camera::getInterpolatedMode() const { + const float SHIFT_THRESHOLD_INTO_FIRST_PERSON = 0.7f; + const float SHIFT_THRESHOLD_OUT_OF_FIRST_PERSON = 0.6f; + if ((_mode == CAMERA_MODE_FIRST_PERSON && _linearModeShift < SHIFT_THRESHOLD_INTO_FIRST_PERSON) || + (_prevMode == CAMERA_MODE_FIRST_PERSON && _linearModeShift < SHIFT_THRESHOLD_OUT_OF_FIRST_PERSON)) { + return _prevMode; + } + return _mode; +} + // call this after reshaping the view frustum void Camera::setFrustumWasReshaped() { _frustumNeedsReshape = false; } +CameraScriptableObject::CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum) : + _camera(camera), _viewFrustum(viewFrustum) +{ +} + +PickRay CameraScriptableObject::computePickRay(float x, float y) { + float screenWidth = Application::getInstance()->getGLWidget()->width(); + float screenHeight = Application::getInstance()->getGLWidget()->height(); + PickRay result; + _viewFrustum->computePickRay(x / screenWidth, y / screenHeight, result.origin, result.direction); + return result; +} + +QString CameraScriptableObject::getMode() const { + QString mode("unknown"); + switch(_camera->getMode()) { + case CAMERA_MODE_THIRD_PERSON: + mode = "third person"; + break; + case CAMERA_MODE_FIRST_PERSON: + mode = "first person"; + break; + case CAMERA_MODE_MIRROR: + mode = "mirror"; + break; + case CAMERA_MODE_INDEPENDENT: + mode = "independent"; + break; + default: + break; + } + return mode; +} + +void CameraScriptableObject::setMode(const QString& mode) { + CameraMode currentMode = _camera->getMode(); + CameraMode targetMode = currentMode; + if (mode == "third person") { + targetMode = CAMERA_MODE_THIRD_PERSON; + Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); + } else if (mode == "first person") { + targetMode = CAMERA_MODE_FIRST_PERSON; + Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true); + } else if (mode == "mirror") { + targetMode = CAMERA_MODE_MIRROR; + Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); + } else if (mode == "independent") { + targetMode = CAMERA_MODE_INDEPENDENT; + Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, false); + Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false); + } + if (currentMode != targetMode) { + _camera->setMode(targetMode); + _camera->setModeShiftRate(10.0f); + } +} + + diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 615135bc80..075f1a7ed6 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -10,8 +10,10 @@ #include #include +#include +#include -const float DEFAULT_FIELD_OF_VIEW_DEGREES = 90.0f; +const float DEFAULT_FIELD_OF_VIEW_DEGREES = 90.0f; enum CameraMode { @@ -19,11 +21,12 @@ enum CameraMode CAMERA_MODE_THIRD_PERSON, CAMERA_MODE_FIRST_PERSON, CAMERA_MODE_MIRROR, + CAMERA_MODE_INDEPENDENT, NUM_CAMERA_MODES }; -class Camera -{ +class Camera { + public: Camera(); @@ -31,70 +34,95 @@ public: void update( float deltaTime ); - void setUpShift ( float u ) { _upShift = u; } - void setDistance ( float d ) { _distance = d; } - void setTargetPosition( const glm::vec3& t ) { _targetPosition = t; } - void setPosition ( const glm::vec3& p ) { _position = p; } - void setTightness ( float t ) { _tightness = t; } - void setTargetRotation( const glm::quat& rotation ); + void setUpShift(float u) { _upShift = u; } + void setDistance(float d) { _distance = d; } + void setPosition(const glm::vec3& p) { _position = p; } + void setTargetPosition(const glm::vec3& t) { _targetPosition = t; } + void setTightness(float t) { _tightness = t; } + void setTargetRotation(const glm::quat& rotation); - void setMode ( CameraMode m ); - void setModeShiftRate ( float r ); - void setFieldOfView ( float f ); - void setAspectRatio ( float a ); - void setNearClip ( float n ); - void setFarClip ( float f ); - void setEyeOffsetPosition ( const glm::vec3& p ); - void setEyeOffsetOrientation( const glm::quat& o ); - void setScale ( const float s ); + void setMode(CameraMode m); + void setModeShiftRate(float r); + void setFieldOfView(float f); + void setAspectRatio(float a); + void setNearClip(float n); + void setFarClip(float f); + void setEyeOffsetPosition(const glm::vec3& p); + void setEyeOffsetOrientation(const glm::quat& o); + void setScale(const float s); - const glm::vec3& getTargetPosition () const { return _targetPosition; } - const glm::vec3& getPosition () const { return _position; } - const glm::quat& getTargetRotation () const { return _targetRotation; } - const glm::quat& getRotation () const { return _rotation; } - CameraMode getMode () const { return _mode; } - float getFieldOfView () const { return _fieldOfView; } - float getAspectRatio () const { return _aspectRatio; } - float getNearClip () const { return _scale * _nearClip; } - float getFarClip () const; - const glm::vec3& getEyeOffsetPosition () const { return _eyeOffsetPosition; } - const glm::quat& getEyeOffsetOrientation () const { return _eyeOffsetOrientation; } - float getScale () const { return _scale; } + const glm::vec3& getPosition() const { return _position; } + const glm::quat& getRotation() const { return _rotation; } + CameraMode getMode() const { return _mode; } + const glm::vec3& getTargetPosition() const { return _targetPosition; } + const glm::quat& getTargetRotation() const { return _targetRotation; } + float getFieldOfView() const { return _fieldOfView; } + float getAspectRatio() const { return _aspectRatio; } + float getNearClip() const { return _scale * _nearClip; } + float getFarClip() const; + const glm::vec3& getEyeOffsetPosition() const { return _eyeOffsetPosition; } + const glm::quat& getEyeOffsetOrientation() const { return _eyeOffsetOrientation; } + float getScale() const { return _scale; } + CameraMode getInterpolatedMode() const; + bool getFrustumNeedsReshape() const; // call to find out if the view frustum needs to be reshaped void setFrustumWasReshaped(); // call this after reshaping the view frustum. - + private: - bool _needsToInitialize; - CameraMode _mode; - bool _frustumNeedsReshape; - glm::vec3 _position; - glm::vec3 _idealPosition; - glm::vec3 _targetPosition; - float _fieldOfView; - float _aspectRatio; - float _nearClip; - float _farClip; - glm::vec3 _eyeOffsetPosition; - glm::quat _eyeOffsetOrientation; - glm::quat _rotation; - glm::quat _targetRotation; - float _upShift; - float _distance; - float _tightness; - float _previousUpShift; - float _previousDistance; - float _previousTightness; - float _newUpShift; - float _newDistance; - float _newTightness; - float _modeShift; - float _linearModeShift; - float _modeShiftRate; - float _scale; - - void updateFollowMode( float deltaTime ); + bool _needsToInitialize; + CameraMode _mode; + CameraMode _prevMode; + bool _frustumNeedsReshape; + glm::vec3 _position; + glm::vec3 _idealPosition; + glm::vec3 _targetPosition; + float _fieldOfView; + float _aspectRatio; + float _nearClip; + float _farClip; + glm::vec3 _eyeOffsetPosition; + glm::quat _eyeOffsetOrientation; + glm::quat _rotation; + glm::quat _targetRotation; + float _upShift; + float _distance; + float _tightness; + float _previousUpShift; + float _previousDistance; + float _previousTightness; + float _newUpShift; + float _newDistance; + float _newTightness; + float _modeShift; + float _linearModeShift; + float _modeShiftRate; + float _scale; + + void updateFollowMode(float deltaTime); }; + +class CameraScriptableObject : public QObject { + Q_OBJECT +public: + CameraScriptableObject(Camera* camera, ViewFrustum* viewFrustum); + +public slots: + QString getMode() const; + void setMode(const QString& mode); + void setPosition(const glm::vec3& value) { _camera->setTargetPosition(value);} + + glm::vec3 getPosition() const { return _camera->getPosition(); } + + void setOrientation(const glm::quat& value) { _camera->setTargetRotation(value); } + glm::quat getOrientation() const { return _camera->getRotation(); } + + PickRay computePickRay(float x, float y); + +private: + Camera* _camera; + ViewFrustum* _viewFrustum; +}; #endif diff --git a/interface/src/ControllerScriptingInterface.cpp b/interface/src/ControllerScriptingInterface.cpp index 4d7540c06c..8716116877 100644 --- a/interface/src/ControllerScriptingInterface.cpp +++ b/interface/src/ControllerScriptingInterface.cpp @@ -219,3 +219,19 @@ void ControllerScriptingInterface::releaseKeyEvents(const KeyEvent& event) { } } +bool ControllerScriptingInterface::isJoystickCaptured(int joystickIndex) const { + return _capturedJoysticks.contains(joystickIndex); +} + +void ControllerScriptingInterface::captureJoystick(int joystickIndex) { + if (!isJoystickCaptured(joystickIndex)) { + _capturedJoysticks.insert(joystickIndex); + } +} + +void ControllerScriptingInterface::releaseJoystick(int joystickIndex) { + if (isJoystickCaptured(joystickIndex)) { + _capturedJoysticks.remove(joystickIndex); + } +} + diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/ControllerScriptingInterface.h index e84039bcb0..1446809a13 100644 --- a/interface/src/ControllerScriptingInterface.h +++ b/interface/src/ControllerScriptingInterface.h @@ -38,7 +38,7 @@ public: bool isMouseCaptured() const { return _mouseCaptured; } bool isTouchCaptured() const { return _touchCaptured; } bool isWheelCaptured() const { return _wheelCaptured; } - + bool isJoystickCaptured(int joystickIndex) const; public slots: virtual bool isPrimaryButtonPressed() const; @@ -70,6 +70,9 @@ public slots: virtual void captureWheelEvents() { _wheelCaptured = true; } virtual void releaseWheelEvents() { _wheelCaptured = false; } + virtual void captureJoystick(int joystickIndex); + virtual void releaseJoystick(int joystickIndex); + private: const PalmData* getPrimaryPalm() const; const PalmData* getPalm(int palmIndex) const; @@ -80,6 +83,7 @@ private: bool _touchCaptured; bool _wheelCaptured; QMultiMap _capturedKeys; + QSet _capturedJoysticks; }; const int NUMBER_OF_SPATIALCONTROLS_PER_PALM = 2; // the hand and the tip diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 52e8b72ab2..6e0c1bf239 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -40,7 +40,7 @@ void DatagramProcessor::processDatagrams() { _packetCount++; _byteCount += incomingPacket.size(); - if (packetVersionMatch(incomingPacket)) { + if (nodeList->packetVersionAndHashMatch(incomingPacket)) { // only process this packet if we have a match on the packet version switch (packetTypeForPacket(incomingPacket)) { case PacketTypeTransmitterData: @@ -84,31 +84,31 @@ void DatagramProcessor::processDatagrams() { printf("got PacketType_VOXEL_DATA, sequence:%d flightTime:%d\n", sequence, flightTime); } - // add this packet to our list of voxel packets and process them on the voxel processing - application->_voxelProcessor.queueReceivedPacket(senderSockAddr, incomingPacket); + SharedNodePointer matchedNode = NodeList::getInstance()->sendingNodeForPacket(incomingPacket); + + if (matchedNode) { + // add this packet to our list of voxel packets and process them on the voxel processing + application->_voxelProcessor.queueReceivedPacket(matchedNode, incomingPacket); + } + break; } case PacketTypeMetavoxelData: application->_metavoxels.processData(incomingPacket, senderSockAddr); break; case PacketTypeBulkAvatarData: - case PacketTypeKillAvatar: { + case PacketTypeKillAvatar: + case PacketTypeAvatarIdentity: { // update having heard from the avatar-mixer and record the bytes received - SharedNodePointer avatarMixer = NodeList::getInstance()->nodeWithAddress(senderSockAddr); + SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket); if (avatarMixer) { avatarMixer->setLastHeardMicrostamp(usecTimestampNow()); avatarMixer->recordBytesReceived(incomingPacket.size()); - if (packetTypeForPacket(incomingPacket) == PacketTypeBulkAvatarData) { - QMetaObject::invokeMethod(&application->getAvatarManager(), "processAvatarMixerDatagram", - Q_ARG(const QByteArray&, incomingPacket), - Q_ARG(const QWeakPointer&, avatarMixer)); - } else { - // this is an avatar kill, pass it to the application AvatarManager - QMetaObject::invokeMethod(&application->getAvatarManager(), "processKillAvatar", - Q_ARG(const QByteArray&, incomingPacket)); - } + QMetaObject::invokeMethod(&application->getAvatarManager(), "processAvatarMixerDatagram", + Q_ARG(const QByteArray&, incomingPacket), + Q_ARG(const QWeakPointer&, avatarMixer)); } application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size()); @@ -124,7 +124,7 @@ void DatagramProcessor::processDatagrams() { DataServerClient::processMessageFromDataServer(incomingPacket); break; default: - NodeList::getInstance()->processNodeData(senderSockAddr, incomingPacket); + nodeList->processNodeData(senderSockAddr, incomingPacket); break; } } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 3b2b09fcd7..1d3c1cf589 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -239,9 +239,12 @@ Menu::Menu() : false, appInstance, SLOT(setFullscreen(bool))); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FirstPerson, Qt::Key_P, true, + appInstance,SLOT(cameraMenuChanged())); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Mirror, Qt::SHIFT | Qt::Key_H); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::FullscreenMirror, Qt::Key_H,false, + appInstance,SLOT(cameraMenuChanged())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Enable3DTVMode, 0, false, appInstance, @@ -324,7 +327,6 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges); addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools())); - addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableLocalVoxelCache); QMenu* voxelProtoOptionsMenu = voxelOptionsMenu->addMenu("Voxel Server Protocol Options"); @@ -698,6 +700,9 @@ void Menu::removeAction(QMenu* menu, const QString& actionName) { menu->removeAction(_actionHash.value(actionName)); } +void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) { + return _actionHash.value(menuOption)->setChecked(isChecked); +} bool Menu::isOptionChecked(const QString& menuOption) { return _actionHash.value(menuOption)->isChecked(); @@ -768,12 +773,12 @@ void Menu::editPreferences() { QFormLayout* form = new QFormLayout(); layout->addLayout(form, 1); - QString faceURLString = applicationInstance->getProfile()->getFaceModelURL().toString(); + QString faceURLString = applicationInstance->getAvatar()->getHead().getFaceModel().getURL().toString(); QLineEdit* faceURLEdit = new QLineEdit(faceURLString); faceURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); form->addRow("Face URL:", faceURLEdit); - QString skeletonURLString = applicationInstance->getProfile()->getSkeletonModelURL().toString(); + QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString(); QLineEdit* skeletonURLEdit = new QLineEdit(skeletonURLString); skeletonURLEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); form->addRow("Skeleton URL:", skeletonURLEdit); @@ -834,27 +839,25 @@ void Menu::editPreferences() { int ret = dialog.exec(); if (ret == QDialog::Accepted) { QUrl faceModelURL(faceURLEdit->text()); + + bool shouldDispatchIdentityPacket = false; if (faceModelURL.toString() != faceURLString) { // change the faceModelURL in the profile, it will also update this user's BlendFace - applicationInstance->getProfile()->setFaceModelURL(faceModelURL); - - // send the new face mesh URL to the data-server (if we have a client UUID) - DataServerClient::putValueForKeyAndUserString(DataServerKey::FaceMeshURL, - faceModelURL.toString().toLocal8Bit().constData(), - applicationInstance->getProfile()->getUserString()); + applicationInstance->getAvatar()->setFaceModelURL(faceModelURL); + shouldDispatchIdentityPacket = true; } QUrl skeletonModelURL(skeletonURLEdit->text()); if (skeletonModelURL.toString() != skeletonURLString) { // change the skeletonModelURL in the profile, it will also update this user's Body - applicationInstance->getProfile()->setSkeletonModelURL(skeletonModelURL); - - // send the new skeleton model URL to the data-server (if we have a client UUID) - DataServerClient::putValueForKeyAndUserString(DataServerKey::SkeletonURL, - skeletonModelURL.toString().toLocal8Bit().constData(), - applicationInstance->getProfile()->getUserString()); + applicationInstance->getAvatar()->setSkeletonModelURL(skeletonModelURL); + shouldDispatchIdentityPacket = true; + } + + if (shouldDispatchIdentityPacket) { + applicationInstance->getAvatar()->sendIdentityPacket(); } applicationInstance->getAvatar()->getHead().setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum()); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 08306f8dd0..8a75d49ab9 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -49,6 +49,7 @@ public: ~Menu(); bool isOptionChecked(const QString& menuOption); + void setIsOptionChecked(const QString& menuOption, bool isChecked); void triggerOption(const QString& menuOption); QAction* getActionForOption(const QString& menuOption); bool isVoxelModeActionChecked(); @@ -179,7 +180,6 @@ namespace MenuOption { const QString DestructiveAddVoxel = "Create Voxel is Destructive"; const QString DisableColorVoxels = "Disable Colored Voxels"; const QString DisableDeltaSending = "Disable Delta Sending"; - const QString DisableLocalVoxelCache = "Disable Local Voxel Cache"; const QString DisableLowRes = "Disable Lower Resolution While Moving"; const QString DisplayFrustum = "Display Frustum"; const QString DisplayLeapHands = "Display Leap Hands"; diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 311c8f2556..34c3be5308 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "Application.h" #include "MetavoxelSystem.h" @@ -37,6 +38,9 @@ void MetavoxelSystem::init() { _program.link(); _pointScaleLocation = _program.uniformLocation("pointScale"); + + // let the script cache know to use our common access manager + ScriptCache::getInstance()->setNetworkAccessManager(Application::getInstance()->getNetworkAccessManager()); } NodeList* nodeList = NodeList::getInstance(); @@ -140,9 +144,9 @@ void MetavoxelSystem::removeClient(const QUuid& uuid) { delete client; } -void MetavoxelSystem::receivedData(const QByteArray& data, const HifiSockAddr& sender) { +void MetavoxelSystem::receivedData(const QByteArray& data, const SharedNodePointer& sendingNode) { int headerPlusIDSize; - QUuid sessionID = readSessionID(data, sender, headerPlusIDSize); + QUuid sessionID = readSessionID(data, sendingNode, headerPlusIDSize); if (sessionID.isNull()) { return; } @@ -226,10 +230,7 @@ void MetavoxelClient::receivedData(const QByteArray& data) { void MetavoxelClient::sendData(const QByteArray& data) { QMutexLocker locker(&_node->getMutex()); - const HifiSockAddr* address = _node->getActiveSocket(); - if (address) { - NodeList::getInstance()->getNodeSocket().writeDatagram(data, address->getAddress(), address->getPort()); - } + NodeList::getInstance()->writeDatagram(data, _node); } void MetavoxelClient::readPacket(Bitstream& in) { diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 0956a9d0d3..7bb7935e4d 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -52,7 +52,7 @@ private: Q_INVOKABLE void addClient(const SharedNodePointer& node); Q_INVOKABLE void removeClient(const QUuid& uuid); - Q_INVOKABLE void receivedData(const QByteArray& data, const HifiSockAddr& sender); + Q_INVOKABLE void receivedData(const QByteArray& data, const SharedNodePointer& sendingNode); class Point { public: diff --git a/interface/src/ParticleTreeRenderer.cpp b/interface/src/ParticleTreeRenderer.cpp index 7a1991ca93..d9d0849c94 100644 --- a/interface/src/ParticleTreeRenderer.cpp +++ b/interface/src/ParticleTreeRenderer.cpp @@ -123,7 +123,6 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg } } -void ParticleTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, - Node* sourceNode) { - static_cast(_tree)->processEraseMessage(dataByteArray, senderSockAddr, sourceNode); +void ParticleTreeRenderer::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) { + static_cast(_tree)->processEraseMessage(dataByteArray, sourceNode); } diff --git a/interface/src/ParticleTreeRenderer.h b/interface/src/ParticleTreeRenderer.h index 48b15f9c67..fe346ae730 100644 --- a/interface/src/ParticleTreeRenderer.h +++ b/interface/src/ParticleTreeRenderer.h @@ -38,7 +38,7 @@ public: ParticleTree* getTree() { return (ParticleTree*)_tree; } - void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode); + void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); virtual void init(); virtual void render(); diff --git a/interface/src/VoxelPacketProcessor.cpp b/interface/src/VoxelPacketProcessor.cpp index bdb934430c..dce391e587 100644 --- a/interface/src/VoxelPacketProcessor.cpp +++ b/interface/src/VoxelPacketProcessor.cpp @@ -14,7 +14,7 @@ #include "Menu.h" #include "VoxelPacketProcessor.h" -void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { +void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "VoxelPacketProcessor::processPacket()"); @@ -44,11 +44,11 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, con // then process any remaining bytes as if it was another packet if (voxelPacketType == PacketTypeOctreeStats) { - int statsMessageLength = app->parseOctreeStats(mutablePacket, senderSockAddr); + int statsMessageLength = app->parseOctreeStats(mutablePacket, sendingNode); wasStatsPacket = true; if (messageLength > statsMessageLength) { mutablePacket = mutablePacket.mid(statsMessageLength); - if (!packetVersionMatch(packet)) { + if (!NodeList::getInstance()->packetVersionAndHashMatch(packet)) { return; // bail since piggyback data doesn't match our versioning } } else { @@ -60,26 +60,25 @@ void VoxelPacketProcessor::processPacket(const HifiSockAddr& senderSockAddr, con voxelPacketType = packetTypeForPacket(mutablePacket); if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { - app->trackIncomingVoxelPacket(mutablePacket, senderSockAddr, wasStatsPacket); + app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket); - SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr); - if (serverNode && serverNode->getActiveSocket() && *serverNode->getActiveSocket() == senderSockAddr) { + if (sendingNode) { switch(voxelPacketType) { case PacketTypeParticleErase: { - app->_particles.processEraseMessage(mutablePacket, senderSockAddr, serverNode.data()); + app->_particles.processEraseMessage(mutablePacket, sendingNode); } break; case PacketTypeParticleData: { - app->_particles.processDatagram(mutablePacket, senderSockAddr, serverNode.data()); + app->_particles.processDatagram(mutablePacket, sendingNode); } break; case PacketTypeEnvironmentData: { - app->_environment.parseData(senderSockAddr, mutablePacket); + app->_environment.parseData(*sendingNode->getActiveSocket(), mutablePacket); } break; default : { - app->_voxels.setDataSourceUUID(serverNode->getUUID()); + app->_voxels.setDataSourceUUID(sendingNode->getUUID()); app->_voxels.parseData(mutablePacket); app->_voxels.setDataSourceUUID(QUuid()); } break; diff --git a/interface/src/VoxelPacketProcessor.h b/interface/src/VoxelPacketProcessor.h index e0c3e9f542..42040fe446 100644 --- a/interface/src/VoxelPacketProcessor.h +++ b/interface/src/VoxelPacketProcessor.h @@ -17,6 +17,6 @@ /// the user is responsible for reading inbound packets and adding them to the processing queue by calling queueReceivedPacket() class VoxelPacketProcessor : public ReceivedPacketProcessor { protected: - virtual void processPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet); + virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet); }; #endif // __shared__VoxelPacketProcessor__ diff --git a/interface/src/VoxelSystem.cpp b/interface/src/VoxelSystem.cpp index 2342aa9de1..1ffc6c3e5e 100644 --- a/interface/src/VoxelSystem.cpp +++ b/interface/src/VoxelSystem.cpp @@ -99,8 +99,6 @@ VoxelSystem::VoxelSystem(float treeScale, int maxVoxels) _culledOnce = false; _inhideOutOfView = false; - _treeIsBusy = false; - } void VoxelSystem::elementDeleted(OctreeElement* element) { @@ -594,9 +592,10 @@ int VoxelSystem::parseData(const QByteArray& packet) { } if (sectionLength) { + PerformanceWarning warn(showTimingDetails, "VoxelSystem::parseData() section"); // ask the VoxelTree to read the bitstream into the tree ReadBitstreamToTreeParams args(packetIsColored ? WANT_COLOR : NO_COLOR, WANT_EXISTS_BITS, NULL, getDataSourceUUID()); - lockTree(); + _tree->lockForWrite(); VoxelPacketData packetData(packetIsCompressed); packetData.loadFinalizedContent(dataAt, sectionLength); if (Application::getInstance()->getLogger()->extraDebugging()) { @@ -608,7 +607,7 @@ int VoxelSystem::parseData(const QByteArray& packet) { packetData.getUncompressedSize()); } _tree->readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); - unlockTree(); + _tree->unlock(); dataBytes -= sectionLength; dataAt += sectionLength; @@ -1395,9 +1394,11 @@ void VoxelSystem::removeScaleAndReleaseProgram(bool texture) { int VoxelSystem::_nodeCount = 0; void VoxelSystem::killLocalVoxels() { - lockTree(); + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "VoxelSystem::killLocalVoxels()"); + _tree->lockForWrite(); _tree->eraseAllOctreeElements(); - unlockTree(); + _tree->unlock(); clearFreeBufferIndexes(); _voxelsInReadArrays = 0; // do we need to do this? setupNewVoxelsForDrawing(); @@ -1416,10 +1417,12 @@ bool VoxelSystem::clearAllNodesBufferIndexOperation(OctreeElement* element, void } void VoxelSystem::clearAllNodesBufferIndex() { + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "VoxelSystem::clearAllNodesBufferIndex()"); _nodeCount = 0; - lockTree(); + _tree->lockForRead(); // we won't change the tree so it's ok to treat this as a read _tree->recurseTreeWithOperation(clearAllNodesBufferIndexOperation); - unlockTree(); + _tree->unlock(); if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { qDebug("clearing buffer index of %d nodes", _nodeCount); } @@ -1481,7 +1484,8 @@ bool VoxelSystem::trueColorizeOperation(OctreeElement* element, void* extraData) } void VoxelSystem::trueColorize() { - PerformanceWarning warn(true, "trueColorize()",true); + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "trueColorize()",true); _nodeCount = 0; _tree->recurseTreeWithOperation(trueColorizeOperation); qDebug("setting true color for %d nodes", _nodeCount); @@ -1951,9 +1955,13 @@ void VoxelSystem::hideOutOfView(bool forceFullFrustum) { return; } - lockTree(); - _tree->recurseTreeWithOperation(hideOutOfViewOperation,(void*)&args); - unlockTree(); + { + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "VoxelSystem::... recurseTreeWithOperation(hideOutOfViewOperation)"); + _tree->lockForRead(); + _tree->recurseTreeWithOperation(hideOutOfViewOperation,(void*)&args); + _tree->unlock(); + } _lastCulledViewFrustum = args.thisViewFrustum; // save last stable _culledOnce = true; @@ -2150,35 +2158,47 @@ bool VoxelSystem::hideOutOfViewOperation(OctreeElement* element, void* extraData bool VoxelSystem::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, VoxelDetail& detail, float& distance, BoxFace& face) { - lockTree(); - OctreeElement* element; - if (!_tree->findRayIntersection(origin, direction, element, distance, face)) { - unlockTree(); - return false; + + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "VoxelSystem::findRayIntersection()"); + bool result = false; // assume no intersection + if (_tree->tryLockForRead()) { + OctreeElement* element; + result = _tree->findRayIntersection(origin, direction, element, distance, face); + if (result) { + VoxelTreeElement* voxel = (VoxelTreeElement*)element; + detail.x = voxel->getCorner().x; + detail.y = voxel->getCorner().y; + detail.z = voxel->getCorner().z; + detail.s = voxel->getScale(); + detail.red = voxel->getColor()[0]; + detail.green = voxel->getColor()[1]; + detail.blue = voxel->getColor()[2]; + } + _tree->unlock(); } - VoxelTreeElement* voxel = (VoxelTreeElement*)element; - detail.x = voxel->getCorner().x; - detail.y = voxel->getCorner().y; - detail.z = voxel->getCorner().z; - detail.s = voxel->getScale(); - detail.red = voxel->getColor()[0]; - detail.green = voxel->getColor()[1]; - detail.blue = voxel->getColor()[2]; - unlockTree(); - return true; + return result; } bool VoxelSystem::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) { - lockTree(); - bool result = _tree->findSpherePenetration(center, radius, penetration); - unlockTree(); + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "VoxelSystem::findSpherePenetration()"); + bool result = false; // assume no penetration + if (_tree->tryLockForRead()) { + result = _tree->findSpherePenetration(center, radius, penetration); + _tree->unlock(); + } return result; } bool VoxelSystem::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) { - lockTree(); - bool result = _tree->findCapsulePenetration(start, end, radius, penetration); - unlockTree(); + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "VoxelSystem::findCapsulePenetration()"); + bool result = false; // assume no penetration + if (_tree->tryLockForRead()) { + result = _tree->findCapsulePenetration(start, end, radius, penetration); + _tree->unlock(); + } return result; } @@ -2354,13 +2374,14 @@ void VoxelSystem::collectStatsForTreesAndVBOs() { void VoxelSystem::deleteVoxelAt(float x, float y, float z, float s) { - lockTree(); + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "VoxelSystem::deleteVoxelAt()"); + _tree->lockForWrite(); _tree->deleteVoxelAt(x, y, z, s); - unlockTree(); + _tree->unlock(); // redraw! setupNewVoxelsForDrawing(); // do we even need to do this? Or will the next network receive kick in? - }; VoxelTreeElement* VoxelSystem::getVoxelAt(float x, float y, float z, float s) const { @@ -2370,10 +2391,12 @@ VoxelTreeElement* VoxelSystem::getVoxelAt(float x, float y, float z, float s) co void VoxelSystem::createVoxel(float x, float y, float z, float s, unsigned char red, unsigned char green, unsigned char blue, bool destructive) { - //qDebug("VoxelSystem::createVoxel(%f,%f,%f,%f)\n",x,y,z,s); - lockTree(); + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), + "VoxelSystem::createVoxel()"); + + _tree->lockForWrite(); _tree->createVoxel(x, y, z, s, red, green, blue, destructive); - unlockTree(); + _tree->unlock(); setupNewVoxelsForDrawing(); }; @@ -2744,37 +2767,3 @@ unsigned long VoxelSystem::getVoxelMemoryUsageGPU() { return (_initialMemoryUsageGPU - currentFreeMemory); } -void VoxelSystem::lockTree() { - _treeLock.lock(); - _treeIsBusy = true; -} - -void VoxelSystem::unlockTree() { - _treeIsBusy = false; - _treeLock.unlock(); -} - - -void VoxelSystem::localVoxelCacheLoaded() { - qDebug() << "localVoxelCacheLoaded()"; - - // Make sure that the application has properly set up the view frustum for our loaded state - Application::getInstance()->initAvatarAndViewFrustum(); - - _tree->setDirtyBit(); // make sure the tree thinks it's dirty - _setupNewVoxelsForDrawingLastFinished = 0; // don't allow the setupNewVoxelsForDrawing() shortcuts - _writeRenderFullVBO = true; // this will disable individual node updates, was reset by killLocalVoxels() - setupNewVoxelsForDrawing(); - _inhideOutOfView = false; // reenable hideOutOfView behavior -} - -void VoxelSystem::beginLoadingLocalVoxelCache() { - qDebug() << "beginLoadingLocalVoxelCache()"; - _writeRenderFullVBO = true; // this will disable individual node updates - _inhideOutOfView = true; // this will disable hidOutOfView which we want to do until local cache is loaded - killLocalVoxels(); - qDebug() << "DONE beginLoadingLocalVoxelCache()"; -} - - - diff --git a/interface/src/VoxelSystem.h b/interface/src/VoxelSystem.h index ca90424b70..d1404668bf 100644 --- a/interface/src/VoxelSystem.h +++ b/interface/src/VoxelSystem.h @@ -110,8 +110,6 @@ public: virtual void elementDeleted(OctreeElement* element); virtual void elementUpdated(OctreeElement* element); - bool treeIsBusy() const { return _treeIsBusy; } - VoxelTreeElement* getVoxelEnclosing(const glm::vec3& point); signals: @@ -144,9 +142,6 @@ public slots: void setUseVoxelShader(bool useVoxelShader); void setVoxelsAsPoints(bool voxelsAsPoints); - void localVoxelCacheLoaded(); - void beginLoadingLocalVoxelCache(); - protected: float _treeScale; unsigned long _maxVoxels; @@ -304,10 +299,6 @@ private: bool _useFastVoxelPipeline; bool _inhideOutOfView; - bool _treeIsBusy; // is the tree mutex locked? if so, it's busy, and if you can avoid it, don't access the tree - - void lockTree(); - void unlockTree(); }; #endif diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 4b4c189729..131eb4d37f 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -242,7 +242,9 @@ void Avatar::renderBody(bool forceRenderHead) { glm::vec3 pos = getPosition(); //printf("Render other at %.3f, %.2f, %.2f\n", pos.x, pos.y, pos.z); _skeletonModel.render(1.0f); - _head.render(1.0f); + if (forceRenderHead) { + _head.render(1.0f); + } _hand.render(false); } @@ -334,6 +336,16 @@ bool Avatar::findSphereCollision(const glm::vec3& sphereCenter, float sphereRadi return false; } +void Avatar::setFaceModelURL(const QUrl &faceModelURL) { + AvatarData::setFaceModelURL(faceModelURL); + _head.getFaceModel().setURL(faceModelURL); +} + +void Avatar::setSkeletonModelURL(const QUrl &skeletonModelURL) { + AvatarData::setSkeletonModelURL(skeletonModelURL); + _skeletonModel.setURL(skeletonModelURL); +} + int Avatar::parseData(const QByteArray& packet) { // change in position implies movement glm::vec3 oldPosition = _position; diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index 61b36706d2..c9b0bcacc2 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -112,7 +112,10 @@ public: virtual bool findSphereCollision(const glm::vec3& sphereCenter, float sphereRadius, CollisionInfo& collision); virtual bool isMyAvatar() { return false; } - + + virtual void setFaceModelURL(const QUrl& faceModelURL); + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + int parseData(const QByteArray& packet); static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d98d30cf6a..1734b755be 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -75,7 +75,6 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) { bool renderLookAtVectors = Menu::getInstance()->isOptionChecked(MenuOption::LookAtVectors); if (!selfAvatarOnly) { - // Render avatars of other nodes foreach (const AvatarSharedPointer& avatarPointer, _avatarHash) { Avatar* avatar = static_cast(avatarPointer.data()); if (!avatar->isInitialized()) { @@ -84,7 +83,7 @@ void AvatarManager::renderAvatars(bool forceRenderHead, bool selfAvatarOnly) { if (avatar == static_cast(_myAvatar.data())) { avatar->render(forceRenderHead); } else { - avatar->render(false); + avatar->render(true); } avatar->setDisplayingLookatVectors(renderLookAtVectors); } @@ -124,47 +123,29 @@ void AvatarManager::renderAvatarFades() { } } -void AvatarManager::processDataServerResponse(const QString& userString, const QStringList& keyList, - const QStringList &valueList) { - QUuid avatarKey = QUuid(userString); - if (avatarKey == MY_AVATAR_KEY) { - // ignore updates to our own mesh - return; - } - for (int i = 0; i < keyList.size(); i++) { - if (valueList[i] != " ") { - if (keyList[i] == DataServerKey::FaceMeshURL || keyList[i] == DataServerKey::SkeletonURL) { - // mesh URL for a UUID, find avatar in our list - AvatarSharedPointer matchingAvatar = _avatarHash.value(avatarKey); - if (matchingAvatar) { - Avatar* avatar = static_cast(matchingAvatar.data()); - if (keyList[i] == DataServerKey::FaceMeshURL) { - qDebug() << "Changing mesh to" << valueList[i] << "for avatar with UUID" - << uuidStringWithoutCurlyBraces(avatarKey); - - QMetaObject::invokeMethod(&(avatar->getHead().getFaceModel()), - "setURL", Q_ARG(QUrl, QUrl(valueList[i]))); - } else if (keyList[i] == DataServerKey::SkeletonURL) { - qDebug() << "Changing skeleton to" << valueList[i] << "for avatar with UUID" - << uuidStringWithoutCurlyBraces(avatarKey.toString()); - - QMetaObject::invokeMethod(&(avatar->getSkeletonModel()), - "setURL", Q_ARG(QUrl, QUrl(valueList[i]))); - } - } - } - } +void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { + switch (packetTypeForPacket(datagram)) { + case PacketTypeBulkAvatarData: + processAvatarDataPacket(datagram, mixerWeakPointer); + break; + case PacketTypeAvatarIdentity: + processAvatarIdentityPacket(datagram); + break; + case PacketTypeKillAvatar: + processKillAvatar(datagram); + break; + default: + break; } } -void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { - +void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer &mixerWeakPointer) { int bytesRead = numBytesForPacketHeader(datagram); QByteArray dummyAvatarByteArray = byteArrayWithPopluatedHeader(PacketTypeAvatarData); int numDummyHeaderBytes = dummyAvatarByteArray.size(); int numDummyHeaderBytesWithoutUUID = numDummyHeaderBytes - NUM_BYTES_RFC4122_UUID; - + // enumerate over all of the avatars in this packet // only add them if mixerWeakPointer points to something (meaning that mixer is still around) while (bytesRead < datagram.size() && mixerWeakPointer.data()) { @@ -181,10 +162,6 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const matchingAvatar = AvatarSharedPointer(avatar); _avatarHash.insert(nodeUUID, matchingAvatar); - // new UUID requires mesh and skeleton request to data-server - DataServerClient::getValuesForKeysAndUUID(QStringList() << DataServerKey::FaceMeshURL << DataServerKey::SkeletonURL, - nodeUUID, this); - qDebug() << "Adding avatar with UUID" << nodeUUID << "to AvatarManager hash."; } @@ -197,6 +174,35 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const // have the matching (or new) avatar parse the data from the packet bytesRead += matchingAvatar->parseData(dummyAvatarByteArray) - numDummyHeaderBytesWithoutUUID; } + +} + +void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet) { + // setup a data stream to parse the packet + QDataStream identityStream(packet); + identityStream.skipRawData(numBytesForPacketHeader(packet)); + + QUuid nodeUUID; + + while (!identityStream.atEnd()) { + + QUrl faceMeshURL, skeletonURL; + identityStream >> nodeUUID >> faceMeshURL >> skeletonURL; + + // mesh URL for a UUID, find avatar in our list + AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); + if (matchingAvatar) { + Avatar* avatar = static_cast(matchingAvatar.data()); + + if (avatar->getFaceModelURL() != faceMeshURL) { + avatar->setFaceModelURL(faceMeshURL); + } + + if (avatar->getSkeletonModelURL() != skeletonURL) { + avatar->setSkeletonModelURL(skeletonURL); + } + } + } } void AvatarManager::processKillAvatar(const QByteArray& datagram) { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 6605f5c234..35dbf8dbb0 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -20,7 +20,7 @@ class MyAvatar; -class AvatarManager : public QObject, public DataServerCallbackObject, public AvatarHashMap { +class AvatarManager : public QObject, public AvatarHashMap { Q_OBJECT public: AvatarManager(QObject* parent = 0); @@ -35,13 +35,14 @@ public: void clearOtherAvatars(); public slots: - void processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList); - void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer); - void processKillAvatar(const QByteArray& datagram); - + private: AvatarManager(const AvatarManager& other); + + void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); + void processAvatarIdentityPacket(const QByteArray& packet); + void processKillAvatar(const QByteArray& datagram); void simulateAvatarFades(float deltaTime); void renderAvatarFades(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0bdc5e9748..85025c1847 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -608,6 +608,9 @@ void MyAvatar::saveData(QSettings* settings) { settings->setValue("leanScale", _leanScale); settings->setValue("scale", _targetScale); + + settings->setValue("faceModelURL", _faceModelURL); + settings->setValue("skeletonModelURL", _skeletonModelURL); settings->endGroup(); } @@ -632,6 +635,9 @@ void MyAvatar::loadData(QSettings* settings) { _targetScale = loadSetting(settings, "scale", 1.0f); setScale(_scale); Application::getInstance()->getCamera()->setScale(_scale); + + setFaceModelURL(settings->value("faceModelURL").toUrl()); + setSkeletonModelURL(settings->value("skeletonModelURL").toUrl()); settings->endGroup(); } @@ -641,6 +647,13 @@ void MyAvatar::sendKillAvatar() { NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer); } +void MyAvatar::sendIdentityPacket() { + QByteArray identityPacket = byteArrayWithPopluatedHeader(PacketTypeAvatarIdentity); + identityPacket.append(AvatarData::identityByteArray()); + + NodeList::getInstance()->broadcastToNodes(identityPacket, NodeSet() << NodeType::AvatarMixer); +} + void MyAvatar::orbit(const glm::vec3& position, int deltaX, int deltaY) { // first orbit horizontally glm::quat orientation = getOrientation(); @@ -792,23 +805,27 @@ void MyAvatar::updateThrust(float deltaTime) { const int VIEW_CONTROLLER = 1; for (size_t i = 0; i < getHand().getPalms().size(); ++i) { PalmData& palm = getHand().getPalms()[i]; - if (palm.isActive() && (palm.getSixenseID() == THRUST_CONTROLLER)) { - if (palm.getJoystickY() != 0.f) { - FingerData& finger = palm.getFingers()[0]; - if (finger.isActive()) { + + // If the script hasn't captured this joystick, then let the default behavior work + if (!Application::getInstance()->getControllerScriptingInterface()->isJoystickCaptured(palm.getSixenseID())) { + if (palm.isActive() && (palm.getSixenseID() == THRUST_CONTROLLER)) { + if (palm.getJoystickY() != 0.f) { + FingerData& finger = palm.getFingers()[0]; + if (finger.isActive()) { + } + _thrust += front * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickY() * _thrustMultiplier * deltaTime; + } + if (palm.getJoystickX() != 0.f) { + _thrust += right * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickX() * _thrustMultiplier * deltaTime; + } + } else if (palm.isActive() && (palm.getSixenseID() == VIEW_CONTROLLER)) { + if (palm.getJoystickX() != 0.f) { + _bodyYawDelta -= palm.getJoystickX() * JOYSTICK_YAW_MAG * deltaTime; + } + if (palm.getJoystickY() != 0.f) { + getHand().setPitchUpdate(getHand().getPitchUpdate() + + (palm.getJoystickY() * JOYSTICK_PITCH_MAG * deltaTime)); } - _thrust += front * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickY() * _thrustMultiplier * deltaTime; - } - if (palm.getJoystickX() != 0.f) { - _thrust += right * _scale * THRUST_MAG_HAND_JETS * palm.getJoystickX() * _thrustMultiplier * deltaTime; - } - } else if (palm.isActive() && (palm.getSixenseID() == VIEW_CONTROLLER)) { - if (palm.getJoystickX() != 0.f) { - _bodyYawDelta -= palm.getJoystickX() * JOYSTICK_YAW_MAG * deltaTime; - } - if (palm.getJoystickY() != 0.f) { - getHand().setPitchUpdate(getHand().getPitchUpdate() + - (palm.getJoystickY() * JOYSTICK_PITCH_MAG * deltaTime)); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c3ef1e4bfb..7dfb8812dd 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -94,6 +94,8 @@ public slots: void increaseSize(); void decreaseSize(); void resetSize(); + + void sendIdentityPacket(); private: bool _mousePressed; diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index 902a0ea12a..a2a412199e 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -20,17 +20,13 @@ Profile::Profile(const QString &username) : _uuid(), _lastDomain(), _lastPosition(0.0, 0.0, 0.0), - _lastOrientationSend(0), - _faceModelURL(), - _skeletonModelURL() + _lastOrientationSend(0) { if (!username.isEmpty()) { setUsername(username); // we've been given a new username, ask the data-server for profile DataServerClient::getValueForKeyAndUserString(DataServerKey::UUID, getUserString(), this); - DataServerClient::getValueForKeyAndUserString(DataServerKey::FaceMeshURL, getUserString(), this); - DataServerClient::getValueForKeyAndUserString(DataServerKey::SkeletonURL, getUserString(), this); // send our current domain server to the data-server updateDomain(NodeList::getInstance()->getDomainHostname()); @@ -45,34 +41,6 @@ QString Profile::getUserString() const { } } -void Profile::setUUID(const QUuid& uuid) { - _uuid = uuid; - - if (!_uuid.isNull()) { - qDebug() << "Changing NodeList owner UUID to" << uuid; - - // when the UUID is changed we need set it appropriately on the NodeList instance - NodeList::getInstance()->setOwnerUUID(uuid); - - // ask for a window title update so the new UUID is presented - Application::getInstance()->updateWindowTitle(); - } -} - -void Profile::setFaceModelURL(const QUrl& faceModelURL) { - _faceModelURL = faceModelURL; - - QMetaObject::invokeMethod(&Application::getInstance()->getAvatar()->getHead().getFaceModel(), - "setURL", Q_ARG(QUrl, _faceModelURL)); -} - -void Profile::setSkeletonModelURL(const QUrl& skeletonModelURL) { - _skeletonModelURL = skeletonModelURL; - - QMetaObject::invokeMethod(&Application::getInstance()->getAvatar()->getSkeletonModel(), - "setURL", Q_ARG(QUrl, _skeletonModelURL)); -} - void Profile::updateDomain(const QString& domain) { if (_lastDomain != domain) { _lastDomain = domain; @@ -137,8 +105,6 @@ void Profile::saveData(QSettings* settings) { settings->setValue("username", _username); settings->setValue("UUID", _uuid); - settings->setValue("faceModelURL", _faceModelURL); - settings->setValue("skeletonModelURL", _skeletonModelURL); settings->endGroup(); } @@ -148,8 +114,6 @@ void Profile::loadData(QSettings* settings) { setUsername(settings->value("username").toString()); this->setUUID(settings->value("UUID").toUuid()); - _faceModelURL = settings->value("faceModelURL").toUrl(); - _skeletonModelURL = settings->value("skeletonModelURL").toUrl(); settings->endGroup(); } @@ -157,17 +121,7 @@ void Profile::loadData(QSettings* settings) { void Profile::processDataServerResponse(const QString& userString, const QStringList& keyList, const QStringList& valueList) { for (int i = 0; i < keyList.size(); i++) { if (valueList[i] != " ") { - if (keyList[i] == DataServerKey::FaceMeshURL) { - if (userString == _username || userString == uuidStringWithoutCurlyBraces(_uuid)) { - qDebug("Changing user's face model URL to %s", valueList[i].toLocal8Bit().constData()); - Application::getInstance()->getProfile()->setFaceModelURL(QUrl(valueList[i])); - } - } else if (keyList[i] == DataServerKey::SkeletonURL) { - if (userString == _username || userString == uuidStringWithoutCurlyBraces(_uuid)) { - qDebug("Changing user's skeleton URL to %s", valueList[i].toLocal8Bit().constData()); - Application::getInstance()->getProfile()->setSkeletonModelURL(QUrl(valueList[i])); - } - } else if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position && + if (keyList[i] == DataServerKey::Domain && keyList[i + 1] == DataServerKey::Position && keyList[i + 2] == DataServerKey::Orientation && valueList[i] != " " && valueList[i + 1] != " " && valueList[i + 2] != " ") { diff --git a/interface/src/avatar/Profile.h b/interface/src/avatar/Profile.h index 5cb2295f5b..c32d89cfea 100644 --- a/interface/src/avatar/Profile.h +++ b/interface/src/avatar/Profile.h @@ -28,15 +28,9 @@ public: const QString& getUsername() const { return _username; } - void setUUID(const QUuid& uuid); + void setUUID(const QUuid& uuid) { _uuid = uuid; } const QUuid& getUUID() { return _uuid; } - void setFaceModelURL(const QUrl& faceModelURL); - const QUrl& getFaceModelURL() const { return _faceModelURL; } - - void setSkeletonModelURL(const QUrl& skeletonModelURL); - const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } - void updateDomain(const QString& domain); void updatePosition(const glm::vec3 position); void updateOrientation(const glm::quat& orientation); @@ -58,8 +52,6 @@ private: glm::vec3 _lastPosition; glm::vec3 _lastOrientation; quint64 _lastOrientationSend; - QUrl _faceModelURL; - QUrl _skeletonModelURL; }; #endif /* defined(__hifi__Profile__) */ diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index d35b44ffef..c97832d791 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -80,6 +81,9 @@ MetavoxelEditor::MetavoxelEditor() : QVBoxLayout* valueLayout = new QVBoxLayout(); _value->setLayout(valueLayout); + valueLayout->addWidget(_valueArea = new QScrollArea()); + _valueArea->setWidgetResizable(true); + updateAttributes(); connect(Application::getInstance(), SIGNAL(renderingInWorldInterface()), SLOT(render())); @@ -145,14 +149,14 @@ void MetavoxelEditor::updateValueEditor() { } _value->setVisible(true); - if (!_value->layout()->isEmpty()) { - delete _value->layout()->takeAt(0); + if (_valueArea->widget()) { + delete _valueArea->widget(); } AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(selected); QWidget* editor = attribute->createEditor(); if (editor) { - _value->layout()->addWidget(editor); + _valueArea->setWidget(editor); } } @@ -274,9 +278,13 @@ void MetavoxelEditor::render() { glutSolidCube(1.0); glDisable(GL_CULL_FACE); } + glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS); glutWireCube(1.0); glPopMatrix(); + + } else { + glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS); } glLineWidth(1.0f); @@ -292,7 +300,6 @@ void MetavoxelEditor::render() { _gridProgram.bind(); - glColor3f(GRID_BRIGHTNESS, GRID_BRIGHTNESS, GRID_BRIGHTNESS); Application::getInstance()->getGeometryCache()->renderGrid(GRID_DIVISIONS, GRID_DIVISIONS); _gridProgram.release(); @@ -363,11 +370,8 @@ void MetavoxelEditor::applyValue(const glm::vec3& minimum, const glm::vec3& maxi } QVariant MetavoxelEditor::getValue() const { - if (_value->layout()->isEmpty()) { - return QVariant(); - } - QWidget* editor = _value->layout()->itemAt(0)->widget(); - return editor->metaObject()->userProperty().read(editor); + QWidget* editor = _valueArea->widget(); + return editor ? editor->metaObject()->userProperty().read(editor) : QVariant(); } ProgramObject MetavoxelEditor::_gridProgram; diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 6fe5b26398..9024837757 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -17,6 +17,7 @@ class QComboBox; class QDoubleSpinBox; class QGroupBox; class QListWidget; +class QScrollArea; /// Allows editing metavoxels. class MetavoxelEditor : public QDialog { @@ -52,6 +53,7 @@ private: QDoubleSpinBox* _gridSpacing; QDoubleSpinBox* _gridPosition; QGroupBox* _value; + QScrollArea* _valueArea; enum State { HOVERING_STATE, DRAGGING_STATE, RAISING_STATE }; diff --git a/interface/src/ui/VoxelStatsDialog.cpp b/interface/src/ui/VoxelStatsDialog.cpp index 27b7f788ec..0ac0a2b933 100644 --- a/interface/src/ui/VoxelStatsDialog.cpp +++ b/interface/src/ui/VoxelStatsDialog.cpp @@ -265,7 +265,7 @@ void VoxelStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t serv std::stringstream extraDetails(""); std::stringstream linkDetails(""); - if (nodeList->getNodeActiveSocketOrPing(node.data())) { + if (node->getActiveSocket()) { serverDetails << "active "; } else { serverDetails << "inactive "; diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 325cd06639..ff243459b7 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -93,12 +93,8 @@ void AudioInjector::injectAudio() { // grab our audio mixer from the NodeList, if it exists SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); - if (audioMixer && nodeList->getNodeActiveSocketOrPing(audioMixer.data())) { - // send off this audio packet - nodeList->getNodeSocket().writeDatagram(injectAudioPacket, - audioMixer->getActiveSocket()->getAddress(), - audioMixer->getActiveSocket()->getPort()); - } + // send off this audio packet + nodeList->writeDatagram(injectAudioPacket, audioMixer); currentSendPosition += bytesToCopy; diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 8a444e50fd..9bac210c39 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -70,9 +70,9 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) { // read to the end of the buffer int numSamplesToEnd = (_buffer + _sampleCapacity) - _nextOutput; memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t)); - + // read the rest from the beginning of the buffer - memcpy(data + numSamplesToEnd, _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); + memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); } else { // read the data memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t)); diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 98afa76107..ca57fff587 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -270,6 +270,48 @@ int AvatarData::parseData(const QByteArray& packet) { return sourceBuffer - startPosition; } +bool AvatarData::hasIdentityChangedAfterParsing(const QByteArray &packet) { + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + QUuid avatarUUID; + QUrl faceModelURL, skeletonModelURL; + packetStream >> avatarUUID >> faceModelURL >> skeletonModelURL; + + bool hasIdentityChanged = false; + + if (faceModelURL != _faceModelURL) { + setFaceModelURL(faceModelURL); + hasIdentityChanged = true; + } + + if (skeletonModelURL != _skeletonModelURL) { + setSkeletonModelURL(skeletonModelURL); + hasIdentityChanged = true; + } + + return hasIdentityChanged; +} + +QByteArray AvatarData::identityByteArray() { + QByteArray identityData; + QDataStream identityStream(&identityData, QIODevice::Append); + + identityStream << QUuid() << _faceModelURL << _skeletonModelURL; + + return identityData; +} + +void AvatarData::setFaceModelURL(const QUrl& faceModelURL) { + qDebug() << "Changing face model for avatar to" << faceModelURL.toString(); + _faceModelURL = faceModelURL; +} + +void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { + qDebug() << "Changing skeleton model for avatar to" << skeletonModelURL.toString(); + _skeletonModelURL = skeletonModelURL; +} + void AvatarData::setClampedTargetScale(float targetScale) { targetScale = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 0869445090..5c3c9ad9af 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -30,6 +30,7 @@ typedef unsigned long long quint64; #include #include +#include #include #include @@ -51,8 +52,7 @@ static const float MIN_AVATAR_SCALE = .005f; const float MAX_AUDIO_LOUDNESS = 1000.0; // close enough for mouth animation -enum KeyState -{ +enum KeyState { NO_KEY_DOWN = 0, INSERT_KEY_DOWN, DELETE_KEY_DOWN @@ -72,7 +72,9 @@ class AvatarData : public NodeData { Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation) Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch) - + + Q_PROPERTY(QUrl faceModelURL READ getFaceModelURL WRITE setFaceModelURL) + Q_PROPERTY(QUrl skeletonModelURL READ getSkeletonModelURL WRITE setSkeletonModelURL) public: AvatarData(); ~AvatarData(); @@ -142,6 +144,14 @@ public: return false; } + bool hasIdentityChangedAfterParsing(const QByteArray& packet); + QByteArray identityByteArray(); + + const QUrl& getFaceModelURL() const { return _faceModelURL; } + const QUrl& getSkeletonModelURL() const { return _skeletonModelURL; } + virtual void setFaceModelURL(const QUrl& faceModelURL); + virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); + protected: glm::vec3 _position; glm::vec3 _handPosition; @@ -168,6 +178,8 @@ protected: HeadData* _headData; HandData* _handData; + QUrl _faceModelURL; + QUrl _skeletonModelURL; private: // privatize the copy constructor and assignment operator so they cannot be called AvatarData(const AvatarData&); diff --git a/libraries/metavoxels/CMakeLists.txt b/libraries/metavoxels/CMakeLists.txt index 98b2baf7ac..989ed6d4a7 100644 --- a/libraries/metavoxels/CMakeLists.txt +++ b/libraries/metavoxels/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cm set(TARGET_NAME metavoxels) +find_package(Qt5Network REQUIRED) find_package(Qt5Widgets REQUIRED) include(${MACRO_DIR}/SetupHifiLibrary.cmake) @@ -16,7 +17,7 @@ setup_hifi_library(${TARGET_NAME}) include(${MACRO_DIR}/AutoMTC.cmake) auto_mtc(${TARGET_NAME} ${ROOT_DIR}) -qt5_use_modules(${TARGET_NAME} Widgets Script) +qt5_use_modules(${TARGET_NAME} Network Script Widgets) include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} ${ROOT_DIR}) diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index 5b7a8859ca..431eaf62c9 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -6,15 +6,13 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // -#include -#include #include -#include #include "AttributeRegistry.h" #include "MetavoxelData.h" REGISTER_META_OBJECT(QRgbAttribute) +REGISTER_META_OBJECT(SharedObjectAttribute) AttributeRegistry* AttributeRegistry::getInstance() { static AttributeRegistry registry; @@ -22,17 +20,29 @@ AttributeRegistry* AttributeRegistry::getInstance() { } AttributeRegistry::AttributeRegistry() : - _guideAttribute(registerAttribute(new PolymorphicAttribute("guide", PolymorphicDataPointer(new DefaultMetavoxelGuide())))), + _guideAttribute(registerAttribute(new SharedObjectAttribute("guide", &MetavoxelGuide::staticMetaObject, + SharedObjectPointer(new DefaultMetavoxelGuide())))), _colorAttribute(registerAttribute(new QRgbAttribute("color"))), _normalAttribute(registerAttribute(new QRgbAttribute("normal", qRgb(0, 127, 0)))) { } +static QScriptValue qDebugFunction(QScriptContext* context, QScriptEngine* engine) { + QDebug debug = qDebug(); + + for (int i = 0; i < context->argumentCount(); i++) { + debug << context->argument(i).toString(); + } + + return QScriptValue(); +} + void AttributeRegistry::configureScriptEngine(QScriptEngine* engine) { QScriptValue registry = engine->newObject(); registry.setProperty("colorAttribute", engine->newQObject(_colorAttribute.data())); registry.setProperty("normalAttribute", engine->newQObject(_normalAttribute.data())); registry.setProperty("getAttribute", engine->newFunction(getAttribute, 1)); engine->globalObject().setProperty("AttributeRegistry", registry); + engine->globalObject().setProperty("qDebug", engine->newFunction(qDebugFunction, 1)); } AttributePointer AttributeRegistry::registerAttribute(AttributePointer attribute) { @@ -84,6 +94,10 @@ OwnedAttributeValue::OwnedAttributeValue(const AttributeValue& other) : AttributeValue(other.getAttribute(), other.getAttribute() ? other.copy() : NULL) { } +OwnedAttributeValue::OwnedAttributeValue(const OwnedAttributeValue& other) : + AttributeValue(other.getAttribute(), other.getAttribute() ? other.copy() : NULL) { +} + OwnedAttributeValue::~OwnedAttributeValue() { if (_attribute) { _attribute->destroy(_value); @@ -100,6 +114,16 @@ OwnedAttributeValue& OwnedAttributeValue::operator=(const AttributeValue& other) return *this; } +OwnedAttributeValue& OwnedAttributeValue::operator=(const OwnedAttributeValue& other) { + if (_attribute) { + _attribute->destroy(_value); + } + if ((_attribute = other.getAttribute())) { + _value = other.copy(); + } + return *this; +} + Attribute::Attribute(const QString& name) { setObjectName(name); } @@ -146,41 +170,48 @@ void* QRgbAttribute::createFromVariant(const QVariant& value) const { } QWidget* QRgbAttribute::createEditor(QWidget* parent) const { - QRgbEditor* editor = new QRgbEditor(parent); + QColorEditor* editor = new QColorEditor(parent); editor->setColor(QColor::fromRgba(_defaultValue)); return editor; } -QRgbEditor::QRgbEditor(QWidget* parent) : QWidget(parent) { - setLayout(new QVBoxLayout()); - layout()->addWidget(_button = new QPushButton()); - connect(_button, SIGNAL(clicked()), SLOT(selectColor())); +SharedObjectAttribute::SharedObjectAttribute(const QString& name, const QMetaObject* metaObject, + const SharedObjectPointer& defaultValue) : + InlineAttribute(name, defaultValue), + _metaObject(metaObject) { + } -void QRgbEditor::setColor(const QColor& color) { - QString name = (_color = color).name(); - _button->setStyleSheet(QString("background: %1; color: %2").arg(name, QColor::fromRgb(~color.rgb()).name())); - _button->setText(name); -} - -void QRgbEditor::selectColor() { - QColor color = QColorDialog::getColor(_color, this, QString(), QColorDialog::ShowAlphaChannel); - if (color.isValid()) { - setColor(color); +void SharedObjectAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { + if (isLeaf) { + in >> *((SharedObjectPointer*)&value); } } -PolymorphicData::~PolymorphicData() { +void SharedObjectAttribute::write(Bitstream& out, void* value, bool isLeaf) const { + if (isLeaf) { + out << decodeInline(value); + } } -template<> PolymorphicData* QExplicitlySharedDataPointer::clone() { - return d->clone(); +bool SharedObjectAttribute::merge(void*& parent, void* children[]) const { + SharedObjectPointer firstChild = decodeInline(children[0]); + for (int i = 1; i < MERGE_COUNT; i++) { + if (firstChild != decodeInline(children[i])) { + *(SharedObjectPointer*)&parent = _defaultValue; + return false; + } + } + *(SharedObjectPointer*)&parent = firstChild; + return true; } -PolymorphicAttribute::PolymorphicAttribute(const QString& name, const PolymorphicDataPointer& defaultValue) : - InlineAttribute(name, defaultValue) { +void* SharedObjectAttribute::createFromVariant(const QVariant& value) const { + return create(encodeInline(value.value())); } -bool PolymorphicAttribute::merge(void*& parent, void* children[]) const { - return false; +QWidget* SharedObjectAttribute::createEditor(QWidget* parent) const { + SharedObjectEditor* editor = new SharedObjectEditor(_metaObject, parent); + editor->setObject(_defaultValue); + return editor; } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index 058b02d78f..db5e54cc4a 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -9,18 +9,15 @@ #ifndef __interface__AttributeRegistry__ #define __interface__AttributeRegistry__ -#include -#include #include #include -#include #include #include #include #include "Bitstream.h" +#include "SharedObject.h" -class QPushButton; class QScriptContext; class QScriptEngine; class QScriptValue; @@ -127,11 +124,17 @@ public: /// Creates an owned attribute with a copy of the specified other value. OwnedAttributeValue(const AttributeValue& other); + /// Creates an owned attribute with a copy of the specified other value. + OwnedAttributeValue(const OwnedAttributeValue& other); + /// Destroys the current value, if any. ~OwnedAttributeValue(); /// Destroys the current value, if any, and copies the specified other value. OwnedAttributeValue& operator=(const AttributeValue& other); + + /// Destroys the current value, if any, and copies the specified other value. + OwnedAttributeValue& operator=(const OwnedAttributeValue& other); }; /// Represents a registered attribute. @@ -247,104 +250,28 @@ public: virtual QWidget* createEditor(QWidget* parent = NULL) const; }; -/// Editor for RGBA values. -class QRgbEditor : public QWidget { +/// An attribute that takes the form of QObjects of a given meta-type (a subclass of SharedObject). +class SharedObjectAttribute : public InlineAttribute { Q_OBJECT - Q_PROPERTY(QColor color MEMBER _color WRITE setColor USER true) - + Q_PROPERTY(const QMetaObject* metaObject MEMBER _metaObject) + public: - QRgbEditor(QWidget* parent); + Q_INVOKABLE SharedObjectAttribute(const QString& name = QString(), const QMetaObject* metaObject = NULL, + const SharedObjectPointer& defaultValue = SharedObjectPointer()); -public slots: - - void setColor(const QColor& color); - -private slots: - - void selectColor(); - -private: - - QPushButton* _button; - QColor _color; -}; - -/// An attribute class that stores pointers to its values. -template class PointerAttribute : public Attribute { -public: - - PointerAttribute(const QString& name, T defaultValue = T()) : Attribute(name), _defaultValue(defaultValue) { } - - virtual void* create(void* copy) const { new T(*static_cast(copy)); } - virtual void destroy(void* value) const { delete static_cast(value); } - virtual void read(Bitstream& in, void*& value, bool isLeaf) const; virtual void write(Bitstream& out, void* value, bool isLeaf) const; - virtual bool equal(void* first, void* second) const { return *static_cast(first) == *static_cast(second); } - - virtual void* getDefaultValue() const { return const_cast((void*)&_defaultValue); } + virtual bool merge(void*& parent, void* children[]) const; + + virtual void* createFromVariant(const QVariant& value) const; + + virtual QWidget* createEditor(QWidget* parent = NULL) const; private: - T _defaultValue; -}; - -template inline void PointerAttribute::read(Bitstream& in, void*& value, bool isLeaf) const { - if (isLeaf) { - in.read(value, sizeof(T) * 8); - } -} - -template inline void PointerAttribute::write(Bitstream& out, void* value, bool isLeaf) const { - if (isLeaf) { - out.write(value, sizeof(T) * 8); - } -} - -/// Provides merging using the =, ==, += and /= operators. -template class SimplePointerAttribute : public PointerAttribute { -public: - - SimplePointerAttribute(const QString& name, T defaultValue = T()) : PointerAttribute(name, defaultValue) { } - - virtual bool merge(void*& parent, void* children[]) const; -}; - -template inline bool SimplePointerAttribute::merge(void*& parent, void* children[]) const { - T& merged = *static_cast(parent); - merged = *static_cast(children[0]); - bool allChildrenEqual = true; - for (int i = 1; i < Attribute::MERGE_COUNT; i++) { - merged += *static_cast(children[i]); - allChildrenEqual &= (*static_cast(children[0]) == *static_cast(children[i])); - } - merged /= Attribute::MERGE_COUNT; - return allChildrenEqual; -} - -/// Base class for polymorphic attribute data. -class PolymorphicData : public QSharedData { -public: - - virtual ~PolymorphicData(); - - /// Creates a new clone of this object. - virtual PolymorphicData* clone() const = 0; -}; - -template<> PolymorphicData* QExplicitlySharedDataPointer::clone(); - -typedef QExplicitlySharedDataPointer PolymorphicDataPointer; - -/// Provides polymorphic streaming and averaging. -class PolymorphicAttribute : public InlineAttribute { -public: - - PolymorphicAttribute(const QString& name, const PolymorphicDataPointer& defaultValue = PolymorphicDataPointer()); - - virtual bool merge(void*& parent, void* children[]) const; + const QMetaObject* _metaObject; }; #endif /* defined(__interface__AttributeRegistry__) */ diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index 4f23c79d78..262f2df7f5 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -10,16 +10,31 @@ #include #include +#include +#include #include +#include +#include + #include "AttributeRegistry.h" #include "Bitstream.h" +#include "ScriptCache.h" -REGISTER_SIMPLE_TYPE_STREAMER(QByteArray) -REGISTER_SIMPLE_TYPE_STREAMER(QString) -REGISTER_SIMPLE_TYPE_STREAMER(QVariantList) REGISTER_SIMPLE_TYPE_STREAMER(bool) REGISTER_SIMPLE_TYPE_STREAMER(int) +REGISTER_SIMPLE_TYPE_STREAMER(float) +REGISTER_SIMPLE_TYPE_STREAMER(QByteArray) +REGISTER_SIMPLE_TYPE_STREAMER(QColor) +REGISTER_SIMPLE_TYPE_STREAMER(QString) +REGISTER_SIMPLE_TYPE_STREAMER(QUrl) +REGISTER_SIMPLE_TYPE_STREAMER(QVariantList) +REGISTER_SIMPLE_TYPE_STREAMER(QVariantHash) + +// some types don't quite work with our macro +static int vec3Streamer = Bitstream::registerTypeStreamer(qMetaTypeId(), new SimpleTypeStreamer()); +static int metaObjectStreamer = Bitstream::registerTypeStreamer(qMetaTypeId(), + new SimpleTypeStreamer()); IDStreamer::IDStreamer(Bitstream& stream) : _stream(stream), @@ -52,6 +67,11 @@ IDStreamer& IDStreamer::operator>>(int& value) { int Bitstream::registerMetaObject(const char* className, const QMetaObject* metaObject) { getMetaObjects().insert(className, metaObject); + + // register it as a subclass of all of its superclasses + for (const QMetaObject* superClass = metaObject->superClass(); superClass != NULL; superClass = superClass->superClass()) { + getMetaObjectSubClasses().insert(superClass, metaObject); + } return 0; } @@ -61,16 +81,22 @@ int Bitstream::registerTypeStreamer(int type, TypeStreamer* streamer) { return 0; } -Bitstream::Bitstream(QDataStream& underlying) : +QList Bitstream::getMetaObjectSubClasses(const QMetaObject* metaObject) { + return getMetaObjectSubClasses().values(metaObject); +} + +Bitstream::Bitstream(QDataStream& underlying, QObject* parent) : + QObject(parent), _underlying(underlying), _byte(0), _position(0), _metaObjectStreamer(*this), _typeStreamerStreamer(*this), - _attributeStreamer(*this) { + _attributeStreamer(*this), + _scriptStringStreamer(*this), + _sharedObjectStreamer(*this) { } -const int BITS_IN_BYTE = 8; const int LAST_BIT_POSITION = BITS_IN_BYTE - 1; Bitstream& Bitstream::write(const void* data, int bits, int offset) { @@ -124,7 +150,9 @@ void Bitstream::reset() { Bitstream::WriteMappings Bitstream::getAndResetWriteMappings() { WriteMappings mappings = { _metaObjectStreamer.getAndResetTransientOffsets(), _typeStreamerStreamer.getAndResetTransientOffsets(), - _attributeStreamer.getAndResetTransientOffsets() }; + _attributeStreamer.getAndResetTransientOffsets(), + _scriptStringStreamer.getAndResetTransientOffsets(), + _sharedObjectStreamer.getAndResetTransientOffsets() }; return mappings; } @@ -132,12 +160,24 @@ void Bitstream::persistWriteMappings(const WriteMappings& mappings) { _metaObjectStreamer.persistTransientOffsets(mappings.metaObjectOffsets); _typeStreamerStreamer.persistTransientOffsets(mappings.typeStreamerOffsets); _attributeStreamer.persistTransientOffsets(mappings.attributeOffsets); + _scriptStringStreamer.persistTransientOffsets(mappings.scriptStringOffsets); + _sharedObjectStreamer.persistTransientOffsets(mappings.sharedObjectOffsets); + + // find out when shared objects' reference counts drop to one in order to clear their mappings + for (QHash::const_iterator it = mappings.sharedObjectOffsets.constBegin(); + it != mappings.sharedObjectOffsets.constEnd(); it++) { + if (it.key()) { + connect(it.key().data(), SIGNAL(referenceCountDroppedToOne()), SLOT(clearSharedObject())); + } + } } Bitstream::ReadMappings Bitstream::getAndResetReadMappings() { ReadMappings mappings = { _metaObjectStreamer.getAndResetTransientValues(), _typeStreamerStreamer.getAndResetTransientValues(), - _attributeStreamer.getAndResetTransientValues() }; + _attributeStreamer.getAndResetTransientValues(), + _scriptStringStreamer.getAndResetTransientValues(), + _sharedObjectStreamer.getAndResetTransientValues() }; return mappings; } @@ -145,6 +185,8 @@ void Bitstream::persistReadMappings(const ReadMappings& mappings) { _metaObjectStreamer.persistTransientValues(mappings.metaObjectValues); _typeStreamerStreamer.persistTransientValues(mappings.typeStreamerValues); _attributeStreamer.persistTransientValues(mappings.attributeValues); + _scriptStringStreamer.persistTransientValues(mappings.scriptStringValues); + _sharedObjectStreamer.persistTransientValues(mappings.sharedObjectValues); } Bitstream& Bitstream::operator<<(bool value) { @@ -205,6 +247,17 @@ Bitstream& Bitstream::operator>>(QByteArray& string) { return read(string.data(), size * BITS_IN_BYTE); } +Bitstream& Bitstream::operator<<(const QColor& color) { + return *this << (int)color.rgba(); +} + +Bitstream& Bitstream::operator>>(QColor& color) { + int rgba; + *this >> rgba; + color.setRgba(rgba); + return *this; +} + Bitstream& Bitstream::operator<<(const QString& string) { *this << string.size(); return write(string.constData(), string.size() * sizeof(QChar) * BITS_IN_BYTE); @@ -217,6 +270,17 @@ Bitstream& Bitstream::operator>>(QString& string) { return read(string.data(), size * sizeof(QChar) * BITS_IN_BYTE); } +Bitstream& Bitstream::operator<<(const QUrl& url) { + return *this << url.toString(); +} + +Bitstream& Bitstream::operator>>(QUrl& url) { + QString string; + *this >> string; + url = string; + return *this; +} + Bitstream& Bitstream::operator<<(const QVariant& value) { const TypeStreamer* streamer = getTypeStreamers().value(value.userType()); if (streamer) { @@ -302,11 +366,61 @@ Bitstream& Bitstream::operator>>(QObject*& object) { } Bitstream& Bitstream::operator<<(const QMetaObject* metaObject) { - return *this << (metaObject ? QByteArray::fromRawData( - metaObject->className(), strlen(metaObject->className())) : QByteArray()); + _metaObjectStreamer << metaObject; + return *this; } Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) { + _metaObjectStreamer >> metaObject; + return *this; +} + +Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) { + _typeStreamerStreamer << streamer; + return *this; +} + +Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) { + _typeStreamerStreamer >> streamer; + return *this; +} + +Bitstream& Bitstream::operator<<(const AttributePointer& attribute) { + _attributeStreamer << attribute; + return *this; +} + +Bitstream& Bitstream::operator>>(AttributePointer& attribute) { + _attributeStreamer >> attribute; + return *this; +} + +Bitstream& Bitstream::operator<<(const QScriptString& string) { + _scriptStringStreamer << string; + return *this; +} + +Bitstream& Bitstream::operator>>(QScriptString& string) { + _scriptStringStreamer >> string; + return *this; +} + +Bitstream& Bitstream::operator<<(const SharedObjectPointer& object) { + _sharedObjectStreamer << object; + return *this; +} + +Bitstream& Bitstream::operator>>(SharedObjectPointer& object) { + _sharedObjectStreamer >> object; + return *this; +} + +Bitstream& Bitstream::operator<(const QMetaObject* metaObject) { + return *this << (metaObject ? QByteArray::fromRawData(metaObject->className(), + strlen(metaObject->className())) : QByteArray()); +} + +Bitstream& Bitstream::operator>(const QMetaObject*& metaObject) { QByteArray className; *this >> className; if (className.isEmpty()) { @@ -320,12 +434,12 @@ Bitstream& Bitstream::operator>>(const QMetaObject*& metaObject) { return *this; } -Bitstream& Bitstream::operator<<(const TypeStreamer* streamer) { +Bitstream& Bitstream::operator<(const TypeStreamer* streamer) { const char* typeName = QMetaType::typeName(streamer->getType()); return *this << QByteArray::fromRawData(typeName, strlen(typeName)); } -Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) { +Bitstream& Bitstream::operator>(const TypeStreamer*& streamer) { QByteArray typeName; *this >> typeName; streamer = getTypeStreamers().value(QMetaType::type(typeName.constData())); @@ -335,22 +449,55 @@ Bitstream& Bitstream::operator>>(const TypeStreamer*& streamer) { return *this; } -Bitstream& Bitstream::operator<<(const AttributePointer& attribute) { +Bitstream& Bitstream::operator<(const AttributePointer& attribute) { return *this << (QObject*)attribute.data(); } -Bitstream& Bitstream::operator>>(AttributePointer& attribute) { +Bitstream& Bitstream::operator>(AttributePointer& attribute) { QObject* object; *this >> object; attribute = AttributeRegistry::getInstance()->registerAttribute(static_cast(object)); return *this; } +Bitstream& Bitstream::operator<(const QScriptString& string) { + return *this << string.toString(); +} + +Bitstream& Bitstream::operator>(QScriptString& string) { + QString rawString; + *this >> rawString; + string = ScriptCache::getInstance()->getEngine()->toStringHandle(rawString); + return *this; +} + +Bitstream& Bitstream::operator<(const SharedObjectPointer& object) { + return *this << object.data(); +} + +Bitstream& Bitstream::operator>(SharedObjectPointer& object) { + QObject* rawObject; + *this >> rawObject; + object = static_cast(rawObject); + return *this; +} + +void Bitstream::clearSharedObject() { + SharedObjectPointer object(static_cast(sender())); + object->disconnect(this); + emit sharedObjectCleared(_sharedObjectStreamer.takePersistentID(object)); +} + QHash& Bitstream::getMetaObjects() { static QHash metaObjects; return metaObjects; } +QMultiHash& Bitstream::getMetaObjectSubClasses() { + static QMultiHash metaObjectSubClasses; + return metaObjectSubClasses; +} + QHash& Bitstream::getTypeStreamers() { static QHash typeStreamers; return typeStreamers; diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 5764cf2cdf..5a79b10766 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -11,16 +11,19 @@ #include #include +#include #include #include #include #include +#include "SharedObject.h" + class QByteArray; +class QColor; class QDataStream; -struct QMetaObject; -class QObject; +class QUrl; class Attribute; class AttributeValue; @@ -65,6 +68,10 @@ public: void persistTransientValues(const QHash& transientValues); + int takePersistentID(T value) { return _persistentIDs.take(value); } + + void removePersistentValue(int id) { _persistentValues.remove(id); } + RepeatedValueStreamer& operator<<(T value); RepeatedValueStreamer& operator>>(T& value); @@ -126,7 +133,7 @@ template inline RepeatedValueStreamer& RepeatedValueStreamer::ope int& offset = _transientOffsets[value]; if (offset == 0) { _idStreamer << (_lastPersistentID + (offset = ++_lastTransientOffset)); - _stream << value; + _stream < value; } else { _idStreamer << (_lastPersistentID + offset); @@ -147,7 +154,7 @@ template inline RepeatedValueStreamer& RepeatedValueStreamer::ope int offset = id - _lastPersistentID; typename QHash::iterator it = _transientValues.find(offset); if (it == _transientValues.end()) { - _stream >> value; + _stream > value; _transientValues.insert(offset, value); } else { @@ -158,7 +165,9 @@ template inline RepeatedValueStreamer& RepeatedValueStreamer::ope } /// A stream for bit-aligned data. -class Bitstream { +class Bitstream : public QObject { + Q_OBJECT + public: class WriteMappings { @@ -166,6 +175,8 @@ public: QHash metaObjectOffsets; QHash typeStreamerOffsets; QHash attributeOffsets; + QHash scriptStringOffsets; + QHash sharedObjectOffsets; }; class ReadMappings { @@ -173,6 +184,8 @@ public: QHash metaObjectValues; QHash typeStreamerValues; QHash attributeValues; + QHash scriptStringValues; + QHash sharedObjectValues; }; /// Registers a metaobject under its name so that instances of it can be streamed. @@ -183,8 +196,11 @@ public: /// \return zero; the function only returns a value so that it can be used in static initialization static int registerTypeStreamer(int type, TypeStreamer* streamer); + /// Returns the list of registered subclasses for the supplied meta-object. + static QList getMetaObjectSubClasses(const QMetaObject* metaObject); + /// Creates a new bitstream. Note: the stream may be used for reading or writing, but not both. - Bitstream(QDataStream& underlying); + Bitstream(QDataStream& underlying, QObject* parent = NULL); /// Writes a set of bits to the underlying stream. /// \param bits the number of bits to write @@ -202,9 +218,6 @@ public: /// Resets to the initial state. void reset(); - /// Returns a reference to the attribute streamer. - RepeatedValueStreamer& getAttributeStreamer() { return _attributeStreamer; } - /// Returns the set of transient mappings gathered during writing and resets them. WriteMappings getAndResetWriteMappings(); @@ -217,6 +230,9 @@ public: /// Persists a set of read mappings recorded earlier. void persistReadMappings(const ReadMappings& mappings); + /// Removes a shared object from the read mappings. + void clearSharedObject(int id) { _sharedObjectStreamer.removePersistentValue(id); } + Bitstream& operator<<(bool value); Bitstream& operator>>(bool& value); @@ -232,9 +248,15 @@ public: Bitstream& operator<<(const QByteArray& string); Bitstream& operator>>(QByteArray& string); + Bitstream& operator<<(const QColor& color); + Bitstream& operator>>(QColor& color); + Bitstream& operator<<(const QString& string); Bitstream& operator>>(QString& string); + Bitstream& operator<<(const QUrl& url); + Bitstream& operator>>(QUrl& url); + Bitstream& operator<<(const QVariant& value); Bitstream& operator>>(QVariant& value); @@ -244,6 +266,9 @@ public: template Bitstream& operator<<(const QList& list); template Bitstream& operator>>(QList& list); + template Bitstream& operator<<(const QHash& hash); + template Bitstream& operator>>(QHash& hash); + Bitstream& operator<<(const QObject* object); Bitstream& operator>>(QObject*& object); @@ -256,6 +281,35 @@ public: Bitstream& operator<<(const AttributePointer& attribute); Bitstream& operator>>(AttributePointer& attribute); + Bitstream& operator<<(const QScriptString& string); + Bitstream& operator>>(QScriptString& string); + + Bitstream& operator<<(const SharedObjectPointer& object); + Bitstream& operator>>(SharedObjectPointer& object); + + Bitstream& operator<(const QMetaObject* metaObject); + Bitstream& operator>(const QMetaObject*& metaObject); + + Bitstream& operator<(const TypeStreamer* streamer); + Bitstream& operator>(const TypeStreamer*& streamer); + + Bitstream& operator<(const AttributePointer& attribute); + Bitstream& operator>(AttributePointer& attribute); + + Bitstream& operator<(const QScriptString& string); + Bitstream& operator>(QScriptString& string); + + Bitstream& operator<(const SharedObjectPointer& object); + Bitstream& operator>(SharedObjectPointer& object); + +signals: + + void sharedObjectCleared(int id); + +private slots: + + void clearSharedObject(); + private: QDataStream& _underlying; @@ -265,8 +319,11 @@ private: RepeatedValueStreamer _metaObjectStreamer; RepeatedValueStreamer _typeStreamerStreamer; RepeatedValueStreamer _attributeStreamer; + RepeatedValueStreamer _scriptStringStreamer; + RepeatedValueStreamer _sharedObjectStreamer; static QHash& getMetaObjects(); + static QMultiHash& getMetaObjectSubClasses(); static QHash& getTypeStreamers(); }; @@ -291,6 +348,32 @@ template inline Bitstream& Bitstream::operator>>(QList& list) { return *this; } +template inline Bitstream& Bitstream::operator<<(const QHash& hash) { + *this << hash.size(); + for (typename QHash::const_iterator it = hash.constBegin(); it != hash.constEnd(); it++) { + *this << it.key(); + *this << it.value(); + } + return *this; +} + +template inline Bitstream& Bitstream::operator>>(QHash& hash) { + int size; + *this >> size; + hash.clear(); + hash.reserve(size); + for (int i = 0; i < size; i++) { + K key; + V value; + *this >> key; + *this >> value; + hash.insertMulti(key, value); + } + return *this; +} + +Q_DECLARE_METATYPE(const QMetaObject*) + /// Macro for registering streamable meta-objects. #define REGISTER_META_OBJECT(x) static int x##Registration = Bitstream::registerMetaObject(#x, &x::staticMetaObject); diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 83ef641b39..af271d97ba 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -10,9 +10,15 @@ #include -#include "DatagramSequencer.h" +#include -const int MAX_DATAGRAM_SIZE = 1500; +#include "DatagramSequencer.h" +#include "MetavoxelMessages.h" + +// in sequencer parlance, a "packet" may consist of multiple datagrams. clarify when we refer to actual datagrams +const int MAX_DATAGRAM_SIZE = MAX_PACKET_SIZE; + +const int DEFAULT_MAX_PACKET_SIZE = 3000; DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader) : _outgoingPacketStream(&_outgoingPacketData, QIODevice::WriteOnly), @@ -26,13 +32,16 @@ DatagramSequencer::DatagramSequencer(const QByteArray& datagramHeader) : _incomingPacketNumber(0), _incomingPacketStream(&_incomingPacketData, QIODevice::ReadOnly), _inputStream(_incomingPacketStream), - _receivedHighPriorityMessages(0) { + _receivedHighPriorityMessages(0), + _maxPacketSize(DEFAULT_MAX_PACKET_SIZE) { _outgoingPacketStream.setByteOrder(QDataStream::LittleEndian); _incomingDatagramStream.setByteOrder(QDataStream::LittleEndian); _incomingPacketStream.setByteOrder(QDataStream::LittleEndian); _outgoingDatagramStream.setByteOrder(QDataStream::LittleEndian); + connect(&_outputStream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int))); + memcpy(_outgoingDatagram.data(), datagramHeader.constData(), _datagramHeaderSize); } @@ -41,6 +50,22 @@ void DatagramSequencer::sendHighPriorityMessage(const QVariant& data) { _highPriorityMessages.append(message); } +ReliableChannel* DatagramSequencer::getReliableOutputChannel(int index) { + ReliableChannel*& channel = _reliableOutputChannels[index]; + if (!channel) { + channel = new ReliableChannel(this, index, true); + } + return channel; +} + +ReliableChannel* DatagramSequencer::getReliableInputChannel(int index) { + ReliableChannel*& channel = _reliableInputChannels[index]; + if (!channel) { + channel = new ReliableChannel(this, index, false); + } + return channel; +} + Bitstream& DatagramSequencer::startPacket() { // start with the list of acknowledgements _outgoingPacketStream << (quint32)_receiveRecords.size(); @@ -60,7 +85,18 @@ Bitstream& DatagramSequencer::startPacket() { void DatagramSequencer::endPacket() { _outputStream.flush(); - sendPacket(QByteArray::fromRawData(_outgoingPacketData.constData(), _outgoingPacketStream.device()->pos())); + + // if we have space remaining, send some data from our reliable channels + int remaining = _maxPacketSize - _outgoingPacketStream.device()->pos(); + const int MINIMUM_RELIABLE_SIZE = sizeof(quint32) * 5; // count, channel number, segment count, offset, size + QVector spans; + if (remaining > MINIMUM_RELIABLE_SIZE) { + appendReliableData(remaining, spans); + } else { + _outgoingPacketStream << (quint32)0; + } + + sendPacket(QByteArray::fromRawData(_outgoingPacketData.constData(), _outgoingPacketStream.device()->pos()), spans); _outgoingPacketStream.device()->seek(0); } @@ -138,20 +174,30 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) { } // read and dispatch the high-priority messages - quint32 highPriorityMessageCount; + int highPriorityMessageCount; _incomingPacketStream >> highPriorityMessageCount; int newHighPriorityMessages = highPriorityMessageCount - _receivedHighPriorityMessages; for (int i = 0; i < highPriorityMessageCount; i++) { QVariant data; _inputStream >> data; if (i >= _receivedHighPriorityMessages) { - emit receivedHighPriorityMessage(data); + handleHighPriorityMessage(data); } } _receivedHighPriorityMessages = highPriorityMessageCount; - // alert external parties so that they can read the rest + // alert external parties so that they can read the middle emit readyToRead(_inputStream); + + // read the reliable data, if any + quint32 reliableChannels; + _incomingPacketStream >> reliableChannels; + for (int i = 0; i < reliableChannels; i++) { + quint32 channelIndex; + _incomingPacketStream >> channelIndex; + getReliableOutputChannel(channelIndex)->readData(_incomingPacketStream); + } + _incomingPacketStream.device()->seek(0); _inputStream.reset(); @@ -160,6 +206,12 @@ void DatagramSequencer::receivedDatagram(const QByteArray& datagram) { _receiveRecords.append(record); } +void DatagramSequencer::sendClearSharedObjectMessage(int id) { + // for now, high priority + ClearSharedObjectMessage message = { id }; + sendHighPriorityMessage(QVariant::fromValue(message)); +} + void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { // stop acknowledging the recorded packets while (!_receiveRecords.isEmpty() && _receiveRecords.first().packetNumber <= record.lastReceivedPacketNumber) { @@ -178,9 +230,46 @@ void DatagramSequencer::sendRecordAcknowledged(const SendRecord& record) { break; } } + + // acknowledge the received spans + foreach (const ChannelSpan& span, record.spans) { + getReliableOutputChannel(span.channel)->spanAcknowledged(span); + } } -void DatagramSequencer::sendPacket(const QByteArray& packet) { +void DatagramSequencer::appendReliableData(int bytes, QVector& spans) { + // gather total number of bytes to write, priority + int totalBytes = 0; + float totalPriority = 0.0f; + int totalChannels = 0; + foreach (ReliableChannel* channel, _reliableOutputChannels) { + int channelBytes = channel->getBytesAvailable(); + if (channelBytes > 0) { + totalBytes += channelBytes; + totalPriority += channel->getPriority(); + totalChannels++; + } + } + _outgoingPacketStream << (quint32)totalChannels; + if (totalChannels == 0) { + return; + } + totalBytes = qMin(bytes, totalBytes); + + foreach (ReliableChannel* channel, _reliableOutputChannels) { + int channelBytes = channel->getBytesAvailable(); + if (channelBytes == 0) { + continue; + } + _outgoingPacketStream << (quint32)channel->getIndex(); + channelBytes = qMin(channelBytes, (int)(totalBytes * channel->getPriority() / totalPriority)); + channel->writeData(_outgoingPacketStream, channelBytes, spans); + totalBytes -= channelBytes; + totalPriority -= channel->getPriority(); + } +} + +void DatagramSequencer::sendPacket(const QByteArray& packet, const QVector& spans) { QIODeviceOpener opener(&_outgoingDatagramBuffer, QIODevice::WriteOnly); // increment the packet number @@ -188,7 +277,7 @@ void DatagramSequencer::sendPacket(const QByteArray& packet) { // record the send SendRecord record = { _outgoingPacketNumber, _receiveRecords.isEmpty() ? 0 : _receiveRecords.last().packetNumber, - _outputStream.getAndResetWriteMappings() }; + _outputStream.getAndResetWriteMappings(), spans }; _sendRecords.append(record); // write the sequence number and size, which are the same between all fragments @@ -213,3 +302,228 @@ void DatagramSequencer::sendPacket(const QByteArray& packet) { } while(offset < packet.size()); } +void DatagramSequencer::handleHighPriorityMessage(const QVariant& data) { + if (data.userType() == ClearSharedObjectMessage::Type) { + _inputStream.clearSharedObject(data.value().id); + + } else { + emit receivedHighPriorityMessage(data); + } +} + +SpanList::SpanList() : _totalSet(0) { +} + +int SpanList::set(int offset, int length) { + // if we intersect the front of the list, consume beginning spans and return advancement + if (offset <= 0) { + int intersection = offset + length; + return (intersection > 0) ? setSpans(_spans.begin(), intersection) : 0; + } + + // look for an intersection within the list + int position = 0; + for (QList::iterator it = _spans.begin(); it != _spans.end(); it++) { + // if we intersect the unset portion, contract it + position += it->unset; + if (offset <= position) { + int remove = position - offset; + it->unset -= remove; + + // if we continue into the set portion, expand it and consume following spans + int extra = offset + length - position; + if (extra >= 0) { + int amount = setSpans(it + 1, extra); + it->set += amount; + _totalSet += amount; + + // otherwise, insert a new span + } else { + Span span = { it->unset, length + extra }; + _spans.insert(it, span); + it->unset = -extra; + _totalSet += span.set; + } + return 0; + } + + // if we intersect the set portion, expand it and consume following spans + position += it->set; + if (offset <= position) { + int extra = offset + length - position; + int amount = setSpans(it + 1, extra); + it->set += amount; + _totalSet += amount; + return 0; + } + } + + // add to end of list + Span span = { offset - position, length }; + _spans.append(span); + _totalSet += length; + + return 0; +} + +int SpanList::setSpans(QList::iterator it, int length) { + int remainingLength = length; + int totalRemoved = 0; + for (; it != _spans.end(); it++) { + if (remainingLength < it->unset) { + it->unset -= remainingLength; + totalRemoved += remainingLength; + break; + } + int combined = it->unset + it->set; + remainingLength = qMax(remainingLength - combined, 0); + totalRemoved += combined; + it = _spans.erase(it); + _totalSet -= it->set; + } + return qMax(length, totalRemoved); +} + +int ReliableChannel::getBytesAvailable() const { + return _buffer.size() - _acknowledged.getTotalSet(); +} + +void ReliableChannel::sendMessage(const QVariant& message) { + _bitstream << message; +} + +void ReliableChannel::sendClearSharedObjectMessage(int id) { + ClearSharedObjectMessage message = { id }; + sendMessage(QVariant::fromValue(message)); +} + +ReliableChannel::ReliableChannel(DatagramSequencer* sequencer, int index, bool output) : + QObject(sequencer), + _index(index), + _dataStream(&_buffer), + _bitstream(_dataStream), + _priority(1.0f), + _offset(0), + _writePosition(0) { + + _buffer.open(output ? QIODevice::WriteOnly : QIODevice::ReadOnly); + _dataStream.setByteOrder(QDataStream::LittleEndian); + + connect(&_bitstream, SIGNAL(sharedObjectCleared(int)), SLOT(sendClearSharedObjectMessage(int))); +} + +void ReliableChannel::writeData(QDataStream& out, int bytes, QVector& spans) { + // find out how many spans we want to write + int spanCount = 0; + int remainingBytes = bytes; + bool first = true; + while (remainingBytes > 0) { + int position = 0; + foreach (const SpanList::Span& span, _acknowledged.getSpans()) { + if (remainingBytes <= 0) { + break; + } + spanCount++; + + remainingBytes -= getBytesToWrite(first, span.unset); + position += (span.unset + span.set); + } + int leftover = _buffer.pos() - position; + if (remainingBytes > 0 && leftover > 0) { + spanCount++; + remainingBytes -= getBytesToWrite(first, leftover); + } + } + + // write the count and the spans + out << (quint32)spanCount; + remainingBytes = bytes; + first = true; + while (remainingBytes > 0) { + int position = 0; + foreach (const SpanList::Span& span, _acknowledged.getSpans()) { + if (remainingBytes <= 0) { + break; + } + remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, span.unset), spans); + position += (span.unset + span.set); + } + if (remainingBytes > 0 && position < _buffer.pos()) { + remainingBytes -= writeSpan(out, first, position, qMin(remainingBytes, (int)(_buffer.pos() - position)), spans); + } + } +} + +int ReliableChannel::getBytesToWrite(bool& first, int length) const { + if (first) { + first = false; + return length - (_writePosition % length); + } + return length; +} + +int ReliableChannel::writeSpan(QDataStream& out, bool& first, int position, int length, QVector& spans) { + if (first) { + first = false; + position = _writePosition % length; + length -= position; + _writePosition += length; + } + DatagramSequencer::ChannelSpan span = { _index, _offset + position, length }; + spans.append(span); + out << (quint32)span.offset; + out << (quint32)length; + out.writeRawData(_buffer.data().constData() + position, length); + return length; +} + +void ReliableChannel::spanAcknowledged(const DatagramSequencer::ChannelSpan& span) { + int advancement = _acknowledged.set(span.offset - _offset, span.length); + if (advancement > 0) { + // TODO: better way of pruning buffer + _buffer.buffer() = _buffer.buffer().right(_buffer.size() - advancement); + _buffer.seek(_buffer.size()); + _offset += advancement; + _writePosition = qMax(_writePosition - advancement, 0); + } +} + +void ReliableChannel::readData(QDataStream& in) { + quint32 segments; + in >> segments; + for (int i = 0; i < segments; i++) { + quint32 offset, size; + in >> offset >> size; + + int position = offset - _offset; + int end = position + size; + if (_assemblyBuffer.size() < end) { + _assemblyBuffer.resize(end); + } + if (end <= 0) { + in.skipRawData(size); + } else if (position < 0) { + in.skipRawData(-position); + in.readRawData(_assemblyBuffer.data(), size + position); + } else { + in.readRawData(_assemblyBuffer.data() + position, size); + } + int advancement = _acknowledged.set(position, size); + if (advancement > 0) { + // TODO: better way of pruning buffer + _buffer.buffer().append(_assemblyBuffer.constData(), advancement); + emit _buffer.readyRead(); + _assemblyBuffer = _assemblyBuffer.right(_assemblyBuffer.size() - advancement); + _offset += advancement; + } + } + + // when the read head is sufficiently advanced into the buffer, prune it off. this along + // with other buffer usages should be replaced with a circular buffer + const int PRUNE_SIZE = 8192; + if (_buffer.pos() > PRUNE_SIZE) { + _buffer.buffer() = _buffer.buffer().right(_buffer.size() - _buffer.pos()); + _buffer.seek(0); + } +} + diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 4ef827abeb..44d3c83116 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -14,9 +14,12 @@ #include #include #include +#include #include "Bitstream.h" +class ReliableChannel; + /// Performs simple datagram sequencing, packet fragmentation and reassembly. class DatagramSequencer : public QObject { Q_OBJECT @@ -46,6 +49,18 @@ public: /// Returns a reference to the list of high priority messages not yet acknowledged. const QList& getHighPriorityMessages() const { return _highPriorityMessages; } + /// Sets the maximum packet size. This is a soft limit that determines how much + /// reliable data we include with each transmission. + void setMaxPacketSize(int maxPacketSize) { _maxPacketSize = maxPacketSize; } + + int getMaxPacketSize() const { return _maxPacketSize; } + + /// Returns the output channel at the specified index, creating it if necessary. + ReliableChannel* getReliableOutputChannel(int index = 0); + + /// Returns the intput channel at the + ReliableChannel* getReliableInputChannel(int index = 0); + /// Starts a new packet for transmission. /// \return a reference to the Bitstream to use for writing to the packet Bitstream& startPacket(); @@ -75,14 +90,28 @@ signals: /// Emitted when our acknowledgement of a received packet has been acknowledged by the remote side. /// \param index the index of the packet in our list of receive records void receiveAcknowledged(int index); + +private slots: + + void sendClearSharedObjectMessage(int id); private: + friend class ReliableChannel; + + class ChannelSpan { + public: + int channel; + int offset; + int length; + }; + class SendRecord { public: int packetNumber; int lastReceivedPacketNumber; Bitstream::WriteMappings mappings; + QVector spans; }; class ReceiveRecord { @@ -97,9 +126,14 @@ private: /// Notes that the described send was acknowledged by the other party. void sendRecordAcknowledged(const SendRecord& record); + /// Appends some reliable data to the outgoing packet. + void appendReliableData(int bytes, QVector& spans); + /// Sends a packet to the other party, fragmenting it into multiple datagrams (and emitting /// readyToWrite) as necessary. - void sendPacket(const QByteArray& packet); + void sendPacket(const QByteArray& packet, const QVector& spans); + + void handleHighPriorityMessage(const QVariant& data); QList _sendRecords; QList _receiveRecords; @@ -126,6 +160,92 @@ private: QList _highPriorityMessages; int _receivedHighPriorityMessages; + + int _maxPacketSize; + + QHash _reliableOutputChannels; + QHash _reliableInputChannels; +}; + +/// A list of contiguous spans, alternating between set and unset. Conceptually, the list is preceeded by a set +/// span of infinite length and followed by an unset span of infinite length. Within those bounds, it alternates +/// between unset and set. +class SpanList { +public: + + class Span { + public: + int unset; + int set; + }; + + SpanList(); + + const QList& getSpans() const { return _spans; } + + /// Returns the total length set. + int getTotalSet() const { return _totalSet; } + + /// Sets a region of the list. + /// \return the advancement of the set length at the beginning of the list + int set(int offset, int length); + +private: + + /// Sets the spans starting at the specified iterator, consuming at least the given length. + /// \return the actual amount set, which may be greater if we ran into an existing set span + int setSpans(QList::iterator it, int length); + + QList _spans; + int _totalSet; +}; + +/// Represents a single reliable channel multiplexed onto the datagram sequence. +class ReliableChannel : public QObject { + Q_OBJECT + +public: + + int getIndex() const { return _index; } + + QDataStream& getDataStream() { return _dataStream; } + Bitstream& getBitstream() { return _bitstream; } + + void setPriority(float priority) { _priority = priority; } + float getPriority() const { return _priority; } + + int getBytesAvailable() const; + + void sendMessage(const QVariant& message); + +private slots: + + void sendClearSharedObjectMessage(int id); + +private: + + friend class DatagramSequencer; + + ReliableChannel(DatagramSequencer* sequencer, int index, bool output); + + void writeData(QDataStream& out, int bytes, QVector& spans); + int getBytesToWrite(bool& first, int length) const; + int writeSpan(QDataStream& out, bool& first, int position, int length, QVector& spans); + + void spanAcknowledged(const DatagramSequencer::ChannelSpan& span); + + void readData(QDataStream& in); + + int _index; + QBuffer _buffer; + QByteArray _assemblyBuffer; + QDataStream _dataStream; + Bitstream _bitstream; + float _priority; + + int _offset; + int _writePosition; + SpanList _acknowledged; }; #endif /* defined(__interface__DatagramSequencer__) */ diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index 2f99684a42..b0e24405e6 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -6,11 +6,18 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // +#include #include #include #include "MetavoxelData.h" #include "MetavoxelUtil.h" +#include "ScriptCache.h" + +REGISTER_META_OBJECT(MetavoxelGuide) +REGISTER_META_OBJECT(DefaultMetavoxelGuide) +REGISTER_META_OBJECT(ScriptedMetavoxelGuide) +REGISTER_META_OBJECT(ThrobbingMetavoxelGuide) MetavoxelData::MetavoxelData() : _size(1.0f) { } @@ -61,7 +68,7 @@ void MetavoxelData::guide(MetavoxelVisitor& visitor) { firstVisitation.outputNodes[i] = node; } static_cast(firstVisitation.info.inputValues.last().getInlineValue< - PolymorphicDataPointer>().data())->guide(firstVisitation); + SharedObjectPointer>().data())->guide(firstVisitation); for (int i = 0; i < outputs.size(); i++) { AttributeValue& value = firstVisitation.info.outputValues[i]; if (!value.getAttribute()) { @@ -129,7 +136,7 @@ void MetavoxelData::read(Bitstream& in) { in >> rootCount; for (int i = 0; i < rootCount; i++) { AttributePointer attribute; - in.getAttributeStreamer() >> attribute; + in >> attribute; MetavoxelNode*& root = _roots[attribute]; root = new MetavoxelNode(attribute); root->read(attribute, in); @@ -140,7 +147,7 @@ void MetavoxelData::write(Bitstream& out) const { out << _size; out << _roots.size(); for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { - out.getAttributeStreamer() << it.key(); + out << it.key(); it.value()->write(it.key(), out); } } @@ -169,7 +176,7 @@ void MetavoxelData::readDelta(const MetavoxelData& reference, Bitstream& in) { in >> changedCount; for (int i = 0; i < changedCount; i++) { AttributePointer attribute; - in.getAttributeStreamer() >> attribute; + in >> attribute; MetavoxelNode*& root = _roots[attribute]; if (root) { MetavoxelNode* oldRoot = root; @@ -187,7 +194,7 @@ void MetavoxelData::readDelta(const MetavoxelData& reference, Bitstream& in) { in >> removedCount; for (int i = 0; i < removedCount; i++) { AttributePointer attribute; - in.getAttributeStreamer() >> attribute; + in >> attribute; _roots.take(attribute)->decrementReferenceCount(attribute); } } @@ -227,7 +234,7 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, Bitstream& out) c for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { MetavoxelNode* referenceRoot = expandedReference->_roots.value(it.key()); if (it.value() != referenceRoot) { - out.getAttributeStreamer() << it.key(); + out << it.key(); if (referenceRoot) { it.value()->writeDelta(it.key(), *referenceRoot, out); } else { @@ -248,7 +255,7 @@ void MetavoxelData::writeDelta(const MetavoxelData& reference, Bitstream& out) c for (QHash::const_iterator it = expandedReference->_roots.constBegin(); it != expandedReference->_roots.constEnd(); it++) { if (!_roots.contains(it.key())) { - out.getAttributeStreamer() << it.key(); + out << it.key(); } } @@ -425,8 +432,7 @@ MetavoxelVisitor::MetavoxelVisitor(const QVector& inputs, cons MetavoxelVisitor::~MetavoxelVisitor() { } -PolymorphicData* DefaultMetavoxelGuide::clone() const { - return new DefaultMetavoxelGuide(); +DefaultMetavoxelGuide::DefaultMetavoxelGuide() { } void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { @@ -470,7 +476,7 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { (i & Y_MAXIMUM_FLAG) ? nextVisitation.info.size : 0.0f, (i & Z_MAXIMUM_FLAG) ? nextVisitation.info.size : 0.0f); static_cast(nextVisitation.info.inputValues.last().getInlineValue< - PolymorphicDataPointer>().data())->guide(nextVisitation); + SharedObjectPointer>().data())->guide(nextVisitation); for (int j = 0; j < nextVisitation.outputNodes.size(); j++) { AttributeValue& value = nextVisitation.info.outputValues[j]; if (!value.getAttribute()) { @@ -514,6 +520,25 @@ void DefaultMetavoxelGuide::guide(MetavoxelVisitation& visitation) { } } +ThrobbingMetavoxelGuide::ThrobbingMetavoxelGuide() : _rate(10.0) { +} + +void ThrobbingMetavoxelGuide::guide(MetavoxelVisitation& visitation) { + AttributePointer colorAttribute = AttributeRegistry::getInstance()->getColorAttribute(); + for (int i = 0; i < visitation.info.inputValues.size(); i++) { + AttributeValue& attributeValue = visitation.info.inputValues[i]; + if (attributeValue.getAttribute() == colorAttribute) { + QRgb base = attributeValue.getInlineValue(); + double seconds = QDateTime::currentMSecsSinceEpoch() / 1000.0; + double amplitude = sin(_rate * seconds) * 0.5 + 0.5; + attributeValue.setInlineValue(qRgba(qRed(base) * amplitude, qGreen(base) * amplitude, + qBlue(base) * amplitude, qAlpha(base))); + } + } + + DefaultMetavoxelGuide::guide(visitation); +} + static QScriptValue getAttributes(QScriptEngine* engine, ScriptedMetavoxelGuide* guide, const QVector& attributes) { @@ -569,35 +594,47 @@ QScriptValue ScriptedMetavoxelGuide::visit(QScriptContext* context, QScriptEngin return result; } -ScriptedMetavoxelGuide::ScriptedMetavoxelGuide(const QScriptValue& guideFunction) : - _guideFunction(guideFunction), - _minimumHandle(guideFunction.engine()->toStringHandle("minimum")), - _sizeHandle(guideFunction.engine()->toStringHandle("size")), - _inputValuesHandle(guideFunction.engine()->toStringHandle("inputValues")), - _outputValuesHandle(guideFunction.engine()->toStringHandle("outputValues")), - _isLeafHandle(guideFunction.engine()->toStringHandle("isLeaf")), - _getInputsFunction(guideFunction.engine()->newFunction(getInputs, 0)), - _getOutputsFunction(guideFunction.engine()->newFunction(getOutputs, 0)), - _visitFunction(guideFunction.engine()->newFunction(visit, 1)), - _info(guideFunction.engine()->newObject()), - _minimum(guideFunction.engine()->newArray(3)) { - - _arguments.append(guideFunction.engine()->newObject()); - QScriptValue visitor = guideFunction.engine()->newObject(); - visitor.setProperty("getInputs", _getInputsFunction); - visitor.setProperty("getOutputs", _getOutputsFunction); - visitor.setProperty("visit", _visitFunction); - _arguments[0].setProperty("visitor", visitor); - _arguments[0].setProperty("info", _info); - _info.setProperty(_minimumHandle, _minimum); -} - -PolymorphicData* ScriptedMetavoxelGuide::clone() const { - return new ScriptedMetavoxelGuide(_guideFunction); +ScriptedMetavoxelGuide::ScriptedMetavoxelGuide() { } void ScriptedMetavoxelGuide::guide(MetavoxelVisitation& visitation) { - QScriptValue data = _guideFunction.engine()->newVariant(QVariant::fromValue(this)); + QScriptValue guideFunction; + if (_guideFunction) { + guideFunction = _guideFunction->getValue(); + + } else if (_url.isValid()) { + _guideFunction = ScriptCache::getInstance()->getValue(_url); + guideFunction = _guideFunction->getValue(); + } + if (!guideFunction.isValid()) { + // before we load, just use the default behavior + DefaultMetavoxelGuide::guide(visitation); + return; + } + QScriptEngine* engine = guideFunction.engine(); + if (!_minimumHandle.isValid()) { + _minimumHandle = engine->toStringHandle("minimum"); + _sizeHandle = engine->toStringHandle("size"); + _inputValuesHandle = engine->toStringHandle("inputValues"); + _outputValuesHandle = engine->toStringHandle("outputValues"); + _isLeafHandle = engine->toStringHandle("isLeaf"); + _getInputsFunction = engine->newFunction(getInputs, 0); + _getOutputsFunction = engine->newFunction(getOutputs, 0); + _visitFunction = engine->newFunction(visit, 1); + _info = engine->newObject(); + _minimum = engine->newArray(3); + + _arguments.clear(); + _arguments.append(engine->newObject()); + QScriptValue visitor = engine->newObject(); + visitor.setProperty("getInputs", _getInputsFunction); + visitor.setProperty("getOutputs", _getOutputsFunction); + visitor.setProperty("visit", _visitFunction); + _arguments[0].setProperty("visitor", visitor); + _arguments[0].setProperty("info", _info); + _info.setProperty(_minimumHandle, _minimum); + } + QScriptValue data = engine->newVariant(QVariant::fromValue(this)); _getInputsFunction.setData(data); _visitFunction.setData(data); _minimum.setProperty(0, visitation.info.minimum.x); @@ -606,12 +643,18 @@ void ScriptedMetavoxelGuide::guide(MetavoxelVisitation& visitation) { _info.setProperty(_sizeHandle, visitation.info.size); _info.setProperty(_isLeafHandle, visitation.info.isLeaf); _visitation = &visitation; - _guideFunction.call(QScriptValue(), _arguments); - if (_guideFunction.engine()->hasUncaughtException()) { - qDebug() << "Script error: " << _guideFunction.engine()->uncaughtException().toString(); + guideFunction.call(QScriptValue(), _arguments); + if (engine->hasUncaughtException()) { + qDebug() << "Script error: " << engine->uncaughtException().toString(); } } +void ScriptedMetavoxelGuide::setURL(const ParameterizedURL& url) { + _url = url; + _guideFunction.reset(); + _minimumHandle = QScriptString(); +} + bool MetavoxelVisitation::allInputNodesLeaves() const { foreach (MetavoxelNode* node, inputNodes) { if (node != NULL && !node->isLeaf()) { diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index 0114f1b4d6..51cdd6cf64 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -19,13 +20,14 @@ #include #include "AttributeRegistry.h" +#include "MetavoxelUtil.h" class QScriptContext; -class Box; class MetavoxelNode; class MetavoxelVisitation; class MetavoxelVisitor; +class NetworkValue; /// The base metavoxel representation shared between server and client. class MetavoxelData { @@ -150,7 +152,9 @@ protected: typedef QSharedPointer MetavoxelVisitorPointer; /// Interface for objects that guide metavoxel visitors. -class MetavoxelGuide : public PolymorphicData { +class MetavoxelGuide : public SharedObject { + Q_OBJECT + public: /// Guides the specified visitor to the contained voxels. @@ -159,30 +163,56 @@ public: /// Guides visitors through the explicit content of the system. class DefaultMetavoxelGuide : public MetavoxelGuide { + Q_OBJECT + public: - virtual PolymorphicData* clone() const; + Q_INVOKABLE DefaultMetavoxelGuide(); virtual void guide(MetavoxelVisitation& visitation); }; -/// Represents a guide implemented in Javascript. -class ScriptedMetavoxelGuide : public MetavoxelGuide { +/// A temporary test guide that just makes the existing voxels throb with delight. +class ThrobbingMetavoxelGuide : public DefaultMetavoxelGuide { + Q_OBJECT + Q_PROPERTY(float rate MEMBER _rate) + public: - - ScriptedMetavoxelGuide(const QScriptValue& guideFunction); - - virtual PolymorphicData* clone() const; + + Q_INVOKABLE ThrobbingMetavoxelGuide(); virtual void guide(MetavoxelVisitation& visitation); + +private: + + float _rate; +}; +/// Represents a guide implemented in Javascript. +class ScriptedMetavoxelGuide : public DefaultMetavoxelGuide { + Q_OBJECT + Q_PROPERTY(ParameterizedURL url MEMBER _url WRITE setURL) + +public: + + Q_INVOKABLE ScriptedMetavoxelGuide(); + + virtual void guide(MetavoxelVisitation& visitation); + +public slots: + + void setURL(const ParameterizedURL& url); + private: static QScriptValue getInputs(QScriptContext* context, QScriptEngine* engine); static QScriptValue getOutputs(QScriptContext* context, QScriptEngine* engine); static QScriptValue visit(QScriptContext* context, QScriptEngine* engine); - QScriptValue _guideFunction; + ParameterizedURL _url; + + QSharedPointer _guideFunction; + QScriptString _minimumHandle; QScriptString _sizeHandle; QScriptString _inputValuesHandle; diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 25f4ffe422..c3cc78c5bc 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -21,6 +21,17 @@ class CloseSessionMessage { DECLARE_STREAMABLE_METATYPE(CloseSessionMessage) +/// Clears the mapping for a shared object. +class ClearSharedObjectMessage { + STREAMABLE + +public: + + STREAM int id; +}; + +DECLARE_STREAMABLE_METATYPE(ClearSharedObjectMessage) + /// A message containing the state of a client. class ClientStateMessage { STREAMABLE diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp index d337b0cc3f..e59cf0fe21 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.cpp +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -7,14 +7,106 @@ // #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include #include "MetavoxelUtil.h" +#include "ScriptCache.h" -QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize) { +static int scriptHashType = qRegisterMetaType(); +static int parameterizedURLType = qRegisterMetaType(); + +REGISTER_SIMPLE_TYPE_STREAMER(ScriptHash) +REGISTER_SIMPLE_TYPE_STREAMER(ParameterizedURL) + +class DelegatingItemEditorFactory : public QItemEditorFactory { +public: + + DelegatingItemEditorFactory(); + + virtual QWidget* createEditor(int userType, QWidget* parent) const; + virtual QByteArray valuePropertyName(int userType) const; + +private: + + const QItemEditorFactory* _parentFactory; +}; + +class DoubleEditor : public QDoubleSpinBox { +public: + + DoubleEditor(QWidget* parent = NULL); +}; + +DoubleEditor::DoubleEditor(QWidget* parent) : QDoubleSpinBox(parent) { + setMinimum(-FLT_MAX); +} + +DelegatingItemEditorFactory::DelegatingItemEditorFactory() : + _parentFactory(QItemEditorFactory::defaultFactory()) { + + QItemEditorFactory::setDefaultFactory(this); +} + +QWidget* DelegatingItemEditorFactory::createEditor(int userType, QWidget* parent) const { + QWidget* editor = QItemEditorFactory::createEditor(userType, parent); + return (editor == NULL) ? _parentFactory->createEditor(userType, parent) : editor; +} + +QByteArray DelegatingItemEditorFactory::valuePropertyName(int userType) const { + QByteArray propertyName = QItemEditorFactory::valuePropertyName(userType); + return propertyName.isNull() ? _parentFactory->valuePropertyName(userType) : propertyName; +} + +static QItemEditorFactory* getItemEditorFactory() { + static QItemEditorFactory* factory = new DelegatingItemEditorFactory(); + return factory; +} + +static QItemEditorCreatorBase* createDoubleEditorCreator() { + QItemEditorCreatorBase* creator = new QStandardItemEditorCreator(); + getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); + getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); + return creator; +} + +static QItemEditorCreatorBase* createQColorEditorCreator() { + QItemEditorCreatorBase* creator = new QStandardItemEditorCreator(); + getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); + return creator; +} + +static QItemEditorCreatorBase* createVec3EditorCreator() { + QItemEditorCreatorBase* creator = new QStandardItemEditorCreator(); + getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); + return creator; +} + +static QItemEditorCreatorBase* createParameterizedURLEditorCreator() { + QItemEditorCreatorBase* creator = new QStandardItemEditorCreator(); + getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); + return creator; +} + +static QItemEditorCreatorBase* doubleEditorCreator = createDoubleEditorCreator(); +static QItemEditorCreatorBase* qColorEditorCreator = createQColorEditorCreator(); +static QItemEditorCreatorBase* vec3EditorCreator = createVec3EditorCreator(); +static QItemEditorCreatorBase* parameterizedURLEditorCreator = createParameterizedURLEditorCreator(); + +QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize) { // get the header size int headerSize = numBytesForPacketHeader(data); @@ -22,7 +114,7 @@ QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& hea const int UUID_BYTES = 16; headerPlusIDSize = headerSize + UUID_BYTES; if (data.size() < headerPlusIDSize) { - qWarning() << "Metavoxel data too short [size=" << data.size() << ", sender=" << sender << "]\n"; + qWarning() << "Metavoxel data too short [size=" << data.size() << ", sendingNode=" << sendingNode << "]\n"; return QUuid(); } return QUuid::fromRfc4122(QByteArray::fromRawData(data.constData() + headerSize, UUID_BYTES)); @@ -33,3 +125,185 @@ bool Box::contains(const Box& other) const { other.minimum.y >= minimum.y && other.maximum.y <= maximum.y && other.minimum.z >= minimum.z && other.maximum.z <= maximum.z; } + +QColorEditor::QColorEditor(QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(); + layout->setContentsMargins(QMargins()); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + layout->addWidget(_button = new QPushButton()); + connect(_button, SIGNAL(clicked()), SLOT(selectColor())); +} + +void QColorEditor::setColor(const QColor& color) { + QString name = (_color = color).name(); + _button->setStyleSheet(QString("background: %1; color: %2").arg(name, QColor::fromRgb(~color.rgb()).name())); + _button->setText(name); +} + +void QColorEditor::selectColor() { + QColor color = QColorDialog::getColor(_color, this, QString(), QColorDialog::ShowAlphaChannel); + if (color.isValid()) { + setColor(color); + emit colorChanged(color); + } +} + +Vec3Editor::Vec3Editor(QWidget* parent) : QWidget(parent) { + QHBoxLayout* layout = new QHBoxLayout(); + layout->setContentsMargins(QMargins()); + setLayout(layout); + + layout->addWidget(_x = createComponentBox()); + layout->addWidget(_y = createComponentBox()); + layout->addWidget(_z = createComponentBox()); +} + +void Vec3Editor::setVector(const glm::vec3& vector) { + _vector = vector; + _x->setValue(vector.x); + _y->setValue(vector.y); + _z->setValue(vector.z); +} + +void Vec3Editor::updateVector() { + emit vectorChanged(_vector = glm::vec3(_x->value(), _y->value(), _z->value())); +} + +QDoubleSpinBox* Vec3Editor::createComponentBox() { + QDoubleSpinBox* box = new QDoubleSpinBox(); + box->setMinimum(-FLT_MAX); + box->setMaximumWidth(100); + connect(box, SIGNAL(valueChanged(double)), SLOT(updateVector())); + return box; +} + +ParameterizedURL::ParameterizedURL(const QUrl& url, const ScriptHash& parameters) : + _url(url), + _parameters(parameters) { +} + +bool ParameterizedURL::operator==(const ParameterizedURL& other) const { + return _url == other._url && _parameters == other._parameters; +} + +bool ParameterizedURL::operator!=(const ParameterizedURL& other) const { + return _url != other._url || _parameters != other._parameters; +} + +uint qHash(const ParameterizedURL& url, uint seed) { + // just hash on the URL, for now + return qHash(url.getURL(), seed); +} + +Bitstream& operator<<(Bitstream& out, const ParameterizedURL& url) { + out << url.getURL(); + out << url.getParameters(); + return out; +} + +Bitstream& operator>>(Bitstream& in, ParameterizedURL& url) { + QUrl qurl; + in >> qurl; + ScriptHash parameters; + in >> parameters; + url = ParameterizedURL(qurl, parameters); + return in; +} + +ParameterizedURLEditor::ParameterizedURLEditor(QWidget* parent) : + QWidget(parent) { + + QVBoxLayout* layout = new QVBoxLayout(); + layout->setContentsMargins(QMargins()); + setLayout(layout); + + QWidget* lineContainer = new QWidget(); + layout->addWidget(lineContainer); + + QHBoxLayout* lineLayout = new QHBoxLayout(); + lineContainer->setLayout(lineLayout); + lineLayout->setContentsMargins(QMargins()); + + lineLayout->addWidget(_line = new QLineEdit(), 1); + connect(_line, SIGNAL(textChanged(const QString&)), SLOT(updateURL())); + + QPushButton* refresh = new QPushButton("..."); + connect(refresh, SIGNAL(clicked(bool)), SLOT(updateParameters())); + lineLayout->addWidget(refresh); +} + +void ParameterizedURLEditor::setURL(const ParameterizedURL& url) { + _url = url; + _line->setText(url.getURL().toString()); + updateParameters(); +} + +void ParameterizedURLEditor::updateURL() { + ScriptHash parameters; + if (layout()->count() > 1) { + QFormLayout* form = static_cast(layout()->itemAt(1)); + for (int i = 0; i < form->rowCount(); i++) { + QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget(); + QByteArray valuePropertyName = widget->property("valuePropertyName").toByteArray(); + const QMetaObject* widgetMetaObject = widget->metaObject(); + QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName)); + parameters.insert(ScriptCache::getInstance()->getEngine()->toStringHandle( + widget->property("parameterName").toString()), widgetProperty.read(widget)); + } + } + emit urlChanged(_url = ParameterizedURL(_line->text(), parameters)); + if (_program) { + _program->disconnect(this); + } +} + +void ParameterizedURLEditor::updateParameters() { + if (_program) { + _program->disconnect(this); + } + _program = ScriptCache::getInstance()->getProgram(_url.getURL()); + if (_program->isLoaded()) { + continueUpdatingParameters(); + } else { + connect(_program.data(), SIGNAL(loaded()), SLOT(continueUpdatingParameters())); + } +} + +void ParameterizedURLEditor::continueUpdatingParameters() { + QVBoxLayout* layout = static_cast(this->layout()); + if (layout->count() > 1) { + QFormLayout* form = static_cast(layout->takeAt(1)); + for (int i = form->count() - 1; i >= 0; i--) { + QLayoutItem* item = form->takeAt(i); + if (item->widget()) { + delete item->widget(); + } + delete item; + } + delete form; + } + QSharedPointer value = ScriptCache::getInstance()->getValue(_url.getURL()); + const QList& parameters = static_cast(value.data())->getParameterInfo(); + if (parameters.isEmpty()) { + return; + } + QFormLayout* form = new QFormLayout(); + layout->addLayout(form); + foreach (const ParameterInfo& parameter, parameters) { + QWidget* widget = QItemEditorFactory::defaultFactory()->createEditor(parameter.type, NULL); + if (widget) { + form->addRow(parameter.name.toString() + ":", widget); + QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(parameter.type); + widget->setProperty("parameterName", parameter.name.toString()); + widget->setProperty("valuePropertyName", valuePropertyName); + const QMetaObject* widgetMetaObject = widget->metaObject(); + QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName)); + widgetProperty.write(widget, _url.getParameters().value(parameter.name)); + if (widgetProperty.hasNotifySignal()) { + connect(widget, QByteArray(SIGNAL()).append(widgetProperty.notifySignal().methodSignature()), + SLOT(updateURL())); + } + } + } +} diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h index d97826fb93..9fa721d21c 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.h +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -9,18 +9,29 @@ #ifndef __interface__MetavoxelUtil__ #define __interface__MetavoxelUtil__ +#include +#include +#include #include +#include + +#include +#include #include "Bitstream.h" class QByteArray; +class QDoubleSpinBox; +class QLineEdit; +class QPushButton; class HifiSockAddr; +class NetworkProgram; /// Reads and returns the session ID from a datagram. /// \param[out] headerPlusIDSize the size of the header (including the session ID) within the data /// \return the session ID, or a null ID if invalid (in which case a warning will be logged) -QUuid readSessionID(const QByteArray& data, const HifiSockAddr& sender, int& headerPlusIDSize); +QUuid readSessionID(const QByteArray& data, const SharedNodePointer& sendingNode, int& headerPlusIDSize); /// A streamable axis-aligned bounding box. class Box { @@ -36,4 +47,127 @@ public: DECLARE_STREAMABLE_METATYPE(Box) +/// Editor for color values. +class QColorEditor : public QWidget { + Q_OBJECT + Q_PROPERTY(QColor color MEMBER _color WRITE setColor NOTIFY colorChanged USER true) + +public: + + QColorEditor(QWidget* parent); + +signals: + + void colorChanged(const QColor& color); + +public slots: + + void setColor(const QColor& color); + +private slots: + + void selectColor(); + +private: + + QPushButton* _button; + QColor _color; +}; + +/// Editor for vector values. +class Vec3Editor : public QWidget { + Q_OBJECT + Q_PROPERTY(glm::vec3 vector MEMBER _vector WRITE setVector NOTIFY vectorChanged USER true) + +public: + + Vec3Editor(QWidget* parent); + +signals: + + void vectorChanged(const glm::vec3& vector); + +public slots: + + void setVector(const glm::vec3& vector); + +private slots: + + void updateVector(); + +private: + + QDoubleSpinBox* createComponentBox(); + + QDoubleSpinBox* _x; + QDoubleSpinBox* _y; + QDoubleSpinBox* _z; + glm::vec3 _vector; +}; + +typedef QHash ScriptHash; + +Q_DECLARE_METATYPE(ScriptHash) + +/// Combines a URL with a set of typed parameters. +class ParameterizedURL { +public: + + ParameterizedURL(const QUrl& url = QUrl(), const ScriptHash& parameters = ScriptHash()); + + bool isValid() const { return _url.isValid(); } + + void setURL(const QUrl& url) { _url = url; } + const QUrl& getURL() const { return _url; } + + void setParameters(const ScriptHash& parameters) { _parameters = parameters; } + const ScriptHash& getParameters() const { return _parameters; } + + bool operator==(const ParameterizedURL& other) const; + bool operator!=(const ParameterizedURL& other) const; + +private: + + QUrl _url; + ScriptHash _parameters; +}; + +uint qHash(const ParameterizedURL& url, uint seed = 0); + +Bitstream& operator<<(Bitstream& out, const ParameterizedURL& url); +Bitstream& operator>>(Bitstream& in, ParameterizedURL& url); + +Q_DECLARE_METATYPE(ParameterizedURL) + +/// Allows editing parameterized URLs. +class ParameterizedURLEditor : public QWidget { + Q_OBJECT + Q_PROPERTY(ParameterizedURL url MEMBER _url WRITE setURL NOTIFY urlChanged USER true) + +public: + + ParameterizedURLEditor(QWidget* parent = NULL); + +signals: + + void urlChanged(const ParameterizedURL& url); + +public slots: + + void setURL(const ParameterizedURL& url); + +private slots: + + void updateURL(); + void updateParameters(); + void continueUpdatingParameters(); + +private: + + ParameterizedURL _url; + QSharedPointer _program; + + QLineEdit* _line; +}; + #endif /* defined(__interface__MetavoxelUtil__) */ diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp new file mode 100644 index 0000000000..e7610038f5 --- /dev/null +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -0,0 +1,177 @@ +// +// ScriptCache.cpp +// metavoxels +// +// Created by Andrzej Kapolka on 2/4/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#include + +#include +#include +#include +#include +#include + +#include "AttributeRegistry.h" +#include "ScriptCache.h" + +ScriptCache* ScriptCache::getInstance() { + static ScriptCache cache; + return &cache; +} + +ScriptCache::ScriptCache() : + _networkAccessManager(NULL), + _engine(NULL) { + + setEngine(new QScriptEngine(this)); +} + +void ScriptCache::setEngine(QScriptEngine* engine) { + if (_engine && _engine->parent() == this) { + delete _engine; + } + AttributeRegistry::getInstance()->configureScriptEngine(_engine = engine); + _parametersString = engine->toStringHandle("parameters"); + _lengthString = engine->toStringHandle("length"); + _nameString = engine->toStringHandle("name"); + _typeString = engine->toStringHandle("type"); + _generatorString = engine->toStringHandle("generator"); +} + +QSharedPointer ScriptCache::getProgram(const QUrl& url) { + QSharedPointer program = _networkPrograms.value(url); + if (program.isNull()) { + program = QSharedPointer(new NetworkProgram(this, url)); + _networkPrograms.insert(url, program); + } + return program; +} + +QSharedPointer ScriptCache::getValue(const ParameterizedURL& url) { + QSharedPointer value = _networkValues.value(url); + if (value.isNull()) { + value = QSharedPointer(url.getParameters().isEmpty() ? + (NetworkValue*)new RootNetworkValue(getProgram(url.getURL())) : + (NetworkValue*)new DerivedNetworkValue(getValue(url.getURL()), url.getParameters())); + _networkValues.insert(url, value); + } + return value; +} + +NetworkProgram::NetworkProgram(ScriptCache* cache, const QUrl& url) : + _cache(cache), + _request(url), + _reply(NULL), + _attempts(0) { + + if (!url.isValid()) { + return; + } + _request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); + makeRequest(); +} + +NetworkProgram::~NetworkProgram() { + if (_reply != NULL) { + delete _reply; + } +} + +void NetworkProgram::makeRequest() { + QNetworkAccessManager* manager = _cache->getNetworkAccessManager(); + if (manager == NULL) { + return; + } + _reply = manager->get(_request); + + connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); + connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError())); +} + +void NetworkProgram::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) { + if (bytesReceived < bytesTotal && !_reply->isFinished()) { + return; + } + _program = QScriptProgram(QTextStream(_reply).readAll(), _reply->url().toString()); + + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; + + emit loaded(); +} + +void NetworkProgram::handleReplyError() { + QDebug debug = qDebug() << _reply->errorString(); + + _reply->disconnect(this); + _reply->deleteLater(); + _reply = NULL; + + // retry with increasing delays + const int MAX_ATTEMPTS = 8; + const int BASE_DELAY_MS = 1000; + if (++_attempts < MAX_ATTEMPTS) { + QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(makeRequest())); + debug << " -- retrying..."; + } +} + +NetworkValue::~NetworkValue() { +} + +RootNetworkValue::RootNetworkValue(const QSharedPointer& program) : + _program(program) { +} + +QScriptValue& RootNetworkValue::getValue() { + if (!_value.isValid() && _program->isLoaded()) { + _value = _program->getCache()->getEngine()->evaluate(_program->getProgram()); + } + return _value; +} + +const QList& RootNetworkValue::getParameterInfo() { + if (isLoaded() && _parameterInfo.isEmpty()) { + ScriptCache* cache = _program->getCache(); + QScriptEngine* engine = cache->getEngine(); + QScriptValue parameters = _value.property(cache->getParametersString()); + if (parameters.isArray()) { + int length = parameters.property(cache->getLengthString()).toInt32(); + for (int i = 0; i < length; i++) { + QScriptValue parameter = parameters.property(i); + ParameterInfo info = { engine->toStringHandle(parameter.property(cache->getNameString()).toString()), + QMetaType::type(parameter.property(cache->getTypeString()).toString().toUtf8().constData()) }; + _parameterInfo.append(info); + } + } + } + return _parameterInfo; +} + +DerivedNetworkValue::DerivedNetworkValue(const QSharedPointer& baseValue, const ScriptHash& parameters) : + _baseValue(baseValue), + _parameters(parameters) { +} + +QScriptValue& DerivedNetworkValue::getValue() { + if (!_value.isValid() && _baseValue->isLoaded()) { + RootNetworkValue* root = static_cast(_baseValue.data()); + ScriptCache* cache = root->getProgram()->getCache(); + QScriptValue generator = _baseValue->getValue().property(cache->getGeneratorString()); + if (generator.isFunction()) { + QScriptValueList arguments; + foreach (const ParameterInfo& parameter, root->getParameterInfo()) { + arguments.append(cache->getEngine()->newVariant(_parameters.value(parameter.name))); + } + _value = generator.call(QScriptValue(), arguments); + + } else { + _value = _baseValue->getValue(); + } + } + return _value; +} diff --git a/libraries/metavoxels/src/ScriptCache.h b/libraries/metavoxels/src/ScriptCache.h new file mode 100644 index 0000000000..3ce525d979 --- /dev/null +++ b/libraries/metavoxels/src/ScriptCache.h @@ -0,0 +1,159 @@ +// +// ScriptCache.h +// metavoxels +// +// Created by Andrzej Kapolka on 2/4/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__ScriptCache__ +#define __interface__ScriptCache__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MetavoxelUtil.h" + +class QNetworkAccessManager; +class QNetworkReply; +class QScriptEngine; + +class NetworkProgram; +class NetworkValue; + +/// Maintains a cache of loaded scripts. +class ScriptCache : public QObject { + Q_OBJECT + +public: + + static ScriptCache* getInstance(); + + ScriptCache(); + + void setNetworkAccessManager(QNetworkAccessManager* manager) { _networkAccessManager = manager; } + QNetworkAccessManager* getNetworkAccessManager() const { return _networkAccessManager; } + + void setEngine(QScriptEngine* engine); + QScriptEngine* getEngine() const { return _engine; } + + /// Loads a script program from the specified URL. + QSharedPointer getProgram(const QUrl& url); + + /// Loads a script value from the specified URL. + QSharedPointer getValue(const ParameterizedURL& url); + + const QScriptString& getParametersString() const { return _parametersString; } + const QScriptString& getLengthString() const { return _lengthString; } + const QScriptString& getNameString() const { return _nameString; } + const QScriptString& getTypeString() const { return _typeString; } + const QScriptString& getGeneratorString() const { return _generatorString; } + +private: + + QNetworkAccessManager* _networkAccessManager; + QScriptEngine* _engine; + QHash > _networkPrograms; + QHash > _networkValues; + QScriptString _parametersString; + QScriptString _lengthString; + QScriptString _nameString; + QScriptString _typeString; + QScriptString _generatorString; +}; + +/// A program loaded from the network. +class NetworkProgram : public QObject { + Q_OBJECT + +public: + + NetworkProgram(ScriptCache* cache, const QUrl& url); + ~NetworkProgram(); + + ScriptCache* getCache() const { return _cache; } + + bool isLoaded() const { return !_program.isNull(); } + + const QScriptProgram& getProgram() const { return _program; } + +signals: + + void loaded(); + +private slots: + + void makeRequest(); + void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void handleReplyError(); + +private: + + ScriptCache* _cache; + QNetworkRequest _request; + QNetworkReply* _reply; + int _attempts; + QScriptProgram _program; +}; + +/// Abstract base class of values loaded from the network. +class NetworkValue { +public: + + virtual ~NetworkValue(); + + bool isLoaded() { return getValue().isValid(); } + + virtual QScriptValue& getValue() = 0; + +protected: + + QScriptValue _value; +}; + +/// Contains information about a script parameter. +class ParameterInfo { +public: + QScriptString name; + int type; +}; + +/// The direct result of running a program. +class RootNetworkValue : public NetworkValue { +public: + + RootNetworkValue(const QSharedPointer& program); + + const QSharedPointer& getProgram() const { return _program; } + + virtual QScriptValue& getValue(); + + const QList& getParameterInfo(); + +private: + + QSharedPointer _program; + QList _parameterInfo; +}; + +/// The result of running a program's generator using a set of arguments. +class DerivedNetworkValue : public NetworkValue { +public: + + DerivedNetworkValue(const QSharedPointer& baseValue, const ScriptHash& parameters); + + virtual QScriptValue& getValue(); + +private: + + QSharedPointer _baseValue; + ScriptHash _parameters; +}; + +#endif /* defined(__interface__ScriptCache__) */ diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp new file mode 100644 index 0000000000..a3af307297 --- /dev/null +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -0,0 +1,228 @@ +// +// SharedObject.cpp +// metavoxels +// +// Created by Andrzej Kapolka on 2/5/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#include +#include +#include +#include +#include + +#include "Bitstream.h" +#include "SharedObject.h" + +SharedObject::SharedObject() : _referenceCount(0) { +} + +void SharedObject::incrementReferenceCount() { + _referenceCount++; +} + +void SharedObject::decrementReferenceCount() { + if (--_referenceCount == 0) { + delete this; + + } else if (_referenceCount == 1) { + emit referenceCountDroppedToOne(); + } +} + +SharedObject* SharedObject::clone() const { + // default behavior is to make a copy using the no-arg constructor and copy the stored properties + const QMetaObject* metaObject = this->metaObject(); + SharedObject* newObject = static_cast(metaObject->newInstance()); + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (property.isStored()) { + property.write(newObject, property.read(this)); + } + } + foreach (const QByteArray& propertyName, dynamicPropertyNames()) { + newObject->setProperty(propertyName, property(propertyName)); + } + return newObject; +} + +bool SharedObject::equals(const SharedObject* other) const { + // default behavior is to compare the properties + const QMetaObject* metaObject = this->metaObject(); + if (metaObject != other->metaObject()) { + return false; + } + for (int i = 0; i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (property.isStored() && property.read(this) != property.read(other)) { + return false; + } + } + QList dynamicPropertyNames = this->dynamicPropertyNames(); + if (dynamicPropertyNames.size() != other->dynamicPropertyNames().size()) { + return false; + } + foreach (const QByteArray& propertyName, dynamicPropertyNames) { + if (property(propertyName) != other->property(propertyName)) { + return false; + } + } + return true; +} + +SharedObjectPointer::SharedObjectPointer(SharedObject* data) : _data(data) { + if (_data) { + _data->incrementReferenceCount(); + } +} + +SharedObjectPointer::SharedObjectPointer(const SharedObjectPointer& other) : _data(other._data) { + if (_data) { + _data->incrementReferenceCount(); + } +} + +SharedObjectPointer::~SharedObjectPointer() { + if (_data) { + _data->decrementReferenceCount(); + } +} + +void SharedObjectPointer::detach() { + if (_data && _data->getReferenceCount() > 1) { + _data->decrementReferenceCount(); + (_data = _data->clone())->incrementReferenceCount(); + } +} + +void SharedObjectPointer::reset() { + if (_data) { + _data->decrementReferenceCount(); + } + _data = NULL; +} + +SharedObjectPointer& SharedObjectPointer::operator=(SharedObject* data) { + if (_data) { + _data->decrementReferenceCount(); + } + if ((_data = data)) { + _data->incrementReferenceCount(); + } + return *this; +} + +SharedObjectPointer& SharedObjectPointer::operator=(const SharedObjectPointer& other) { + if (_data) { + _data->decrementReferenceCount(); + } + if ((_data = other._data)) { + _data->incrementReferenceCount(); + } + return *this; +} + +uint qHash(const SharedObjectPointer& pointer, uint seed) { + return qHash(pointer.data(), seed); +} + +SharedObjectEditor::SharedObjectEditor(const QMetaObject* metaObject, QWidget* parent) : QWidget(parent) { + QVBoxLayout* layout = new QVBoxLayout(); + layout->setAlignment(Qt::AlignTop); + setLayout(layout); + + QFormLayout* form = new QFormLayout(); + layout->addLayout(form); + + form->addRow("Type:", _type = new QComboBox()); + _type->addItem("(none)"); + foreach (const QMetaObject* metaObject, Bitstream::getMetaObjectSubClasses(metaObject)) { + _type->addItem(metaObject->className(), QVariant::fromValue(metaObject)); + } + connect(_type, SIGNAL(currentIndexChanged(int)), SLOT(updateType())); +} + +void SharedObjectEditor::setObject(const SharedObjectPointer& object) { + _object = object; + const QMetaObject* metaObject = object ? object->metaObject() : NULL; + int index = _type->findData(QVariant::fromValue(metaObject)); + if (index != -1) { + // ensure that we call updateType to obtain the values + if (_type->currentIndex() == index) { + updateType(); + } else { + _type->setCurrentIndex(index); + } + } +} + +const QMetaObject* getOwningAncestor(const QMetaObject* metaObject, int propertyIndex) { + while (propertyIndex < metaObject->propertyOffset()) { + metaObject = metaObject->superClass(); + } + return metaObject; +} + +void SharedObjectEditor::updateType() { + // delete the existing rows + if (layout()->count() > 1) { + QFormLayout* form = static_cast(layout()->takeAt(1)); + while (!form->isEmpty()) { + QLayoutItem* item = form->takeAt(0); + if (item->widget()) { + delete item->widget(); + } + delete item; + } + delete form; + } + const QMetaObject* metaObject = _type->itemData(_type->currentIndex()).value(); + if (metaObject == NULL) { + _object.reset(); + return; + } + QObject* oldObject = static_cast(_object.data()); + const QMetaObject* oldMetaObject = oldObject ? oldObject->metaObject() : NULL; + QObject* newObject = metaObject->newInstance(); + + QFormLayout* form = new QFormLayout(); + static_cast(layout())->addLayout(form); + for (int i = QObject::staticMetaObject.propertyCount(); i < metaObject->propertyCount(); i++) { + QMetaProperty property = metaObject->property(i); + if (oldMetaObject && i < oldMetaObject->propertyCount() && + getOwningAncestor(metaObject, i) == getOwningAncestor(oldMetaObject, i)) { + // copy the state of the shared ancestry + property.write(newObject, property.read(oldObject)); + } + QWidget* widget = QItemEditorFactory::defaultFactory()->createEditor(property.userType(), NULL); + if (widget) { + widget->setProperty("propertyIndex", i); + form->addRow(QByteArray(property.name()) + ':', widget); + QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType()); + const QMetaObject* widgetMetaObject = widget->metaObject(); + QMetaProperty widgetProperty = widgetMetaObject->property(widgetMetaObject->indexOfProperty(valuePropertyName)); + widgetProperty.write(widget, property.read(newObject)); + if (widgetProperty.hasNotifySignal()) { + connect(widget, QByteArray(SIGNAL()).append(widgetProperty.notifySignal().methodSignature()), + SLOT(propertyChanged())); + } + } + } + _object = static_cast(newObject); +} + +void SharedObjectEditor::propertyChanged() { + QFormLayout* form = static_cast(layout()->itemAt(1)); + for (int i = 0; i < form->rowCount(); i++) { + QWidget* widget = form->itemAt(i, QFormLayout::FieldRole)->widget(); + if (widget != sender()) { + continue; + } + _object.detach(); + QObject* object = _object.data(); + QMetaProperty property = object->metaObject()->property(widget->property("propertyIndex").toInt()); + QByteArray valuePropertyName = QItemEditorFactory::defaultFactory()->valuePropertyName(property.userType()); + property.write(object, widget->property(valuePropertyName)); + } +} diff --git a/libraries/metavoxels/src/SharedObject.h b/libraries/metavoxels/src/SharedObject.h new file mode 100644 index 0000000000..e439c4c7f0 --- /dev/null +++ b/libraries/metavoxels/src/SharedObject.h @@ -0,0 +1,115 @@ +// +// SharedObject.h +// metavoxels +// +// Created by Andrzej Kapolka on 2/5/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __interface__SharedObject__ +#define __interface__SharedObject__ + +#include +#include +#include + +class QComboBox; + +/// A QObject that may be shared over the network. +class SharedObject : public QObject { + Q_OBJECT + +public: + + SharedObject(); + + int getReferenceCount() const { return _referenceCount; } + void incrementReferenceCount(); + void decrementReferenceCount(); + + /// Creates a new clone of this object. + virtual SharedObject* clone() const; + + /// Tests this object for equality with another. + virtual bool equals(const SharedObject* other) const; + +signals: + + /// Emitted when the reference count drops to one. + void referenceCountDroppedToOne(); + +private: + + int _referenceCount; +}; + +/// A pointer to a shared object. +class SharedObjectPointer { +public: + + SharedObjectPointer(SharedObject* data = NULL); + SharedObjectPointer(const SharedObjectPointer& other); + ~SharedObjectPointer(); + + SharedObject* data() { return _data; } + const SharedObject* data() const { return _data; } + const SharedObject* constData() const { return _data; } + + void detach(); + + void swap(SharedObjectPointer& other) { qSwap(_data, other._data); } + + void reset(); + + operator SharedObject*() { return _data; } + operator const SharedObject*() const { return _data; } + + bool operator!() const { return !_data; } + + bool operator!=(const SharedObjectPointer& other) const { return _data != other._data; } + + SharedObject& operator*() { return *_data; } + const SharedObject& operator*() const { return *_data; } + + SharedObject* operator->() { return _data; } + const SharedObject* operator->() const { return _data; } + + SharedObjectPointer& operator=(SharedObject* data); + SharedObjectPointer& operator=(const SharedObjectPointer& other); + + bool operator==(const SharedObjectPointer& other) const { return _data == other._data; } + +private: + + SharedObject* _data; +}; + +Q_DECLARE_METATYPE(SharedObjectPointer) + +uint qHash(const SharedObjectPointer& pointer, uint seed = 0); + +/// Allows editing shared object instances. +class SharedObjectEditor : public QWidget { + Q_OBJECT + Q_PROPERTY(SharedObjectPointer object MEMBER _object WRITE setObject USER true) + +public: + + SharedObjectEditor(const QMetaObject* metaObject, QWidget* parent); + +public slots: + + void setObject(const SharedObjectPointer& object); + +private slots: + + void updateType(); + void propertyChanged(); + +private: + + QComboBox* _type; + SharedObjectPointer _object; +}; + +#endif /* defined(__interface__SharedObject__) */ diff --git a/libraries/octree-server/CMakeLists.txt b/libraries/octree-server/CMakeLists.txt deleted file mode 100644 index 64c73a4bd4..0000000000 --- a/libraries/octree-server/CMakeLists.txt +++ /dev/null @@ -1,36 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -set(ROOT_DIR ../..) -set(MACRO_DIR ${ROOT_DIR}/cmake/macros) - -# setup for find modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") - -set(TARGET_NAME octree-server) - -find_package(Qt5Network REQUIRED) -find_package(Qt5Widgets REQUIRED) - -include(${MACRO_DIR}/SetupHifiLibrary.cmake) - -setup_hifi_library(${TARGET_NAME} ${OPTIONAL_SRCS}) - -qt5_use_modules(${TARGET_NAME} Network Widgets) - -include(${MACRO_DIR}/IncludeGLM.cmake) -include_glm(${TARGET_NAME} ${ROOT_DIR}) - -# link ZLIB -find_package(ZLIB) -include_directories(${ZLIB_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) - -# link in the shared library -include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) - -# link in the hifi octree library -link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) - -# link the embedded webserver -link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR}) diff --git a/libraries/octree/src/JurisdictionListener.cpp b/libraries/octree/src/JurisdictionListener.cpp index d2c24021ac..20e5af012f 100644 --- a/libraries/octree/src/JurisdictionListener.cpp +++ b/libraries/octree/src/JurisdictionListener.cpp @@ -45,9 +45,8 @@ bool JurisdictionListener::queueJurisdictionRequest() { NodeList* nodeList = NodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (nodeList->getNodeActiveSocketOrPing(node.data()) && node->getType() == getNodeType()) { - const HifiSockAddr* nodeAddress = node->getActiveSocket(); - _packetSender.queuePacketForSending(*nodeAddress, QByteArray(reinterpret_cast(bufferOut), sizeOut)); + if (node->getType() == getNodeType() && node->getActiveSocket()) { + _packetSender.queuePacketForSending(node, QByteArray(reinterpret_cast(bufferOut), sizeOut)); nodeCount++; } } @@ -62,16 +61,13 @@ bool JurisdictionListener::queueJurisdictionRequest() { return isStillRunning(); } -void JurisdictionListener::processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet) { +void JurisdictionListener::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) { //qDebug() << "JurisdictionListener::processPacket()"; - if (packetTypeForPacket(packet) == PacketTypeJurisdictionRequest) { - SharedNodePointer node = NodeList::getInstance()->nodeWithAddress(senderAddress); - if (node) { - QUuid nodeUUID = node->getUUID(); - JurisdictionMap map; - map.unpackFromMessage(reinterpret_cast(packet.data()), packet.size()); - _jurisdictions[nodeUUID] = map; - } + if (packetTypeForPacket(packet) == PacketTypeJurisdictionRequest && sendingNode) { + QUuid nodeUUID = sendingNode->getUUID(); + JurisdictionMap map; + map.unpackFromMessage(reinterpret_cast(packet.data()), packet.size()); + _jurisdictions[nodeUUID] = map; } } diff --git a/libraries/octree/src/JurisdictionListener.h b/libraries/octree/src/JurisdictionListener.h index 712749aae7..b3dc9664d7 100644 --- a/libraries/octree/src/JurisdictionListener.h +++ b/libraries/octree/src/JurisdictionListener.h @@ -49,7 +49,7 @@ protected: /// \param packetData pointer to received data /// \param ssize_t packetLength size of received data /// \thread "this" individual processing thread - virtual void processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet); + virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet); private: NodeToJurisdictionMap _jurisdictions; diff --git a/libraries/octree/src/JurisdictionSender.cpp b/libraries/octree/src/JurisdictionSender.cpp index 854496e40f..49dddf75df 100644 --- a/libraries/octree/src/JurisdictionSender.cpp +++ b/libraries/octree/src/JurisdictionSender.cpp @@ -28,13 +28,11 @@ JurisdictionSender::~JurisdictionSender() { } -void JurisdictionSender::processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet) { +void JurisdictionSender::processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) { if (packetTypeForPacket(packet) == PacketTypeJurisdictionRequest) { - QUuid nodeUUID; - deconstructPacketHeader(packet, nodeUUID); - if (!nodeUUID.isNull()) { + if (sendingNode) { lockRequestingNodes(); - _nodesRequestingJurisdictions.push(nodeUUID); + _nodesRequestingJurisdictions.push(sendingNode->getUUID()); unlockRequestingNodes(); } } @@ -65,8 +63,7 @@ bool JurisdictionSender::process() { SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(nodeUUID); if (node && node->getActiveSocket() != NULL) { - const HifiSockAddr* nodeAddress = node->getActiveSocket(); - _packetSender.queuePacketForSending(*nodeAddress, QByteArray(reinterpret_cast(bufferOut), sizeOut)); + _packetSender.queuePacketForSending(node, QByteArray(reinterpret_cast(bufferOut), sizeOut)); nodeCount++; } } diff --git a/libraries/octree/src/JurisdictionSender.h b/libraries/octree/src/JurisdictionSender.h index 19ce727a57..18365fda63 100644 --- a/libraries/octree/src/JurisdictionSender.h +++ b/libraries/octree/src/JurisdictionSender.h @@ -37,7 +37,7 @@ public: void setNodeType(NodeType_t type) { _nodeType = type; } protected: - virtual void processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet); + virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet); /// Locks all the resources of the thread. void lockRequestingNodes() { _requestingNodeMutex.lock(); } diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 93ce5c9174..5a1e31e0d0 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1347,7 +1347,7 @@ bool Octree::readFromSVOFile(const char* fileName) { fileOk = true; // assume the file is ok } if (fileOk) { - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, NULL, wantImportProgress); + ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, NULL, 0, SharedNodePointer(), wantImportProgress); readBitstreamToTree(dataAt, dataLength, args); } delete[] entireFile; @@ -1485,7 +1485,7 @@ void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinat // ask destination tree to read the bitstream bool wantImportProgress = true; - ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationNode, 0, NULL, wantImportProgress); + ReadBitstreamToTreeParams args(WANT_COLOR, NO_EXISTS_BITS, destinationNode, 0, SharedNodePointer(), wantImportProgress); readBitstreamToTree(packetData.getUncompressedData(), packetData.getUncompressedSize(), args); } } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 9b8921208b..5287e3ce37 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -156,7 +156,7 @@ public: bool includeExistsBits; OctreeElement* destinationNode; QUuid sourceUUID; - Node* sourceNode; + SharedNodePointer sourceNode; bool wantImportProgress; ReadBitstreamToTreeParams( @@ -164,7 +164,7 @@ public: bool includeExistsBits = WANT_EXISTS_BITS, OctreeElement* destinationNode = NULL, QUuid sourceUUID = QUuid(), - Node* sourceNode = NULL, + SharedNodePointer sourceNode = SharedNodePointer(), bool wantImportProgress = false) : includeColor(includeColor), includeExistsBits(includeExistsBits), @@ -190,7 +190,7 @@ public: virtual PacketType expectedDataPacketType() const { return PacketTypeUnknown; } virtual bool handlesEditPacketType(PacketType packetType) const { return false; } virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, - const unsigned char* editData, int maxLength, Node* senderNode) { return 0; } + const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } virtual void update() { }; // nothing to do by default diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 5b84b819cc..1c1ed13f8d 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -60,19 +60,17 @@ bool OctreeEditPacketSender::serversExist() const { foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { // only send to the NodeTypes that are getMyNodeType() - if (node->getType() == getMyNodeType()) { - if (nodeList->getNodeActiveSocketOrPing(node.data())) { - QUuid nodeUUID = node->getUUID(); - // If we've got Jurisdictions set, then check to see if we know the jurisdiction for this server - if (_serverJurisdictions) { - // lookup our nodeUUID in the jurisdiction map, if it's missing then we're - // missing at least one jurisdiction - if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) { - atLeastOnJurisdictionMissing = true; - } + if (node->getType() == getMyNodeType() && node->getActiveSocket()) { + QUuid nodeUUID = node->getUUID(); + // If we've got Jurisdictions set, then check to see if we know the jurisdiction for this server + if (_serverJurisdictions) { + // lookup our nodeUUID in the jurisdiction map, if it's missing then we're + // missing at least one jurisdiction + if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) { + atLeastOnJurisdictionMissing = true; } - hasServers = true; } + hasServers = true; } if (atLeastOnJurisdictionMissing) { break; // no point in looking further... @@ -91,9 +89,8 @@ void OctreeEditPacketSender::queuePacketToNode(const QUuid& nodeUUID, unsigned c // only send to the NodeTypes that are getMyNodeType() if (node->getType() == getMyNodeType() && ((node->getUUID() == nodeUUID) || (nodeUUID.isNull()))) { - if (nodeList->getNodeActiveSocketOrPing(node.data())) { - const HifiSockAddr* nodeAddress = node->getActiveSocket(); - queuePacketForSending(*nodeAddress, QByteArray(reinterpret_cast(buffer), length)); + if (node->getActiveSocket()) { + queuePacketForSending(node, QByteArray(reinterpret_cast(buffer), length)); // debugging output... bool wantDebugging = false; diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 76bb138516..a71a3f893c 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -224,4 +224,4 @@ private: static quint64 _totalBytesOfRawData; }; -#endif /* defined(__hifi__OctreePacketData__) */ \ No newline at end of file +#endif /* defined(__hifi__OctreePacketData__) */ diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index b636d9baac..9a00ce245b 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -26,7 +26,7 @@ void OctreeRenderer::init() { OctreeRenderer::~OctreeRenderer() { } -void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode) { +void OctreeRenderer::processDatagram(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) { bool showTimingDetails = false; // Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); bool extraDebugging = false; // Menu::getInstance()->isOptionChecked(MenuOption::ExtraDebugging) PerformanceWarning warn(showTimingDetails, "OctreeRenderer::processDatagram()",showTimingDetails); diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index ff385ec62d..e29cdc4020 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -43,7 +43,7 @@ public: virtual void renderElement(OctreeElement* element, RenderArgs* args) = 0; /// process incoming data - virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode); + virtual void processDatagram(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); /// initialize and GPU/rendering related resources virtual void init(); diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index bf65e85ad4..e106f53589 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -12,6 +12,7 @@ #include #include +#include #include "JurisdictionMap.h" #define GREENISH 0x40ff40d0 diff --git a/libraries/particle-server/CMakeLists.txt b/libraries/particle-server/CMakeLists.txt deleted file mode 100644 index 9c63645e8d..0000000000 --- a/libraries/particle-server/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -set(ROOT_DIR ../..) -set(MACRO_DIR ${ROOT_DIR}/cmake/macros) - -# setup for find modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") - -set(TARGET_NAME particle-server) - -find_package(Qt5Widgets REQUIRED) - -include(${MACRO_DIR}/SetupHifiLibrary.cmake) - -setup_hifi_library(${TARGET_NAME} ${OPTIONAL_SRCS}) - -qt5_use_modules(${TARGET_NAME} Widgets) - -# inluce GLM -include(${MACRO_DIR}/IncludeGLM.cmake) -include_glm(${TARGET_NAME} ${ROOT_DIR}) - -# link in the shared library -include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) - -# link ZLIB -find_package(ZLIB) -include_directories(${ZLIB_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) - -link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(particles ${TARGET_NAME} ${ROOT_DIR}) - -# link in the embedded webserver -link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR}) \ No newline at end of file diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index 51150722d0..afffe7943c 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -90,7 +90,7 @@ bool ParticleTree::findAndUpdateOperation(OctreeElement* element, void* extraDat return true; } -void ParticleTree::storeParticle(const Particle& particle, Node* senderNode) { +void ParticleTree::storeParticle(const Particle& particle, const SharedNodePointer& senderNode) { // First, look for the existing particle in the tree.. FindAndUpdateParticleArgs args = { particle, false }; recurseTreeWithOperation(findAndUpdateOperation, &args); @@ -101,7 +101,7 @@ void ParticleTree::storeParticle(const Particle& particle, Node* senderNode) { float size = std::max(MINIMUM_PARTICLE_ELEMENT_SIZE, particle.getRadius()); ParticleTreeElement* element = (ParticleTreeElement*)getOrCreateChildElementAt(position.x, position.y, position.z, size); - element->storeParticle(particle, senderNode); + element->storeParticle(particle); } // what else do we need to do here to get reaveraging to work _isDirty = true; @@ -386,7 +386,7 @@ const Particle* ParticleTree::findParticleByID(uint32_t id, bool alreadyLocked) int ParticleTree::processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, - const unsigned char* editData, int maxLength, Node* senderNode) { + const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) { int processedBytes = 0; // we handle these types of "edit" packets @@ -415,7 +415,7 @@ int ParticleTree::processEditPacketData(PacketType packetType, const unsigned ch return processedBytes; } -void ParticleTree::notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode) { +void ParticleTree::notifyNewlyCreatedParticle(const Particle& newParticle, const SharedNodePointer& senderNode) { _newlyCreatedHooksLock.lockForRead(); for (size_t i = 0; i < _newlyCreatedHooks.size(); i++) { _newlyCreatedHooks[i]->particleCreated(newParticle, senderNode); @@ -596,8 +596,7 @@ void ParticleTree::forgetParticlesDeletedBefore(quint64 sinceTime) { } -void ParticleTree::processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, - Node* sourceNode) { +void ParticleTree::processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode) { const unsigned char* packetData = (const unsigned char*)dataByteArray.constData(); const unsigned char* dataAt = packetData; diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index e3b7906778..f3b8f5183d 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -14,7 +14,7 @@ class NewlyCreatedParticleHook { public: - virtual void particleCreated(const Particle& newParticle, Node* senderNode) = 0; + virtual void particleCreated(const Particle& newParticle, const SharedNodePointer& senderNode) = 0; }; class ParticleTree : public Octree { @@ -35,11 +35,11 @@ public: virtual PacketType expectedDataPacketType() const { return PacketTypeParticleData; } virtual bool handlesEditPacketType(PacketType packetType) const; virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, - const unsigned char* editData, int maxLength, Node* senderNode); + const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode); virtual void update(); - void storeParticle(const Particle& particle, Node* senderNode = NULL); + void storeParticle(const Particle& particle, const SharedNodePointer& senderNode = SharedNodePointer()); void updateParticle(const ParticleID& particleID, const ParticleProperties& properties); void addParticle(const ParticleID& particleID, const ParticleProperties& properties); void deleteParticle(const ParticleID& particleID); @@ -67,7 +67,7 @@ public: bool encodeParticlesDeletedSince(quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength); void forgetParticlesDeletedBefore(quint64 sinceTime); - void processEraseMessage(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr, Node* sourceNode); + void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); void handleAddParticleResponse(const QByteArray& packet); private: @@ -82,7 +82,7 @@ private: static bool findAndDeleteOperation(OctreeElement* element, void* extraData); static bool findAndUpdateParticleIDOperation(OctreeElement* element, void* extraData); - void notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode); + void notifyNewlyCreatedParticle(const Particle& newParticle, const SharedNodePointer& senderNode); QReadWriteLock _newlyCreatedHooksLock; std::vector _newlyCreatedHooks; diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index 48e619da59..4930263d64 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -332,7 +332,7 @@ bool ParticleTreeElement::collapseChildren() { } -void ParticleTreeElement::storeParticle(const Particle& particle, Node* senderNode) { +void ParticleTreeElement::storeParticle(const Particle& particle) { _particles->push_back(particle); markWithChangedTime(); } diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index 59a03f7b41..2854066f3b 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -121,7 +121,7 @@ public: protected: virtual void init(unsigned char * octalCode); - void storeParticle(const Particle& particle, Node* senderNode = NULL); + void storeParticle(const Particle& particle); ParticleTree* _myTree; QList* _particles; diff --git a/libraries/script-engine/src/DataServerScriptingInterface.cpp b/libraries/script-engine/src/DataServerScriptingInterface.cpp deleted file mode 100644 index 6effd7ebdc..0000000000 --- a/libraries/script-engine/src/DataServerScriptingInterface.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// -// DataServerScriptingInterface.cpp -// hifi -// -// Created by Stephen Birarda on 1/20/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#include -#include - -#include "DataServerScriptingInterface.h" - -DataServerScriptingInterface::DataServerScriptingInterface() -{ - -} - -void DataServerScriptingInterface::setValueForKey(const QString& key, const QString& value) { - DataServerClient::putValueForKeyAndUUID(key, value, NodeList::getInstance()->getOwnerUUID()); -} \ No newline at end of file diff --git a/libraries/script-engine/src/DataServerScriptingInterface.h b/libraries/script-engine/src/DataServerScriptingInterface.h deleted file mode 100644 index b46c0bda7a..0000000000 --- a/libraries/script-engine/src/DataServerScriptingInterface.h +++ /dev/null @@ -1,25 +0,0 @@ -// -// DataServerScriptingInterface.h -// hifi -// -// Created by Stephen Birarda on 1/20/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#ifndef __hifi__DataServerScriptingInterface__ -#define __hifi__DataServerScriptingInterface__ - -#include -#include -#include - -class DataServerScriptingInterface : public QObject { - Q_OBJECT -public: - DataServerScriptingInterface(); - -public slots: - void setValueForKey(const QString& key, const QString& value); -}; - -#endif /* defined(__hifi__DataServerScriptingInterface__) */ diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index ff27282b73..a642183eb4 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -124,7 +124,7 @@ QScriptValue keyEventToScriptValue(QScriptEngine* engine, const KeyEvent& event) return obj; } -void keyEventFromScriptValue(const QScriptValue &object, KeyEvent& event) { +void keyEventFromScriptValue(const QScriptValue& object, KeyEvent& event) { event.isValid = false; // assume the worst event.isMeta = object.property("isMeta").toVariant().toBool(); @@ -231,7 +231,7 @@ QScriptValue mouseEventToScriptValue(QScriptEngine* engine, const MouseEvent& ev return obj; } -void mouseEventFromScriptValue(const QScriptValue &object, MouseEvent& event) { +void mouseEventFromScriptValue(const QScriptValue& object, MouseEvent& event) { // nothing for now... } @@ -242,7 +242,7 @@ QScriptValue touchEventToScriptValue(QScriptEngine* engine, const TouchEvent& ev return obj; } -void touchEventFromScriptValue(const QScriptValue &object, TouchEvent& event) { +void touchEventFromScriptValue(const QScriptValue& object, TouchEvent& event) { // nothing for now... } @@ -253,6 +253,8 @@ QScriptValue wheelEventToScriptValue(QScriptEngine* engine, const WheelEvent& ev return obj; } -void wheelEventFromScriptValue(const QScriptValue &object, WheelEvent& event) { +void wheelEventFromScriptValue(const QScriptValue& object, WheelEvent& event) { // nothing for now... } + + diff --git a/libraries/script-engine/src/EventTypes.h b/libraries/script-engine/src/EventTypes.h index 81cba03cea..ebc85d04d3 100644 --- a/libraries/script-engine/src/EventTypes.h +++ b/libraries/script-engine/src/EventTypes.h @@ -65,7 +65,6 @@ public: int y; }; - Q_DECLARE_METATYPE(KeyEvent) Q_DECLARE_METATYPE(MouseEvent) Q_DECLARE_METATYPE(TouchEvent) @@ -74,15 +73,15 @@ Q_DECLARE_METATYPE(WheelEvent) void registerEventTypes(QScriptEngine* engine); QScriptValue keyEventToScriptValue(QScriptEngine* engine, const KeyEvent& event); -void keyEventFromScriptValue(const QScriptValue &object, KeyEvent& event); +void keyEventFromScriptValue(const QScriptValue& object, KeyEvent& event); QScriptValue mouseEventToScriptValue(QScriptEngine* engine, const MouseEvent& event); -void mouseEventFromScriptValue(const QScriptValue &object, MouseEvent& event); +void mouseEventFromScriptValue(const QScriptValue& object, MouseEvent& event); QScriptValue touchEventToScriptValue(QScriptEngine* engine, const TouchEvent& event); -void touchEventFromScriptValue(const QScriptValue &object, TouchEvent& event); +void touchEventFromScriptValue(const QScriptValue& object, TouchEvent& event); QScriptValue wheelEventToScriptValue(QScriptEngine* engine, const WheelEvent& event); -void wheelEventFromScriptValue(const QScriptValue &object, WheelEvent& event); +void wheelEventFromScriptValue(const QScriptValue& object, WheelEvent& event); #endif // __hifi_EventTypes_h__ diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index 12900c29c7..a197d59aeb 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -9,6 +9,7 @@ // // +#include #include "Quat.h" glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) { @@ -18,3 +19,19 @@ glm::quat Quat::multiply(const glm::quat& q1, const glm::quat& q2) { glm::quat Quat::fromVec3(const glm::vec3& vec3) { return glm::quat(vec3); } + +glm::quat Quat::fromPitchYawRoll(float pitch, float yaw, float roll) { + return glm::quat(glm::radians(glm::vec3(pitch, yaw, roll))); +} + +glm::vec3 Quat::getFront(const glm::quat& orientation) { + return orientation * IDENTITY_FRONT; +} + +glm::vec3 Quat::getRight(const glm::quat& orientation) { + return orientation * IDENTITY_RIGHT; +} + +glm::vec3 Quat::getUp(const glm::quat& orientation) { + return orientation * IDENTITY_UP; +} diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 6bcd121808..72fec6d6dc 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -22,6 +22,10 @@ class Quat : public QObject { public slots: glm::quat multiply(const glm::quat& q1, const glm::quat& q2); glm::quat fromVec3(const glm::vec3& vec3); + glm::quat fromPitchYawRoll(float pitch, float yaw, float roll); + glm::vec3 getFront(const glm::quat& orientation); + glm::vec3 getRight(const glm::quat& orientation); + glm::vec3 getUp(const glm::quat& orientation); }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b3b0736114..e18878eec9 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -44,7 +44,6 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, co AbstractMenuInterface* menu, AbstractControllerScriptingInterface* controllerScriptingInterface) : _isAvatar(false), - _dataServerScriptingInterface(), _avatarData(NULL) { _scriptContents = scriptContents; @@ -137,9 +136,9 @@ void ScriptEngine::init() { registerGlobalObject("Script", this); registerGlobalObject("Audio", &_audioScriptingInterface); registerGlobalObject("Controller", _controllerScriptingInterface); - registerGlobalObject("Data", &_dataServerScriptingInterface); registerGlobalObject("Particles", &_particlesScriptingInterface); registerGlobalObject("Quat", &_quatLibrary); + registerGlobalObject("Vec3", &_vec3Library); registerGlobalObject("Voxels", &_voxelsScriptingInterface); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 98d5860332..3de16348d4 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -24,8 +24,8 @@ class ParticlesScriptingInterface; #include "AbstractControllerScriptingInterface.h" -#include "DataServerScriptingInterface.h" #include "Quat.h" +#include "Vec3.h" const QString NO_SCRIPT(""); @@ -43,9 +43,6 @@ public: /// Access the ParticlesScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener static ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; } - - /// Access the DataServerScriptingInterface for access to its underlying UUID - const DataServerScriptingInterface& getDataServerScriptingInterface() { return _dataServerScriptingInterface; } /// sets the script contents, will return false if failed, will fail if script is already running bool setScriptContents(const QString& scriptContents); @@ -97,7 +94,6 @@ private: static ParticlesScriptingInterface _particlesScriptingInterface; AbstractControllerScriptingInterface* _controllerScriptingInterface; AudioScriptingInterface _audioScriptingInterface; - DataServerScriptingInterface _dataServerScriptingInterface; AvatarData* _avatarData; bool _wantMenuItems; QString _scriptMenuName; @@ -105,6 +101,7 @@ private: AbstractMenuInterface* _menu; static int _scriptNumber; Quat _quatLibrary; + Vec3 _vec3Library; }; #endif /* defined(__hifi__ScriptEngine__) */ diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp new file mode 100644 index 0000000000..87b1b510a4 --- /dev/null +++ b/libraries/script-engine/src/Vec3.cpp @@ -0,0 +1,24 @@ +// +// Vec3.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 1/29/14 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Scriptable Vec3 class library. +// +// + +#include "Vec3.h" + +glm::vec3 Vec3::multiply(const glm::vec3& v1, const glm::vec3& v2) { + return v1 * v2; +} + +glm::vec3 Vec3::multiply(const glm::vec3& v1, float f) { + return v1 * f; +} + +glm::vec3 Vec3::sum(const glm::vec3& v1, const glm::vec3& v2) { + return v1 + v2; +} diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h new file mode 100644 index 0000000000..1cc44f3061 --- /dev/null +++ b/libraries/script-engine/src/Vec3.h @@ -0,0 +1,31 @@ +// +// Vec3.h +// hifi +// +// Created by Brad Hefta-Gaub on 1/29/14 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Scriptable Vec3 class library. +// +// + +#ifndef __hifi__Vec3__ +#define __hifi__Vec3__ + +#include +#include +#include + +/// Scriptable interface a Vec3ernion helper class object. Used exclusively in the JavaScript API +class Vec3 : public QObject { + Q_OBJECT + +public slots: + glm::vec3 multiply(const glm::vec3& v1, const glm::vec3& v2); + glm::vec3 multiply(const glm::vec3& v1, float f); + glm::vec3 sum(const glm::vec3& v1, const glm::vec3& v2); +}; + + + +#endif /* defined(__hifi__Vec3__) */ diff --git a/libraries/shared/src/DataServerClient.h b/libraries/shared/src/DataServerClient.h index d2f846cc6b..014fa8fcd9 100644 --- a/libraries/shared/src/DataServerClient.h +++ b/libraries/shared/src/DataServerClient.h @@ -59,8 +59,6 @@ private: namespace DataServerKey { const QString Domain = "domain"; - const QString FaceMeshURL = "mesh"; - const QString SkeletonURL = "skeleton"; const QString Position = "position"; const QString Orientation = "orientation"; const QString UUID = "uuid"; diff --git a/libraries/shared/src/HifiSockAddr.cpp b/libraries/shared/src/HifiSockAddr.cpp index 44eedc6b17..cc031525d8 100644 --- a/libraries/shared/src/HifiSockAddr.cpp +++ b/libraries/shared/src/HifiSockAddr.cpp @@ -45,8 +45,6 @@ HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) { } HifiSockAddr& HifiSockAddr::operator=(const HifiSockAddr& rhsSockAddr) { - //HifiSockAddr temp(rhsSockAddr); - //swap(temp); _address = rhsSockAddr._address; _port = rhsSockAddr._port; diff --git a/libraries/shared/src/Logging.cpp b/libraries/shared/src/Logging.cpp index 6fb716e638..bc0d4af084 100644 --- a/libraries/shared/src/Logging.cpp +++ b/libraries/shared/src/Logging.cpp @@ -126,4 +126,4 @@ void Logging::verboseMessageHandler(QtMsgType type, const QMessageLogContext& co } fprintf(stdout, "%s %s\n", prefixString.toLocal8Bit().constData(), message.toLocal8Bit().constData()); -} \ No newline at end of file +} diff --git a/libraries/shared/src/NetworkPacket.cpp b/libraries/shared/src/NetworkPacket.cpp index aacd95dbbf..1671dc2435 100644 --- a/libraries/shared/src/NetworkPacket.cpp +++ b/libraries/shared/src/NetworkPacket.cpp @@ -8,15 +8,17 @@ // A really simple class that stores a network packet between being received and being processed // +#include #include #include -#include + +#include "SharedUtil.h" #include "NetworkPacket.h" -void NetworkPacket::copyContents(const HifiSockAddr& sockAddr, const QByteArray& packet) { +void NetworkPacket::copyContents(const SharedNodePointer& destinationNode, const QByteArray& packet) { if (packet.size() && packet.size() <= MAX_PACKET_SIZE) { - _sockAddr = sockAddr; + _destinationNode = destinationNode; _byteArray = packet; } else { qDebug(">>> NetworkPacket::copyContents() unexpected length = %d", packet.size()); @@ -24,28 +26,28 @@ void NetworkPacket::copyContents(const HifiSockAddr& sockAddr, const QByteArray& } NetworkPacket::NetworkPacket(const NetworkPacket& packet) { - copyContents(packet.getSockAddr(), packet.getByteArray()); + copyContents(packet.getDestinationNode(), packet.getByteArray()); } -NetworkPacket::NetworkPacket(const HifiSockAddr& sockAddr, const QByteArray& packet) { - copyContents(sockAddr, packet); +NetworkPacket::NetworkPacket(const SharedNodePointer& destinationNode, const QByteArray& packet) { + copyContents(destinationNode, packet); }; // copy assignment NetworkPacket& NetworkPacket::operator=(NetworkPacket const& other) { - copyContents(other.getSockAddr(), other.getByteArray()); + copyContents(other.getDestinationNode(), other.getByteArray()); return *this; } #ifdef HAS_MOVE_SEMANTICS // move, same as copy, but other packet won't be used further NetworkPacket::NetworkPacket(NetworkPacket && packet) { - copyContents(packet.getAddress(), packet.getByteArray()); + copyContents(packet.getDestinationNode(), packet.getByteArray()); } // move assignment NetworkPacket& NetworkPacket::operator=(NetworkPacket&& other) { - copyContents(other.getAddress(), other.getByteArray()); + copyContents(other.getDestinationNode(), other.getByteArray()); return *this; } -#endif \ No newline at end of file +#endif diff --git a/libraries/shared/src/NetworkPacket.h b/libraries/shared/src/NetworkPacket.h index 480a793a49..0875b9f131 100644 --- a/libraries/shared/src/NetworkPacket.h +++ b/libraries/shared/src/NetworkPacket.h @@ -18,9 +18,7 @@ #include #endif -#include "HifiSockAddr.h" - -#include "NodeList.h" // for MAX_PACKET_SIZE +#include "NodeList.h" /// Storage of not-yet processed inbound, or not yet sent outbound generic UDP network packet class NetworkPacket { @@ -33,15 +31,15 @@ public: NetworkPacket& operator= (NetworkPacket&& other); // move assignment #endif - NetworkPacket(const HifiSockAddr& sockAddr, const QByteArray& byteArray); + NetworkPacket(const SharedNodePointer& destinationNode, const QByteArray& byteArray); - const HifiSockAddr& getSockAddr() const { return _sockAddr; } + const SharedNodePointer& getDestinationNode() const { return _destinationNode; } const QByteArray& getByteArray() const { return _byteArray; } private: - void copyContents(const HifiSockAddr& sockAddr, const QByteArray& byteArray); + void copyContents(const SharedNodePointer& destinationNode, const QByteArray& byteArray); - HifiSockAddr _sockAddr; + SharedNodePointer _destinationNode; QByteArray _byteArray; }; diff --git a/libraries/shared/src/Node.cpp b/libraries/shared/src/Node.cpp index 8efbf5782a..bb45265f8b 100644 --- a/libraries/shared/src/Node.cpp +++ b/libraries/shared/src/Node.cpp @@ -52,6 +52,7 @@ Node::Node(const QUuid& uuid, char type, const HifiSockAddr& publicSocket, const _publicSocket(publicSocket), _localSocket(localSocket), _activeSocket(NULL), + _connectionSecret(), _bytesReceivedMovingAverage(NULL), _linkedData(NULL), _isAlive(true), diff --git a/libraries/shared/src/Node.h b/libraries/shared/src/Node.h index 952e1b1be2..43ec5baf81 100644 --- a/libraries/shared/src/Node.h +++ b/libraries/shared/src/Node.h @@ -56,7 +56,6 @@ public: char getType() const { return _type; } void setType(char type) { _type = type; } - const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } @@ -76,6 +75,9 @@ public: void activatePublicSocket(); void activateLocalSocket(); + + const QUuid& getConnectionSecret() const { return _connectionSecret; } + void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } NodeData* getLinkedData() const { return _linkedData; } void setLinkedData(NodeData* linkedData) { _linkedData = linkedData; } @@ -109,6 +111,7 @@ private: HifiSockAddr _publicSocket; HifiSockAddr _localSocket; HifiSockAddr* _activeSocket; + QUuid _connectionSecret; SimpleMovingAverage* _bytesReceivedMovingAverage; NodeData* _linkedData; bool _isAlive; diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index cd9356ab15..2672f3b27b 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -64,7 +64,7 @@ NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) : _nodeSocket(this), _ownerType(newOwnerType), _nodeTypesOfInterest(), - _ownerUUID(QUuid::createUuid()), + _sessionUUID(), _numNoReplyDomainCheckIns(0), _assignmentServerSocket(), _publicSockAddr(), @@ -80,6 +80,78 @@ NodeList::~NodeList() { clear(); } +bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) { + // currently this just checks if the version in the packet matches our return from versionForPacketType + // may need to be expanded in the future for types and versions that take > than 1 byte + + if (packet[1] != versionForPacketType(packetTypeForPacket(packet)) + && packetTypeForPacket(packet) != PacketTypeStunResponse) { + PacketType mismatchType = packetTypeForPacket(packet); + int numPacketTypeBytes = arithmeticCodingValueFromBuffer(packet.data()); + + qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender" + << uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but" + << qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected."; + } + + const QSet NON_VERIFIED_PACKETS = QSet() << PacketTypeDomainList + << PacketTypeDomainListRequest << PacketTypeStunResponse << PacketTypeDataServerConfirm + << PacketTypeDataServerGet << PacketTypeDataServerPut << PacketTypeDataServerSend + << PacketTypeCreateAssignment << PacketTypeRequestAssignment; + + if (!NON_VERIFIED_PACKETS.contains(packetTypeForPacket(packet))) { + // figure out which node this is from + SharedNodePointer sendingNode = sendingNodeForPacket(packet); + if (sendingNode) { + // check if the md5 hash in the header matches the hash we would expect + if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) { + return true; + } else { + qDebug() << "Packet hash mismatch on" << packetTypeForPacket(packet) << "- Sender" + << uuidFromPacketHeader(packet); + } + } else { + qDebug() << "Packet of type" << packetTypeForPacket(packet) << "received from unknown node with UUID" + << uuidFromPacketHeader(packet); + } + } else { + return true; + } + + return false; +} + +qint64 NodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + if (destinationNode) { + // if we don't have an ovveriden address, assume they want to send to the node's active socket + const HifiSockAddr* destinationSockAddr = &overridenSockAddr; + if (overridenSockAddr.isNull()) { + if (destinationNode->getActiveSocket()) { + // use the node's active socket as the destination socket + destinationSockAddr = destinationNode->getActiveSocket(); + } else { + // we don't have a socket to send to, return 0 + return 0; + } + } + + QByteArray datagramCopy = datagram; + // setup the MD5 hash for source verification in the header + replaceHashInPacketGivenConnectionUUID(datagramCopy, destinationNode->getConnectionSecret()); + + return _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr->getAddress(), destinationSockAddr->getPort()); + } + + // didn't have a destinationNode to send to, return 0 + return 0; +} + +qint64 NodeList::writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); +} + void NodeList::setDomainHostname(const QString& domainHostname) { if (domainHostname != _domainHostname) { @@ -111,44 +183,38 @@ void NodeList::setDomainHostname(const QString& domainHostname) { } } -void NodeList::timePingReply(const QByteArray& packet) { - QUuid nodeUUID; - deconstructPacketHeader(packet, nodeUUID); +void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) { + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); - SharedNodePointer matchingNode = nodeWithUUID(nodeUUID); + quint8 pingType; + quint64 ourOriginalTime, othersReplyTime; - if (matchingNode) { - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - quint64 ourOriginalTime, othersReplyTime; - - packetStream >> ourOriginalTime >> othersReplyTime; - - quint64 now = usecTimestampNow(); - int pingTime = now - ourOriginalTime; - int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight - - // The other node's expected time should be our original time plus the one way flight time - // anything other than that is clock skew - quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime; - int clockSkew = othersReplyTime - othersExprectedReply; - - matchingNode->setPingMs(pingTime / 1000); - matchingNode->setClockSkewUsec(clockSkew); - - const bool wantDebug = false; - - if (wantDebug) { - qDebug() << "PING_REPLY from node " << *matchingNode << "\n" << - " now: " << now << "\n" << - " ourTime: " << ourOriginalTime << "\n" << - " pingTime: " << pingTime << "\n" << - " oneWayFlightTime: " << oneWayFlightTime << "\n" << - " othersReplyTime: " << othersReplyTime << "\n" << - " othersExprectedReply: " << othersExprectedReply << "\n" << - " clockSkew: " << clockSkew; - } + packetStream >> pingType >> ourOriginalTime >> othersReplyTime; + + quint64 now = usecTimestampNow(); + int pingTime = now - ourOriginalTime; + int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight + + // The other node's expected time should be our original time plus the one way flight time + // anything other than that is clock skew + quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime; + int clockSkew = othersReplyTime - othersExprectedReply; + + sendingNode->setPingMs(pingTime / 1000); + sendingNode->setClockSkewUsec(clockSkew); + + const bool wantDebug = false; + + if (wantDebug) { + qDebug() << "PING_REPLY from node " << *sendingNode << "\n" << + " now: " << now << "\n" << + " ourTime: " << ourOriginalTime << "\n" << + " pingTime: " << pingTime << "\n" << + " oneWayFlightTime: " << oneWayFlightTime << "\n" << + " othersReplyTime: " << othersReplyTime << "\n" << + " othersExprectedReply: " << othersExprectedReply << "\n" << + " clockSkew: " << clockSkew; } } @@ -164,16 +230,25 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr } case PacketTypePing: { // send back a reply - QByteArray replyPacket = constructPingReplyPacket(packet); - _nodeSocket.writeDatagram(replyPacket, senderSockAddr.getAddress(), senderSockAddr.getPort()); + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + if (matchingNode) { + QByteArray replyPacket = constructPingReplyPacket(packet); + writeDatagram(replyPacket, matchingNode, senderSockAddr); + } + break; } case PacketTypePingReply: { - // activate the appropriate socket for this node, if not yet updated - activateSocketFromNodeCommunication(senderSockAddr); - - // set the ping time for this node for stat collection - timePingReply(packet); + SharedNodePointer sendingNode = sendingNodeForPacket(packet); + + if (sendingNode) { + // activate the appropriate socket for this node, if not yet updated + activateSocketFromNodeCommunication(packet, sendingNode); + + // set the ping time for this node for stat collection + timePingReply(packet, sendingNode); + } + break; } case PacketTypeStunResponse: { @@ -187,44 +262,28 @@ void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteAr } } -int NodeList::updateNodeWithData(Node *node, const HifiSockAddr& senderSockAddr, const QByteArray& packet) { - QMutexLocker locker(&node->getMutex()); - - node->setLastHeardMicrostamp(usecTimestampNow()); - - if (!senderSockAddr.isNull() && !node->getActiveSocket()) { - if (senderSockAddr == node->getPublicSocket()) { - node->activatePublicSocket(); - } else if (senderSockAddr == node->getLocalSocket()) { - node->activateLocalSocket(); - } - } - - if (node->getActiveSocket() || senderSockAddr.isNull()) { - node->recordBytesReceived(packet.size()); - - if (!node->getLinkedData() && linkedDataCreateCallback) { - linkedDataCreateCallback(node); - } - - int numParsedBytes = node->getLinkedData()->parseData(packet); - return numParsedBytes; - } else { - // we weren't able to match the sender address to the address we have for this node, unlock and don't parse - return 0; +int NodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray &packet) { + QMutexLocker locker(&matchingNode->getMutex()); + + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + matchingNode->recordBytesReceived(packet.size()); + + if (!matchingNode->getLinkedData() && linkedDataCreateCallback) { + linkedDataCreateCallback(matchingNode.data()); } + + return matchingNode->getLinkedData()->parseData(packet); } -SharedNodePointer NodeList::nodeWithAddress(const HifiSockAddr &senderSockAddr) { - // naively returns the first node that has a matching active HifiSockAddr - // note that there can be multiple nodes that have a matching active socket, so this isn't a good way to uniquely identify - foreach (const SharedNodePointer& node, getNodeHash()) { - if (node->getActiveSocket() && *node->getActiveSocket() == senderSockAddr) { - return node; - } +int NodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + + if (matchingNode) { + updateNodeWithDataFromPacket(matchingNode, packet); } - - return SharedNodePointer(); + + // we weren't able to match the sender address to the address we have for this node, unlock and don't parse + return 0; } SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID) { @@ -232,6 +291,13 @@ SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID) { return _nodeHash.value(nodeUUID); } +SharedNodePointer NodeList::sendingNodeForPacket(const QByteArray& packet) { + QUuid nodeUUID = uuidFromPacketHeader(packet); + + // return the matching node, or NULL if there is no match + return nodeWithUUID(nodeUUID); +} + NodeHash NodeList::getNodeHash() { QMutexLocker locker(&_nodeHashMutex); return NodeHash(_nodeHash); @@ -257,7 +323,7 @@ void NodeList::reset() { _nodeTypesOfInterest.clear(); // refresh the owner UUID - _ownerUUID = QUuid::createUuid(); + _sessionUUID = QUuid::createUuid(); } void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { @@ -337,7 +403,7 @@ void NodeList::processSTUNResponse(const QByteArray& packet) { const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); - size_t attributeStartIndex = NUM_BYTES_STUN_HEADER; + int attributeStartIndex = NUM_BYTES_STUN_HEADER; if (memcmp(packet.data() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, @@ -492,6 +558,16 @@ void NodeList::sendDomainServerCheckIn() { } } +void NodeList::setSessionUUID(const QUuid& sessionUUID) { + QUuid oldUUID = _sessionUUID; + _sessionUUID = sessionUUID; + + if (sessionUUID != oldUUID) { + qDebug() << "NodeList UUID changed from" << oldUUID << "to" << _sessionUUID; + emit uuidChanged(sessionUUID); + } +} + int NodeList::processDomainServerList(const QByteArray& packet) { // this is a packet from the domain server, reset the count of un-replied check-ins _numNoReplyDomainCheckIns = 0; @@ -501,14 +577,20 @@ int NodeList::processDomainServerList(const QByteArray& packet) { // setup variables to read into from QDataStream qint8 nodeType; - QUuid nodeUUID; + QUuid nodeUUID, connectionUUID; HifiSockAddr nodePublicSocket; HifiSockAddr nodeLocalSocket; QDataStream packetStream(packet); packetStream.skipRawData(numBytesForPacketHeader(packet)); - + + // pull our owner UUID from the packet, it's always the first thing + QUuid newUUID; + packetStream >> newUUID; + setSessionUUID(newUUID); + + // pull each node in the packet while(packetStream.device()->pos() < packet.size()) { packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket; @@ -518,8 +600,15 @@ int NodeList::processDomainServerList(const QByteArray& packet) { nodePublicSocket.setAddress(_domainSockAddr.getAddress()); } - addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket); + SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket); + + packetStream >> connectionUUID; + node->setConnectionSecret(connectionUUID); } + + // ping inactive nodes in conjunction with receipt of list from domain-server + // this makes it happen every second and also pings any newly added nodes + pingInactiveNodes(); return readNodes; } @@ -544,10 +633,12 @@ void NodeList::sendAssignment(Assignment& assignment) { _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); } -QByteArray NodeList::constructPingPacket() { +QByteArray NodeList::constructPingPacket(PingType_t pingType) { QByteArray pingPacket = byteArrayWithPopluatedHeader(PacketTypePing); QDataStream packetStream(&pingPacket, QIODevice::Append); + + packetStream << pingType; packetStream << usecTimestampNow(); return pingPacket; @@ -557,23 +648,28 @@ QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { QDataStream pingPacketStream(pingPacket); pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); + PingType_t typeFromOriginalPing; + pingPacketStream >> typeFromOriginalPing; + quint64 timeFromOriginalPing; pingPacketStream >> timeFromOriginalPing; QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypePingReply); QDataStream packetStream(&replyPacket, QIODevice::Append); - packetStream << timeFromOriginalPing << usecTimestampNow(); + packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); return replyPacket; } -void NodeList::pingPublicAndLocalSocketsForInactiveNode(Node* node) { - QByteArray pingPacket = constructPingPacket(); - +void NodeList::pingPublicAndLocalSocketsForInactiveNode(const SharedNodePointer& node) { + // send the ping packet to the local and public sockets for this node - _nodeSocket.writeDatagram(pingPacket, node->getLocalSocket().getAddress(), node->getLocalSocket().getPort()); - _nodeSocket.writeDatagram(pingPacket, node->getPublicSocket().getAddress(), node->getPublicSocket().getPort()); + QByteArray localPingPacket = constructPingPacket(PingType::Local); + writeDatagram(localPingPacket, node, node->getLocalSocket()); + + QByteArray publicPingPacket = constructPingPacket(PingType::Public); + writeDatagram(publicPingPacket, node, node->getPublicSocket()); } SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, @@ -586,9 +682,9 @@ SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, // we didn't have this node, so add them Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); SharedNodePointer newNodeSharedPointer(newNode, &QObject::deleteLater); - + _nodeHash.insert(newNode->getUUID(), newNodeSharedPointer); - + _nodeHashMutex.unlock(); qDebug() << "Added" << *newNode; @@ -630,11 +726,8 @@ unsigned NodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& des foreach (const SharedNodePointer& node, getNodeHash()) { // only send to the NodeTypes we are asked to send to. if (destinationNodeTypes.contains(node->getType())) { - if (getNodeActiveSocketOrPing(node.data())) { - // we know which socket is good for this node, send there - _nodeSocket.writeDatagram(packet, node->getActiveSocket()->getAddress(), node->getActiveSocket()->getPort()); - ++n; - } + writeDatagram(packet, node); + ++n; } } @@ -645,34 +738,25 @@ void NodeList::pingInactiveNodes() { foreach (const SharedNodePointer& node, getNodeHash()) { if (!node->getActiveSocket()) { // we don't have an active link to this node, ping it to set that up - pingPublicAndLocalSocketsForInactiveNode(node.data()); + pingPublicAndLocalSocketsForInactiveNode(node); } } } -const HifiSockAddr* NodeList::getNodeActiveSocketOrPing(Node* node) { - if (node->getActiveSocket()) { - return node->getActiveSocket(); - } else { - pingPublicAndLocalSocketsForInactiveNode(node); - return NULL; - } -} - -void NodeList::activateSocketFromNodeCommunication(const HifiSockAddr& nodeAddress) { - - foreach (const SharedNodePointer& node, _nodeHash) { - if (!node->getActiveSocket()) { - // check both the public and local addresses for each node to see if we find a match - // prioritize the private address so that we prune erroneous local matches - if (node->getPublicSocket() == nodeAddress) { - node->activatePublicSocket(); - break; - } else if (node->getLocalSocket() == nodeAddress) { - node->activateLocalSocket(); - break; - } - } +void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { + // deconstruct this ping packet to see if it is a public or local reply + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + quint8 pingType; + packetStream >> pingType; + + // if this is a local or public ping then we can activate a socket + // we do nothing with agnostic pings, those are simply for timing + if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) { + sendingNode->activateLocalSocket(); + } else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) { + sendingNode->activatePublicSocket(); } } diff --git a/libraries/shared/src/NodeList.h b/libraries/shared/src/NodeList.h index 6b8acd6e99..ca4e8ab7b4 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/shared/src/NodeList.h @@ -30,8 +30,6 @@ #include "Node.h" -const int MAX_PACKET_SIZE = 1500; - const quint64 NODE_SILENCE_THRESHOLD_USECS = 2 * 1000 * 1000; const quint64 DOMAIN_SERVER_CHECK_IN_USECS = 1 * 1000000; const quint64 PING_INACTIVE_NODE_INTERVAL_USECS = 1 * 1000 * 1000; @@ -54,6 +52,13 @@ typedef QSharedPointer SharedNodePointer; typedef QHash NodeHash; Q_DECLARE_METATYPE(SharedNodePointer) +typedef quint8 PingType_t; +namespace PingType { + const PingType_t Agnostic = 0; + const PingType_t Local = 1; + const PingType_t Public = 2; +} + class NodeList : public QObject { Q_OBJECT public: @@ -72,10 +77,17 @@ public: unsigned short getDomainPort() const { return _domainSockAddr.getPort(); } - const QUuid& getOwnerUUID() const { return _ownerUUID; } - void setOwnerUUID(const QUuid& ownerUUID) { _ownerUUID = ownerUUID; } + const QUuid& getSessionUUID() const { return _sessionUUID; } + void setSessionUUID(const QUuid& sessionUUID); QUdpSocket& getNodeSocket() { return _nodeSocket; } + + bool packetVersionAndHashMatch(const QByteArray& packet); + + qint64 writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr = HifiSockAddr()); + qint64 writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr = HifiSockAddr()); void(*linkedDataCreateCallback)(Node *); @@ -95,27 +107,27 @@ public: void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } void sendAssignment(Assignment& assignment); - QByteArray constructPingPacket(); + QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); QByteArray constructPingReplyPacket(const QByteArray& pingPacket); - void pingPublicAndLocalSocketsForInactiveNode(Node* node); + void pingPublicAndLocalSocketsForInactiveNode(const SharedNodePointer& node); - SharedNodePointer nodeWithAddress(const HifiSockAddr& senderSockAddr); SharedNodePointer nodeWithUUID(const QUuid& nodeUUID); - - SharedNodePointer addOrUpdateNode(const QUuid& uuid, char nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); + SharedNodePointer sendingNodeForPacket(const QByteArray& packet); + + SharedNodePointer addOrUpdateNode(const QUuid& uuid, char nodeType, + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet); void processKillNode(const QByteArray& datagram); - int updateNodeWithData(Node *node, const HifiSockAddr& senderSockAddr, const QByteArray& packet); + int updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray& packet); + int findNodeAndUpdateWithDataFromPacket(const QByteArray& packet); unsigned broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes); SharedNodePointer soloNodeOfType(char nodeType); void loadData(QSettings* settings); void saveData(QSettings* settings); - - const HifiSockAddr* getNodeActiveSocketOrPing(Node* node); public slots: void sendDomainServerCheckIn(); void pingInactiveNodes(); @@ -124,6 +136,7 @@ public slots: void killNodeWithUUID(const QUuid& nodeUUID); signals: void domainChanged(const QString& domainHostname); + void uuidChanged(const QUuid& ownerUUID); void nodeAdded(SharedNodePointer); void nodeKilled(SharedNodePointer); private: @@ -145,15 +158,15 @@ private: QUdpSocket _nodeSocket; NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; - QUuid _ownerUUID; + QUuid _sessionUUID; int _numNoReplyDomainCheckIns; HifiSockAddr _assignmentServerSocket; HifiSockAddr _publicSockAddr; bool _hasCompletedInitialSTUNFailure; unsigned int _stunRequestsSinceSuccess; - void activateSocketFromNodeCommunication(const HifiSockAddr& nodeSockAddr); - void timePingReply(const QByteArray& packet); + void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); + void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); void resetDomainData(char domainField[], const char* domainData); void domainLookup(); void clear(); diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index 34d4264de4..36f3e74f63 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -12,7 +12,6 @@ #include #include -const int BITS_IN_BYTE = 8; const int BITS_IN_OCTAL = 3; const int NUMBER_OF_COLORS = 3; // RGB! const int SIZE_OF_COLOR_DATA = NUMBER_OF_COLORS * sizeof(unsigned char); // size in bytes diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index 67b534bc34..b46a57d4aa 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -37,7 +37,7 @@ int packArithmeticallyCodedValue(int value, char* destination) { return 1; } else { // pack 255 and then recursively pack on - destination[0] = 255; + ((unsigned char*)destination)[0] = 255; return 1 + packArithmeticallyCodedValue(value - 255, destination + 1); } } @@ -46,6 +46,17 @@ PacketVersion versionForPacketType(PacketType type) { switch (type) { case PacketTypeParticleData: return 1; + case PacketTypeDomainList: + case PacketTypeDomainListRequest: + return 1; + case PacketTypeCreateAssignment: + case PacketTypeRequestAssignment: + return 1; + case PacketTypeDataServerGet: + case PacketTypeDataServerPut: + case PacketTypeDataServerConfirm: + case PacketTypeDataServerSend: + return 1; default: return 0; } @@ -69,53 +80,53 @@ int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionU int numTypeBytes = packArithmeticallyCodedValue(type, packet); packet[numTypeBytes] = versionForPacketType(type); - QUuid packUUID = connectionUUID.isNull() ? NodeList::getInstance()->getOwnerUUID() : connectionUUID; + char* position = packet + numTypeBytes + sizeof(PacketVersion); + + QUuid packUUID = connectionUUID.isNull() ? NodeList::getInstance()->getSessionUUID() : connectionUUID; QByteArray rfcUUID = packUUID.toRfc4122(); - memcpy(packet + numTypeBytes + sizeof(PacketVersion), rfcUUID.constData(), NUM_BYTES_RFC4122_UUID); + memcpy(position, rfcUUID.constData(), NUM_BYTES_RFC4122_UUID); + position += NUM_BYTES_RFC4122_UUID; + + // pack 16 bytes of zeros where the md5 hash will be placed one data is packed + memset(position, 0, NUM_BYTES_MD5_HASH); + position += NUM_BYTES_MD5_HASH; // return the number of bytes written for pointer pushing - return numTypeBytes + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; -} - -bool packetVersionMatch(const QByteArray& packet) { - // currently this just checks if the version in the packet matches our return from versionForPacketType - // may need to be expanded in the future for types and versions that take > than 1 byte - - if (packet[1] == versionForPacketType(packetTypeForPacket(packet)) || packetTypeForPacket(packet) == PacketTypeStunResponse) { - return true; - } else { - PacketType mismatchType = packetTypeForPacket(packet); - int numPacketTypeBytes = arithmeticCodingValueFromBuffer(packet.data()); - - QUuid nodeUUID; - deconstructPacketHeader(packet, nodeUUID); - - qDebug() << "Packet mismatch on" << packetTypeForPacket(packet) << "- Sender" - << nodeUUID << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but" - << qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected."; - - return false; - } + return position - packet; } int numBytesForPacketHeader(const QByteArray& packet) { // returns the number of bytes used for the type, version, and UUID - return numBytesArithmeticCodingFromBuffer(packet.data()) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; + return numBytesArithmeticCodingFromBuffer(packet.data()) + NUM_STATIC_HEADER_BYTES; } int numBytesForPacketHeader(const char* packet) { // returns the number of bytes used for the type, version, and UUID - return numBytesArithmeticCodingFromBuffer(packet) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; + return numBytesArithmeticCodingFromBuffer(packet) + NUM_STATIC_HEADER_BYTES; } int numBytesForPacketHeaderGivenPacketType(PacketType type) { - return (int) ceilf((float)type / 255) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; + return (int) ceilf((float)type / 255) + NUM_STATIC_HEADER_BYTES; } -void deconstructPacketHeader(const QByteArray& packet, QUuid& senderUUID) { - senderUUID = QUuid::fromRfc4122(packet.mid(numBytesArithmeticCodingFromBuffer(packet.data()) + sizeof(PacketVersion), - NUM_BYTES_RFC4122_UUID)); +QUuid uuidFromPacketHeader(const QByteArray& packet) { + return QUuid::fromRfc4122(packet.mid(numBytesArithmeticCodingFromBuffer(packet.data()) + sizeof(PacketVersion), + NUM_BYTES_RFC4122_UUID)); +} + +QByteArray hashFromPacketHeader(const QByteArray& packet) { + return packet.mid(numBytesForPacketHeader(packet) - NUM_BYTES_MD5_HASH, NUM_BYTES_MD5_HASH); +} + +QByteArray hashForPacketAndConnectionUUID(const QByteArray& packet, const QUuid& connectionUUID) { + return QCryptographicHash::hash(packet.mid(numBytesForPacketHeader(packet)) + connectionUUID.toRfc4122(), + QCryptographicHash::Md5); +} + +void replaceHashInPacketGivenConnectionUUID(QByteArray& packet, const QUuid& connectionUUID) { + packet.replace(numBytesForPacketHeader(packet) - NUM_BYTES_MD5_HASH, NUM_BYTES_MD5_HASH, + hashForPacketAndConnectionUUID(packet, connectionUUID)); } PacketType packetTypeForPacket(const QByteArray& packet) { diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index 84f074b0a7..577412e51f 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -12,6 +12,7 @@ #ifndef hifi_PacketHeaders_h #define hifi_PacketHeaders_h +#include #include #include "UUID.h" @@ -54,12 +55,15 @@ enum PacketType { PacketTypeParticleAddOrEdit, PacketTypeParticleErase, PacketTypeParticleAddResponse, - PacketTypeMetavoxelData + PacketTypeMetavoxelData, + PacketTypeAvatarIdentity }; typedef char PacketVersion; -const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID;; +const int NUM_BYTES_MD5_HASH = 16; +const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; +const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + NUM_STATIC_HEADER_BYTES; PacketVersion versionForPacketType(PacketType type); @@ -69,17 +73,20 @@ QByteArray byteArrayWithPopluatedHeader(PacketType type, const QUuid& connection int populatePacketHeader(QByteArray& packet, PacketType type, const QUuid& connectionUUID = nullUUID); int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionUUID = nullUUID); -bool packetVersionMatch(const QByteArray& packet); - int numBytesForPacketHeader(const QByteArray& packet); int numBytesForPacketHeader(const char* packet); int numBytesForPacketHeaderGivenPacketType(PacketType type); -void deconstructPacketHeader(const QByteArray& packet, QUuid& senderUUID); +QUuid uuidFromPacketHeader(const QByteArray& packet); + +QByteArray hashFromPacketHeader(const QByteArray& packet); +QByteArray hashForPacketAndConnectionUUID(const QByteArray& packet, const QUuid& connectionUUID); +void replaceHashInPacketGivenConnectionUUID(QByteArray& packet, const QUuid& connectionUUID); PacketType packetTypeForPacket(const QByteArray& packet); PacketType packetTypeForPacket(const char* packet); int arithmeticCodingValueFromBuffer(const char* checkValue); +int numBytesArithmeticCodingFromBuffer(const char* checkValue); #endif diff --git a/libraries/shared/src/PacketSender.cpp b/libraries/shared/src/PacketSender.cpp index 8a031cbc39..9d819b96d7 100644 --- a/libraries/shared/src/PacketSender.cpp +++ b/libraries/shared/src/PacketSender.cpp @@ -47,8 +47,8 @@ PacketSender::~PacketSender() { } -void PacketSender::queuePacketForSending(const HifiSockAddr& address, const QByteArray& packet) { - NetworkPacket networkPacket(address, packet); +void PacketSender::queuePacketForSending(const SharedNodePointer& destinationNode, const QByteArray& packet) { + NetworkPacket networkPacket(destinationNode, packet); lock(); _packets.push_back(networkPacket); unlock(); @@ -263,9 +263,7 @@ bool PacketSender::nonThreadedProcess() { unlock(); // send the packet through the NodeList... - NodeList::getInstance()->getNodeSocket().writeDatagram(temporary.getByteArray(), - temporary.getSockAddr().getAddress(), - temporary.getSockAddr().getPort()); + NodeList::getInstance()->writeDatagram(temporary.getByteArray(), temporary.getDestinationNode()); packetsSentThisCall++; _packetsOverCheckInterval++; _totalPacketsSent++; diff --git a/libraries/shared/src/PacketSender.h b/libraries/shared/src/PacketSender.h index 4a1e62bd0c..7d00e622b9 100644 --- a/libraries/shared/src/PacketSender.h +++ b/libraries/shared/src/PacketSender.h @@ -13,6 +13,7 @@ #include "GenericThread.h" #include "NetworkPacket.h" +#include "NodeList.h" #include "SharedUtil.h" /// Generalized threaded processor for queueing and sending of outbound packets. @@ -37,7 +38,7 @@ public: /// \param packetData pointer to data /// \param ssize_t packetLength size of data /// \thread any thread, typically the application thread - void queuePacketForSending(const HifiSockAddr& address, const QByteArray& packet); + void queuePacketForSending(const SharedNodePointer& destinationNode, const QByteArray& packet); void setPacketsPerSecond(int packetsPerSecond); int getPacketsPerSecond() const { return _packetsPerSecond; } diff --git a/libraries/shared/src/ReceivedPacketProcessor.cpp b/libraries/shared/src/ReceivedPacketProcessor.cpp index 5faec02ea1..b966109903 100644 --- a/libraries/shared/src/ReceivedPacketProcessor.cpp +++ b/libraries/shared/src/ReceivedPacketProcessor.cpp @@ -16,14 +16,11 @@ ReceivedPacketProcessor::ReceivedPacketProcessor() { _dontSleep = false; } -void ReceivedPacketProcessor::queueReceivedPacket(const HifiSockAddr& address, const QByteArray& packet) { +void ReceivedPacketProcessor::queueReceivedPacket(const SharedNodePointer& destinationNode, const QByteArray& packet) { // Make sure our Node and NodeList knows we've heard from this node. - SharedNodePointer node = NodeList::getInstance()->nodeWithAddress(address); - if (node) { - node->setLastHeardMicrostamp(usecTimestampNow()); - } + destinationNode->setLastHeardMicrostamp(usecTimestampNow()); - NetworkPacket networkPacket(address, packet); + NetworkPacket networkPacket(destinationNode, packet); lock(); _packets.push_back(networkPacket); unlock(); @@ -44,7 +41,7 @@ bool ReceivedPacketProcessor::process() { NetworkPacket temporary = packet; // make a copy of the packet in case the vector is resized on us _packets.erase(_packets.begin()); // remove the oldest packet unlock(); // let others add to the packets - processPacket(temporary.getSockAddr(), temporary.getByteArray()); // process our temporary copy + processPacket(temporary.getDestinationNode(), temporary.getByteArray()); // process our temporary copy } return isStillRunning(); // keep running till they terminate us } diff --git a/libraries/shared/src/ReceivedPacketProcessor.h b/libraries/shared/src/ReceivedPacketProcessor.h index dfb4ed55a9..043dd6c6c4 100644 --- a/libraries/shared/src/ReceivedPacketProcessor.h +++ b/libraries/shared/src/ReceivedPacketProcessor.h @@ -24,7 +24,7 @@ public: /// \param packetData pointer to received data /// \param ssize_t packetLength size of received data /// \thread network receive thread - void queueReceivedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& packet); + void queueReceivedPacket(const SharedNodePointer& destinationNode, const QByteArray& packet); /// Are there received packets waiting to be processed bool hasPacketsToProcess() const { return _packets.size() > 0; } @@ -38,7 +38,7 @@ protected: /// \param packetData pointer to received data /// \param ssize_t packetLength size of received data /// \thread "this" individual processing thread - virtual void processPacket(const HifiSockAddr& senderAddress, const QByteArray& packet) = 0; + virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet) = 0; /// Implements generic processing behavior for this thread. virtual bool process(); diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index 4b0f2c403e..4bfb2a2de1 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -15,6 +15,7 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, vec2toScriptValue, vec2FromScriptValue); qScriptRegisterMetaType(engine, quatToScriptValue, quatFromScriptValue); qScriptRegisterMetaType(engine, xColorToScriptValue, xColorFromScriptValue); + qScriptRegisterMetaType(engine, pickRayToScriptValue, pickRayFromScriptValue); } QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3) { @@ -73,3 +74,28 @@ void xColorFromScriptValue(const QScriptValue &object, xColor& color) { color.blue = object.property("blue").toVariant().toInt(); } +QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay) { + QScriptValue obj = engine->newObject(); + QScriptValue origin = vec3toScriptValue(engine, pickRay.origin); + obj.setProperty("origin", origin); + QScriptValue direction = vec3toScriptValue(engine, pickRay.direction); + obj.setProperty("direction", direction); + return obj; + +} + +void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { + QScriptValue originValue = object.property("origin"); + if (originValue.isValid()) { + pickRay.origin.x = originValue.property("x").toVariant().toFloat(); + pickRay.origin.y = originValue.property("y").toVariant().toFloat(); + pickRay.origin.z = originValue.property("z").toVariant().toFloat(); + } + QScriptValue directionValue = object.property("direction"); + if (directionValue.isValid()) { + pickRay.direction.x = directionValue.property("x").toVariant().toFloat(); + pickRay.direction.y = directionValue.property("y").toVariant().toFloat(); + pickRay.direction.z = directionValue.property("z").toVariant().toFloat(); + } +} + diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index b198add7c2..91eb3848c7 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -22,7 +22,6 @@ Q_DECLARE_METATYPE(glm::vec2) Q_DECLARE_METATYPE(glm::quat) Q_DECLARE_METATYPE(xColor) - void registerMetaTypes(QScriptEngine* engine); QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3); @@ -37,4 +36,15 @@ void quatFromScriptValue(const QScriptValue &object, glm::quat& quat); QScriptValue xColorToScriptValue(QScriptEngine* engine, const xColor& color); void xColorFromScriptValue(const QScriptValue &object, xColor& color); +class PickRay { +public: + PickRay() : origin(0), direction(0) { }; + glm::vec3 origin; + glm::vec3 direction; +}; +Q_DECLARE_METATYPE(PickRay) +QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay); +void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay); + + #endif diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index c982ab3596..603a09a0c3 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -61,6 +61,10 @@ static const quint64 USECS_PER_MSEC = 1000; static const quint64 MSECS_PER_SECOND = 1000; static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; +const int BITS_IN_BYTE = 8; + +const int MAX_PACKET_SIZE = 1500; + quint64 usecTimestamp(const timeval *time); quint64 usecTimestampNow(); void usecTimestampNowForceClockSkew(int clockSkew); diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/shared/src/ThreadedAssignment.cpp index 2c1110491b..5f66ae4a4e 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/shared/src/ThreadedAssignment.cpp @@ -60,3 +60,16 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { NodeList::getInstance()->sendDomainServerCheckIn(); } } + +bool ThreadedAssignment::readAvailableDatagram(QByteArray& destinationByteArray, HifiSockAddr& senderSockAddr) { + NodeList* nodeList = NodeList::getInstance(); + + if (nodeList->getNodeSocket().hasPendingDatagrams()) { + destinationByteArray.resize(nodeList->getNodeSocket().pendingDatagramSize()); + nodeList->getNodeSocket().readDatagram(destinationByteArray.data(), destinationByteArray.size(), + senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); + return true; + } else { + return false; + } +} diff --git a/libraries/shared/src/ThreadedAssignment.h b/libraries/shared/src/ThreadedAssignment.h index 7c7aa9a63e..c8aa1c419c 100644 --- a/libraries/shared/src/ThreadedAssignment.h +++ b/libraries/shared/src/ThreadedAssignment.h @@ -23,8 +23,10 @@ public slots: virtual void deleteLater(); - virtual void processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) = 0; + virtual void readPendingDatagrams() = 0; protected: + bool readAvailableDatagram(QByteArray& destinationByteArray, HifiSockAddr& senderSockAddr); + void commonInit(const char* targetName, NodeType_t nodeType); bool _isFinished; private slots: diff --git a/libraries/voxel-server/CMakeLists.txt b/libraries/voxel-server/CMakeLists.txt deleted file mode 100644 index 594d037767..0000000000 --- a/libraries/voxel-server/CMakeLists.txt +++ /dev/null @@ -1,37 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -set(ROOT_DIR ../..) -set(MACRO_DIR ${ROOT_DIR}/cmake/macros) - -# setup for find modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/") - -set(TARGET_NAME voxel-server) - -find_package(Qt5Widgets REQUIRED) - -include(${MACRO_DIR}/SetupHifiLibrary.cmake) - -setup_hifi_library(${TARGET_NAME} ${OPTIONAL_SRCS}) - -qt5_use_modules(${TARGET_NAME} Widgets) - -include(${MACRO_DIR}/IncludeGLM.cmake) -include_glm(${TARGET_NAME} ${ROOT_DIR}) - -# link ZLIB -find_package(ZLIB) -include_directories(${ZLIB_INCLUDE_DIRS}) -target_link_libraries(${TARGET_NAME} ${ZLIB_LIBRARIES}) - -# link in the shared library -include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) - -# link in the embedded webserver -link_hifi_library(embedded-webserver ${TARGET_NAME} ${ROOT_DIR}) - -# link in the hifi octree library -link_hifi_library(octree ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(octree-server ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(voxels ${TARGET_NAME} ${ROOT_DIR}) \ No newline at end of file diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 90fdbb8a05..e29cfda41d 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -522,7 +522,7 @@ bool VoxelTree::handlesEditPacketType(PacketType packetType) const { } int VoxelTree::processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, - const unsigned char* editData, int maxLength, Node* senderNode) { + const unsigned char* editData, int maxLength, const SharedNodePointer& node) { // we handle these types of "edit" packets switch (packetType) { diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 4f30293766..b5f28811fc 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -54,7 +54,7 @@ public: virtual PacketType expectedDataPacketType() const { return PacketTypeVoxelData; } virtual bool handlesEditPacketType(PacketType packetType) const; virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength, - const unsigned char* editData, int maxLength, Node* senderNode); + const unsigned char* editData, int maxLength, const SharedNodePointer& node); void processSetVoxelsBitstream(const unsigned char* bitstream, int bufferSizeBytes); /** diff --git a/libraries/voxels/src/VoxelTreeElement.cpp b/libraries/voxels/src/VoxelTreeElement.cpp index c98ed77b74..6fd3997165 100644 --- a/libraries/voxels/src/VoxelTreeElement.cpp +++ b/libraries/voxels/src/VoxelTreeElement.cpp @@ -54,6 +54,8 @@ void VoxelTreeElement::splitChildren() { for (int i = 0; i < NUMBER_OF_CHILDREN; i++) { addChildAtIndex(i)->setColor(ourColor); } + nodeColor noColor = { 0, 0, 0, 0}; + setColor(noColor); // set our own color to noColor so we are a pure non-leaf } } @@ -143,7 +145,11 @@ void VoxelTreeElement::setColor(const nodeColor& color) { memcpy(&_currentColor,&color,sizeof(nodeColor)); } _isDirty = true; - _density = 1.0f; // If color set, assume leaf, re-averaging will update density if needed. + if (color[3]) { + _density = 1.0f; // If color set, assume leaf, re-averaging will update density if needed. + } else { + _density = 0.0f; + } markWithChangedTime(); } }