diff --git a/README.md b/README.md
index bc32998113..a42bbd1626 100644
--- a/README.md
+++ b/README.md
@@ -71,8 +71,15 @@ We have successfully built on OS X 10.8, Ubuntu and a few other modern Linux
distributions. A Windows build is planned for the future, but not currently in
development.
-On a fresh Ubuntu 13.10 install, these are all the packages you need to grab and build the hifi project:
-
+On a fresh Ubuntu 13.10 install, get these requirements from Ubuntu repositories:
+
+ sudo apt-get install build-essential cmake git libcurl4-openssl-dev libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev
+
+Then [download lastest Qt packages](http://qt-project.org/downloads), untar/install to your prefered path
+and set your `QT_CMAKE_PREFIX_PATH` environment variable as described above in the CMake section. It's
+recommended to set the variable automatically on each shell instance to save this task in the future:
+
+ echo 'export QT_CMAKE_PREFIX_PATH=~/Qt5.2.0/5.2.0/gcc_64/lib/cmake' >> ~/.bashrc
Running Interface
-----
diff --git a/animation-server/src/AnimationServer.cpp b/animation-server/src/AnimationServer.cpp
index 6d493b62e5..24ea7ede9c 100644
--- a/animation-server/src/AnimationServer.cpp
+++ b/animation-server/src/AnimationServer.cpp
@@ -16,7 +16,6 @@
#include
#include
-#include
#include
#include
#include
@@ -152,7 +151,7 @@ static void renderMovingBug() {
}
// send the "erase message" first...
- PACKET_TYPE message = PACKET_TYPE_VOXEL_ERASE;
+ PacketType message = PacketTypeVoxelErase;
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_BUG, (VoxelDetail*)&details);
// Move the bug...
@@ -212,7 +211,7 @@ static void renderMovingBug() {
}
// send the "create message" ...
- message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE;
+ message = PacketTypeVoxelSetDestructive;
::voxelEditPacketSender->queueVoxelEditMessages(message, VOXELS_PER_BUG, (VoxelDetail*)&details);
}
@@ -247,7 +246,7 @@ static void sendVoxelBlinkMessage() {
detail.green = 0 * ::intensity;
detail.blue = 0 * ::intensity;
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE;
+ PacketType message = PacketTypeVoxelSetDestructive;
::voxelEditPacketSender->sendVoxelEditMessage(message, detail);
}
@@ -264,7 +263,7 @@ unsigned char onColor[3] = { 0, 255, 255 };
const float STRING_OF_LIGHTS_SIZE = 0.125f / TREE_SCALE; // approximately 1/8th meter
static void sendBlinkingStringOfLights() {
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
+ PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = STRING_OF_LIGHTS_SIZE;
static VoxelDetail details[LIGHTS_PER_SEGMENT];
@@ -370,7 +369,7 @@ const int PACKETS_PER_DANCE_FLOOR = DANCE_FLOOR_VOXELS_PER_PACKET / (DANCE_FLOOR
int danceFloorColors[DANCE_FLOOR_WIDTH][DANCE_FLOOR_LENGTH];
void sendDanceFloor() {
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
+ PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = DANCE_FLOOR_LIGHT_SIZE;
static VoxelDetail details[DANCE_FLOOR_VOXELS_PER_PACKET];
@@ -486,7 +485,7 @@ bool billboardMessage[BILLBOARD_HEIGHT][BILLBOARD_WIDTH] = {
};
static void sendBillboard() {
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
+ PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
float lightScale = BILLBOARD_LIGHT_SIZE;
static VoxelDetail details[VOXELS_PER_PACKET];
@@ -557,7 +556,7 @@ void doBuildStreet() {
return;
}
- PACKET_TYPE message = PACKET_TYPE_VOXEL_SET_DESTRUCTIVE; // we're a bully!
+ PacketType message = PacketTypeVoxelSetDestructive; // we're a bully!
static VoxelDetail details[BRICKS_PER_PACKET];
for (int z = 0; z < ROAD_LENGTH; z++) {
@@ -592,8 +591,8 @@ double start = 0;
void* animateVoxels(void* args) {
- uint64_t lastAnimateTime = 0;
- uint64_t lastProcessTime = 0;
+ quint64 lastAnimateTime = 0;
+ quint64 lastProcessTime = 0;
int processesPerAnimate = 0;
bool firstTime = true;
@@ -623,8 +622,8 @@ void* animateVoxels(void* args) {
// The while loop will be running at PROCESSING_FPS, but we only want to call these animation functions at
// ANIMATE_FPS. So we check out last animate time and only call these if we've elapsed that time.
- uint64_t now = usecTimestampNow();
- uint64_t animationElapsed = now - lastAnimateTime;
+ quint64 now = usecTimestampNow();
+ quint64 animationElapsed = now - lastAnimateTime;
int withinAnimationTarget = ANIMATE_VOXELS_INTERVAL_USECS - animationElapsed;
const int CLOSE_ENOUGH_TO_ANIMATE = 2000; // approximately 2 ms
@@ -677,7 +676,7 @@ void* animateVoxels(void* args) {
processesPerAnimate++;
}
// dynamically sleep until we need to fire off the next set of voxels
- uint64_t usecToSleep = PROCESSING_INTERVAL_USECS - (usecTimestampNow() - lastProcessTime);
+ quint64 usecToSleep = PROCESSING_INTERVAL_USECS - (usecTimestampNow() - lastProcessTime);
if (usecToSleep > PROCESSING_INTERVAL_USECS) {
usecToSleep = PROCESSING_INTERVAL_USECS;
}
@@ -695,7 +694,7 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
{
::start = usecTimestampNow();
- NodeList* nodeList = NodeList::createInstance(NODE_TYPE_ANIMATION_SERVER, ANIMATION_LISTEN_PORT);
+ NodeList* nodeList = NodeList::createInstance(NodeType::AnimationServer, ANIMATION_LISTEN_PORT);
setvbuf(stdout, NULL, _IOLBF, 0);
// Handle Local Domain testing with the --local command line
@@ -807,7 +806,7 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
pthread_create(&::animateVoxelThread, NULL, animateVoxels, NULL);
- NodeList::getInstance()->addNodeTypeToInterestSet(NODE_TYPE_VOXEL_SERVER);
+ NodeList::getInstance()->addNodeTypeToInterestSet(NodeType::VoxelServer);
QTimer* domainServerTimer = new QTimer(this);
connect(domainServerTimer, SIGNAL(timeout()), nodeList, SLOT(sendDomainServerCheckIn()));
@@ -823,25 +822,24 @@ AnimationServer::AnimationServer(int &argc, char **argv) :
void AnimationServer::readPendingDatagrams() {
NodeList* nodeList = NodeList::getInstance();
- static int receivedBytes = 0;
- static unsigned char packetData[MAX_PACKET_SIZE];
+ static QByteArray receivedPacket;
static HifiSockAddr nodeSockAddr;
// Nodes sending messages to us...
- while (nodeList->getNodeSocket().hasPendingDatagrams()
- && (receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE,
- nodeSockAddr.getAddressPointer(),
- nodeSockAddr.getPortPointer())) &&
- packetVersionMatch(packetData, nodeSockAddr)) {
-
- if (packetData[0] == PACKET_TYPE_JURISDICTION) {
- int headerBytes = numBytesForPacketHeader(packetData);
- // PACKET_TYPE_JURISDICTION, first byte is the node type...
- if (packetData[headerBytes] == NODE_TYPE_VOXEL_SERVER && ::jurisdictionListener) {
- ::jurisdictionListener->queueReceivedPacket(nodeSockAddr, packetData, receivedBytes);
+ while (nodeList->getNodeSocket().hasPendingDatagrams()) {
+ receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
+ nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
+ nodeSockAddr.getAddressPointer(), nodeSockAddr.getPortPointer());
+ if (packetVersionMatch(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);
+ }
}
+ NodeList::getInstance()->processNodeData(nodeSockAddr, receivedPacket);
}
- NodeList::getInstance()->processNodeData(nodeSockAddr, packetData, receivedBytes);
}
}
diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp
index 4f81c42046..2694bf83e2 100644
--- a/assignment-client/src/Agent.cpp
+++ b/assignment-client/src/Agent.cpp
@@ -22,37 +22,42 @@
#include "Agent.h"
-Agent::Agent(const unsigned char* dataBuffer, int numBytes) :
- ThreadedAssignment(dataBuffer, numBytes)
+Agent::Agent(const QByteArray& packet) :
+ ThreadedAssignment(packet)
{
}
void Agent::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
- if (dataByteArray[0] == PACKET_TYPE_JURISDICTION) {
- int headerBytes = numBytesForPacketHeader((const unsigned char*) dataByteArray.constData());
- // PACKET_TYPE_JURISDICTION, first byte is the node type...
+ PacketType datagramPacketType = packetTypeForPacket(dataByteArray);
+ if (datagramPacketType == PacketTypeJurisdiction) {
+ int headerBytes = numBytesForPacketHeader(dataByteArray);
+ // PacketType_JURISDICTION, first byte is the node type...
switch (dataByteArray[headerBytes]) {
- case NODE_TYPE_VOXEL_SERVER:
+ case NodeType::VoxelServer:
_scriptEngine.getVoxelsScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
- (unsigned char*) dataByteArray.data(),
- dataByteArray.size());
+ dataByteArray);
break;
- case NODE_TYPE_PARTICLE_SERVER:
+ case NodeType::ParticleServer:
_scriptEngine.getParticlesScriptingInterface()->getJurisdictionListener()->queueReceivedPacket(senderSockAddr,
- (unsigned char*) dataByteArray.data(),
- dataByteArray.size());
+ dataByteArray);
break;
}
+ } 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, (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
}
}
void Agent::run() {
NodeList* nodeList = NodeList::getInstance();
- nodeList->setOwnerType(NODE_TYPE_AGENT);
+ nodeList->setOwnerType(NodeType::Agent);
- nodeList->addSetOfNodeTypesToNodeInterestSet(QSet() << NODE_TYPE_AUDIO_MIXER << NODE_TYPE_AVATAR_MIXER);
+ nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer);
// figure out the URL for the script for this agent assignment
QString scriptURLString("http://%1:8080/assignment/%2");
@@ -88,11 +93,17 @@ void Agent::run() {
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);
+
// setup an Avatar for the script to use
AvatarData scriptedAvatar;
// give this AvatarData object to the script engine
_scriptEngine.setAvatarData(&scriptedAvatar, "Avatar");
+
+ // register ourselves to the script engine
+ _scriptEngine.registerGlobalObject("Agent", this);
_scriptEngine.setScriptContents(scriptContents);
_scriptEngine.run();
diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h
index 980885fcf3..8b2038a8b0 100644
--- a/assignment-client/src/Agent.h
+++ b/assignment-client/src/Agent.h
@@ -15,13 +15,19 @@
#include
#include
+#include
#include
#include
class Agent : public ThreadedAssignment {
Q_OBJECT
+
+ Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar)
public:
- Agent(const unsigned char* dataBuffer, int numBytes);
+ Agent(const QByteArray& packet);
+
+ void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); }
+ bool isAvatar() const { return _scriptEngine.isAvatar(); }
public slots:
void run();
@@ -32,6 +38,7 @@ signals:
void willSendVisualDataCallback();
private:
ScriptEngine _scriptEngine;
+ ParticleTree _particleTree;
};
#endif /* defined(__hifi__Agent__) */
diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp
index fc8d2deffd..b2a5555e36 100644
--- a/assignment-client/src/AssignmentClient.cpp
+++ b/assignment-client/src/AssignmentClient.cpp
@@ -52,7 +52,7 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
_requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, requestAssignmentPool);
// create a NodeList as an unassigned client
- NodeList* nodeList = NodeList::createInstance(NODE_TYPE_UNASSIGNED);
+ NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned);
const char CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION[] = "-a";
const char CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION[] = "-p";
@@ -90,7 +90,8 @@ 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(), SIGNAL(readyRead()), this, SLOT(readPendingDatagrams()));
+ connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &AssignmentClient::readPendingDatagrams,
+ Qt::QueuedConnection);
}
void AssignmentClient::sendAssignmentRequest() {
@@ -102,34 +103,33 @@ void AssignmentClient::sendAssignmentRequest() {
void AssignmentClient::readPendingDatagrams() {
NodeList* nodeList = NodeList::getInstance();
- static unsigned char packetData[1500];
- static qint64 receivedBytes = 0;
- static HifiSockAddr senderSockAddr;
+ QByteArray receivedPacket;
+ HifiSockAddr senderSockAddr;
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
+ receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
+ nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
+ senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
- if ((receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE,
- senderSockAddr.getAddressPointer(),
- senderSockAddr.getPortPointer()))
- && packetVersionMatch(packetData, senderSockAddr)) {
-
+ if (packetVersionMatch(receivedPacket)) {
if (_currentAssignment) {
// have the threaded current assignment handle this datagram
QMetaObject::invokeMethod(_currentAssignment, "processDatagram", Qt::QueuedConnection,
- Q_ARG(QByteArray, QByteArray((char*) packetData, receivedBytes)),
+ Q_ARG(QByteArray, receivedPacket),
Q_ARG(HifiSockAddr, senderSockAddr));
- } else if (packetData[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT || packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT) {
+ } else if (packetTypeForPacket(receivedPacket) == PacketTypeCreateAssignment) {
if (_currentAssignment) {
qDebug() << "Dropping received assignment since we are currently running one.";
} else {
// construct the deployed assignment from the packet data
- _currentAssignment = AssignmentFactory::unpackAssignment(packetData, receivedBytes);
+ _currentAssignment = AssignmentFactory::unpackAssignment(receivedPacket);
- qDebug() << "Received an assignment -" << *_currentAssignment;
-
- // switch our nodelist domain IP and port to whoever sent us the assignment
- if (packetData[0] == PACKET_TYPE_CREATE_ASSIGNMENT) {
+ 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());
@@ -153,12 +153,12 @@ void AssignmentClient::readPendingDatagrams() {
// Starts an event loop, and emits workerThread->started()
workerThread->start();
} else {
- qDebug("Received a bad destination socket for assignment.");
+ qDebug() << "Received an assignment that could not be unpacked. Re-requesting.";
}
}
} else {
// have the NodeList attempt to handle it
- nodeList->processNodeData(senderSockAddr, packetData, receivedBytes);
+ nodeList->processNodeData(senderSockAddr, receivedPacket);
}
}
}
@@ -175,6 +175,6 @@ void AssignmentClient::assignmentCompleted() {
NodeList* nodeList = NodeList::getInstance();
// reset our NodeList by switching back to unassigned and clearing the list
- nodeList->setOwnerType(NODE_TYPE_UNASSIGNED);
+ nodeList->setOwnerType(NodeType::Unassigned);
nodeList->reset();
}
diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp
index b32a909bfe..5bf0417f22 100644
--- a/assignment-client/src/AssignmentFactory.cpp
+++ b/assignment-client/src/AssignmentFactory.cpp
@@ -18,25 +18,28 @@
#include "avatars/AvatarMixer.h"
#include "metavoxels/MetavoxelServer.h"
-ThreadedAssignment* AssignmentFactory::unpackAssignment(const unsigned char* dataBuffer, int numBytes) {
- int headerBytes = numBytesForPacketHeader(dataBuffer);
+ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet) {
+ QDataStream packetStream(packet);
+ packetStream.skipRawData(numBytesForPacketHeader(packet));
+
+ quint8 packedType;
+ packetStream >> packedType;
- Assignment::Type assignmentType = Assignment::AllTypes;
- memcpy(&assignmentType, dataBuffer + headerBytes, sizeof(Assignment::Type));
+ Assignment::Type unpackedType = (Assignment::Type) packedType;
- switch (assignmentType) {
+ switch (unpackedType) {
case Assignment::AudioMixerType:
- return new AudioMixer(dataBuffer, numBytes);
+ return new AudioMixer(packet);
case Assignment::AvatarMixerType:
- return new AvatarMixer(dataBuffer, numBytes);
+ return new AvatarMixer(packet);
case Assignment::AgentType:
- return new Agent(dataBuffer, numBytes);
+ return new Agent(packet);
case Assignment::VoxelServerType:
- return new VoxelServer(dataBuffer, numBytes);
+ return new VoxelServer(packet);
case Assignment::ParticleServerType:
- return new ParticleServer(dataBuffer, numBytes);
+ return new ParticleServer(packet);
case Assignment::MetavoxelServerType:
- return new MetavoxelServer(dataBuffer, numBytes);
+ return new MetavoxelServer(packet);
default:
return NULL;
}
diff --git a/assignment-client/src/AssignmentFactory.h b/assignment-client/src/AssignmentFactory.h
index 4605d961ec..9eff29a468 100644
--- a/assignment-client/src/AssignmentFactory.h
+++ b/assignment-client/src/AssignmentFactory.h
@@ -13,7 +13,7 @@
class AssignmentFactory {
public:
- static ThreadedAssignment* unpackAssignment(const unsigned char* dataBuffer, int numBytes);
+ static ThreadedAssignment* unpackAssignment(const QByteArray& packet);
};
#endif /* defined(__hifi__AssignmentFactory__) */
diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp
index 705a877a00..90e2810f27 100644
--- a/assignment-client/src/audio/AudioMixer.cpp
+++ b/assignment-client/src/audio/AudioMixer.cpp
@@ -37,7 +37,6 @@
#include
#include
#include
-#include
#include
#include
#include
@@ -61,8 +60,8 @@ void attachNewBufferToNode(Node *newNode) {
}
}
-AudioMixer::AudioMixer(const unsigned char* dataBuffer, int numBytes) :
- ThreadedAssignment(dataBuffer, numBytes)
+AudioMixer::AudioMixer(const QByteArray& packet) :
+ ThreadedAssignment(packet)
{
}
@@ -210,18 +209,19 @@ 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
- if (dataByteArray[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO
- || dataByteArray[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO
- || dataByteArray[0] == PACKET_TYPE_INJECT_AUDIO) {
- QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader((unsigned char*) dataByteArray.data()),
- NUM_BYTES_RFC4122_UUID));
+ 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, (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ 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
@@ -231,17 +231,17 @@ void AudioMixer::processDatagram(const QByteArray& dataByteArray, const HifiSock
}
} else {
// let processNodeData handle it.
- NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
}
}
void AudioMixer::run() {
- commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NODE_TYPE_AUDIO_MIXER);
+ commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer);
NodeList* nodeList = NodeList::getInstance();
- nodeList->addNodeTypeToInterestSet(NODE_TYPE_AGENT);
+ nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->linkedDataCreateCallback = attachNewBufferToNode;
@@ -250,14 +250,14 @@ void AudioMixer::run() {
gettimeofday(&startTime, NULL);
- int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MIXED_AUDIO);
+ int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio);
// note: Visual Studio 2010 doesn't support variable sized local arrays
#ifdef _WIN32
unsigned char clientPacket[MAX_PACKET_SIZE];
#else
unsigned char clientPacket[NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesPacketHeader];
#endif
- populateTypeAndVersion(clientPacket, PACKET_TYPE_MIXED_AUDIO);
+ populatePacketHeader(reinterpret_cast(clientPacket), PacketTypeMixedAudio);
while (!_isFinished) {
@@ -274,7 +274,7 @@ void AudioMixer::run() {
}
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getType() == NODE_TYPE_AGENT && node->getActiveSocket() && node->getLinkedData()
+ if (node->getType() == NodeType::Agent && node->getActiveSocket() && node->getLinkedData()
&& ((AudioMixerClientData*) node->getLinkedData())->getAvatarAudioRingBuffer()) {
prepareMixForListeningNode(node.data());
diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h
index 7326e1a161..2d42a6b629 100644
--- a/assignment-client/src/audio/AudioMixer.h
+++ b/assignment-client/src/audio/AudioMixer.h
@@ -20,7 +20,7 @@ class AvatarAudioRingBuffer;
class AudioMixer : public ThreadedAssignment {
Q_OBJECT
public:
- AudioMixer(const unsigned char* dataBuffer, int numBytes);
+ AudioMixer(const QByteArray& packet);
public slots:
/// threaded run of assignment
void run();
diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp
index 0838de4f45..a41889e77c 100644
--- a/assignment-client/src/audio/AudioMixerClientData.cpp
+++ b/assignment-client/src/audio/AudioMixerClientData.cpp
@@ -31,9 +31,10 @@ AvatarAudioRingBuffer* AudioMixerClientData::getAvatarAudioRingBuffer() const {
return NULL;
}
-int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
- if (packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO
- || packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO) {
+int AudioMixerClientData::parseData(const QByteArray& packet) {
+ PacketType packetType = packetTypeForPacket(packet);
+ if (packetType == PacketTypeMicrophoneAudioWithEcho
+ || packetType == PacketTypeMicrophoneAudioNoEcho) {
// grab the AvatarAudioRingBuffer from the vector (or create it if it doesn't exist)
AvatarAudioRingBuffer* avatarRingBuffer = getAvatarAudioRingBuffer();
@@ -45,14 +46,12 @@ int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
}
// ask the AvatarAudioRingBuffer instance to parse the data
- avatarRingBuffer->parseData(packetData, numBytes);
+ avatarRingBuffer->parseData(packet);
} else {
// this is injected audio
// grab the stream identifier for this injected audio
- QByteArray rfcUUID = QByteArray((char*) packetData + numBytesForPacketHeader(packetData) + NUM_BYTES_RFC4122_UUID,
- NUM_BYTES_RFC4122_UUID);
- QUuid streamIdentifier = QUuid::fromRfc4122(rfcUUID);
+ QUuid streamIdentifier = QUuid::fromRfc4122(packet.mid(numBytesForPacketHeader(packet), NUM_BYTES_RFC4122_UUID));
InjectedAudioRingBuffer* matchingInjectedRingBuffer = NULL;
@@ -69,7 +68,7 @@ int AudioMixerClientData::parseData(unsigned char* packetData, int numBytes) {
_ringBuffers.push_back(matchingInjectedRingBuffer);
}
- matchingInjectedRingBuffer->parseData(packetData, numBytes);
+ matchingInjectedRingBuffer->parseData(packet);
}
return 0;
diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h
index 05cfa51e5f..8031dfec3e 100644
--- a/assignment-client/src/audio/AudioMixerClientData.h
+++ b/assignment-client/src/audio/AudioMixerClientData.h
@@ -23,7 +23,7 @@ public:
const std::vector getRingBuffers() const { return _ringBuffers; }
AvatarAudioRingBuffer* getAvatarAudioRingBuffer() const;
- int parseData(unsigned char* packetData, int numBytes);
+ int parseData(const QByteArray& packet);
void checkBuffersBeforeFrameSend(int jitterBufferLengthSamples);
void pushBuffersAfterFrameSend();
private:
diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
index 64d71d9836..4b1907efb8 100644
--- a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
+++ b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp
@@ -15,7 +15,7 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer() :
}
-int AvatarAudioRingBuffer::parseData(unsigned char* sourceBuffer, int numBytes) {
- _shouldLoopbackForNode = (sourceBuffer[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO);
- return PositionalAudioRingBuffer::parseData(sourceBuffer, numBytes);
+int AvatarAudioRingBuffer::parseData(const QByteArray& packet) {
+ _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho);
+ return PositionalAudioRingBuffer::parseData(packet);
}
\ No newline at end of file
diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.h b/assignment-client/src/audio/AvatarAudioRingBuffer.h
index 15542383fb..0d4f28467b 100644
--- a/assignment-client/src/audio/AvatarAudioRingBuffer.h
+++ b/assignment-client/src/audio/AvatarAudioRingBuffer.h
@@ -17,7 +17,7 @@ class AvatarAudioRingBuffer : public PositionalAudioRingBuffer {
public:
AvatarAudioRingBuffer();
- int parseData(unsigned char* sourceBuffer, int numBytes);
+ int parseData(const QByteArray& packet);
private:
// disallow copying of AvatarAudioRingBuffer objects
AvatarAudioRingBuffer(const AvatarAudioRingBuffer&);
diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp
index 64e93fd751..358e507fc4 100644
--- a/assignment-client/src/avatars/AvatarMixer.cpp
+++ b/assignment-client/src/avatars/AvatarMixer.cpp
@@ -27,24 +27,13 @@ const char AVATAR_MIXER_LOGGING_NAME[] = "avatar-mixer";
const unsigned int AVATAR_DATA_SEND_INTERVAL_USECS = (1 / 60.0) * 1000 * 1000;
-AvatarMixer::AvatarMixer(const unsigned char* dataBuffer, int numBytes) :
- ThreadedAssignment(dataBuffer, numBytes)
+AvatarMixer::AvatarMixer(const QByteArray& packet) :
+ ThreadedAssignment(packet)
{
// make sure we hear about node kills so we can tell the other nodes
connect(NodeList::getInstance(), &NodeList::nodeKilled, this, &AvatarMixer::nodeKilled);
}
-unsigned char* addNodeToBroadcastPacket(unsigned char *currentPosition, Node *nodeToAdd) {
- QByteArray rfcUUID = nodeToAdd->getUUID().toRfc4122();
- memcpy(currentPosition, rfcUUID.constData(), rfcUUID.size());
- currentPosition += rfcUUID.size();
-
- AvatarData *nodeData = (AvatarData *)nodeToAdd->getLinkedData();
- currentPosition += nodeData->getBroadcastData(currentPosition);
-
- return currentPosition;
-}
-
void attachAvatarDataToNode(Node* newNode) {
if (newNode->getLinkedData() == NULL) {
newNode->setLinkedData(new AvatarData());
@@ -59,58 +48,50 @@ void attachAvatarDataToNode(Node* newNode) {
// determine which avatars are included in the packet stream
// 4) we should optimize the avatar data format to be more compact (100 bytes is pretty wasteful).
void broadcastAvatarData() {
- static unsigned char broadcastPacket[MAX_PACKET_SIZE];
- static unsigned char avatarDataBuffer[MAX_PACKET_SIZE];
- int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_BULK_AVATAR_DATA);
- unsigned char* currentBufferPosition = broadcastPacket + numHeaderBytes;
- int packetLength = currentBufferPosition - broadcastPacket;
+ static QByteArray mixedAvatarByteArray;
+
+ int numPacketHeaderBytes = populatePacketHeader(mixedAvatarByteArray, PacketTypeBulkAvatarData);
+
int packetsSent = 0;
NodeList* nodeList = NodeList::getInstance();
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getLinkedData() && node->getType() == NODE_TYPE_AGENT && node->getActiveSocket()) {
+ if (node->getLinkedData() && node->getType() == NodeType::Agent && node->getActiveSocket()) {
// reset packet pointers for this node
- currentBufferPosition = broadcastPacket + numHeaderBytes;
- packetLength = currentBufferPosition - broadcastPacket;
+ mixedAvatarByteArray.resize(numPacketHeaderBytes);
// this is an AGENT we have received head data from
// send back a packet with other active node data to this node
foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) {
if (otherNode->getLinkedData() && otherNode->getUUID() != node->getUUID()) {
- unsigned char* avatarDataEndpoint = addNodeToBroadcastPacket((unsigned char*)&avatarDataBuffer[0],
- otherNode.data());
- int avatarDataLength = avatarDataEndpoint - (unsigned char*)&avatarDataBuffer;
+ QByteArray avatarByteArray;
+ avatarByteArray.append(otherNode->getUUID().toRfc4122());
- if (avatarDataLength + packetLength <= MAX_PACKET_SIZE) {
- memcpy(currentBufferPosition, &avatarDataBuffer[0], avatarDataLength);
- packetLength += avatarDataLength;
- currentBufferPosition += avatarDataLength;
- } else {
+ AvatarData* nodeData = (AvatarData*) 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((char*) broadcastPacket,
- currentBufferPosition - broadcastPacket,
+ nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
// reset the packet
- currentBufferPosition = broadcastPacket + numHeaderBytes;
- packetLength = currentBufferPosition - broadcastPacket;
-
- // copy the avatar that didn't fit into the next packet
- memcpy(currentBufferPosition, &avatarDataBuffer[0], avatarDataLength);
- packetLength += avatarDataLength;
- currentBufferPosition += avatarDataLength;
+ mixedAvatarByteArray.resize(numPacketHeaderBytes);
}
+
+ // copy the avatar into the mixedAvatarByteArray packet
+ mixedAvatarByteArray.append(avatarByteArray);
}
}
packetsSent++;
//printf("packetsSent=%d packetLength=%d\n", packetsSent, packetLength);
- nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket, currentBufferPosition - broadcastPacket,
+ nodeList->getNodeSocket().writeDatagram(mixedAvatarByteArray,
node->getActiveSocket()->getAddress(),
node->getActiveSocket()->getPort());
}
@@ -118,18 +99,15 @@ void broadcastAvatarData() {
}
void AvatarMixer::nodeKilled(SharedNodePointer killedNode) {
- if (killedNode->getType() == NODE_TYPE_AGENT
+ if (killedNode->getType() == NodeType::Agent
&& killedNode->getLinkedData()) {
// this was an avatar we were sending to other people
// send a kill packet for it to our other nodes
- unsigned char packetData[MAX_PACKET_SIZE];
- int numHeaderBytes = populateTypeAndVersion(packetData, PACKET_TYPE_KILL_AVATAR);
+ QByteArray killPacket = byteArrayWithPopluatedHeader(PacketTypeKillAvatar);
+ killPacket += killedNode->getUUID().toRfc4122();
- QByteArray rfcUUID = killedNode->getUUID().toRfc4122();
- memcpy(packetData + numHeaderBytes, rfcUUID.constData(), rfcUUID.size());
-
- NodeList::getInstance()->broadcastToNodes(packetData, numHeaderBytes + NUM_BYTES_RFC4122_UUID,
- QSet() << NODE_TYPE_AGENT);
+ NodeList::getInstance()->broadcastToNodes(killPacket,
+ NodeSet() << NodeType::Agent);
}
}
@@ -137,39 +115,38 @@ void AvatarMixer::processDatagram(const QByteArray& dataByteArray, const HifiSoc
NodeList* nodeList = NodeList::getInstance();
- switch (dataByteArray[0]) {
- case PACKET_TYPE_HEAD_DATA: {
- QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader((unsigned char*) dataByteArray.data()),
- NUM_BYTES_RFC4122_UUID));
+ 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,
- (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ nodeList->updateNodeWithData(avatarNode.data(), senderSockAddr, dataByteArray);
}
break;
}
- case PACKET_TYPE_KILL_AVATAR: {
+ case PacketTypeKillAvatar: {
nodeList->processKillNode(dataByteArray);
break;
}
default:
// hand this off to the NodeList
- nodeList->processNodeData(senderSockAddr, (unsigned char*) dataByteArray.data(), dataByteArray.size());
+ nodeList->processNodeData(senderSockAddr, dataByteArray);
break;
}
}
void AvatarMixer::run() {
- commonInit(AVATAR_MIXER_LOGGING_NAME, NODE_TYPE_AVATAR_MIXER);
+ commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
NodeList* nodeList = NodeList::getInstance();
- nodeList->addNodeTypeToInterestSet(NODE_TYPE_AGENT);
+ nodeList->addNodeTypeToInterestSet(NodeType::Agent);
nodeList->linkedDataCreateCallback = attachAvatarDataToNode;
diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h
index 2da0ed98eb..92ab1a5c53 100644
--- a/assignment-client/src/avatars/AvatarMixer.h
+++ b/assignment-client/src/avatars/AvatarMixer.h
@@ -14,7 +14,7 @@
/// Handles assignments of type AvatarMixer - distribution of avatar data to various clients
class AvatarMixer : public ThreadedAssignment {
public:
- AvatarMixer(const unsigned char* dataBuffer, int numBytes);
+ AvatarMixer(const QByteArray& packet);
public slots:
/// runs the avatar mixer
diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp
index af982abf9b..3cd81d8ac7 100644
--- a/assignment-client/src/metavoxels/MetavoxelServer.cpp
+++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp
@@ -17,22 +17,25 @@
const int SEND_INTERVAL = 50;
-MetavoxelServer::MetavoxelServer(const unsigned char* dataBuffer, int numBytes) :
- ThreadedAssignment(dataBuffer, numBytes),
- _data(new MetavoxelData()) {
+MetavoxelServer::MetavoxelServer(const QByteArray& packet) :
+ ThreadedAssignment(packet) {
_sendTimer.setSingleShot(true);
connect(&_sendTimer, SIGNAL(timeout()), SLOT(sendDeltas()));
}
+void MetavoxelServer::applyEdit(const MetavoxelEditMessage& edit) {
+ edit.apply(_data);
+}
+
void MetavoxelServer::removeSession(const QUuid& sessionId) {
- delete _sessions.take(sessionId);
+ _sessions.take(sessionId)->deleteLater();
}
const char METAVOXEL_SERVER_LOGGING_NAME[] = "metavoxel-server";
void MetavoxelServer::run() {
- commonInit(METAVOXEL_SERVER_LOGGING_NAME, NODE_TYPE_METAVOXEL_SERVER);
+ commonInit(METAVOXEL_SERVER_LOGGING_NAME, NodeType::MetavoxelServer);
_lastSend = QDateTime::currentMSecsSinceEpoch();
_sendTimer.start(SEND_INTERVAL);
@@ -40,12 +43,12 @@ void MetavoxelServer::run() {
void MetavoxelServer::processDatagram(const QByteArray& dataByteArray, const HifiSockAddr& senderSockAddr) {
switch (dataByteArray.at(0)) {
- case PACKET_TYPE_METAVOXEL_DATA:
+ case PacketTypeMetavoxelData:
processData(dataByteArray, senderSockAddr);
break;
default:
- NodeList::getInstance()->processNodeData(senderSockAddr, (unsigned char*)dataByteArray.data(), dataByteArray.size());
+ NodeList::getInstance()->processNodeData(senderSockAddr, dataByteArray);
break;
}
}
@@ -75,16 +78,18 @@ 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));
+ session = new MetavoxelSession(this, sessionID, QByteArray::fromRawData(data.constData(), headerPlusIDSize), sender);
}
session->receivedData(data, sender);
}
-MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader) :
+MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
+ const QByteArray& datagramHeader, const HifiSockAddr& sender) :
QObject(server),
_server(server),
_sessionId(sessionId),
- _sequencer(datagramHeader) {
+ _sequencer(datagramHeader),
+ _sender(sender) {
const int TIMEOUT_INTERVAL = 30 * 1000;
_timeoutTimer.setInterval(TIMEOUT_INTERVAL);
@@ -94,10 +99,13 @@ MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const QUuid& session
connect(&_sequencer, SIGNAL(readyToWrite(const QByteArray&)), SLOT(sendData(const QByteArray&)));
connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&)));
connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int)));
+ connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&)));
// insert the baseline send record
- SendRecord record = { 0, MetavoxelDataPointer(new MetavoxelData()) };
+ SendRecord record = { 0 };
_sendRecords.append(record);
+
+ qDebug() << "Opened session [sessionId=" << _sessionId << ", sender=" << _sender << "]";
}
void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr& sender) {
@@ -114,7 +122,7 @@ void MetavoxelSession::receivedData(const QByteArray& data, const HifiSockAddr&
void MetavoxelSession::sendDelta() {
Bitstream& out = _sequencer.startPacket();
out << QVariant::fromValue(MetavoxelDeltaMessage());
- writeDelta(_server->getData(), _sendRecords.first().data, out);
+ _server->getData().writeDelta(_sendRecords.first().data, out);
_sequencer.endPacket();
// record the send
@@ -134,25 +142,29 @@ void MetavoxelSession::sendData(const QByteArray& data) {
void MetavoxelSession::readPacket(Bitstream& in) {
QVariant message;
in >> message;
- handleMessage(message, in);
+ handleMessage(message);
}
void MetavoxelSession::clearSendRecordsBefore(int index) {
_sendRecords.erase(_sendRecords.begin(), _sendRecords.begin() + index + 1);
}
-void MetavoxelSession::handleMessage(const QVariant& message, Bitstream& in) {
+void MetavoxelSession::handleMessage(const QVariant& message) {
int userType = message.userType();
- if (userType == ClientStateMessage::Type) {
+ if (userType == CloseSessionMessage::Type) {
+ qDebug() << "Session closed [sessionId=" << _sessionId << ", sender=" << _sender << "]";
+ _server->removeSession(_sessionId);
+
+ } else if (userType == ClientStateMessage::Type) {
ClientStateMessage state = message.value();
_position = state.position;
- } else if (userType == MetavoxelDeltaMessage::Type) {
+ } else if (userType == MetavoxelEditMessage::Type) {
+ _server->applyEdit(message.value());
-
} else if (userType == QMetaType::QVariantList) {
foreach (const QVariant& element, message.toList()) {
- handleMessage(element, in);
+ handleMessage(element);
}
}
}
diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h
index 407c520116..f106bd3494 100644
--- a/assignment-client/src/metavoxels/MetavoxelServer.h
+++ b/assignment-client/src/metavoxels/MetavoxelServer.h
@@ -20,6 +20,7 @@
#include
#include
+class MetavoxelEditMessage;
class MetavoxelSession;
/// Maintains a shared metavoxel system, accepting change requests and broadcasting updates.
@@ -28,9 +29,11 @@ class MetavoxelServer : public ThreadedAssignment {
public:
- MetavoxelServer(const unsigned char* dataBuffer, int numBytes);
+ MetavoxelServer(const QByteArray& packet);
- const MetavoxelDataPointer& getData() const { return _data; }
+ void applyEdit(const MetavoxelEditMessage& edit);
+
+ const MetavoxelData& getData() const { return _data; }
void removeSession(const QUuid& sessionId);
@@ -51,7 +54,7 @@ private:
QHash _sessions;
- MetavoxelDataPointer _data;
+ MetavoxelData _data;
};
/// Contains the state of a single client session.
@@ -60,7 +63,8 @@ class MetavoxelSession : public QObject {
public:
- MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId, const QByteArray& datagramHeader);
+ MetavoxelSession(MetavoxelServer* server, const QUuid& sessionId,
+ const QByteArray& datagramHeader, const HifiSockAddr& sender);
void receivedData(const QByteArray& data, const HifiSockAddr& sender);
@@ -76,14 +80,14 @@ private slots:
void clearSendRecordsBefore(int index);
-private:
+ void handleMessage(const QVariant& message);
- void handleMessage(const QVariant& message, Bitstream& in);
+private:
class SendRecord {
public:
int packetNumber;
- MetavoxelDataPointer data;
+ MetavoxelData data;
};
MetavoxelServer* _server;
diff --git a/cmake/macros/AutoMTC.cmake b/cmake/macros/AutoMTC.cmake
index fca8170e64..f0c9ebc6a0 100644
--- a/cmake/macros/AutoMTC.cmake
+++ b/cmake/macros/AutoMTC.cmake
@@ -9,10 +9,12 @@ macro(AUTO_MTC TARGET ROOT_DIR)
${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES})
find_package(Qt5Core REQUIRED)
+ find_package(Qt5Script REQUIRED)
+ find_package(Qt5Widgets REQUIRED)
add_library(${TARGET}_automtc STATIC ${TARGET}_automtc.cpp)
- qt5_use_modules(${TARGET}_automtc Core)
+ qt5_use_modules(${TARGET}_automtc Core Script Widgets)
target_link_libraries(${TARGET} ${TARGET}_automtc)
diff --git a/data-server/src/DataServer.cpp b/data-server/src/DataServer.cpp
index c59749ed08..43fc52fb06 100644
--- a/data-server/src/DataServer.cpp
+++ b/data-server/src/DataServer.cpp
@@ -6,9 +6,12 @@
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
+#include
+#include
#include
#include
+#include
#include
#include "DataServer.h"
@@ -21,7 +24,8 @@ const unsigned short REDIS_PORT = 6379;
DataServer::DataServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_socket(),
- _redis(NULL)
+ _redis(NULL),
+ _uuid(QUuid::createUuid())
{
_socket.bind(QHostAddress::Any, DATA_SERVER_LISTEN_PORT);
@@ -51,66 +55,60 @@ DataServer::~DataServer() {
const int MAX_PACKET_SIZE = 1500;
void DataServer::readPendingDatagrams() {
- qint64 receivedBytes = 0;
- static unsigned char packetData[MAX_PACKET_SIZE];
-
+ QByteArray receivedPacket;
HifiSockAddr senderSockAddr;
- while (_socket.hasPendingDatagrams() &&
- (receivedBytes = _socket.readDatagram(reinterpret_cast(packetData), MAX_PACKET_SIZE,
- senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()))) {
- if ((packetData[0] == PACKET_TYPE_DATA_SERVER_PUT || packetData[0] == PACKET_TYPE_DATA_SERVER_GET) &&
- packetVersionMatch(packetData, senderSockAddr)) {
-
- int readBytes = numBytesForPacketHeader(packetData);
+ while (_socket.hasPendingDatagrams()) {
+ receivedPacket.resize(_socket.pendingDatagramSize());
+ _socket.readDatagram(receivedPacket.data(), _socket.pendingDatagramSize(),
+ senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
+
+ PacketType requestType = packetTypeForPacket(receivedPacket);
+
+ if ((requestType == PacketTypeDataServerPut || requestType == PacketTypeDataServerGet) &&
+ packetVersionMatch(receivedPacket)) {
+
+ QDataStream packetStream(receivedPacket);
+ int numReceivedHeaderBytes = numBytesForPacketHeader(receivedPacket);
+ packetStream.skipRawData(numReceivedHeaderBytes);
// pull the sequence number used for this packet
quint8 sequenceNumber = 0;
- memcpy(&sequenceNumber, packetData + readBytes, sizeof(sequenceNumber));
- readBytes += sizeof(sequenceNumber);
-
+
+ packetStream >> sequenceNumber;
+
// pull the UUID that we will need as part of the key
- QString uuidString(reinterpret_cast(packetData + readBytes));
- QUuid parsedUUID(uuidString);
+ QString userString;
+ packetStream >> userString;
+ QUuid parsedUUID(userString);
if (parsedUUID.isNull()) {
// we failed to parse a UUID, this means the user has sent us a username
- QString username(reinterpret_cast(packetData + readBytes));
- readBytes += username.size() + sizeof('\0');
-
// ask redis for the UUID for this user
- redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(username));
+ redisReply* reply = (redisReply*) redisCommand(_redis, "GET user:%s", qPrintable(userString));
if (reply->type == REDIS_REPLY_STRING) {
parsedUUID = QUuid(QString(reply->str));
}
if (!parsedUUID.isNull()) {
- qDebug() << "Found UUID" << parsedUUID << "for username" << username;
+ qDebug() << "Found UUID" << parsedUUID << "for username" << userString;
} else {
- qDebug() << "Failed UUID lookup for username" << username;
+ qDebug() << "Failed UUID lookup for username" << userString;
}
freeReplyObject(reply);
reply = NULL;
- } else {
- readBytes += uuidString.size() + sizeof('\0');
}
if (!parsedUUID.isNull()) {
- // pull the number of keys the user has sent
- unsigned char numKeys = packetData[readBytes++];
-
- if (packetData[0] == PACKET_TYPE_DATA_SERVER_PUT) {
+ if (requestType == PacketTypeDataServerPut) {
- // pull the key that specifies the data the user is putting/getting
- QString dataKey(reinterpret_cast(packetData + readBytes));
- readBytes += dataKey.size() + sizeof('\0');
+ // pull the key and value that specifies the data the user is putting/getting
+ QString dataKey, dataValue;
- // grab the string value the user wants us to put, null terminate it
- QString dataValue(reinterpret_cast(packetData + readBytes));
- readBytes += dataValue.size() + sizeof('\0');
+ packetStream >> dataKey >> dataValue;
qDebug("Sending command to redis: SET uuid:%s:%s %s",
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
@@ -122,78 +120,72 @@ void DataServer::readPendingDatagrams() {
if (reply->type == REDIS_REPLY_STATUS && strcmp("OK", reply->str) == 0) {
// if redis stored the value successfully reply back with a confirm
- // which is the sent packet with the header replaced
- packetData[0] = PACKET_TYPE_DATA_SERVER_CONFIRM;
- _socket.writeDatagram(reinterpret_cast(packetData), receivedBytes,
- senderSockAddr.getAddress(), senderSockAddr.getPort());
+ // which is a reply packet with the sequence number
+ QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerConfirm, _uuid);
+
+ replyPacket.append(sequenceNumber);
+
+ _socket.writeDatagram(replyPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
}
freeReplyObject(reply);
} else {
// setup a send packet with the returned data
// leverage the packetData sent by overwriting and appending
- int numSendPacketBytes = receivedBytes;
+ QByteArray sendPacket = byteArrayWithPopluatedHeader(PacketTypeDataServerSend, _uuid);
+ QDataStream sendPacketStream(&sendPacket, QIODevice::Append);
- packetData[0] = PACKET_TYPE_DATA_SERVER_SEND;
+ sendPacketStream << sequenceNumber;
- if (strcmp((char*) packetData + readBytes, "uuid") != 0) {
+ // pull the key list that specifies the data the user is putting/getting
+ QString keyListString;
+ packetStream >> keyListString;
+
+ if (keyListString != "uuid") {
+
+ // copy the parsed UUID
+ sendPacketStream << uuidStringWithoutCurlyBraces(parsedUUID);
const char MULTI_KEY_VALUE_SEPARATOR = '|';
- // the user has sent one or more keys - make the associated requests
- for (int j = 0; j < numKeys; j++) {
-
- // pull the key that specifies the data the user is putting/getting, null terminate it
- int numDataKeyBytes = 0;
-
- // look for the key separator or the null terminator
- while (packetData[readBytes + numDataKeyBytes] != MULTI_KEY_VALUE_SEPARATOR
- && packetData[readBytes + numDataKeyBytes] != '\0') {
- numDataKeyBytes++;
- }
-
- QString dataKey(QByteArray(reinterpret_cast(packetData + readBytes), numDataKeyBytes));
- readBytes += dataKey.size() + sizeof('\0');
-
+ // append the keyListString back to the sendPacket
+ sendPacketStream << keyListString;
+
+ QStringList keyList = keyListString.split(MULTI_KEY_VALUE_SEPARATOR);
+ QStringList valueList;
+
+ foreach (const QString& dataKey, keyList) {
qDebug("Sending command to redis: GET uuid:%s:%s",
qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
qPrintable(dataKey));
redisReply* reply = (redisReply*) redisCommand(_redis, "GET uuid:%s:%s",
- qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
- qPrintable(dataKey));
+ qPrintable(uuidStringWithoutCurlyBraces(parsedUUID)),
+ qPrintable(dataKey));
if (reply->len) {
// copy the value that redis returned
- memcpy(packetData + numSendPacketBytes, reply->str, reply->len);
- numSendPacketBytes += reply->len;
-
+ valueList << QString(reply->str);
} else {
// didn't find a value - insert a space
- packetData[numSendPacketBytes++] = ' ';
+ valueList << QChar(' ');
}
- // add the multi-value separator
- packetData[numSendPacketBytes++] = MULTI_KEY_VALUE_SEPARATOR;
-
freeReplyObject(reply);
}
- // null terminate the packet we're sending back (erases the trailing separator)
- packetData[(numSendPacketBytes - 1)] = '\0';
+ // append the value QStringList using the right separator
+ sendPacketStream << valueList.join(MULTI_KEY_VALUE_SEPARATOR);
} else {
- // user is asking for a UUID matching username, copy the UUID we found
- QString uuidString = uuidStringWithoutCurlyBraces(parsedUUID);
- memcpy(packetData + numSendPacketBytes, qPrintable(uuidString), uuidString.size() + sizeof('\0'));
- numSendPacketBytes += uuidString.size() + sizeof('\0');
+ // user was asking for their UUID
+ sendPacketStream << userString;
+ sendPacketStream << QString("uuid");
+ sendPacketStream << uuidStringWithoutCurlyBraces(parsedUUID);
}
// reply back with the send packet
- _socket.writeDatagram(reinterpret_cast(packetData), numSendPacketBytes,
- senderSockAddr.getAddress(), senderSockAddr.getPort());
-
-
+ _socket.writeDatagram(sendPacket, senderSockAddr.getAddress(), senderSockAddr.getPort());
}
}
}
}
-}
\ No newline at end of file
+}
diff --git a/data-server/src/DataServer.h b/data-server/src/DataServer.h
index 097f6a6533..392387f4d8 100644
--- a/data-server/src/DataServer.h
+++ b/data-server/src/DataServer.h
@@ -10,6 +10,7 @@
#define __hifi__DataServer__
#include
+#include
#include
#include
@@ -22,6 +23,7 @@ public:
private:
QUdpSocket _socket;
redisContext* _redis;
+ QUuid _uuid;
private slots:
void readPendingDatagrams();
};
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index 0ee7cca467..37626e8a43 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -8,9 +8,11 @@
#include
+#include
#include
#include
#include
+#include
#include
#include
@@ -27,65 +29,37 @@ const char* VOXEL_SERVER_CONFIG = "voxelServerConfig";
const char* PARTICLE_SERVER_CONFIG = "particleServerConfig";
const char* METAVOXEL_SERVER_CONFIG = "metavoxelServerConfig";
-void signalhandler(int sig){
- if (sig == SIGINT) {
- qApp->quit();
- }
-}
-
const quint16 DOMAIN_SERVER_HTTP_PORT = 8080;
DomainServer::DomainServer(int argc, char* argv[]) :
QCoreApplication(argc, argv),
_HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this),
- _assignmentQueueMutex(),
+ _staticAssignmentHash(),
_assignmentQueue(),
- _staticAssignmentFile(QString("%1/config.ds").arg(QCoreApplication::applicationDirPath())),
- _staticAssignmentFileData(NULL),
- _voxelServerConfig(NULL),
- _metavoxelServerConfig(NULL),
_hasCompletedRestartHold(false)
{
- signal(SIGINT, signalhandler);
-
const char CUSTOM_PORT_OPTION[] = "-p";
const char* customPortString = getCmdOption(argc, (const char**) argv, CUSTOM_PORT_OPTION);
unsigned short domainServerPort = customPortString ? atoi(customPortString) : DEFAULT_DOMAIN_SERVER_PORT;
-
- const char CONFIG_FILE_OPTION[] = "-c";
- const char* configFilePath = getCmdOption(argc, (const char**) argv, CONFIG_FILE_OPTION);
-
- if (!readConfigFile(configFilePath)) {
- QByteArray voxelConfigOption = QString("--%1").arg(VOXEL_SERVER_CONFIG).toLocal8Bit();
- _voxelServerConfig = getCmdOption(argc, (const char**) argv, voxelConfigOption.constData());
-
- QByteArray particleConfigOption = QString("--%1").arg(PARTICLE_SERVER_CONFIG).toLocal8Bit();
- _particleServerConfig = getCmdOption(argc, (const char**) argv, particleConfigOption.constData());
-
- QByteArray metavoxelConfigOption = QString("--%1").arg(METAVOXEL_SERVER_CONFIG).toLocal8Bit();
- _metavoxelServerConfig = getCmdOption(argc, (const char**) argv, metavoxelConfigOption.constData());
+
+ QStringList argumentList = arguments();
+ int argumentIndex = 0;
+
+ QSet parsedTypes(QSet() << Assignment::AgentType);
+ parseCommandLineTypeConfigs(argumentList, parsedTypes);
+
+ const QString CONFIG_FILE_OPTION = "--configFile";
+ if ((argumentIndex = argumentList.indexOf(CONFIG_FILE_OPTION)) != -1) {
+ QString configFilePath = argumentList.value(argumentIndex + 1);
+ readConfigFile(configFilePath, parsedTypes);
}
+
+ populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
- NodeList* nodeList = NodeList::createInstance(NODE_TYPE_DOMAIN, domainServerPort);
+ NodeList* nodeList = NodeList::createInstance(NodeType::DomainServer, domainServerPort);
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), this, SLOT(nodeKilled(SharedNodePointer)));
- if (!_staticAssignmentFile.exists() || _voxelServerConfig) {
-
- if (_voxelServerConfig) {
- // we have a new VS config, clear the existing file to start fresh
- _staticAssignmentFile.remove();
- }
-
- prepopulateStaticAssignmentFile();
- }
-
- _staticAssignmentFile.open(QIODevice::ReadWrite);
-
- _staticAssignmentFileData = _staticAssignmentFile.map(0, _staticAssignmentFile.size());
-
- _staticAssignments = (Assignment*) _staticAssignmentFileData;
-
QTimer* silentNodeTimer = new QTimer(this);
connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes()));
silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000);
@@ -94,47 +68,184 @@ DomainServer::DomainServer(int argc, char* argv[]) :
// fire a single shot timer to add static assignments back into the queue after a restart
QTimer::singleShot(RESTART_HOLD_TIME_MSECS, this, SLOT(addStaticAssignmentsBackToQueueAfterRestart()));
+}
- connect(this, SIGNAL(aboutToQuit()), SLOT(cleanup()));
+void DomainServer::parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes) {
+ // check for configs from the command line, these take precedence
+ const QString CONFIG_TYPE_OPTION = "--configType";
+ int clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION);
+
+ // enumerate all CL config overrides and parse them to files
+ while (clConfigIndex != -1) {
+ int clConfigType = argumentList.value(clConfigIndex + 1).toInt();
+ if (clConfigType < Assignment::AllTypes && !excludedTypes.contains((Assignment::Type) clConfigIndex)) {
+ Assignment::Type assignmentType = (Assignment::Type) clConfigType;
+ createStaticAssignmentsForTypeGivenConfigString((Assignment::Type) assignmentType,
+ argumentList.value(clConfigIndex + 2));
+ excludedTypes.insert(assignmentType);
+ }
+
+ clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION, clConfigIndex + 1);
+ }
+}
+
+// Attempts to read configuration from specified path
+// returns true on success, false otherwise
+void DomainServer::readConfigFile(const QString& path, QSet& excludedTypes) {
+ if (path.isEmpty()) {
+ // config file not specified
+ return;
+ }
+
+ if (!QFile::exists(path)) {
+ qWarning("Specified configuration file does not exist!");
+ return;
+ }
+
+ QFile configFile(path);
+ if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
+ qWarning("Can't open specified configuration file!");
+ return;
+ } else {
+ qDebug() << "Reading configuration from" << path;
+ }
+
+ QTextStream configStream(&configFile);
+ QByteArray configStringByteArray = configStream.readAll().toUtf8();
+ QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object();
+ configFile.close();
+
+ QSet appendedExcludedTypes = excludedTypes;
+
+ foreach (const QString& rootStringValue, configDocObject.keys()) {
+ int possibleConfigType = rootStringValue.toInt();
+
+ if (possibleConfigType < Assignment::AllTypes
+ && !excludedTypes.contains((Assignment::Type) possibleConfigType)) {
+ // this is an appropriate config type and isn't already in our excluded types
+ // we are good to parse it
+ Assignment::Type assignmentType = (Assignment::Type) possibleConfigType;
+ QString configString = readServerAssignmentConfig(configDocObject, rootStringValue);
+ createStaticAssignmentsForTypeGivenConfigString(assignmentType, configString);
+
+ excludedTypes.insert(assignmentType);
+ }
+ }
+}
+
+// find assignment configurations on the specified node name and json object
+// returns a string in the form of its equivalent cmd line params
+QString DomainServer::readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName) {
+ QJsonArray nodeArray = jsonObject[nodeName].toArray();
+
+ QStringList serverConfig;
+ foreach (const QJsonValue& childValue, nodeArray) {
+ QString cmdParams;
+ QJsonObject childObject = childValue.toObject();
+ QStringList keys = childObject.keys();
+ for (int i = 0; i < keys.size(); i++) {
+ QString key = keys[i];
+ QString value = childObject[key].toString();
+ // both cmd line params and json keys are the same
+ cmdParams += QString("--%1 %2 ").arg(key, value);
+ }
+ serverConfig << cmdParams;
+ }
+
+ // according to split() calls from DomainServer::prepopulateStaticAssignmentFile
+ // we shold simply join them with semicolons
+ return serverConfig.join(';');
+}
+
+void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) {
+ qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash.";
+ _staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment));
+}
+
+void DomainServer::createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString) {
+ // we have a string for config for this type
+ qDebug() << "Parsing command line config for assignment type" << type;
+
+ QStringList multiConfigList = configString.split(";", QString::SkipEmptyParts);
+
+ const QString ASSIGNMENT_CONFIG_POOL_REGEX = "--pool\\s*(\\w+)";
+ QRegExp poolRegex(ASSIGNMENT_CONFIG_POOL_REGEX);
+
+ // read each config to a payload for this type of assignment
+ for (int i = 0; i < multiConfigList.size(); i++) {
+ QString config = multiConfigList.at(i);
+
+ // check the config string for a pool
+ QString assignmentPool;
+
+ int poolIndex = poolRegex.indexIn(config);
+
+ if (poolIndex != -1) {
+ assignmentPool = poolRegex.cap(1);
+
+ // remove the pool from the config string, the assigned node doesn't need it
+ config.remove(poolIndex, poolRegex.matchedLength());
+ }
+
+ qDebug("Type %d config[%d] = %s", type, i, config.toLocal8Bit().constData());
+
+ Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool);
+
+ configAssignment->setPayload(config.toUtf8());
+
+ addStaticAssignmentToAssignmentHash(configAssignment);
+ }
+}
+
+void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes) {
+ // enumerate over all assignment types and see if we've already excluded it
+ for (int defaultedType = Assignment::AudioMixerType; defaultedType != Assignment::AllTypes; defaultedType++) {
+ if (!excludedTypes.contains((Assignment::Type) defaultedType)) {
+ // type has not been set from a command line or config file config, use the default
+ // by clearing whatever exists and writing a single default assignment with no payload
+ Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType);
+ addStaticAssignmentToAssignmentHash(newAssignment);
+ }
+ }
}
void DomainServer::readAvailableDatagrams() {
NodeList* nodeList = NodeList::getInstance();
HifiSockAddr senderSockAddr, nodePublicAddress, nodeLocalAddress;
-
- static unsigned char packetData[MAX_PACKET_SIZE];
-
- static unsigned char broadcastPacket[MAX_PACKET_SIZE];
-
- static unsigned char* currentBufferPos;
- static unsigned char* startPointer;
-
- int receivedBytes = 0;
+
+ static QByteArray broadcastPacket = byteArrayWithPopluatedHeader(PacketTypeDomainList);
+ static int numBroadcastPacketHeaderBytes = broadcastPacket.size();
+
+ static QByteArray assignmentPacket = byteArrayWithPopluatedHeader(PacketTypeCreateAssignment);
+ static int numAssignmentPacketHeaderBytes = assignmentPacket.size();
+
+ QByteArray receivedPacket;
+ NodeType_t nodeType;
+ QUuid nodeUUID;
while (nodeList->getNodeSocket().hasPendingDatagrams()) {
- if ((receivedBytes = nodeList->getNodeSocket().readDatagram((char*) packetData, MAX_PACKET_SIZE,
- senderSockAddr.getAddressPointer(),
- senderSockAddr.getPortPointer()))
- && packetVersionMatch((unsigned char*) packetData, senderSockAddr)) {
- if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY || packetData[0] == PACKET_TYPE_DOMAIN_LIST_REQUEST) {
+ receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
+ nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(),
+ senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
+
+ if (packetVersionMatch(receivedPacket)) {
+ PacketType requestType = packetTypeForPacket(receivedPacket);
+ if (requestType == PacketTypeDomainListRequest) {
+
// this is an RFD or domain list request packet, and there is a version match
-
- int numBytesSenderHeader = numBytesForPacketHeader((unsigned char*) packetData);
-
- NODE_TYPE nodeType = *(packetData + numBytesSenderHeader);
-
- int packetIndex = numBytesSenderHeader + sizeof(NODE_TYPE);
- QUuid nodeUUID = QUuid::fromRfc4122(QByteArray(((char*) packetData + packetIndex), NUM_BYTES_RFC4122_UUID));
- packetIndex += NUM_BYTES_RFC4122_UUID;
-
- int numBytesPrivateSocket = HifiSockAddr::unpackSockAddr(packetData + packetIndex, nodePublicAddress);
- packetIndex += numBytesPrivateSocket;
-
+ QDataStream packetStream(receivedPacket);
+ packetStream.skipRawData(numBytesForPacketHeader(receivedPacket));
+
+ deconstructPacketHeader(receivedPacket, nodeUUID);
+
+ packetStream >> nodeType;
+ packetStream >> nodePublicAddress >> nodeLocalAddress;
+
if (nodePublicAddress.getAddress().isNull()) {
// this node wants to use us its STUN server
// so set the node public address to whatever we perceive the public address to be
-
+
// if the sender is on our box then leave its public address to 0 so that
// other users attempt to reach it on the same address they have for the domain-server
if (senderSockAddr.getAddress().isLoopback()) {
@@ -143,105 +254,90 @@ void DomainServer::readAvailableDatagrams() {
nodePublicAddress.setAddress(senderSockAddr.getAddress());
}
}
-
- int numBytesPublicSocket = HifiSockAddr::unpackSockAddr(packetData + packetIndex, nodeLocalAddress);
- packetIndex += numBytesPublicSocket;
-
- const char STATICALLY_ASSIGNED_NODES[] = {
- NODE_TYPE_AUDIO_MIXER,
- NODE_TYPE_AVATAR_MIXER,
- NODE_TYPE_VOXEL_SERVER,
- NODE_TYPE_METAVOXEL_SERVER
- };
-
- Assignment* matchingStaticAssignment = NULL;
-
- if (memchr(STATICALLY_ASSIGNED_NODES, nodeType, sizeof(STATICALLY_ASSIGNED_NODES)) == NULL
- || ((matchingStaticAssignment = matchingStaticAssignmentForCheckIn(nodeUUID, nodeType))
- || checkInWithUUIDMatchesExistingNode(nodePublicAddress,
- nodeLocalAddress,
- nodeUUID)))
+
+ 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))
{
SharedNodePointer checkInNode = nodeList->addOrUpdateNode(nodeUUID,
nodeType,
nodePublicAddress,
nodeLocalAddress);
-
+
+ // resize our broadcast packet in preparation to set it up again
+ broadcastPacket.resize(numBroadcastPacketHeaderBytes);
+
if (matchingStaticAssignment) {
// this was a newly added node with a matching static assignment
-
+
+ // remove the matching assignment from the assignment queue so we don't take the next check in
+ // (if it exists)
if (_hasCompletedRestartHold) {
- // remove the matching assignment from the assignment queue so we don't take the next check in
- removeAssignmentFromQueue(matchingStaticAssignment);
+ removeMatchingAssignmentFromQueue(matchingStaticAssignment);
}
-
- // set the linked data for this node to a copy of the matching assignment
- // so we can re-queue it should the node die
- Assignment* nodeCopyOfMatchingAssignment = new Assignment(*matchingStaticAssignment);
-
- checkInNode->setLinkedData(nodeCopyOfMatchingAssignment);
}
-
- int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_DOMAIN);
-
- currentBufferPos = broadcastPacket + numHeaderBytes;
- startPointer = currentBufferPos;
-
- unsigned char* nodeTypesOfInterest = packetData + packetIndex + sizeof(unsigned char);
- int numInterestTypes = *(nodeTypesOfInterest - 1);
-
+
+ quint8 numInterestTypes = 0;
+ packetStream >> numInterestTypes;
+
+ NodeType_t* nodeTypesOfInterest = reinterpret_cast(receivedPacket.data()
+ + packetStream.device()->pos());
+
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)) {
-
+
// don't send avatar nodes to other avatars, that will come from avatar mixer
- if (nodeType != NODE_TYPE_AGENT || node->getType() != NODE_TYPE_AGENT) {
- currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, node.data());
- }
-
+ broadcastDataStream << *node.data();
}
}
}
-
+
// update last receive to now
- uint64_t timeNow = usecTimestampNow();
+ quint64 timeNow = usecTimestampNow();
checkInNode->setLastHeardMicrostamp(timeNow);
-
+
// send the constructed list back to this node
- nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket,
- (currentBufferPos - startPointer) + numHeaderBytes,
+ nodeList->getNodeSocket().writeDatagram(broadcastPacket,
senderSockAddr.getAddress(), senderSockAddr.getPort());
}
- } else if (packetData[0] == PACKET_TYPE_REQUEST_ASSIGNMENT) {
-
- if (_assignmentQueue.size() > 0) {
- // construct the requested assignment from the packet data
- Assignment requestAssignment(packetData, receivedBytes);
-
- qDebug("Received a request for assignment type %i from %s.",
- requestAssignment.getType(), qPrintable(senderSockAddr.getAddress().toString()));
-
- Assignment* assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
-
- if (assignmentToDeploy) {
-
- // give this assignment out, either the type matches or the requestor said they will take any
- int numHeaderBytes = populateTypeAndVersion(broadcastPacket, PACKET_TYPE_CREATE_ASSIGNMENT);
- int numAssignmentBytes = assignmentToDeploy->packToBuffer(broadcastPacket + numHeaderBytes);
-
- nodeList->getNodeSocket().writeDatagram((char*) broadcastPacket, numHeaderBytes + numAssignmentBytes,
- senderSockAddr.getAddress(), senderSockAddr.getPort());
-
- if (assignmentToDeploy->getNumberOfInstances() == 0) {
- // there are no more instances of this script to send out, delete it
- delete assignmentToDeploy;
- }
- }
-
+ } else if (requestType == PacketTypeRequestAssignment) {
+
+ // construct the requested assignment from the packet data
+ Assignment requestAssignment(receivedPacket);
+
+ qDebug() << "Received a request for assignment type" << requestAssignment.getType()
+ << "from" << senderSockAddr;
+
+ SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
+
+ if (assignmentToDeploy) {
+ qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << senderSockAddr;
+
+ // give this assignment out, either the type matches or the requestor said they will take any
+ assignmentPacket.resize(numAssignmentPacketHeaderBytes);
+
+ QDataStream assignmentStream(&assignmentPacket, QIODevice::Append);
+
+ assignmentStream << *assignmentToDeploy.data();
+
+ nodeList->getNodeSocket().writeDatagram(assignmentPacket,
+ senderSockAddr.getAddress(), senderSockAddr.getPort());
} else {
- qDebug() << "Received an invalid assignment request from" << senderSockAddr.getAddress();
+ qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
+ << "from" << senderSockAddr;
}
}
}
@@ -266,7 +362,7 @@ QJsonObject jsonObjectForNode(Node* node) {
QJsonObject nodeJson;
// re-format the type name so it matches the target name
- QString nodeTypeName(node->getTypeName());
+ QString nodeTypeName = NodeType::getNodeTypeName(node->getType());
nodeTypeName = nodeTypeName.toLower();
nodeTypeName.replace(' ', '-');
@@ -278,77 +374,13 @@ QJsonObject jsonObjectForNode(Node* node) {
nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket());
// if the node has pool information, add it
- if (node->getLinkedData() && ((Assignment*) node->getLinkedData())->hasPool()) {
- nodeJson[JSON_KEY_POOL] = QString(((Assignment*) node->getLinkedData())->getPool());
+ if (node->getLinkedData() && !((Assignment*) node->getLinkedData())->getPool().isEmpty()) {
+ nodeJson[JSON_KEY_POOL] = ((Assignment*) node->getLinkedData())->getPool();
}
return nodeJson;
}
-// Attempts to read configuration from specified path
-// returns true on success, false otherwise
-bool DomainServer::readConfigFile(const char* path) {
- if (!path) {
- // config file not specified
- return false;
- }
-
- if (!QFile::exists(path)) {
- qWarning("Specified configuration file does not exist!\n");
- return false;
- }
-
- QFile configFile(path);
- if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
- qWarning("Can't open specified configuration file!\n");
- return false;
- } else {
- qDebug("Reading configuration from %s\n", path);
- }
- QTextStream configStream(&configFile);
- QByteArray configStringByteArray = configStream.readAll().toUtf8();
- QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object();
- configFile.close();
-
- QString voxelServerConfig = readServerAssignmentConfig(configDocObject, VOXEL_SERVER_CONFIG);
- _voxelServerConfig = new char[strlen(voxelServerConfig.toLocal8Bit().constData()) +1];
- _voxelServerConfig = strcpy((char *) _voxelServerConfig, voxelServerConfig.toLocal8Bit().constData() + '\0');
-
- QString particleServerConfig = readServerAssignmentConfig(configDocObject, PARTICLE_SERVER_CONFIG);
- _particleServerConfig = new char[strlen(particleServerConfig.toLocal8Bit().constData()) +1];
- _particleServerConfig = strcpy((char *) _particleServerConfig, particleServerConfig.toLocal8Bit().constData() + '\0');
-
- QString metavoxelServerConfig = readServerAssignmentConfig(configDocObject, METAVOXEL_SERVER_CONFIG);
- _metavoxelServerConfig = new char[strlen(metavoxelServerConfig.toLocal8Bit().constData()) +1];
- _metavoxelServerConfig = strcpy((char *) _metavoxelServerConfig, metavoxelServerConfig.toLocal8Bit().constData() + '\0');
-
- return true;
-}
-
-// find assignment configurations on the specified node name and json object
-// returns a string in the form of its equivalent cmd line params
-QString DomainServer::readServerAssignmentConfig(QJsonObject jsonObject, const char* nodeName) {
- QJsonArray nodeArray = jsonObject[nodeName].toArray();
-
- QStringList serverConfig;
- foreach (const QJsonValue & childValue, nodeArray) {
- QString cmdParams;
- QJsonObject childObject = childValue.toObject();
- QStringList keys = childObject.keys();
- for (int i = 0; i < keys.size(); i++) {
- QString key = keys[i];
- QString value = childObject[key].toString();
- // both cmd line params and json keys are the same
- cmdParams += QString("--%1 %2 ").arg(key, value);
- }
- serverConfig << cmdParams;
- }
-
- // according to split() calls from DomainServer::prepopulateStaticAssignmentFile
- // we shold simply join them with semicolons
- return serverConfig.join(';');
-}
-
bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString& path) {
const QString JSON_MIME_TYPE = "application/json";
@@ -377,24 +409,19 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
QJsonObject queuedAssignmentsJSON;
// add the queued but unfilled assignments to the json
- std::deque::iterator assignment = _assignmentQueue.begin();
-
- while (assignment != _assignmentQueue.end()) {
+ foreach(const SharedAssignmentPointer& assignment, _assignmentQueue) {
QJsonObject queuedAssignmentJSON;
- QString uuidString = uuidStringWithoutCurlyBraces((*assignment)->getUUID());
- queuedAssignmentJSON[JSON_KEY_TYPE] = QString((*assignment)->getTypeName());
+ QString uuidString = uuidStringWithoutCurlyBraces(assignment->getUUID());
+ queuedAssignmentJSON[JSON_KEY_TYPE] = QString(assignment->getTypeName());
// if the assignment has a pool, add it
- if ((*assignment)->hasPool()) {
- queuedAssignmentJSON[JSON_KEY_POOL] = QString((*assignment)->getPool());
+ if (!assignment->getPool().isEmpty()) {
+ queuedAssignmentJSON[JSON_KEY_POOL] = assignment->getPool();
}
// add this queued assignment to the JSON
queuedAssignmentsJSON[uuidString] = queuedAssignmentJSON;
-
- // push forward the iterator to check the next assignment
- assignment++;
}
assignmentJSON["queued"] = queuedAssignmentsJSON;
@@ -432,11 +459,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
// this is a script upload - ask the HTTPConnection to parse the form data
QList formData = connection->parseFormData();
- // create an assignment for this saved script, for now make it local only
- Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand,
- Assignment::AgentType,
- NULL,
- Assignment::LocalLocation);
+ // create an assignment for this saved script
+ Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType);
// check how many instances of this assignment the user wants by checking the ASSIGNMENT-INSTANCES header
const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES";
@@ -470,10 +494,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
connection->respond(HTTPConnection::StatusCode200);
// add the script assigment to the assignment queue
- // lock the assignment queue mutex since we're operating on a different thread than DS main
- _assignmentQueueMutex.lock();
- _assignmentQueue.push_back(scriptAssignment);
- _assignmentQueueMutex.unlock();
+ _assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment));
}
} else if (connection->requestOperation() == QNetworkAccessManager::DeleteOperation) {
if (path.startsWith(URI_NODE)) {
@@ -508,326 +529,137 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QString&
return false;
}
-void DomainServer::addReleasedAssignmentBackToQueue(Assignment* releasedAssignment) {
- qDebug() << "Adding assignment" << *releasedAssignment << " back to queue.";
-
- // find this assignment in the static file
- for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) {
- if (_staticAssignments[i].getUUID() == releasedAssignment->getUUID()) {
- // reset the UUID on the static assignment
- _staticAssignments[i].resetUUID();
-
- // put this assignment back in the queue so it goes out
- _assignmentQueueMutex.lock();
- _assignmentQueue.push_back(&_staticAssignments[i]);
- _assignmentQueueMutex.unlock();
-
- } else if (_staticAssignments[i].getUUID().isNull()) {
- // we are at the blank part of the static assignments - break out
- break;
- }
- }
+void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment) {
+ QUuid oldUUID = assignment->getUUID();
+ assignment->resetUUID();
+
+ qDebug() << "Reset UUID for assignment -" << *assignment.data() << "- and added to queue. Old UUID was"
+ << uuidStringWithoutCurlyBraces(oldUUID);
+
+ // add the static assignment back under the right UUID, and to the queue
+ _staticAssignmentHash.insert(assignment->getUUID(), assignment);
+
+ _assignmentQueue.enqueue(assignment);
+
+ // remove the old assignment from the _staticAssignmentHash
+ // this must be done last so copies are created before the assignment passed by reference is killed
+ _staticAssignmentHash.remove(oldUUID);
}
void DomainServer::nodeKilled(SharedNodePointer node) {
- // if this node has linked data it was from an assignment
- if (node->getLinkedData()) {
- Assignment* nodeAssignment = (Assignment*) node->getLinkedData();
-
- addReleasedAssignmentBackToQueue(nodeAssignment);
+ // 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());
+
+ if (matchedAssignment) {
+ refreshStaticAssignmentAndAddToQueue(matchedAssignment);
}
}
-unsigned char* DomainServer::addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd) {
- *currentPosition++ = nodeToAdd->getType();
-
-
- QByteArray rfcUUID = nodeToAdd->getUUID().toRfc4122();
- memcpy(currentPosition, rfcUUID.constData(), rfcUUID.size());
- currentPosition += rfcUUID.size();
-
- currentPosition += HifiSockAddr::packSockAddr(currentPosition, nodeToAdd->getPublicSocket());
- currentPosition += HifiSockAddr::packSockAddr(currentPosition, nodeToAdd->getLocalSocket());
-
- // return the new unsigned char * for broadcast packet
- return currentPosition;
-}
-
-void DomainServer::prepopulateStaticAssignmentFile() {
- int numFreshStaticAssignments = 0;
-
- // write a fresh static assignment array to file
-
- Assignment freshStaticAssignments[MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS];
-
- // pre-populate the first static assignment list with assignments for root AuM, AvM, VS
- freshStaticAssignments[numFreshStaticAssignments++] = Assignment(Assignment::CreateCommand, Assignment::AudioMixerType);
- freshStaticAssignments[numFreshStaticAssignments++] = Assignment(Assignment::CreateCommand, Assignment::AvatarMixerType);
-
- // Handle Domain/Voxel Server configuration command line arguments
- if (_voxelServerConfig) {
- qDebug("Reading Voxel Server Configuration.");
- qDebug() << "config: " << _voxelServerConfig;
-
- QString multiConfig((const char*) _voxelServerConfig);
- QStringList multiConfigList = multiConfig.split(";");
-
- // read each config to a payload for a VS assignment
- for (int i = 0; i < multiConfigList.size(); i++) {
- QString config = multiConfigList.at(i);
-
- qDebug("config[%d]=%s", i, config.toLocal8Bit().constData());
-
- // Now, parse the config to check for a pool
- const char ASSIGNMENT_CONFIG_POOL_OPTION[] = "--pool";
- QString assignmentPool;
-
- int poolIndex = config.indexOf(ASSIGNMENT_CONFIG_POOL_OPTION);
-
- if (poolIndex >= 0) {
- int spaceBeforePoolIndex = config.indexOf(' ', poolIndex);
- int spaceAfterPoolIndex = config.indexOf(' ', spaceBeforePoolIndex);
-
- assignmentPool = config.mid(spaceBeforePoolIndex + 1, spaceAfterPoolIndex);
- qDebug() << "The pool for this voxel-assignment is" << assignmentPool;
- }
-
- Assignment voxelServerAssignment(Assignment::CreateCommand,
- Assignment::VoxelServerType,
- (assignmentPool.isEmpty() ? NULL : assignmentPool.toLocal8Bit().constData()));
-
- int payloadLength = config.length() + sizeof(char);
- voxelServerAssignment.setPayload((uchar*)config.toLocal8Bit().constData(), payloadLength);
-
- freshStaticAssignments[numFreshStaticAssignments++] = voxelServerAssignment;
- }
- } else {
- Assignment rootVoxelServerAssignment(Assignment::CreateCommand, Assignment::VoxelServerType);
- freshStaticAssignments[numFreshStaticAssignments++] = rootVoxelServerAssignment;
- }
-
- // Handle Domain/Particle Server configuration command line arguments
- if (_particleServerConfig) {
- qDebug("Reading Particle Server Configuration.");
- qDebug() << "config: " << _particleServerConfig;
-
- QString multiConfig((const char*) _particleServerConfig);
- QStringList multiConfigList = multiConfig.split(";");
-
- // read each config to a payload for a VS assignment
- for (int i = 0; i < multiConfigList.size(); i++) {
- QString config = multiConfigList.at(i);
-
- qDebug("config[%d]=%s", i, config.toLocal8Bit().constData());
-
- // Now, parse the config to check for a pool
- const char ASSIGNMENT_CONFIG_POOL_OPTION[] = "--pool";
- QString assignmentPool;
-
- int poolIndex = config.indexOf(ASSIGNMENT_CONFIG_POOL_OPTION);
-
- if (poolIndex >= 0) {
- int spaceBeforePoolIndex = config.indexOf(' ', poolIndex);
- int spaceAfterPoolIndex = config.indexOf(' ', spaceBeforePoolIndex);
-
- assignmentPool = config.mid(spaceBeforePoolIndex + 1, spaceAfterPoolIndex);
- qDebug() << "The pool for this particle-assignment is" << assignmentPool;
- }
-
- Assignment particleServerAssignment(Assignment::CreateCommand,
- Assignment::ParticleServerType,
- (assignmentPool.isEmpty() ? NULL : assignmentPool.toLocal8Bit().constData()));
-
- int payloadLength = config.length() + sizeof(char);
- particleServerAssignment.setPayload((uchar*)config.toLocal8Bit().constData(), payloadLength);
-
- freshStaticAssignments[numFreshStaticAssignments++] = particleServerAssignment;
- }
- } else {
- Assignment rootParticleServerAssignment(Assignment::CreateCommand, Assignment::ParticleServerType);
- freshStaticAssignments[numFreshStaticAssignments++] = rootParticleServerAssignment;
- }
-
- // handle metavoxel configuration command line argument
- Assignment& metavoxelAssignment = (freshStaticAssignments[numFreshStaticAssignments++] =
- Assignment(Assignment::CreateCommand, Assignment::MetavoxelServerType));
- if (_metavoxelServerConfig) {
- metavoxelAssignment.setPayload((const unsigned char*)_metavoxelServerConfig, strlen(_metavoxelServerConfig));
- }
-
- qDebug() << "Adding" << numFreshStaticAssignments << "static assignments to fresh file.";
-
- _staticAssignmentFile.open(QIODevice::WriteOnly);
- _staticAssignmentFile.write((char*) &freshStaticAssignments, sizeof(freshStaticAssignments));
- _staticAssignmentFile.resize(MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS * sizeof(Assignment));
- _staticAssignmentFile.close();
-}
-
-Assignment* DomainServer::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType) {
- // pull the UUID passed with the check in
-
+SharedAssignmentPointer DomainServer::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
if (_hasCompletedRestartHold) {
- _assignmentQueueMutex.lock();
-
- // iterate the assignment queue to check for a match
- std::deque::iterator assignment = _assignmentQueue.begin();
- while (assignment != _assignmentQueue.end()) {
- if ((*assignment)->getUUID() == checkInUUID) {
- // return the matched assignment
- _assignmentQueueMutex.unlock();
- return *assignment;
+ // look for a match in the assignment hash
+
+ QQueue::iterator i = _assignmentQueue.begin();
+
+ while (i != _assignmentQueue.end()) {
+ if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
+ return _assignmentQueue.takeAt(i - _assignmentQueue.begin());
} else {
- // no match, push deque iterator forwards
- assignment++;
+ ++i;
}
}
-
- _assignmentQueueMutex.unlock();
} else {
- for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) {
- if (_staticAssignments[i].getUUID() == checkInUUID) {
- // return matched assignment
- return &_staticAssignments[i];
- } else if (_staticAssignments[i].getUUID().isNull()) {
- // end of static assignments, no match - return NULL
- return NULL;
- }
+ SharedAssignmentPointer matchingStaticAssignment = _staticAssignmentHash.value(checkInUUID);
+ if (matchingStaticAssignment && matchingStaticAssignment->getType() == nodeType) {
+ return matchingStaticAssignment;
}
}
- return NULL;
+ return SharedAssignmentPointer();
}
-Assignment* DomainServer::deployableAssignmentForRequest(Assignment& requestAssignment) {
- _assignmentQueueMutex.lock();
-
+SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assignment& requestAssignment) {
// this is an unassigned client talking to us directly for an assignment
// go through our queue and see if there are any assignments to give out
- std::deque::iterator assignment = _assignmentQueue.begin();
+ QQueue::iterator sharedAssignment = _assignmentQueue.begin();
- while (assignment != _assignmentQueue.end()) {
+ while (sharedAssignment != _assignmentQueue.end()) {
+ Assignment* assignment = sharedAssignment->data();
bool requestIsAllTypes = requestAssignment.getType() == Assignment::AllTypes;
- bool assignmentTypesMatch = (*assignment)->getType() == requestAssignment.getType();
- bool nietherHasPool = !(*assignment)->hasPool() && !requestAssignment.hasPool();
- bool assignmentPoolsMatch = memcmp((*assignment)->getPool(),
- requestAssignment.getPool(),
- MAX_ASSIGNMENT_POOL_BYTES) == 0;
-
+ bool assignmentTypesMatch = assignment->getType() == requestAssignment.getType();
+ bool nietherHasPool = assignment->getPool().isEmpty() && requestAssignment.getPool().isEmpty();
+ bool assignmentPoolsMatch = assignment->getPool() == requestAssignment.getPool();
+
if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) {
- Assignment* deployableAssignment = *assignment;
-
- if ((*assignment)->getType() == Assignment::AgentType) {
+ if (assignment->getType() == Assignment::AgentType) {
// if there is more than one instance to send out, simply decrease the number of instances
- if ((*assignment)->getNumberOfInstances() == 1) {
- _assignmentQueue.erase(assignment);
+ if (assignment->getNumberOfInstances() == 1) {
+ return _assignmentQueue.takeAt(sharedAssignment - _assignmentQueue.begin());
+ } else {
+ assignment->decrementNumberOfInstances();
+ return *sharedAssignment;
}
- deployableAssignment->decrementNumberOfInstances();
-
} else {
// remove the assignment from the queue
- _assignmentQueue.erase(assignment);
+ SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment
+ - _assignmentQueue.begin());
// until we get a check-in from that GUID
// put assignment back in queue but stick it at the back so the others have a chance to go out
- _assignmentQueue.push_back(deployableAssignment);
+ _assignmentQueue.enqueue(deployableAssignment);
+
+ // stop looping, we've handed out an assignment
+ return deployableAssignment;
}
-
- // stop looping, we've handed out an assignment
- _assignmentQueueMutex.unlock();
- return deployableAssignment;
} else {
// push forward the iterator to check the next assignment
- assignment++;
+ ++sharedAssignment;
}
}
-
- _assignmentQueueMutex.unlock();
- return NULL;
+
+ return SharedAssignmentPointer();
}
-void DomainServer::removeAssignmentFromQueue(Assignment* removableAssignment) {
-
- _assignmentQueueMutex.lock();
-
- std::deque::iterator assignment = _assignmentQueue.begin();
-
- while (assignment != _assignmentQueue.end()) {
- if ((*assignment)->getUUID() == removableAssignment->getUUID()) {
- _assignmentQueue.erase(assignment);
+void DomainServer::removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment) {
+ QQueue::iterator potentialMatchingAssignment = _assignmentQueue.begin();
+ while (potentialMatchingAssignment != _assignmentQueue.end()) {
+ if (potentialMatchingAssignment->data()->getUUID() == removableAssignment->getUUID()) {
+ _assignmentQueue.erase(potentialMatchingAssignment);
+
+ // we matched and removed an assignment, bail out
break;
} else {
- // push forward the iterator to check the next assignment
- assignment++;
+ ++potentialMatchingAssignment;
}
}
-
- _assignmentQueueMutex.unlock();
-}
-
-bool DomainServer::checkInWithUUIDMatchesExistingNode(const HifiSockAddr& nodePublicSocket,
- const HifiSockAddr& nodeLocalSocket,
- const QUuid& checkInUUID) {
- NodeList* nodeList = NodeList::getInstance();
-
- foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getLinkedData()
- && nodePublicSocket == node->getPublicSocket()
- && nodeLocalSocket == node->getLocalSocket()
- && node->getUUID() == checkInUUID) {
- // this is a matching existing node if the public socket, local socket, and UUID match
- return true;
- }
- }
-
- return false;
}
void DomainServer::addStaticAssignmentsBackToQueueAfterRestart() {
_hasCompletedRestartHold = true;
// if the domain-server has just restarted,
- // check if there are static assignments in the file that we need to
- // throw into the assignment queue
-
- // pull anything in the static assignment file that isn't spoken for and add to the assignment queue
- for (int i = 0; i < MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS; i++) {
- if (_staticAssignments[i].getUUID().isNull()) {
- // reached the end of static assignments, bail
- break;
- }
-
+ // check if there are static assignments that we need to throw into the assignment queue
+ QHash staticHashCopy = _staticAssignmentHash;
+ QHash::iterator staticAssignment = staticHashCopy.begin();
+ while (staticAssignment != staticHashCopy.end()) {
+ // add any of the un-matched static assignments to the queue
bool foundMatchingAssignment = false;
-
- NodeList* nodeList = NodeList::getInstance();
-
+
// enumerate the nodes and check if there is one with an attached assignment with matching UUID
- foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getLinkedData()) {
- Assignment* linkedAssignment = (Assignment*) node->getLinkedData();
- if (linkedAssignment->getUUID() == _staticAssignments[i].getUUID()) {
- foundMatchingAssignment = true;
- break;
- }
+ foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) {
+ if (node->getUUID() == staticAssignment->data()->getUUID()) {
+ foundMatchingAssignment = true;
}
}
-
+
if (!foundMatchingAssignment) {
// this assignment has not been fulfilled - reset the UUID and add it to the assignment queue
- _staticAssignments[i].resetUUID();
-
- qDebug() << "Adding static assignment to queue -" << _staticAssignments[i];
-
- _assignmentQueueMutex.lock();
- _assignmentQueue.push_back(&_staticAssignments[i]);
- _assignmentQueueMutex.unlock();
+ refreshStaticAssignmentAndAddToQueue(*staticAssignment);
}
+
+ ++staticAssignment;
}
}
-
-void DomainServer::cleanup() {
- _staticAssignmentFile.unmap(_staticAssignmentFileData);
- _staticAssignmentFile.close();
-}
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index fed5aaaa43..60251b3bb4 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -9,17 +9,16 @@
#ifndef __hifi__DomainServer__
#define __hifi__DomainServer__
-#include
-
#include
-#include
-#include
+#include
+#include
+#include
#include
#include
#include
-const int MAX_STATIC_ASSIGNMENT_FILE_ASSIGNMENTS = 1000;
+typedef QSharedPointer SharedAssignmentPointer;
class DomainServer : public QCoreApplication, public HTTPRequestHandler {
Q_OBJECT
@@ -35,39 +34,27 @@ public slots:
void nodeKilled(SharedNodePointer node);
private:
- bool readConfigFile(const char* path);
- QString readServerAssignmentConfig(QJsonObject jsonObj, const char* nodeName);
-
- void prepopulateStaticAssignmentFile();
- Assignment* matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NODE_TYPE nodeType);
- Assignment* deployableAssignmentForRequest(Assignment& requestAssignment);
- void removeAssignmentFromQueue(Assignment* removableAssignment);
- bool checkInWithUUIDMatchesExistingNode(const HifiSockAddr& nodePublicSocket,
- const HifiSockAddr& nodeLocalSocket,
- const QUuid& checkInUUI);
- void addReleasedAssignmentBackToQueue(Assignment* releasedAssignment);
+ void parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes);
+ void readConfigFile(const QString& path, QSet& excludedTypes);
+ QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName);
+ void addStaticAssignmentToAssignmentHash(Assignment* newAssignment);
+ void createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString);
+ void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes);
- unsigned char* addNodeToBroadcastPacket(unsigned char* currentPosition, Node* nodeToAdd);
+ SharedAssignmentPointer matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType);
+ SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
+ void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment);
+ void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
HTTPManager _HTTPManager;
- QMutex _assignmentQueueMutex;
- std::deque _assignmentQueue;
-
- QFile _staticAssignmentFile;
- uchar* _staticAssignmentFileData;
-
- Assignment* _staticAssignments;
-
- const char* _voxelServerConfig;
- const char* _particleServerConfig;
- const char* _metavoxelServerConfig;
+ QHash _staticAssignmentHash;
+ QQueue _assignmentQueue;
bool _hasCompletedRestartHold;
private slots:
void readAvailableDatagrams();
void addStaticAssignmentsBackToQueueAfterRestart();
- void cleanup();
};
#endif /* defined(__hifi__DomainServer__) */
diff --git a/examples/clap.js b/examples/clap.js
index fdd2b29aa2..81ccda64b7 100644
--- a/examples/clap.js
+++ b/examples/clap.js
@@ -62,4 +62,4 @@ function maybePlaySound() {
}
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(maybePlaySound);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(maybePlaySound);
\ No newline at end of file
diff --git a/examples/collidingParticles.js b/examples/collidingParticles.js
index cf1fce5660..81ccfe108b 100644
--- a/examples/collidingParticles.js
+++ b/examples/collidingParticles.js
@@ -132,7 +132,7 @@ function draw() {
print(scriptB);
numberParticlesAdded++;
} else {
- Agent.stop();
+ Script.stop();
}
print("Particles Stats: " + Particles.getLifetimeInSeconds() + " seconds," +
@@ -150,5 +150,5 @@ function draw() {
// register the call back so it fires before each data send
print("here...\n");
Particles.setPacketsPerSecond(40000);
-Agent.willSendVisualDataCallback.connect(draw);
+Script.willSendVisualDataCallback.connect(draw);
print("and here...\n");
diff --git a/examples/controllerExample.js b/examples/controllerExample.js
new file mode 100644
index 0000000000..43eb516cee
--- /dev/null
+++ b/examples/controllerExample.js
@@ -0,0 +1,202 @@
+//
+// controllerExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/28/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Controller class
+//
+//
+
+// initialize our triggers
+var triggerPulled = new Array();
+var numberOfTriggers = Controller.getNumberOfTriggers();
+for (t = 0; t < numberOfTriggers; t++) {
+ triggerPulled[t] = false;
+}
+
+function checkController() {
+ var numberOfTriggers = Controller.getNumberOfTriggers();
+ var numberOfSpatialControls = Controller.getNumberOfSpatialControls();
+ var controllersPerTrigger = numberOfSpatialControls / numberOfTriggers;
+
+ // this is expected for hydras
+ if (numberOfTriggers == 2 && controllersPerTrigger == 2) {
+ for (var t = 0; t < numberOfTriggers; t++) {
+ var triggerValue = Controller.getTriggerValue(t);
+
+ if (triggerPulled[t]) {
+ // must release to at least 0.1
+ if (triggerValue < 0.1) {
+ triggerPulled[t] = false; // unpulled
+ }
+ } else {
+ // must pull to at least 0.9
+ if (triggerValue > 0.9) {
+ triggerPulled[t] = true; // pulled
+ triggerToggled = true;
+ }
+ }
+
+ if (triggerToggled) {
+ print("a trigger was toggled");
+ }
+ }
+ }
+}
+
+function keyPressEvent(event) {
+ print("keyPressEvent event.key=" + event.key);
+ print("keyPressEvent event.text=" + event.text);
+
+ print("keyPressEvent event.isShifted=" + event.isShifted);
+ print("keyPressEvent event.isControl=" + event.isControl);
+ print("keyPressEvent event.isMeta=" + event.isMeta);
+ print("keyPressEvent event.isAlt=" + event.isAlt);
+ print("keyPressEvent event.isKeypad=" + event.isKeypad);
+
+
+ if (event.key == "A".charCodeAt(0)) {
+ print("the A key was pressed");
+ }
+ if (event.key == " ".charCodeAt(0)) {
+ print("the key was pressed");
+ }
+}
+
+function mouseMoveEvent(event) {
+ print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
+}
+
+function touchBeginEvent(event) {
+ print("touchBeginEvent event.x,y=" + event.x + ", " + event.y);
+}
+
+function touchUpdateEvent(event) {
+ print("touchUpdateEvent event.x,y=" + event.x + ", " + event.y);
+}
+
+function touchEndEvent(event) {
+ print("touchEndEvent event.x,y=" + event.x + ", " + event.y);
+}
+
+// register the call back so it fires before each data send
+Script.willSendVisualDataCallback.connect(checkController);
+
+// Map keyPress and mouse move events to our callbacks
+Controller.keyPressEvent.connect(keyPressEvent);
+var KeyEvent_A = {
+ key: "A".charCodeAt(0),
+ text: "A",
+ isShifted: false,
+ isMeta: false
+};
+
+var KeyEvent_a = {
+ text: "a",
+ isShifted: false,
+ isMeta: false
+};
+
+var KeyEvent_a2 = {
+ key: "a".charCodeAt(0),
+ isShifted: false,
+ isMeta: false
+};
+
+var KeyEvent_a3 = {
+ text: "a"
+};
+
+var KeyEvent_A2 = {
+ text: "A"
+};
+
+
+var KeyEvent_9 = {
+ text: "9"
+};
+
+var KeyEvent_Num = {
+ text: "#"
+};
+
+var KeyEvent_At = {
+ text: "@"
+};
+
+var KeyEvent_MetaAt = {
+ text: "@",
+ isMeta: true
+};
+
+var KeyEvent_Up = {
+ text: "up"
+};
+var KeyEvent_Down = {
+ text: "down"
+};
+var KeyEvent_Left = {
+ text: "left"
+};
+var KeyEvent_Right = {
+ text: "right"
+};
+
+// prevent the A key from going through to the application
+print("KeyEvent_A");
+Controller.captureKeyEvents(KeyEvent_A);
+
+print("KeyEvent_A2");
+Controller.captureKeyEvents(KeyEvent_A2);
+
+print("KeyEvent_a");
+Controller.captureKeyEvents(KeyEvent_a);
+
+print("KeyEvent_a2");
+Controller.captureKeyEvents(KeyEvent_a2);
+
+print("KeyEvent_a3");
+Controller.captureKeyEvents(KeyEvent_a3);
+
+print("KeyEvent_9");
+Controller.captureKeyEvents(KeyEvent_9);
+
+print("KeyEvent_Num");
+Controller.captureKeyEvents(KeyEvent_Num);
+
+print("KeyEvent_At");
+Controller.captureKeyEvents(KeyEvent_At);
+
+print("KeyEvent_MetaAt");
+Controller.captureKeyEvents(KeyEvent_MetaAt);
+
+print("KeyEvent_Up");
+Controller.captureKeyEvents(KeyEvent_Up);
+print("KeyEvent_Down");
+Controller.captureKeyEvents(KeyEvent_Down);
+print("KeyEvent_Left");
+Controller.captureKeyEvents(KeyEvent_Left);
+print("KeyEvent_Right");
+Controller.captureKeyEvents(KeyEvent_Right);
+
+
+
+
+Controller.mouseMoveEvent.connect(mouseMoveEvent);
+
+// Map touch events to our callbacks
+Controller.touchBeginEvent.connect(touchBeginEvent);
+Controller.touchUpdateEvent.connect(touchUpdateEvent);
+Controller.touchEndEvent.connect(touchEndEvent);
+
+// disable the standard application for touch events
+Controller.captureTouchEvents();
+
+function scriptEnding() {
+ // re-enabled the standard application for touch events
+ Controller.releaseTouchEvents();
+}
+
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/count.js b/examples/count.js
index 917bec342d..29799a8271 100644
--- a/examples/count.js
+++ b/examples/count.js
@@ -20,7 +20,7 @@ function scriptEnding() {
}
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(displayCount);
+Script.willSendVisualDataCallback.connect(displayCount);
// register our scriptEnding callback
-Agent.scriptEnding.connect(scriptEnding);
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/drumStick.js b/examples/drumStick.js
index 955fddbdee..78de8351db 100644
--- a/examples/drumStick.js
+++ b/examples/drumStick.js
@@ -69,4 +69,4 @@ function checkSticks() {
}
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(checkSticks);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(checkSticks);
\ No newline at end of file
diff --git a/examples/editParticleExample.js b/examples/editParticleExample.js
index 4eb5dfe907..152bb18fca 100644
--- a/examples/editParticleExample.js
+++ b/examples/editParticleExample.js
@@ -9,6 +9,9 @@
//
var count = 0;
+var moveUntil = 2000;
+var stopAfter = moveUntil + 100;
+var expectedLifetime = (moveUntil/60) + 1; // 1 second after done moving...
var originalProperties = {
position: { x: 10,
@@ -28,7 +31,9 @@ var originalProperties = {
color: { red: 0,
green: 255,
- blue: 0 }
+ blue: 0 },
+
+ lifetime: expectedLifetime
};
@@ -38,19 +43,18 @@ var positionDelta = { x: 0.05, y: 0, z: 0 };
var particleID = Particles.addParticle(originalProperties);
function moveParticle() {
- if (count >= 100) {
- //Agent.stop();
+ if (count >= moveUntil) {
// delete it...
- if (count == 100) {
+ if (count == moveUntil) {
print("calling Particles.deleteParticle()");
Particles.deleteParticle(particleID);
}
// stop it...
- if (count >= 200) {
- print("calling Agent.stop()");
- Agent.stop();
+ if (count >= stopAfter) {
+ print("calling Script.stop()");
+ Script.stop();
}
count++;
@@ -77,19 +81,9 @@ function moveParticle() {
print("newProperties.position = " + newProperties.position.x + "," + newProperties.position.y+ "," + newProperties.position.z);
Particles.editParticle(particleID, newProperties);
-
- // also check to see if we can "find" particles...
- var searchAt = { x: 9, y: 0, z: 0};
- var searchRadius = 2;
- var foundParticle = Particles.findClosestParticle(searchAt, searchRadius);
- if (foundParticle.isKnownID) {
- print("found particle:" + foundParticle.id);
- } else {
- print("could not find particle in or around x=9 to x=11:");
- }
}
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(moveParticle);
+Script.willSendVisualDataCallback.connect(moveParticle);
diff --git a/examples/findParticleExample.js b/examples/findParticleExample.js
index f582ee6469..5eb257d502 100644
--- a/examples/findParticleExample.js
+++ b/examples/findParticleExample.js
@@ -1,5 +1,5 @@
//
-// editParticleExample.js
+// findParticleExample.js
// hifi
//
// Created by Brad Hefta-Gaub on 1/24/14.
@@ -65,8 +65,8 @@ function findParticles() {
// run for a while, then clean up
// stop it...
if (iteration >= 100) {
- print("calling Agent.stop()");
- Agent.stop();
+ print("calling Script.stop()");
+ Script.stop();
}
print("--------------------------");
@@ -122,7 +122,7 @@ function findParticles() {
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(findParticles);
+Script.willSendVisualDataCallback.connect(findParticles);
// register our scriptEnding callback
-Agent.scriptEnding.connect(scriptEnding);
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/fountain.js b/examples/fountain.js
index a095f91ed3..3c943d72a0 100644
--- a/examples/fountain.js
+++ b/examples/fountain.js
@@ -60,8 +60,8 @@ function makeFountain() {
totalParticles++;
}
if (totalParticles > 100) {
- Agent.stop();
+ Script.stop();
}
}
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(makeFountain);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(makeFountain);
\ No newline at end of file
diff --git a/examples/gameoflife.js b/examples/gameoflife.js
new file mode 100644
index 0000000000..6779941dc7
--- /dev/null
+++ b/examples/gameoflife.js
@@ -0,0 +1,133 @@
+// Add your JavaScript for assignment below this line
+
+// The following is an example of Conway's Game of Life (http://en.wikipedia.org/wiki/Conway's_Game_of_Life)
+
+var NUMBER_OF_CELLS_EACH_DIMENSION = 64;
+var NUMBER_OF_CELLS = NUMBER_OF_CELLS_EACH_DIMENSION * NUMBER_OF_CELLS_EACH_DIMENSION;
+
+var currentCells = [];
+var nextCells = [];
+
+var METER_LENGTH = 1;
+var cellScale = (NUMBER_OF_CELLS_EACH_DIMENSION * METER_LENGTH) / NUMBER_OF_CELLS_EACH_DIMENSION;
+
+// randomly populate the cell start values
+for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
+ // create the array to hold this row
+ currentCells[i] = [];
+
+ // create the array to hold this row in the nextCells array
+ nextCells[i] = [];
+
+ for (var j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
+ currentCells[i][j] = Math.floor(Math.random() * 2);
+
+ // put the same value in the nextCells array for first board draw
+ nextCells[i][j] = currentCells[i][j];
+ }
+}
+
+function isNeighbourAlive(i, j) {
+ if (i < 0 || i >= NUMBER_OF_CELLS_EACH_DIMENSION
+ || i < 0 || j >= NUMBER_OF_CELLS_EACH_DIMENSION) {
+ return 0;
+ } else {
+ return currentCells[i][j];
+ }
+}
+
+function updateCells() {
+ var i = 0;
+ var j = 0;
+
+ for (i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
+ for (j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
+ // figure out the number of live neighbours for the i-j cell
+ var liveNeighbours =
+ isNeighbourAlive(i + 1, j - 1) + isNeighbourAlive(i + 1, j) + isNeighbourAlive(i + 1, j + 1) +
+ isNeighbourAlive(i, j - 1) + isNeighbourAlive(i, j + 1) +
+ isNeighbourAlive(i - 1, j - 1) + isNeighbourAlive(i - 1, j) + isNeighbourAlive(i - 1, j + 1);
+
+ if (currentCells[i][j]) {
+ // live cell
+
+ if (liveNeighbours < 2) {
+ // rule #1 - under-population - this cell will die
+ // mark it zero to mark the change
+ nextCells[i][j] = 0;
+ } else if (liveNeighbours < 4) {
+ // rule #2 - this cell lives
+ // mark it -1 to mark no change
+ nextCells[i][j] = -1;
+ } else {
+ // rule #3 - overcrowding - this cell dies
+ // mark it zero to mark the change
+ nextCells[i][j] = 0;
+ }
+ } else {
+ // dead cell
+ if (liveNeighbours == 3) {
+ // rule #4 - reproduction - this cell revives
+ // mark it one to mark the change
+ nextCells[i][j] = 1;
+ } else {
+ // this cell stays dead
+ // mark it -1 for no change
+ nextCells[i][j] = -1;
+ }
+ }
+
+ if (Math.random() < 0.001) {
+ // Random mutation to keep things interesting in there.
+ nextCells[i][j] = 1;
+ }
+ }
+ }
+
+ for (i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
+ for (j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
+ if (nextCells[i][j] != -1) {
+ // there has been a change to this cell, change the value in the currentCells array
+ currentCells[i][j] = nextCells[i][j];
+ }
+ }
+ }
+}
+
+function sendNextCells() {
+ for (var i = 0; i < NUMBER_OF_CELLS_EACH_DIMENSION; i++) {
+ for (var j = 0; j < NUMBER_OF_CELLS_EACH_DIMENSION; j++) {
+ if (nextCells[i][j] != -1) {
+ // there has been a change to the state of this cell, send it
+
+ // find the x and y position for this voxel, z = 0
+ var x = j * cellScale;
+ var y = i * cellScale;
+
+ // queue a packet to add a voxel for the new cell
+ var color = (nextCells[i][j] == 1) ? 255 : 1;
+ Voxels.setVoxel(x, y, 0, cellScale, color, color, color);
+ }
+ }
+ }
+}
+
+var sentFirstBoard = false;
+
+function step() {
+print("step()...");
+ if (sentFirstBoard) {
+ // we've already sent the first full board, perform a step in time
+ updateCells();
+ } else {
+ // this will be our first board send
+ sentFirstBoard = true;
+ }
+
+ sendNextCells();
+}
+
+print("here");
+Script.willSendVisualDataCallback.connect(step);
+Voxels.setPacketsPerSecond(200);
+print("now here");
diff --git a/examples/globalCollisionsExample.js b/examples/globalCollisionsExample.js
new file mode 100644
index 0000000000..4db4c808e5
--- /dev/null
+++ b/examples/globalCollisionsExample.js
@@ -0,0 +1,26 @@
+//
+// globalCollisionsExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/29/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Controller class
+//
+//
+
+
+function particleCollisionWithVoxel(particle, voxel) {
+ print("particleCollisionWithVoxel()..");
+ print(" particle.getID()=" + particle.id);
+ print(" voxel color...=" + voxel.red + ", " + voxel.green + ", " + voxel.blue);
+}
+
+function particleCollisionWithParticle(particleA, particleB) {
+ print("particleCollisionWithParticle()..");
+ print(" particleA.getID()=" + particleA.id);
+ print(" particleB.getID()=" + particleB.id);
+}
+
+Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);
+Particles.particleCollisionWithParticle.connect(particleCollisionWithParticle);
diff --git a/examples/gun.js b/examples/gun.js
index 30d2b41449..e7cd2973e2 100644
--- a/examples/gun.js
+++ b/examples/gun.js
@@ -72,13 +72,13 @@ function checkController() {
" function collisionWithVoxel(voxel) { " +
" print('collisionWithVoxel(voxel)... '); " +
" print('myID=' + Particle.getID() + '\\n'); " +
- " var voxelColor = voxel.getColor();" +
- " print('voxelColor=' + voxelColor.red + ', ' + voxelColor.green + ', ' + voxelColor.blue + '\\n'); " +
+ " var voxelColor = { red: voxel.red, green: voxel.green, blue: voxel.blue };" +
+ " var voxelAt = { x: voxel.x, y: voxel.y, z: voxel.z };" +
+ " var voxelScale = voxel.s;" +
+ " print('voxelColor=' + voxel.red + ', ' + voxel.green + ', ' + voxel.blue + '\\n'); " +
" var myColor = Particle.getColor();" +
" print('myColor=' + myColor.red + ', ' + myColor.green + ', ' + myColor.blue + '\\n'); " +
" Particle.setColor(voxelColor); " +
- " var voxelAt = voxel.getPosition();" +
- " var voxelScale = voxel.getScale();" +
" Voxels.eraseVoxel(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale); " +
" print('Voxels.eraseVoxel(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
" } " +
@@ -99,4 +99,4 @@ function checkController() {
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(checkController);
+Script.willSendVisualDataCallback.connect(checkController);
diff --git a/examples/lookWithMouse.js b/examples/lookWithMouse.js
new file mode 100644
index 0000000000..f404019bc1
--- /dev/null
+++ b/examples/lookWithMouse.js
@@ -0,0 +1,74 @@
+//
+// lookWithMouse.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/28/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates use of the Controller class
+//
+//
+
+var isMouseDown = false;
+var lastX = 0;
+var lastY = 0;
+var yawFromMouse = 0;
+var pitchFromMouse = 0;
+
+function mousePressEvent(event) {
+ print("mousePressEvent event.x,y=" + event.x + ", " + event.y);
+ isMouseDown = true;
+ lastX = event.x;
+ lastY = event.y;
+}
+
+function mouseReleaseEvent(event) {
+ print("mouseReleaseEvent event.x,y=" + event.x + ", " + event.y);
+ isMouseDown = false;
+}
+
+function mouseMoveEvent(event) {
+ print("mouseMoveEvent event.x,y=" + event.x + ", " + event.y);
+
+ if (isMouseDown) {
+ print("isMouseDown... attempting to change pitch...");
+ var MOUSE_YAW_SCALE = -0.25;
+ var MOUSE_PITCH_SCALE = -12.5;
+ var FIXED_MOUSE_TIMESTEP = 0.016;
+ yawFromMouse += ((event.x - lastX) * MOUSE_YAW_SCALE * FIXED_MOUSE_TIMESTEP);
+ pitchFromMouse += ((event.y - lastY) * MOUSE_PITCH_SCALE * FIXED_MOUSE_TIMESTEP);
+ lastX = event.x;
+ lastY = event.y;
+ }
+}
+
+function update() {
+ // rotate body yaw for yaw received from mouse
+ MyAvatar.orientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3( { x: 0, y: yawFromMouse, z: 0 } ));
+ yawFromMouse = 0;
+
+ // apply pitch from mouse
+ MyAvatar.headPitch = MyAvatar.headPitch + pitchFromMouse;
+ pitchFromMouse = 0;
+}
+
+// Map the mouse events to our functions
+Controller.mousePressEvent.connect(mousePressEvent);
+Controller.mouseMoveEvent.connect(mouseMoveEvent);
+Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
+
+// disable the standard application for mouse events
+Controller.captureMouseEvents();
+
+function scriptEnding() {
+ // re-enabled the standard application for mouse events
+ Controller.releaseMouseEvents();
+}
+
+MyAvatar.bodyYaw = 0;
+MyAvatar.bodyPitch = 0;
+MyAvatar.bodyRoll = 0;
+
+// would be nice to change to update
+Script.willSendVisualDataCallback.connect(update);
+Script.scriptEnding.connect(scriptEnding);
diff --git a/examples/movingVoxel.js b/examples/movingVoxel.js
index 0aadf7b30c..14a7e671c6 100644
--- a/examples/movingVoxel.js
+++ b/examples/movingVoxel.js
@@ -41,4 +41,4 @@ function moveVoxel() {
Voxels.setPacketsPerSecond(300);
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(moveVoxel);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(moveVoxel);
\ No newline at end of file
diff --git a/examples/paintGun.js b/examples/paintGun.js
index 0cbe3ff366..56e916a183 100644
--- a/examples/paintGun.js
+++ b/examples/paintGun.js
@@ -65,13 +65,13 @@ function checkController() {
" function collisionWithVoxel(voxel) { " +
" print('collisionWithVoxel(voxel)... '); " +
" print('myID=' + Particle.getID() + '\\n'); " +
- " var voxelColor = voxel.getColor();" +
+ " var voxelColor = { red: voxel.red, green: voxel.green, blue: voxel.blue };" +
+ " var voxelAt = { x: voxel.x, y: voxel.y, z: voxel.z };" +
+ " var voxelScale = voxel.s;" +
" print('voxelColor=' + voxelColor.red + ', ' + voxelColor.green + ', ' + voxelColor.blue + '\\n'); " +
" var myColor = Particle.getColor();" +
" print('myColor=' + myColor.red + ', ' + myColor.green + ', ' + myColor.blue + '\\n'); " +
" Particle.setColor(voxelColor); " +
- " var voxelAt = voxel.getPosition();" +
- " var voxelScale = voxel.getScale();" +
" Voxels.setVoxel(voxelAt.x, voxelAt.y, voxelAt.z, voxelScale, 255, 255, 0); " +
" print('Voxels.setVoxel(' + voxelAt.x + ', ' + voxelAt.y + ', ' + voxelAt.z + ', ' + voxelScale + ')... \\n'); " +
" } " +
@@ -93,4 +93,4 @@ function checkController() {
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(checkController);
+Script.willSendVisualDataCallback.connect(checkController);
diff --git a/examples/particleBird.js b/examples/particleBird.js
index 6a4cf79a40..c1c26058e6 100644
--- a/examples/particleBird.js
+++ b/examples/particleBird.js
@@ -95,7 +95,7 @@ function moveBird() {
var nowTimeInSeconds = new Date().getTime() / 1000;
if ((nowTimeInSeconds - startTimeInSeconds) >= birdLifetime) {
print("our bird is dying, stop our script");
- Agent.stop();
+ Script.stop();
return;
}
@@ -171,4 +171,4 @@ function moveBird() {
}
}
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(moveBird);
+Script.willSendVisualDataCallback.connect(moveBird);
diff --git a/examples/particleModelExample.js b/examples/particleModelExample.js
new file mode 100644
index 0000000000..e95cc0c2bf
--- /dev/null
+++ b/examples/particleModelExample.js
@@ -0,0 +1,51 @@
+//
+// particleModelExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/28/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates creating and editing a particle
+//
+
+var count = 0;
+var stopAfter = 100;
+
+var modelProperties = {
+ position: { x: 1, y: 1, z: 1 },
+ velocity: { x: 0.5, y: 0, z: 0.5 },
+ gravity: { x: 0, y: 0, z: 0 },
+ damping: 0,
+ radius : 0.25,
+ modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
+ lifetime: 20
+};
+
+var ballProperties = {
+ position: { x: 1, y: 0.5, z: 1 },
+ velocity: { x: 0.5, y: 0, z: 0.5 },
+ gravity: { x: 0, y: 0, z: 0 },
+ damping: 0,
+ radius : 0.25,
+ color: { red: 255, green: 0, blue: 0 },
+ lifetime: 20
+};
+
+var modelParticleID = Particles.addParticle(modelProperties);
+var ballParticleID = Particles.addParticle(ballProperties);
+
+function endAfterAWhile() {
+ // stop it...
+ if (count >= stopAfter) {
+ print("calling Script.stop()");
+ Script.stop();
+ }
+
+ print("count =" + count);
+ count++;
+}
+
+
+// register the call back so it fires before each data send
+Script.willSendVisualDataCallback.connect(endAfterAWhile);
+
diff --git a/examples/playSound.js b/examples/playSound.js
index 6631d5526a..657f052aa5 100644
--- a/examples/playSound.js
+++ b/examples/playSound.js
@@ -17,4 +17,4 @@ function maybePlaySound() {
}
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(maybePlaySound);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(maybePlaySound);
\ No newline at end of file
diff --git a/examples/rideAlongWithAParticleExample.js b/examples/rideAlongWithAParticleExample.js
new file mode 100644
index 0000000000..b2b6627063
--- /dev/null
+++ b/examples/rideAlongWithAParticleExample.js
@@ -0,0 +1,50 @@
+//
+// rideAlongWithAParticleExample.js
+// hifi
+//
+// Created by Brad Hefta-Gaub on 1/24/14.
+// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
+//
+// This is an example script that demonstrates "finding" particles
+//
+
+var iteration = 0;
+var lengthOfRide = 2000; // in iterations
+
+var particleA = Particles.addParticle(
+ {
+ position: { x: 10, y: 0, z: 10 },
+ velocity: { x: 5, y: 0, z: 5 },
+ gravity: { x: 0, y: 0, z: 0 },
+ radius : 0.1,
+ color: { red: 0, green: 255, blue: 0 },
+ damping: 0,
+ lifetime: (lengthOfRide * 60) + 1
+ });
+
+function rideWithParticle() {
+
+ if (iteration <= lengthOfRide) {
+
+ // Check to see if we've been notified of the actual identity of the particles we created
+ if (!particleA.isKnownID) {
+ particleA = Particles.identifyParticle(particleA);
+ }
+
+ var propertiesA = Particles.getParticleProperties(particleA);
+ var newPosition = propertiesA.position;
+ MyAvatar.position = { x: propertiesA.position.x,
+ y: propertiesA.position.y + 2,
+ z: propertiesA.position.z };
+ } else {
+ Script.stop();
+ }
+
+ iteration++;
+ print("iteration="+iteration);
+}
+
+
+// register the call back so it fires before each data send
+Script.willSendVisualDataCallback.connect(rideWithParticle);
+
diff --git a/examples/toyball.js b/examples/toyball.js
index 6c40fc2932..c5672877f7 100644
--- a/examples/toyball.js
+++ b/examples/toyball.js
@@ -226,4 +226,4 @@ function checkController() {
// register the call back so it fires before each data send
-Agent.willSendVisualDataCallback.connect(checkController);
+Script.willSendVisualDataCallback.connect(checkController);
diff --git a/examples/voxelBird.js b/examples/voxelBird.js
index 54c0129045..254f93c21e 100644
--- a/examples/voxelBird.js
+++ b/examples/voxelBird.js
@@ -130,4 +130,4 @@ function moveBird() {
Voxels.setPacketsPerSecond(10000);
// Connect a call back that happens every frame
-Agent.willSendVisualDataCallback.connect(moveBird);
\ No newline at end of file
+Script.willSendVisualDataCallback.connect(moveBird);
\ No newline at end of file
diff --git a/interface/resources/config/config.json b/interface/resources/config/config.json
new file mode 100644
index 0000000000..8f7b3bb327
--- /dev/null
+++ b/interface/resources/config/config.json
@@ -0,0 +1,19 @@
+{
+ "importFormats" : [
+ {
+ "extension": "png",
+ "description": "Square PNG",
+ "icon": "raster.svg"
+ },
+ {
+ "extension": "svo",
+ "description": "Sparse Voxel Octree Files",
+ "icon": "voxel.svg"
+ },
+ {
+ "extension": "schematic",
+ "description": "Schematic Files",
+ "icon": "voxel.svg"
+ }
+ ]
+}
diff --git a/interface/resources/icons/computer.svg b/interface/resources/icons/computer.svg
new file mode 100644
index 0000000000..f7e792afb5
--- /dev/null
+++ b/interface/resources/icons/computer.svg
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/interface/resources/icons/desktop.svg b/interface/resources/icons/desktop.svg
new file mode 100644
index 0000000000..acf440f361
--- /dev/null
+++ b/interface/resources/icons/desktop.svg
@@ -0,0 +1,17 @@
+
+
+
+
diff --git a/interface/resources/icons/documents.svg b/interface/resources/icons/documents.svg
new file mode 100644
index 0000000000..e3a68301a6
--- /dev/null
+++ b/interface/resources/icons/documents.svg
@@ -0,0 +1,15 @@
+
+
+
+
diff --git a/interface/resources/icons/file.svg b/interface/resources/icons/file.svg
new file mode 100644
index 0000000000..4f3799b100
--- /dev/null
+++ b/interface/resources/icons/file.svg
@@ -0,0 +1,14 @@
+
+
+
+
diff --git a/interface/resources/icons/folder.svg b/interface/resources/icons/folder.svg
new file mode 100644
index 0000000000..1c486211f4
--- /dev/null
+++ b/interface/resources/icons/folder.svg
@@ -0,0 +1,11 @@
+
+
+
+
diff --git a/interface/resources/icons/home.svg b/interface/resources/icons/home.svg
new file mode 100644
index 0000000000..58983e71d5
--- /dev/null
+++ b/interface/resources/icons/home.svg
@@ -0,0 +1,16 @@
+
+
+
+
diff --git a/interface/resources/icons/raster.svg b/interface/resources/icons/raster.svg
new file mode 100644
index 0000000000..c6117137a9
--- /dev/null
+++ b/interface/resources/icons/raster.svg
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/interface/resources/icons/voxel.svg b/interface/resources/icons/voxel.svg
new file mode 100644
index 0000000000..77cc82307f
--- /dev/null
+++ b/interface/resources/icons/voxel.svg
@@ -0,0 +1,18 @@
+
+
+
+
diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag
index 877cdca885..e035f9bfe9 100644
--- a/interface/resources/shaders/model.frag
+++ b/interface/resources/shaders/model.frag
@@ -25,5 +25,5 @@ void main(void) {
// modulate texture by base color and add specular contribution
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
- pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
+ vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
}
diff --git a/interface/resources/shaders/model_normal_map.frag b/interface/resources/shaders/model_normal_map.frag
index 9740a4d4b1..9a0e964500 100644
--- a/interface/resources/shaders/model_normal_map.frag
+++ b/interface/resources/shaders/model_normal_map.frag
@@ -37,5 +37,5 @@ void main(void) {
// modulate texture by base color and add specular contribution
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
- pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular;
+ vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
}
diff --git a/interface/resources/sounds/snap.wav b/interface/resources/sounds/snap.wav
new file mode 100644
index 0000000000..bb562e1db9
Binary files /dev/null and b/interface/resources/sounds/snap.wav differ
diff --git a/interface/resources/styles/import_dialog.qss b/interface/resources/styles/import_dialog.qss
new file mode 100644
index 0000000000..dd9761b7ed
--- /dev/null
+++ b/interface/resources/styles/import_dialog.qss
@@ -0,0 +1,88 @@
+/*
+* import_dialog.qss
+* hifi
+*
+* Created by Stojce on 1/5/2014.
+* Copyright (c) 2014 High Fidelity, Inc. All rights reserved.
+*/
+
+* {
+ font-family: Helvetica, Arial, sans-serif;
+}
+
+QLabel {
+ font-size: 16px;
+ color: #333333;
+}
+
+QLabel#infoLabel {
+ padding: 7px 0 0 7px;
+ color: #666666;
+}
+
+QPushButton {
+ border-width: 0;
+ border-radius: 9px;
+ font-size: 18px;
+ padding: 17px 0px 15px;
+ width: 107px;
+ margin-top: 20px;
+ margin-bottom: 18px;
+}
+
+QPushButton#importButton {
+ color: #FFFFFF;
+ font-weight: bold;
+ margin-right: 6px;
+}
+
+QPushButton#importButton:enabled {
+ background: #333333;
+}
+
+QPushButton#importButton:!enabled {
+ background: rgba(50, 50, 50, 0.5);
+}
+
+QPushButton#cancelButton {
+ color: #333333;
+ background-color: #FFFFFF;
+ width: 74px;
+ margin-right: 11px;
+}
+
+QSidebar, QTreeView {
+ border: 1px solid #C5C5C5;
+ font-size: 14px;
+ margin: 0px;
+ selection-background-color: #BDE4E3;
+ selection-color: #333333;
+
+}
+
+QTreeView {
+ border-left: none;
+}
+
+QSplitter::handle, QDialog {
+ background-color: white;
+}
+
+QTreeView QHeaderView {
+ background: white;
+}
+
+QTreeView QHeaderView:section {
+ border-left: none;
+ border-top: none;
+ border-bottom: 1px solid #C5C5C5;
+ border-right: 1px solid #C5C5C5;
+ background: white;
+ color: #666666;
+ padding: 10px 20px;
+}
+
+QSidebar::item,
+QTreeView::item {
+ padding: 5px 0 4px;
+}
diff --git a/interface/src/AbstractLoggerInterface.h b/interface/src/AbstractLoggerInterface.h
index d8a48fc2fd..cedab1fad2 100644
--- a/interface/src/AbstractLoggerInterface.h
+++ b/interface/src/AbstractLoggerInterface.h
@@ -17,6 +17,7 @@ class AbstractLoggerInterface : public QObject {
Q_OBJECT
public:
+ AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {};
inline bool extraDebugging() { return _extraDebugging; };
inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; };
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index e2778fd6a8..d0d0fc4c40 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -47,9 +47,9 @@
#include
#include
#include
+#include
#include
-#include
#include
#include
#include
@@ -70,6 +70,7 @@
#include "renderer/ProgramObject.h"
#include "ui/TextRenderer.h"
#include "InfoView.h"
+#include "ui/Snapshot.h"
using namespace std;
@@ -98,6 +99,8 @@ const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f;
const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml";
const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion";
+const int STATS_PELS_PER_LINE = 20;
+
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message) {
QString messageWithNewLine = message + "\n";
fprintf(stdout, "%s", messageWithNewLine.toLocal8Bit().constData());
@@ -108,15 +111,16 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
QApplication(argc, argv),
_window(new QMainWindow(desktop())),
_glWidget(new GLCanvas()),
+ _statsExpanded(false),
_nodeThread(new QThread(this)),
_datagramProcessor(),
_frameCount(0),
_fps(120.0f),
_justStarted(true),
- _voxelImporter(_window),
+ _voxelImporter(NULL),
_wantToKillLocalVoxels(false),
_audioScope(256, 200, true),
- _avatarManager(),
+ _myAvatar(),
_profile(QString()),
_mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)),
_mouseX(0),
@@ -150,9 +154,11 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_resetRecentMaxPacketsSoon(true),
_swatch(NULL),
_pasteMode(false),
- _logger(new FileLogger()),
+ _logger(new FileLogger(this)),
_persistThread(NULL)
{
+ _myAvatar = _avatarManager.getMyAvatar();
+
_applicationStartupTime = startup_time;
switchToResourcesParentIfRequired();
@@ -178,7 +184,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_nodeThread->setPriority(QThread::TimeCriticalPriority);
// put the NodeList and datagram processing on the node thread
- NodeList* nodeList = NodeList::createInstance(NODE_TYPE_AGENT, listenPort);
+ NodeList* nodeList = NodeList::createInstance(NodeType::Agent, listenPort);
nodeList->moveToThread(_nodeThread);
_datagramProcessor.moveToThread(_nodeThread);
@@ -225,9 +231,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
#endif
// tell the NodeList instance who to tell the domain server we care about
- nodeList->addSetOfNodeTypesToNodeInterestSet(QSet() << NODE_TYPE_AUDIO_MIXER << NODE_TYPE_AVATAR_MIXER
- << NODE_TYPE_VOXEL_SERVER << NODE_TYPE_PARTICLE_SERVER
- << NODE_TYPE_METAVOXEL_SERVER);
+ nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
+ << NodeType::VoxelServer << NodeType::ParticleServer
+ << NodeType::MetavoxelServer);
// connect to the packet sent signal of the _voxelEditSender and the _particleEditSender
connect(&_voxelEditSender, &VoxelEditPacketSender::packetSent, this, &Application::packetSent);
@@ -249,7 +255,10 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_window->setCentralWidget(_glWidget);
restoreSizeAndPosition();
- loadScripts();
+
+ QFontDatabase fontDatabase;
+ fontDatabase.addApplicationFont("resources/styles/Inconsolata.otf");
+
_window->setVisible(true);
_glWidget->setFocusPolicy(Qt::StrongFocus);
_glWidget->setFocus();
@@ -275,6 +284,9 @@ Application::Application(int& argc, char** argv, timeval &startup_time) :
_sixenseManager.setFilter(Menu::getInstance()->isOptionChecked(MenuOption::FilterSixense));
checkVersion();
+
+ // do this as late as possible so that all required subsystems are inialized
+ loadScripts();
}
Application::~Application() {
@@ -309,16 +321,14 @@ Application::~Application() {
_persistThread->deleteLater();
_persistThread = NULL;
}
-
+
storeSizeAndPosition();
saveScripts();
_sharedVoxelSystem.changeTree(new VoxelTree);
VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown
Menu::getInstance()->deleteLater();
-
- delete _logger;
- delete _settings;
+
delete _glWidget;
}
@@ -431,25 +441,25 @@ void Application::paintGL() {
_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());
+ _myCamera.setTargetPosition(_myAvatar->getHead().calculateAverageEyePosition());
+ _myCamera.setTargetRotation(_myAvatar->getHead().getOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
_myCamera.setTightness(0.0f); // In first person, camera follows head exactly without delay
- _myCamera.setTargetPosition(_myAvatar.getHead().calculateAverageEyePosition());
- _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation());
+ _myCamera.setTargetPosition(_myAvatar->getHead().calculateAverageEyePosition());
+ _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.setTargetPosition(_myAvatar.getUprightHeadPosition());
- _myCamera.setTargetRotation(_myAvatar.getHead().getCameraOrientation());
+ _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
+ _myCamera.setTargetRotation(_myAvatar->getHead().getCameraOrientation());
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_myCamera.setTightness(0.0f);
- float headHeight = _myAvatar.getHead().calculateAverageEyePosition().y - _myAvatar.getPosition().y;
- _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar.getScale());
- _myCamera.setTargetPosition(_myAvatar.getPosition() + glm::vec3(0, headHeight, 0));
- _myCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
+ float headHeight = _myAvatar->getHead().calculateAverageEyePosition().y - _myAvatar->getPosition().y;
+ _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale());
+ _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0));
+ _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
}
// Update camera position
@@ -506,22 +516,22 @@ void Application::paintGL() {
bool eyeRelativeCamera = false;
if (_rearMirrorTools->getZoomLevel() == BODY) {
- _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar.getScale());
- _mirrorCamera.setTargetPosition(_myAvatar.getChestPosition());
+ _mirrorCamera.setDistance(MIRROR_REARVIEW_BODY_DISTANCE * _myAvatar->getScale());
+ _mirrorCamera.setTargetPosition(_myAvatar->getChestPosition());
} else { // HEAD zoom level
- _mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar.getScale());
- if (_myAvatar.getSkeletonModel().isActive() && _myAvatar.getHead().getFaceModel().isActive()) {
+ _mirrorCamera.setDistance(MIRROR_REARVIEW_DISTANCE * _myAvatar->getScale());
+ if (_myAvatar->getSkeletonModel().isActive() && _myAvatar->getHead().getFaceModel().isActive()) {
// as a hack until we have a better way of dealing with coordinate precision issues, reposition the
// face/body so that the average eye position lies at the origin
eyeRelativeCamera = true;
_mirrorCamera.setTargetPosition(glm::vec3());
} else {
- _mirrorCamera.setTargetPosition(_myAvatar.getHead().calculateAverageEyePosition());
+ _mirrorCamera.setTargetPosition(_myAvatar->getHead().calculateAverageEyePosition());
}
}
- _mirrorCamera.setTargetRotation(_myAvatar.getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
+ _mirrorCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PIf, 0.0f)));
_mirrorCamera.update(1.0f/_fps);
// set the bounds of rear mirror view
@@ -538,27 +548,27 @@ void Application::paintGL() {
glPushMatrix();
if (eyeRelativeCamera) {
// save absolute translations
- glm::vec3 absoluteSkeletonTranslation = _myAvatar.getSkeletonModel().getTranslation();
- glm::vec3 absoluteFaceTranslation = _myAvatar.getHead().getFaceModel().getTranslation();
+ glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
+ glm::vec3 absoluteFaceTranslation = _myAvatar->getHead().getFaceModel().getTranslation();
// get the eye positions relative to the neck and use them to set the face translation
glm::vec3 leftEyePosition, rightEyePosition;
- _myAvatar.getHead().getFaceModel().setTranslation(glm::vec3());
- _myAvatar.getHead().getFaceModel().getEyePositions(leftEyePosition, rightEyePosition);
- _myAvatar.getHead().getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f);
+ _myAvatar->getHead().getFaceModel().setTranslation(glm::vec3());
+ _myAvatar->getHead().getFaceModel().getEyePositions(leftEyePosition, rightEyePosition);
+ _myAvatar->getHead().getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f);
// get the neck position relative to the body and use it to set the skeleton translation
glm::vec3 neckPosition;
- _myAvatar.getSkeletonModel().setTranslation(glm::vec3());
- _myAvatar.getSkeletonModel().getNeckPosition(neckPosition);
- _myAvatar.getSkeletonModel().setTranslation(_myAvatar.getHead().getFaceModel().getTranslation() -
+ _myAvatar->getSkeletonModel().setTranslation(glm::vec3());
+ _myAvatar->getSkeletonModel().getNeckPosition(neckPosition);
+ _myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead().getFaceModel().getTranslation() -
neckPosition);
displaySide(_mirrorCamera, true);
// restore absolute translations
- _myAvatar.getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
- _myAvatar.getHead().getFaceModel().setTranslation(absoluteFaceTranslation);
+ _myAvatar->getSkeletonModel().setTranslation(absoluteSkeletonTranslation);
+ _myAvatar->getHead().getFaceModel().setTranslation(absoluteFaceTranslation);
} else {
displaySide(_mirrorCamera, true);
}
@@ -640,45 +650,51 @@ void Application::resetProfile(const QString& username) {
updateWindowTitle();
}
-void Application::controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes,
- const QSet& destinationNodeTypes) {
- foreach(NODE_TYPE type, destinationNodeTypes) {
+void Application::controlledBroadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) {
+ foreach(NodeType_t type, destinationNodeTypes) {
// Intercept data to voxel server when voxels are disabled
- if (type == NODE_TYPE_VOXEL_SERVER && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
+ if (type == NodeType::VoxelServer && !Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
continue;
}
// Perform the broadcast for one type
- int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(broadcastData, dataBytes,
- QSet() << type);
+ int nReceivingNodes = NodeList::getInstance()->broadcastToNodes(packet, NodeSet() << type);
// Feed number of bytes to corresponding channel of the bandwidth meter, if any (done otherwise)
BandwidthMeter::ChannelIndex channel;
switch (type) {
- case NODE_TYPE_AGENT:
- case NODE_TYPE_AVATAR_MIXER:
+ case NodeType::Agent:
+ case NodeType::AvatarMixer:
channel = BandwidthMeter::AVATARS;
break;
- case NODE_TYPE_VOXEL_SERVER:
+ case NodeType::VoxelServer:
channel = BandwidthMeter::VOXELS;
break;
default:
continue;
}
- _bandwidthMeter.outputStream(channel).updateValue(nReceivingNodes * dataBytes);
+ _bandwidthMeter.outputStream(channel).updateValue(nReceivingNodes * packet.size());
}
}
void Application::keyPressEvent(QKeyEvent* event) {
+
+ _controllerScriptingInterface.emitKeyPressEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isKeyCaptured(event)) {
+ return;
+ }
+
if (activeWindow() == _window) {
if (_chatEntryOn) {
if (_chatEntry.keyPressEvent(event)) {
- _myAvatar.setKeyState(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete ?
+ _myAvatar->setKeyState(event->key() == Qt::Key_Backspace || event->key() == Qt::Key_Delete ?
DELETE_KEY_DOWN : INSERT_KEY_DOWN);
- _myAvatar.setChatMessage(string(_chatEntry.getContents().size(), SOLID_BLOCK_CHAR));
+ _myAvatar->setChatMessage(string(_chatEntry.getContents().size(), SOLID_BLOCK_CHAR));
} else {
- _myAvatar.setChatMessage(_chatEntry.getContents());
+ _myAvatar->setChatMessage(_chatEntry.getContents());
_chatEntry.clear();
_chatEntryOn = false;
setMenuShortcutsEnabled(true);
@@ -722,10 +738,10 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (_nudgeStarted) {
_nudgeGuidePosition.y += _mouseVoxel.s;
} else {
- if (!_myAvatar.getDriveKeys(UP)) {
- _myAvatar.jump();
+ if (!_myAvatar->getDriveKeys(UP)) {
+ _myAvatar->jump();
}
- _myAvatar.setDriveKeys(UP, 1);
+ _myAvatar->setDriveKeys(UP, 1);
}
break;
@@ -737,7 +753,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
if (_nudgeStarted) {
_nudgeGuidePosition.y -= _mouseVoxel.s;
} else {
- _myAvatar.setDriveKeys(DOWN, 1);
+ _myAvatar->setDriveKeys(DOWN, 1);
}
break;
@@ -757,7 +773,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(FWD, 1);
+ _myAvatar->setDriveKeys(FWD, 1);
}
break;
@@ -766,6 +782,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
_voxels.collectStatsForTreesAndVBOs();
} else if (isShifted && isMeta) {
Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings);
+ } else if (!isShifted && isMeta) {
+ takeSnapshot();
} else if (_nudgeStarted) {
if (_lookingAlongX) {
if (_lookingAwayFromOrigin) {
@@ -781,7 +799,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(BACK, 1);
+ _myAvatar->setDriveKeys(BACK, 1);
}
break;
@@ -815,7 +833,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(ROT_LEFT, 1);
+ _myAvatar->setDriveKeys(ROT_LEFT, 1);
}
break;
@@ -835,7 +853,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(ROT_RIGHT, 1);
+ _myAvatar->setDriveKeys(ROT_RIGHT, 1);
}
break;
@@ -845,8 +863,8 @@ void Application::keyPressEvent(QKeyEvent* event) {
nudgeVoxels();
} else {
_chatEntryOn = true;
- _myAvatar.setKeyState(NO_KEY_DOWN);
- _myAvatar.setChatMessage(string());
+ _myAvatar->setKeyState(NO_KEY_DOWN);
+ _myAvatar->setChatMessage(string());
setMenuShortcutsEnabled(false);
}
break;
@@ -869,7 +887,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
} else if (_nudgeStarted && isShifted) {
_nudgeGuidePosition.y += _mouseVoxel.s;
} else {
- _myAvatar.setDriveKeys(isShifted ? UP : FWD, 1);
+ _myAvatar->setDriveKeys(isShifted ? UP : FWD, 1);
}
break;
@@ -891,7 +909,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
} else if (_nudgeStarted && isShifted) {
_nudgeGuidePosition.y -= _mouseVoxel.s;
} else {
- _myAvatar.setDriveKeys(isShifted ? DOWN : BACK, 1);
+ _myAvatar->setDriveKeys(isShifted ? DOWN : BACK, 1);
}
break;
@@ -911,7 +929,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1);
+ _myAvatar->setDriveKeys(isShifted ? LEFT : ROT_LEFT, 1);
}
break;
@@ -931,7 +949,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
}
} else {
- _myAvatar.setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1);
+ _myAvatar->setDriveKeys(isShifted ? RIGHT : ROT_RIGHT, 1);
}
break;
@@ -1049,13 +1067,13 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
break;
case Qt::Key_Plus:
- _myAvatar.increaseSize();
+ _myAvatar->increaseSize();
break;
case Qt::Key_Minus:
- _myAvatar.decreaseSize();
+ _myAvatar->decreaseSize();
break;
case Qt::Key_Equal:
- _myAvatar.resetSize();
+ _myAvatar->resetSize();
break;
case Qt::Key_1:
@@ -1069,7 +1087,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
_swatch.handleEvent(event->key(), Menu::getInstance()->isOptionChecked(MenuOption::VoxelGetColorMode));
break;
case Qt::Key_At:
- Menu::getInstance()->goToUser();
+ Menu::getInstance()->goTo();
break;
default:
event->ignore();
@@ -1079,9 +1097,18 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
void Application::keyReleaseEvent(QKeyEvent* event) {
+
+ _controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isKeyCaptured(event)) {
+ return;
+ }
+
+
if (activeWindow() == _window) {
if (_chatEntryOn) {
- _myAvatar.setKeyState(NO_KEY_DOWN);
+ _myAvatar->setKeyState(NO_KEY_DOWN);
return;
}
@@ -1090,47 +1117,47 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
_pasteMode = false;
break;
case Qt::Key_E:
- _myAvatar.setDriveKeys(UP, 0);
+ _myAvatar->setDriveKeys(UP, 0);
break;
case Qt::Key_C:
- _myAvatar.setDriveKeys(DOWN, 0);
+ _myAvatar->setDriveKeys(DOWN, 0);
break;
case Qt::Key_W:
- _myAvatar.setDriveKeys(FWD, 0);
+ _myAvatar->setDriveKeys(FWD, 0);
break;
case Qt::Key_S:
- _myAvatar.setDriveKeys(BACK, 0);
+ _myAvatar->setDriveKeys(BACK, 0);
break;
case Qt::Key_A:
- _myAvatar.setDriveKeys(ROT_LEFT, 0);
+ _myAvatar->setDriveKeys(ROT_LEFT, 0);
break;
case Qt::Key_D:
- _myAvatar.setDriveKeys(ROT_RIGHT, 0);
+ _myAvatar->setDriveKeys(ROT_RIGHT, 0);
break;
case Qt::Key_Up:
- _myAvatar.setDriveKeys(FWD, 0);
- _myAvatar.setDriveKeys(UP, 0);
+ _myAvatar->setDriveKeys(FWD, 0);
+ _myAvatar->setDriveKeys(UP, 0);
break;
case Qt::Key_Down:
- _myAvatar.setDriveKeys(BACK, 0);
- _myAvatar.setDriveKeys(DOWN, 0);
+ _myAvatar->setDriveKeys(BACK, 0);
+ _myAvatar->setDriveKeys(DOWN, 0);
break;
case Qt::Key_Left:
- _myAvatar.setDriveKeys(LEFT, 0);
- _myAvatar.setDriveKeys(ROT_LEFT, 0);
+ _myAvatar->setDriveKeys(LEFT, 0);
+ _myAvatar->setDriveKeys(ROT_LEFT, 0);
break;
case Qt::Key_Right:
- _myAvatar.setDriveKeys(RIGHT, 0);
- _myAvatar.setDriveKeys(ROT_RIGHT, 0);
+ _myAvatar->setDriveKeys(RIGHT, 0);
+ _myAvatar->setDriveKeys(ROT_RIGHT, 0);
break;
default:
@@ -1141,6 +1168,14 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
}
void Application::mouseMoveEvent(QMouseEvent* event) {
+ _controllerScriptingInterface.emitMouseMoveEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isMouseCaptured()) {
+ return;
+ }
+
+
_lastMouseMove = usecTimestampNow();
if (_mouseHidden) {
getGLWidget()->setCursor(Qt::ArrowCursor);
@@ -1157,11 +1192,11 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
// orbit behavior
if (_mousePressed && !Menu::getInstance()->isVoxelModeActionChecked()) {
if (_avatarManager.getLookAtTargetAvatar()) {
- _myAvatar.orbit(_avatarManager.getLookAtTargetAvatar()->getPosition(), deltaX, deltaY);
+ _myAvatar->orbit(_avatarManager.getLookAtTargetAvatar()->getPosition(), deltaX, deltaY);
return;
}
if (_isHoverVoxel) {
- _myAvatar.orbit(getMouseVoxelWorldCoordinates(_hoverVoxel), deltaX, deltaY);
+ _myAvatar->orbit(getMouseVoxelWorldCoordinates(_hoverVoxel), deltaX, deltaY);
return;
}
}
@@ -1187,6 +1222,14 @@ const float HOVER_VOXEL_FREQUENCY = 7040.f;
const float HOVER_VOXEL_DECAY = 0.999f;
void Application::mousePressEvent(QMouseEvent* event) {
+ _controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isMouseCaptured()) {
+ return;
+ }
+
+
if (activeWindow() == _window) {
if (event->button() == Qt::LeftButton) {
_mouseX = event->x();
@@ -1236,14 +1279,14 @@ void Application::mousePressEvent(QMouseEvent* event) {
const float PERCENTAGE_TO_MOVE_TOWARD = 0.90f;
glm::vec3 newTarget = getMouseVoxelWorldCoordinates(_hoverVoxel);
- glm::vec3 myPosition = _myAvatar.getPosition();
+ glm::vec3 myPosition = _myAvatar->getPosition();
// If there is not an action tool set (add, delete, color), move to this voxel
if (Menu::getInstance()->isOptionChecked(MenuOption::ClickToFly) &&
!(Menu::getInstance()->isOptionChecked(MenuOption::VoxelAddMode) ||
Menu::getInstance()->isOptionChecked(MenuOption::VoxelDeleteMode) ||
Menu::getInstance()->isOptionChecked(MenuOption::VoxelColorMode))) {
- _myAvatar.setMoveTarget(myPosition + (newTarget - myPosition) * PERCENTAGE_TO_MOVE_TOWARD);
+ _myAvatar->setMoveTarget(myPosition + (newTarget - myPosition) * PERCENTAGE_TO_MOVE_TOWARD);
}
}
@@ -1254,12 +1297,22 @@ void Application::mousePressEvent(QMouseEvent* event) {
}
void Application::mouseReleaseEvent(QMouseEvent* event) {
+ _controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isMouseCaptured()) {
+ return;
+ }
+
if (activeWindow() == _window) {
if (event->button() == Qt::LeftButton) {
_mouseX = event->x();
_mouseY = event->y();
_mousePressed = false;
checkBandwidthMeterClick();
+ if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
+ checkStatsClick();
+ }
_pieMenu.mouseReleaseEvent(_mouseX, _mouseY);
}
@@ -1267,6 +1320,13 @@ void Application::mouseReleaseEvent(QMouseEvent* event) {
}
void Application::touchUpdateEvent(QTouchEvent* event) {
+ _controllerScriptingInterface.emitTouchUpdateEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isTouchCaptured()) {
+ return;
+ }
+
bool validTouch = false;
if (activeWindow() == _window) {
const QList& tPoints = event->touchPoints();
@@ -1291,19 +1351,46 @@ void Application::touchUpdateEvent(QTouchEvent* event) {
}
void Application::touchBeginEvent(QTouchEvent* event) {
+ _controllerScriptingInterface.emitTouchBeginEvent(event); // send events to any registered scripts
+
touchUpdateEvent(event);
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isTouchCaptured()) {
+ return;
+ }
+
+ // put any application specific touch behavior below here..
_lastTouchAvgX = _touchAvgX;
_lastTouchAvgY = _touchAvgY;
+
}
void Application::touchEndEvent(QTouchEvent* event) {
+ _controllerScriptingInterface.emitTouchEndEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isTouchCaptured()) {
+ return;
+ }
+
+ // put any application specific touch behavior below here..
_touchDragStartedAvgX = _touchAvgX;
_touchDragStartedAvgY = _touchAvgY;
_isTouchPressed = false;
+
}
const bool USE_MOUSEWHEEL = false;
void Application::wheelEvent(QWheelEvent* event) {
+
+ _controllerScriptingInterface.emitWheelEvent(event); // send events to any registered scripts
+
+ // if one of our scripts have asked to capture this event, then stop processing it
+ if (_controllerScriptingInterface.isWheelCaptured()) {
+ return;
+ }
+
// Wheel Events disabled for now because they are also activated by touch look pitch up/down.
if (USE_MOUSEWHEEL && (activeWindow() == _window)) {
if (!Menu::getInstance()->isVoxelModeActionChecked()) {
@@ -1319,13 +1406,11 @@ void Application::wheelEvent(QWheelEvent* event) {
}
void Application::sendPingPackets() {
- unsigned char pingPacket[MAX_PACKET_SIZE];
- int length = NodeList::getInstance()->fillPingPacket(pingPacket);
-
- getInstance()->controlledBroadcastToNodes(pingPacket, length, QSet()
- << NODE_TYPE_VOXEL_SERVER << NODE_TYPE_PARTICLE_SERVER
- << NODE_TYPE_AUDIO_MIXER << NODE_TYPE_AVATAR_MIXER
- << NODE_TYPE_METAVOXEL_SERVER);
+ QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
+ controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer
+ << NodeType::ParticleServer
+ << NodeType::AudioMixer << NodeType::AvatarMixer
+ << NodeType::MetavoxelServer);
}
// Every second, check the frame rates and other stuff
@@ -1353,8 +1438,8 @@ void Application::timer() {
DataServerClient::resendUnmatchedPackets();
// give the MyAvatar object position, orientation to the Profile so it can propagate to the data-server
- _profile.updatePosition(_myAvatar.getPosition());
- _profile.updateOrientation(_myAvatar.getOrientation());
+ _profile.updatePosition(_myAvatar->getPosition());
+ _profile.updateOrientation(_myAvatar->getOrientation());
}
static glm::vec3 getFaceVector(BoxFace face) {
@@ -1463,7 +1548,7 @@ void Application::removeVoxel(glm::vec3 position,
voxel.y = position.y / TREE_SCALE;
voxel.z = position.z / TREE_SCALE;
voxel.s = scale / TREE_SCALE;
- _voxelEditSender.sendVoxelEditMessage(PACKET_TYPE_VOXEL_ERASE, voxel);
+ _voxelEditSender.sendVoxelEditMessage(PacketTypeVoxelErase, voxel);
// delete it locally to see the effect immediately (and in case no voxel server is present)
_voxels.deleteVoxelAt(voxel.x, voxel.y, voxel.z, voxel.s);
@@ -1484,7 +1569,7 @@ void Application::makeVoxel(glm::vec3 position,
voxel.red = red;
voxel.green = green;
voxel.blue = blue;
- PACKET_TYPE message = isDestructive ? PACKET_TYPE_VOXEL_SET_DESTRUCTIVE : PACKET_TYPE_VOXEL_SET;
+ PacketType message = isDestructive ? PacketTypeVoxelSetDestructive : PacketTypeVoxelSet;
_voxelEditSender.sendVoxelEditMessage(message, voxel);
// create the voxel locally so it appears immediately
@@ -1554,7 +1639,7 @@ bool Application::sendVoxelsOperation(OctreeElement* element, void* extraData) {
codeColorBuffer[bytesInCode + RED_INDEX] = voxel->getColor()[RED_INDEX];
codeColorBuffer[bytesInCode + GREEN_INDEX] = voxel->getColor()[GREEN_INDEX];
codeColorBuffer[bytesInCode + BLUE_INDEX] = voxel->getColor()[BLUE_INDEX];
- getInstance()->_voxelEditSender.queueVoxelEditMessage(PACKET_TYPE_VOXEL_SET_DESTRUCTIVE,
+ getInstance()->_voxelEditSender.queueVoxelEditMessage(PacketTypeVoxelSetDestructive,
codeColorBuffer, codeAndColorLength);
delete[] codeColorBuffer;
@@ -1582,7 +1667,12 @@ void Application::exportVoxels() {
}
void Application::importVoxels() {
- if (_voxelImporter.exec()) {
+ if (!_voxelImporter) {
+ _voxelImporter = new VoxelImporter(_window);
+ _voxelImporter->init(_settings);
+ }
+
+ if (_voxelImporter->exec()) {
qDebug("[DEBUG] Import succeeded.");
} else {
qDebug("[DEBUG] Import failed.");
@@ -1649,7 +1739,7 @@ void Application::pasteVoxels() {
}
void Application::findAxisAlignment() {
- glm::vec3 direction = _myAvatar.getMouseRayDirection();
+ glm::vec3 direction = _myAvatar->getMouseRayDirection();
if (fabs(direction.z) > fabs(direction.x)) {
_lookingAlongX = false;
if (direction.z < 0) {
@@ -1722,8 +1812,6 @@ void Application::init() {
_sharedVoxelSystem.changeTree(&_clipboard);
delete tmpTree;
- _voxelImporter.init();
-
_environment.init();
_glowEffect.init();
@@ -1735,11 +1823,10 @@ void Application::init() {
_headMouseY = _mouseY = _glWidget->height() / 2;
QCursor::setPos(_headMouseX, _headMouseY);
- _myAvatar.init();
- _myAvatar.setPosition(START_LOCATION);
+ // TODO: move _myAvatar out of Application. Move relevant code to MyAvataar or AvatarManager
+ _avatarManager.init();
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
_myCamera.setModeShiftRate(1.0f);
- _myAvatar.setDisplayingLookatVectors(false);
_mirrorCamera.setMode(CAMERA_MODE_MIRROR);
_mirrorCamera.setAspectRatio((float)MIRROR_VIEW_WIDTH / (float)MIRROR_VIEW_HEIGHT);
@@ -1786,7 +1873,18 @@ void Application::init() {
_metavoxels.init();
- _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_myAvatar);
+ _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager);
+
+ // connect the _particleCollisionSystem to our script engine's ParticleScriptingInterface
+ connect(&_particleCollisionSystem,
+ SIGNAL(particleCollisionWithVoxel(const ParticleID&, const VoxelDetail&)),
+ ScriptEngine::getParticlesScriptingInterface(),
+ SLOT(forwardParticleCollisionWithVoxel(const ParticleID&, const VoxelDetail&)));
+
+ connect(&_particleCollisionSystem,
+ SIGNAL(particleCollisionWithParticle(const ParticleID&, const ParticleID&)),
+ ScriptEngine::getParticlesScriptingInterface(),
+ SLOT(forwardParticleCollisionWithParticle(const ParticleID&, const ParticleID&)));
_palette.init(_glWidget->width(), _glWidget->height());
_palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelAddMode), 0, 0);
@@ -1844,9 +1942,9 @@ const float HEAD_SPHERE_RADIUS = 0.07f;
bool Application::isLookingAtMyAvatar(Avatar* avatar) {
glm::vec3 theirLookat = avatar->getHead().getLookAtPosition();
- glm::vec3 myHeadPosition = _myAvatar.getHead().getPosition();
+ glm::vec3 myHeadPosition = _myAvatar->getHead().getPosition();
- if (pointInSphere(theirLookat, myHeadPosition, HEAD_SPHERE_RADIUS * _myAvatar.getScale())) {
+ if (pointInSphere(theirLookat, myHeadPosition, HEAD_SPHERE_RADIUS * _myAvatar->getScale())) {
return true;
}
return false;
@@ -1884,10 +1982,10 @@ void Application::updateMouseRay() {
}
// tell my avatar if the mouse is being pressed...
- _myAvatar.setMousePressed(_mousePressed);
+ _myAvatar->setMousePressed(_mousePressed);
// tell my avatar the posiion and direction of the ray projected ino the world based on the mouse position
- _myAvatar.setMouseRay(_mouseRayOrigin, _mouseRayDirection);
+ _myAvatar->setMouseRay(_mouseRayOrigin, _mouseRayDirection);
}
void Application::updateFaceshift() {
@@ -1900,7 +1998,7 @@ void Application::updateFaceshift() {
// Copy angular velocity if measured by faceshift, to the head
if (_faceshift.isActive()) {
- _myAvatar.getHead().setAngularVelocity(_faceshift.getHeadAngularVelocity());
+ _myAvatar->getHead().setAngularVelocity(_faceshift.getHeadAngularVelocity());
}
}
@@ -1924,14 +2022,14 @@ void Application::updateMyAvatarLookAtPosition(glm::vec3& lookAtSpot) {
}
if (_faceshift.isActive()) {
// deflect using Faceshift gaze data
- glm::vec3 origin = _myAvatar.getHead().calculateAverageEyePosition();
+ glm::vec3 origin = _myAvatar->getHead().calculateAverageEyePosition();
float pitchSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? -1.0f : 1.0f;
float deflection = Menu::getInstance()->getFaceshiftEyeDeflection();
lookAtSpot = origin + _myCamera.getRotation() * glm::quat(glm::radians(glm::vec3(
_faceshift.getEstimatedEyePitch() * pitchSign * deflection, _faceshift.getEstimatedEyeYaw() * deflection, 0.0f))) *
glm::inverse(_myCamera.getRotation()) * (lookAtSpot - origin);
}
- _myAvatar.getHead().setLookAtPosition(lookAtSpot);
+ _myAvatar->getHead().setLookAtPosition(lookAtSpot);
}
void Application::updateHoverVoxels(float deltaTime, float& distance, BoxFace& face) {
@@ -1989,9 +2087,9 @@ void Application::updateMouseVoxels(float deltaTime, float& distance, BoxFace& f
_mouseVoxel.s = 0.0f;
bool wasInitialized = _mouseVoxelScaleInitialized;
if (Menu::getInstance()->isVoxelModeActionChecked() &&
- (fabs(_myAvatar.getVelocity().x) +
- fabs(_myAvatar.getVelocity().y) +
- fabs(_myAvatar.getVelocity().z)) / 3 < MAX_AVATAR_EDIT_VELOCITY) {
+ (fabs(_myAvatar->getVelocity().x) +
+ fabs(_myAvatar->getVelocity().y) +
+ fabs(_myAvatar->getVelocity().z)) / 3 < MAX_AVATAR_EDIT_VELOCITY) {
if (_voxels.findRayIntersection(_mouseRayOrigin, _mouseRayDirection, _mouseVoxel, distance, face)) {
if (distance < MAX_VOXEL_EDIT_DISTANCE) {
@@ -2123,16 +2221,16 @@ void Application::updateMyAvatarSimulation(float deltaTime) {
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarSimulation()");
if (Menu::getInstance()->isOptionChecked(MenuOption::Gravity)) {
- _myAvatar.setGravity(_environment.getGravity(_myAvatar.getPosition()));
+ _myAvatar->setGravity(_environment.getGravity(_myAvatar->getPosition()));
}
else {
- _myAvatar.setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
+ _myAvatar->setGravity(glm::vec3(0.0f, 0.0f, 0.0f));
}
if (Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) {
- _myAvatar.simulate(deltaTime, &_myTransmitter);
+ _myAvatar->simulate(deltaTime, &_myTransmitter);
} else {
- _myAvatar.simulate(deltaTime, NULL);
+ _myAvatar->simulate(deltaTime, NULL);
}
}
@@ -2160,8 +2258,8 @@ void Application::updateTransmitter(float deltaTime) {
// no transmitter drive implies transmitter pick
if (!Menu::getInstance()->isOptionChecked(MenuOption::TransmitterDrive) && _myTransmitter.isConnected()) {
- _transmitterPickStart = _myAvatar.getChestPosition();
- glm::vec3 direction = _myAvatar.getOrientation() *
+ _transmitterPickStart = _myAvatar->getChestPosition();
+ glm::vec3 direction = _myAvatar->getOrientation() *
glm::quat(glm::radians(_myTransmitter.getEstimatedRotation())) * IDENTITY_FRONT;
// check against voxels, avatars
@@ -2235,8 +2333,8 @@ void Application::updateAudio(float deltaTime) {
PerformanceWarning warn(showWarnings, "Application::updateAudio()");
// Update audio stats for procedural sounds
- _audio.setLastAcceleration(_myAvatar.getThrust());
- _audio.setLastVelocity(_myAvatar.getVelocity());
+ _audio.setLastAcceleration(_myAvatar->getThrust());
+ _audio.setLastVelocity(_myAvatar->getVelocity());
}
void Application::updateCursor(float deltaTime) {
@@ -2246,7 +2344,7 @@ void Application::updateCursor(float deltaTime) {
// watch mouse position, if it hasn't moved, hide the cursor
bool underMouse = _glWidget->underMouse();
if (!_mouseHidden) {
- uint64_t now = usecTimestampNow();
+ quint64 now = usecTimestampNow();
int elapsed = now - _lastMouseMove;
const int HIDE_CURSOR_TIMEOUT = 1 * 1000 * 1000; // 1 second
if (elapsed > HIDE_CURSOR_TIMEOUT && (underMouse || !_seenMouseMove)) {
@@ -2300,7 +2398,7 @@ void Application::update(float deltaTime) {
updateCursor(deltaTime); // Handle cursor updates
_particles.update(); // update the particles...
- _particleCollisionSystem.update(); // handle collisions for the particles...
+ _particleCollisionSystem.update(); // collide the particles...
}
void Application::updateAvatar(float deltaTime) {
@@ -2308,19 +2406,16 @@ void Application::updateAvatar(float deltaTime) {
PerformanceWarning warn(showWarnings, "Application::updateAvatar()");
// rotate body yaw for yaw received from multitouch
- _myAvatar.setOrientation(_myAvatar.getOrientation()
+ _myAvatar->setOrientation(_myAvatar->getOrientation()
* glm::quat(glm::vec3(0, _yawFromTouch, 0)));
_yawFromTouch = 0.f;
// apply pitch from touch
- _myAvatar.getHead().setMousePitch(_myAvatar.getHead().getMousePitch() +
- _myAvatar.getHand().getPitchUpdate() +
- _pitchFromTouch);
- _myAvatar.getHand().setPitchUpdate(0.f);
+ _myAvatar->getHead().setPitch(_myAvatar->getHead().getPitch() + _pitchFromTouch);
_pitchFromTouch = 0.0f;
// Update my avatar's state from gyros
- _myAvatar.updateFromGyros(Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead));
+ _myAvatar->updateFromGyros(Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead));
// Update head mouse from faceshift if active
if (_faceshift.isActive()) {
@@ -2341,27 +2436,19 @@ void Application::updateAvatar(float deltaTime) {
float yaw, pitch, roll;
OculusManager::getEulerAngles(yaw, pitch, roll);
- _myAvatar.getHead().setYaw(yaw);
- _myAvatar.getHead().setPitch(pitch);
- _myAvatar.getHead().setRoll(roll);
+ _myAvatar->getHead().setYaw(yaw);
+ _myAvatar->getHead().setPitch(pitch);
+ _myAvatar->getHead().setRoll(roll);
}
// Get audio loudness data from audio input device
- _myAvatar.getHead().setAudioLoudness(_audio.getLastInputLoudness());
+ _myAvatar->getHead().setAudioLoudness(_audio.getLastInputLoudness());
// send head/hand data to the avatar mixer and voxel server
- unsigned char broadcastString[MAX_PACKET_SIZE];
- unsigned char* endOfBroadcastStringWrite = broadcastString;
+ QByteArray avatarData = byteArrayWithPopluatedHeader(PacketTypeAvatarData);
+ avatarData.append(_myAvatar->toByteArray());
- endOfBroadcastStringWrite += populateTypeAndVersion(endOfBroadcastStringWrite, PACKET_TYPE_HEAD_DATA);
-
- // pack the NodeList owner UUID
- endOfBroadcastStringWrite += NodeList::getInstance()->packOwnerUUID(endOfBroadcastStringWrite);
-
- endOfBroadcastStringWrite += _myAvatar.getBroadcastData(endOfBroadcastStringWrite);
-
- controlledBroadcastToNodes(broadcastString, endOfBroadcastStringWrite - broadcastString,
- QSet() << NODE_TYPE_AVATAR_MIXER);
+ controlledBroadcastToNodes(avatarData, NodeSet() << NodeType::AvatarMixer);
// Update _viewFrustum with latest camera and view frustum data...
// NOTE: we get this from the view frustum, to make it simpler, since the
@@ -2372,11 +2459,11 @@ void Application::updateAvatar(float deltaTime) {
loadViewFrustum(_myCamera, _viewFrustum);
// Update my voxel servers with my current voxel query...
- queryOctree(NODE_TYPE_VOXEL_SERVER, PACKET_TYPE_VOXEL_QUERY, _voxelServerJurisdictions);
- queryOctree(NODE_TYPE_PARTICLE_SERVER, PACKET_TYPE_PARTICLE_QUERY, _particleServerJurisdictions);
+ queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions);
+ queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions);
}
-void Application::queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions) {
+void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) {
// if voxels are disabled, then don't send this at all...
if (!Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
@@ -2544,10 +2631,7 @@ void Application::queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, Node
unsigned char* endOfVoxelQueryPacket = voxelQueryPacket;
// insert packet type/version and node UUID
- endOfVoxelQueryPacket += populateTypeAndVersion(endOfVoxelQueryPacket, packetType);
- QByteArray ownerUUID = nodeList->getOwnerUUID().toRfc4122();
- memcpy(endOfVoxelQueryPacket, ownerUUID.constData(), ownerUUID.size());
- endOfVoxelQueryPacket += ownerUUID.size();
+ endOfVoxelQueryPacket += populatePacketHeader(reinterpret_cast(endOfVoxelQueryPacket), packetType);
// encode the query data...
endOfVoxelQueryPacket += _voxelQuery.getBroadcastData(endOfVoxelQueryPacket);
@@ -2977,9 +3061,11 @@ void Application::displayOverlay() {
}
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
+ displayStatsBackground(0x33333399, 0, _glWidget->height() - 68, 296, 68);
_audio.render(_glWidget->width(), _glWidget->height());
if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) {
- _audioScope.render(45, _glWidget->height() - 200);
+ int oscilloscopeTop = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) ? 130 : 25;
+ _audioScope.render(25, oscilloscopeTop);
}
}
@@ -3034,17 +3120,9 @@ void Application::displayOverlay() {
displayStats();
// Bandwidth meter
if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) {
+ displayStatsBackground(0x33333399, _glWidget->width() - 296, _glWidget->height() - 68, 296, 68);
_bandwidthMeter.render(_glWidget->width(), _glWidget->height());
}
- // Stats at upper right of screen about who domain server is telling us about
- glPointSize(1.0f);
- char nodes[100];
-
- int totalAvatars = _avatarManager.size();
- int totalServers = NodeList::getInstance()->size();
-
- sprintf(nodes, "Servers: %d, Avatars: %d\n", totalServers, totalAvatars);
- drawtext(_glWidget->width() - 150, 20, 0.10f, 0, 1.0f, 0, nodes, 1, 0, 0);
}
// testing rendering coverage map
@@ -3064,10 +3142,14 @@ void Application::displayOverlay() {
// Show on-screen msec timer
if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) {
char frameTimer[10];
- uint64_t mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5);
+ quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5);
sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000));
- drawtext(_glWidget->width() - 100, _glWidget->height() - 20, 0.30f, 0, 1.0f, 0, frameTimer, 0, 0, 0);
- drawtext(_glWidget->width() - 102, _glWidget->height() - 22, 0.30f, 0, 1.0f, 0, frameTimer, 1, 1, 1);
+ int timerBottom =
+ (Menu::getInstance()->isOptionChecked(MenuOption::Stats) &&
+ Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth))
+ ? 80 : 20;
+ drawtext(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 0, 1.0f, 0, frameTimer, 0, 0, 0);
+ drawtext(_glWidget->width() - 102, _glWidget->height() - timerBottom - 2, 0.30f, 0, 1.0f, 0, frameTimer, 1, 1, 1);
}
_palette.render(_glWidget->width(), _glWidget->height());
@@ -3125,33 +3207,89 @@ void Application::displayOverlay() {
glPopMatrix();
}
+// translucent background box that makes stats more readable
+void Application::displayStatsBackground(unsigned int rgba, int x, int y, int width, int height) {
+ glBegin(GL_QUADS);
+ glColor4f(((rgba >> 24) & 0xff) / 255.0f,
+ ((rgba >> 16) & 0xff) / 255.0f,
+ ((rgba >> 8) & 0xff) / 255.0f,
+ (rgba & 0xff) / 255.0f);
+ glVertex3f(x, y, 0);
+ glVertex3f(x + width, y, 0);
+ glVertex3f(x + width, y + height, 0);
+ glVertex3f(x , y + height, 0);
+ glEnd();
+ glColor4f(1, 1, 1, 1);
+}
+// display expanded or contracted stats
void Application::displayStats() {
- int statsVerticalOffset = 8;
- const int PELS_PER_LINE = 15;
- char stats[200];
- statsVerticalOffset += PELS_PER_LINE;
- sprintf(stats, "%3.0f FPS, %d Pkts/sec, %3.2f Mbps ",
- _fps, _packetsPerSecond, (float)_bytesPerSecond * 8.f / 1000000.f);
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, stats);
+ unsigned int backgroundColor = 0x33333399;
+ int verticalOffset = 0, horizontalOffset = 0, lines = 0;
+ bool mirrorEnabled = Menu::getInstance()->isOptionChecked(MenuOption::Mirror);
+
+ QLocale locale(QLocale::English);
+ std::stringstream voxelStats;
+
+ glPointSize(1.0f);
+
+ // we need to take one avatar out so we don't include ourselves
+ int totalAvatars = _avatarManager.size() - 1;
+ int totalServers = NodeList::getInstance()->size();
+
+ if (mirrorEnabled) {
+ horizontalOffset += MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2;
+ }
+
+ lines = _statsExpanded ? 5 : 3;
+ displayStatsBackground(backgroundColor, horizontalOffset, 0, 165, lines * STATS_PELS_PER_LINE + 10);
+ horizontalOffset += 5;
+
+ char serverNodes[30];
+ sprintf(serverNodes, "Servers: %d", totalServers);
+ char avatarNodes[30];
+ sprintf(avatarNodes, "Avatars: %d", totalAvatars);
+ char framesPerSecond[30];
+ sprintf(framesPerSecond, "Framerate: %3.0f FPS", _fps);
+
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0f, 2, serverNodes, .93f, .93f, .93f);
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0f, 2, avatarNodes, .93f, .93f, .93f);
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, framesPerSecond, .93f, .93f, .93f);
+
+ if (_statsExpanded) {
+ char packetsPerSecond[30];
+ sprintf(packetsPerSecond, "Pkts/sec: %d", _packetsPerSecond);
+ char averageMegabitsPerSecond[30];
+ sprintf(averageMegabitsPerSecond, "Avg Mbps: %3.2f", (float)_bytesPerSecond * 8.f / 1000000.f);
+
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, packetsPerSecond, .93f, .93f, .93f);
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, averageMegabitsPerSecond, .93f, .93f, .93f);
+ }
+
+ verticalOffset = 0;
+ horizontalOffset += 161;
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
int pingAudio = 0, pingAvatar = 0, pingVoxel = 0, pingVoxelMax = 0;
NodeList* nodeList = NodeList::getInstance();
- SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER);
- SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NODE_TYPE_AVATAR_MIXER);
+ SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
+ SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
pingAudio = audioMixerNode ? audioMixerNode->getPingMs() : 0;
pingAvatar = avatarMixerNode ? avatarMixerNode->getPingMs() : 0;
-
// Now handle voxel servers, since there could be more than one, we average their ping times
unsigned long totalPingVoxel = 0;
int voxelServerCount = 0;
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
- if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (node->getType() == NodeType::VoxelServer) {
totalPingVoxel += node->getPingMs();
voxelServerCount++;
if (pingVoxelMax < node->getPingMs()) {
@@ -3164,35 +3302,115 @@ void Application::displayStats() {
pingVoxel = totalPingVoxel/voxelServerCount;
}
- char pingStats[200];
- statsVerticalOffset += PELS_PER_LINE;
- sprintf(pingStats, "Ping audio/avatar/voxel: %d / %d / %d avg %d max ", pingAudio, pingAvatar, pingVoxel, pingVoxelMax);
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, pingStats);
+ lines = _statsExpanded ? 4 : 3;
+ displayStatsBackground(backgroundColor, horizontalOffset, 0, 175, lines * STATS_PELS_PER_LINE + 10);
+ horizontalOffset += 5;
+
+ char audioPing[30];
+ sprintf(audioPing, "Audio ping: %d", pingAudio);
+ char avatarPing[30];
+ sprintf(avatarPing, "Avatar ping: %d", pingAvatar);
+ char voxelAvgPing[30];
+ sprintf(voxelAvgPing, "Voxel avg ping: %d", pingVoxel);
+
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, audioPing, .93f, .93f, .93f);
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarPing, .93f, .93f, .93f);
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, voxelAvgPing, .93f, .93f, .93f);
+
+ if (_statsExpanded) {
+ char voxelMaxPing[30];
+ sprintf(voxelMaxPing, "Voxel max ping: %d", pingVoxelMax);
+
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, voxelMaxPing, .93f, .93f, .93f);
+ }
+
+ verticalOffset = 0;
+ horizontalOffset += 171;
}
- char avatarStats[200];
- statsVerticalOffset += PELS_PER_LINE;
- glm::vec3 avatarPos = _myAvatar.getPosition();
- sprintf(avatarStats, "Avatar: pos %.3f, %.3f, %.3f, vel %.1f, yaw = %.2f", avatarPos.x, avatarPos.y, avatarPos.z, glm::length(_myAvatar.getVelocity()), _myAvatar.getBodyYaw());
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarStats);
+ glm::vec3 avatarPos = _myAvatar->getPosition();
- SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER);
- char avatarMixerStats[200];
- if (avatarMixer) {
- sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps",
- roundf(avatarMixer->getAverageKilobitsPerSecond()),
- roundf(avatarMixer->getAveragePacketsPerSecond()));
+ lines = _statsExpanded ? 4 : 3;
+ displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - (mirrorEnabled ? 301 : 411) - horizontalOffset, lines * STATS_PELS_PER_LINE + 10);
+ horizontalOffset += 5;
+
+ char avatarPosition[200];
+ if (mirrorEnabled) {
+ // shorthand formatting
+ sprintf(avatarPosition, "Pos: %.0f,%.0f,%.0f", avatarPos.x, avatarPos.y, avatarPos.z);
} else {
- sprintf(avatarMixerStats, "No Avatar Mixer");
+ // longhand way
+ sprintf(avatarPosition, "Position: %.3f, %.3f, %.3f", avatarPos.x, avatarPos.y, avatarPos.z);
+ }
+ char avatarVelocity[30];
+ sprintf(avatarVelocity, "Velocity: %.1f", glm::length(_myAvatar->getVelocity()));
+ char avatarBodyYaw[30];
+ sprintf(avatarBodyYaw, "Yaw: %.2f", _myAvatar->getBodyYaw());
+ char avatarMixerStats[200];
+
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarPosition, .93f, .93f, .93f);
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarVelocity, .93f, .93f, .93f);
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarBodyYaw, .93f, .93f, .93f);
+
+ if (_statsExpanded) {
+ SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AvatarMixer);
+ if (avatarMixer) {
+ sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps",
+ roundf(avatarMixer->getAverageKilobitsPerSecond()),
+ roundf(avatarMixer->getAveragePacketsPerSecond()));
+ } else {
+ sprintf(avatarMixerStats, "No Avatar Mixer");
+ }
+
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarMixerStats, .93f, .93f, .93f);
}
- statsVerticalOffset += PELS_PER_LINE;
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarMixerStats);
+ verticalOffset = 0;
+ horizontalOffset = _glWidget->width() - (mirrorEnabled ? 300 : 410);
- // Used for formatting voxel stats details
- statsVerticalOffset += PELS_PER_LINE; // skip a line for voxels
- QLocale locale(QLocale::English);
- std::stringstream voxelStats;
+ lines = _statsExpanded ? 11 : 3;
+ displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10);
+ horizontalOffset += 5;
+
+ if (_statsExpanded) {
+ // Local Voxel Memory Usage
+ voxelStats.str("");
+ voxelStats << "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB";
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
+
+ voxelStats.str("");
+ voxelStats <<
+ "Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB / " <<
+ "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB";
+ if (_voxels.hasVoxelMemoryUsageGPU()) {
+ voxelStats << " / GPU: " << _voxels.getVoxelMemoryUsageGPU() / 1000000.f << "MB";
+ }
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
+
+ // Voxel Rendering
+ voxelStats.str("");
+ voxelStats.precision(4);
+ voxelStats << "Voxel Rendering Slots Max: " << _voxels.getMaxVoxels() / 1000.f << "K";
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
+ }
+
+ voxelStats.str("");
+ voxelStats.precision(4);
+ voxelStats << "Drawn: " << _voxels.getVoxelsWritten() / 1000.f << "K " <<
+ "Abandoned: " << _voxels.getAbandonedVoxels() / 1000.f << "K ";
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
// iterate all the current voxel stats, and list their sending modes, and total voxel counts
std::stringstream sendingMode("");
@@ -3206,93 +3424,50 @@ void Application::displayStats() {
//const QUuid& uuid = i->first;
VoxelSceneStats& stats = i->second;
serverCount++;
- if (serverCount > 1) {
- sendingMode << ",";
- }
- if (stats.isMoving()) {
- sendingMode << "M";
- movingServerCount++;
- } else {
- sendingMode << "S";
+ if (_statsExpanded) {
+ if (serverCount > 1) {
+ sendingMode << ",";
+ }
+ if (stats.isMoving()) {
+ sendingMode << "M";
+ movingServerCount++;
+ } else {
+ sendingMode << "S";
+ }
}
// calculate server node totals
totalNodes += stats.getTotalElements();
- totalInternal += stats.getTotalInternal();
- totalLeaves += stats.getTotalLeaves();
+ if (_statsExpanded) {
+ totalInternal += stats.getTotalInternal();
+ totalLeaves += stats.getTotalLeaves();
+ }
}
- if (serverCount == 0) {
- sendingMode << "---";
+ if (_statsExpanded) {
+ if (serverCount == 0) {
+ sendingMode << "---";
+ }
+ sendingMode << "] " << serverCount << " servers";
+ if (movingServerCount > 0) {
+ sendingMode << " ";
+ } else {
+ sendingMode << " ";
+ }
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)sendingMode.str().c_str(), .93f, .93f, .93f);
}
- sendingMode << "] " << serverCount << " servers";
- if (movingServerCount > 0) {
- sendingMode << " ";
- } else {
- sendingMode << " ";
- }
-
- QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' ');
- QString serversInternalString = locale.toString((uint)totalInternal);
- QString serversLeavesString = locale.toString((uint)totalLeaves);
-
- // Server Voxels
- voxelStats.str("");
- voxelStats <<
- "Server Voxels Total: " << serversTotalString.toLocal8Bit().constData() << " / " <<
- "Internal: " << serversInternalString.toLocal8Bit().constData() << " / " <<
- "Leaves: " << serversLeavesString.toLocal8Bit().constData() << "";
- statsVerticalOffset += PELS_PER_LINE;
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
-
- unsigned long localTotal = VoxelTreeElement::getNodeCount();
- unsigned long localInternal = VoxelTreeElement::getInternalNodeCount();
- unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount();
- QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' ');
- QString localInternalString = locale.toString((uint)localInternal);
- QString localLeavesString = locale.toString((uint)localLeaves);
-
- // Local Voxels
- voxelStats.str("");
- voxelStats <<
- "Local Voxels Total: " << localTotalString.toLocal8Bit().constData() << " / " <<
- "Internal: " << localInternalString.toLocal8Bit().constData() << " / " <<
- "Leaves: " << localLeavesString.toLocal8Bit().constData() << "";
- statsVerticalOffset += PELS_PER_LINE;
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
-
- // Local Voxel Memory Usage
- voxelStats.str("");
- voxelStats <<
- "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB "
- "Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB " <<
- "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB ";
- if (_voxels.hasVoxelMemoryUsageGPU()) {
- voxelStats << "GPU: " << _voxels.getVoxelMemoryUsageGPU() / 1000000.f << "MB ";
- }
- statsVerticalOffset += PELS_PER_LINE;
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
-
- // Voxel Rendering
- voxelStats.str("");
- voxelStats.precision(4);
- voxelStats << "Voxel Rendering Slots " <<
- "Max: " << _voxels.getMaxVoxels() / 1000.f << "K " <<
- "Drawn: " << _voxels.getVoxelsWritten() / 1000.f << "K " <<
- "Abandoned: " << _voxels.getAbandonedVoxels() / 1000.f << "K ";
- statsVerticalOffset += PELS_PER_LINE;
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
-
- // draw Sending mode AFTER server node stats
- statsVerticalOffset += PELS_PER_LINE;
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)sendingMode.str().c_str());
// Incoming packets
- voxelStats.str("");
int voxelPacketsToProcess = _voxelProcessor.packetsToProcessCount();
- QString packetsString = locale.toString((int)voxelPacketsToProcess);
- QString maxString = locale.toString((int)_recentMaxPackets);
- voxelStats << "Voxel Packets to Process: " << packetsString.toLocal8Bit().constData()
- << " [Recent Max: " << maxString.toLocal8Bit().constData() << "]";
+ if (_statsExpanded) {
+ voxelStats.str("");
+ QString packetsString = locale.toString((int)voxelPacketsToProcess);
+ QString maxString = locale.toString((int)_recentMaxPackets);
+ voxelStats << "Voxel Packets to Process: " << packetsString.toLocal8Bit().constData()
+ << " [Recent Max: " << maxString.toLocal8Bit().constData() << "]";
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
+ }
if (_resetRecentMaxPacketsSoon && voxelPacketsToProcess > 0) {
_recentMaxPackets = 0;
@@ -3305,8 +3480,109 @@ void Application::displayStats() {
_recentMaxPackets = voxelPacketsToProcess;
}
}
- statsVerticalOffset += PELS_PER_LINE;
- drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str());
+
+ verticalOffset += (_statsExpanded ? STATS_PELS_PER_LINE : 0);
+
+ QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' ');
+
+ // Server Voxels
+ voxelStats.str("");
+ voxelStats << "Server voxels: " << serversTotalString.toLocal8Bit().constData();
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
+
+ if (_statsExpanded) {
+ QString serversInternalString = locale.toString((uint)totalInternal);
+ QString serversLeavesString = locale.toString((uint)totalLeaves);
+
+ voxelStats.str("");
+ voxelStats <<
+ "Internal: " << serversInternalString.toLocal8Bit().constData() << " " <<
+ "Leaves: " << serversLeavesString.toLocal8Bit().constData() << "";
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
+ }
+
+ unsigned long localTotal = VoxelTreeElement::getNodeCount();
+ QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' ');
+
+ // Local Voxels
+ voxelStats.str("");
+ voxelStats << "Local voxels: " << localTotalString.toLocal8Bit().constData();
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
+
+ if (_statsExpanded) {
+ unsigned long localInternal = VoxelTreeElement::getInternalNodeCount();
+ unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount();
+ QString localInternalString = locale.toString((uint)localInternal);
+ QString localLeavesString = locale.toString((uint)localLeaves);
+
+ voxelStats.str("");
+ voxelStats <<
+ "Internal: " << localInternalString.toLocal8Bit().constData() << " " <<
+ "Leaves: " << localLeavesString.toLocal8Bit().constData() << "";
+ verticalOffset += STATS_PELS_PER_LINE;
+ drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f);
+ }
+}
+
+// called on mouse click release
+// check for clicks over stats in order to expand or contract them
+void Application::checkStatsClick() {
+ if (0 != glm::compMax(glm::abs(glm::ivec2(_mouseX - _mouseDragStartedX, _mouseY - _mouseDragStartedY)))) {
+ // not worried about dragging on stats
+ return;
+ }
+
+ int statsHeight = 0, statsWidth = 0, statsX = 0, statsY = 0, lines = 0;
+
+ if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) {
+ statsX += MIRROR_VIEW_WIDTH;
+ }
+
+ // top-left stats click
+ lines = _statsExpanded ? 5 : 3;
+ statsHeight = lines * STATS_PELS_PER_LINE + 10;
+ statsWidth = 165;
+ if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) {
+ toggleStatsExpanded();
+ return;
+ }
+
+ // ping stats click
+ lines = _statsExpanded ? 4 : 3;
+ statsX += statsWidth;
+ statsHeight = lines * STATS_PELS_PER_LINE + 10;
+ statsWidth = 175;
+ if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) {
+ toggleStatsExpanded();
+ return;
+ }
+
+ // top-center stats panel click
+ lines = _statsExpanded ? 4 : 3;
+ statsX += statsWidth;
+ statsHeight = lines * STATS_PELS_PER_LINE + 10;
+ statsWidth = _glWidget->width() - 411 - statsX;
+ if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) {
+ toggleStatsExpanded();
+ return;
+ }
+
+ // top-right stats click
+ lines = _statsExpanded ? 11 : 3;
+ statsX = _glWidget->width() - 410;
+ statsHeight = lines * STATS_PELS_PER_LINE + 10;
+ statsWidth = _glWidget->width() - statsX;
+ if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) {
+ toggleStatsExpanded();
+ return;
+ }
+}
+
+void Application::toggleStatsExpanded() {
+ _statsExpanded = !_statsExpanded;
}
void Application::renderThrustAtVoxel(const glm::vec3& thrust) {
@@ -3658,7 +3934,7 @@ bool Application::maybeEditVoxelUnderCursor() {
void Application::deleteVoxelUnderCursor() {
if (_mouseVoxel.s != 0) {
// sending delete to the server is sufficient, server will send new version so we see updates soon enough
- _voxelEditSender.sendVoxelEditMessage(PACKET_TYPE_VOXEL_ERASE, _mouseVoxel);
+ _voxelEditSender.sendVoxelEditMessage(PacketTypeVoxelErase, _mouseVoxel);
// delete it locally to see the effect immediately (and in case no voxel server is present)
_voxels.deleteVoxelAt(_mouseVoxel.x, _mouseVoxel.y, _mouseVoxel.z, _mouseVoxel.s);
@@ -3694,10 +3970,10 @@ void Application::resetSensors() {
}
QCursor::setPos(_headMouseX, _headMouseY);
- _myAvatar.reset();
+ _myAvatar->reset();
_myTransmitter.resetLevels();
- _myAvatar.setVelocity(glm::vec3(0,0,0));
- _myAvatar.setThrust(glm::vec3(0,0,0));
+ _myAvatar->setVelocity(glm::vec3(0,0,0));
+ _myAvatar->setThrust(glm::vec3(0,0,0));
QMetaObject::invokeMethod(&_audio, "reset", Qt::QueuedConnection);
}
@@ -3722,18 +3998,12 @@ void Application::setMenuShortcutsEnabled(bool enabled) {
}
void Application::updateWindowTitle(){
- QString title = "";
-
- QString buildVersion = " (build " + applicationVersion() + ")";
-
- QString username = _profile.getUsername();
- if(!username.isEmpty()){
- title += username;
- title += " @ ";
- }
- title += NodeList::getInstance()->getDomainHostname();
- title += buildVersion;
+ QString buildVersion = " (build " + applicationVersion() + ")";
+ NodeList* nodeList = NodeList::getInstance();
+
+ QString title = QString() + _profile.getUsername() + " " + nodeList->getOwnerUUID().toString()
+ + " @ " + nodeList->getDomainHostname() + buildVersion;
qDebug("Application title set to: %s", title.toStdString().c_str());
_window->setWindowTitle(title);
@@ -3762,7 +4032,7 @@ void Application::domainChanged(const QString& domainHostname) {
}
void Application::nodeKilled(SharedNodePointer node) {
- if (node->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (node->getType() == NodeType::VoxelServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
if (_voxelServerJurisdictions.find(nodeUUID) != _voxelServerJurisdictions.end()) {
@@ -3793,7 +4063,7 @@ void Application::nodeKilled(SharedNodePointer node) {
}
_voxelSceneStatsLock.unlock();
- } else if (node->getType() == NODE_TYPE_PARTICLE_SERVER) {
+ } else if (node->getType() == NodeType::ParticleServer) {
QUuid nodeUUID = node->getUUID();
// see if this is the first we've heard of this node...
if (_particleServerJurisdictions.find(nodeUUID) != _particleServerJurisdictions.end()) {
@@ -3824,14 +4094,13 @@ void Application::nodeKilled(SharedNodePointer node) {
}
_voxelSceneStatsLock.unlock();
- } else if (node->getType() == NODE_TYPE_AVATAR_MIXER) {
+ } else if (node->getType() == NodeType::AvatarMixer) {
// our avatar mixer has gone away - clear the hash of avatars
- _avatarManager.clearHash();
+ _avatarManager.clearMixedAvatars();
}
}
-void Application::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
- const HifiSockAddr& senderSockAddr, bool wasStatsPacket) {
+void Application::trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket) {
// Attempt to identify the sender from it's address.
SharedNodePointer serverNode = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
@@ -3842,13 +4111,13 @@ void Application::trackIncomingVoxelPacket(unsigned char* messageData, ssize_t m
_voxelSceneStatsLock.lockForWrite();
if (_octreeServerSceneStats.find(nodeUUID) != _octreeServerSceneStats.end()) {
VoxelSceneStats& stats = _octreeServerSceneStats[nodeUUID];
- stats.trackIncomingOctreePacket(messageData, messageLength, wasStatsPacket, serverNode->getClockSkewUsec());
+ stats.trackIncomingOctreePacket(packet, wasStatsPacket, serverNode->getClockSkewUsec());
}
_voxelSceneStatsLock.unlock();
}
}
-int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderSockAddr) {
+int Application::parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderSockAddr) {
// But, also identify the sender, and keep track of the contained jurisdiction root for this server
SharedNodePointer server = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
@@ -3856,7 +4125,7 @@ int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLen
// parse the incoming stats datas stick it in a temporary object for now, while we
// determine which server it belongs to
VoxelSceneStats temp;
- int statsMessageLength = temp.unpackFromMessage(messageData, messageLength);
+ int statsMessageLength = temp.unpackFromMessage(reinterpret_cast(packet.data()), packet.size());
// quick fix for crash... why would voxelServer be NULL?
if (server) {
@@ -3865,7 +4134,8 @@ int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLen
// 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()) {
- _octreeServerSceneStats[nodeUUID].unpackFromMessage(messageData, messageLength);
+ _octreeServerSceneStats[nodeUUID].unpackFromMessage(reinterpret_cast(packet.data()),
+ packet.size());
} else {
_octreeServerSceneStats[nodeUUID] = temp;
}
@@ -3876,7 +4146,7 @@ int Application::parseOctreeStats(unsigned char* messageData, ssize_t messageLen
// see if this is the first we've heard of this node...
NodeToJurisdictionMap* jurisdiction = NULL;
- if (server->getType() == NODE_TYPE_VOXEL_SERVER) {
+ if (server->getType() == NodeType::VoxelServer) {
jurisdiction = &_voxelServerJurisdictions;
} else {
jurisdiction = &_particleServerJurisdictions;
@@ -3911,37 +4181,37 @@ void Application::packetSent(quint64 length) {
_bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(length);
}
-void Application::loadScripts(){
- // loads all saved scripts
- QSettings* settings = new QSettings(this);
- int size = settings->beginReadArray("Settings");
- for(int i=0; isetArrayIndex(i);
- QString string = settings->value("script").toString();
- loadScript(string);
- }
- settings->endArray();
-
+void Application::loadScripts() {
+ // loads all saved scripts
+ QSettings* settings = new QSettings(this);
+ int size = settings->beginReadArray("Settings");
+
+ for (int i = 0; i < size; ++i){
+ settings->setArrayIndex(i);
+ QString string = settings->value("script").toString();
+ loadScript(string);
+ }
+
+ settings->endArray();
}
-void Application::saveScripts(){
- // saves all current running scripts
- QSettings* settings = new QSettings(this);
- settings->beginWriteArray("Settings");
- for(int i=0; i<_activeScripts.size(); ++i){
- settings->setArrayIndex(i);
- settings->setValue("script", _activeScripts.at(i));
- }
- settings->endArray();
-
+void Application::saveScripts() {
+ // saves all current running scripts
+ QSettings* settings = new QSettings(this);
+ settings->beginWriteArray("Settings");
+ for (int i = 0; i < _activeScripts.size(); ++i){
+ settings->setArrayIndex(i);
+ settings->setValue("script", _activeScripts.at(i));
+ }
+
+ settings->endArray();
}
-void Application::removeScriptName(const QString& fileNameString)
-{
+void Application::removeScriptName(const QString& fileNameString) {
_activeScripts.removeOne(fileNameString);
}
-void Application::loadScript(const QString& fileNameString){
+void Application::loadScript(const QString& fileNameString) {
_activeScripts.append(fileNameString);
QByteArray fileNameAscii = fileNameString.toLocal8Bit();
const char* fileName = fileNameAscii.data();
@@ -3969,7 +4239,8 @@ void Application::loadScript(const QString& fileNameString){
// start the script on a new thread...
bool wantMenuItems = true; // tells the ScriptEngine object to add menu items for itself
- ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(), &_controllerScriptingInterface);
+ ScriptEngine* scriptEngine = new ScriptEngine(script, wantMenuItems, fileName, Menu::getInstance(),
+ &_controllerScriptingInterface);
scriptEngine->setupMenuItems();
// setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so
@@ -3979,7 +4250,7 @@ void Application::loadScript(const QString& fileNameString){
scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree());
// hook our avatar object into this script engine
- scriptEngine->setAvatarData(&_myAvatar, "MyAvatar");
+ scriptEngine->setAvatarData( static_cast(_myAvatar), "MyAvatar");
QThread* workerThread = new QThread(this);
@@ -4138,3 +4409,14 @@ void Application::skipVersion(QString latestVersion) {
skipFile.seek(0);
skipFile.write(latestVersion.toStdString().c_str());
}
+
+void Application::takeSnapshot() {
+ switchToResourcesParentIfRequired();
+ QMediaPlayer* player = new QMediaPlayer();
+ QFileInfo inf = QFileInfo("resources/sounds/snap.wav");
+ player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath()));
+ player->play();
+
+ Snapshot::saveSnapshot(_glWidget, _profile.getUsername(), _myAvatar->getPosition());
+}
+
diff --git a/interface/src/Application.h b/interface/src/Application.h
index defe4dc8aa..c83437949c 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -142,7 +142,7 @@ public:
glm::vec3 getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVoxel);
QGLWidget* getGLWidget() { return _glWidget; }
- MyAvatar* getAvatar() { return &_myAvatar; }
+ MyAvatar* getAvatar() { return _myAvatar; }
Audio* getAudio() { return &_audio; }
Camera* getCamera() { return &_myCamera; }
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
@@ -175,8 +175,7 @@ public:
Profile* getProfile() { return &_profile; }
void resetProfile(const QString& username);
- void controlledBroadcastToNodes(unsigned char* broadcastData, size_t dataBytes,
- const QSet& destinationNodeTypes);
+ void controlledBroadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes);
void setupWorldLight();
@@ -265,7 +264,7 @@ private:
void updateProjectionMatrix(Camera& camera, bool updateViewFrustum = true);
static bool sendVoxelsOperation(OctreeElement* node, void* extraData);
- static void sendPingPackets();
+ void sendPingPackets();
void initDisplay();
void init();
@@ -299,14 +298,18 @@ private:
void renderHighlightVoxel(VoxelDetail voxel);
void updateAvatar(float deltaTime);
- void queryOctree(NODE_TYPE serverType, PACKET_TYPE packetType, NodeToJurisdictionMap& jurisdictions);
+ void queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions);
void loadViewFrustum(Camera& camera, ViewFrustum& viewFrustum);
glm::vec3 getSunDirection();
void updateShadowMap();
void displayOverlay();
+ void displayStatsBackground(unsigned int rgba, int x, int y, int width, int height);
void displayStats();
+ void checkStatsClick();
+ void toggleStatsExpanded();
+ void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false);
void renderViewFrustum(ViewFrustum& viewFrustum);
void checkBandwidthMeterClick();
@@ -327,6 +330,7 @@ private:
QMainWindow* _window;
QGLWidget* _glWidget;
+ bool _statsExpanded;
BandwidthMeter _bandwidthMeter;
QThread* _nodeThread;
@@ -351,7 +355,7 @@ private:
VoxelSystem _voxels;
VoxelTree _clipboard; // if I copy/paste
- VoxelImporter _voxelImporter;
+ VoxelImporter* _voxelImporter;
VoxelSystem _sharedVoxelSystem;
ViewFrustum _sharedVoxelSystemViewFrustum;
@@ -370,10 +374,10 @@ private:
VoxelQuery _voxelQuery; // NodeData derived class for querying voxels from voxel server
AvatarManager _avatarManager;
- MyAvatar _myAvatar; // The rendered avatar of oneself
- Profile _profile; // The data-server linked profile for this user
+ MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be)
+ Profile _profile; // The data-server linked profile for this user
- Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar
+ Transmitter _myTransmitter; // Gets UDP data from transmitter app used to animate the avatar
Faceshift _faceshift;
@@ -399,7 +403,7 @@ private:
int _mouseY;
int _mouseDragStartedX;
int _mouseDragStartedY;
- uint64_t _lastMouseMove;
+ quint64 _lastMouseMove;
bool _mouseHidden;
bool _seenMouseMove;
@@ -477,9 +481,8 @@ private:
PieMenu _pieMenu;
- int parseOctreeStats(unsigned char* messageData, ssize_t messageLength, const HifiSockAddr& senderAddress);
- void trackIncomingVoxelPacket(unsigned char* messageData, ssize_t messageLength,
- const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
+ int parseOctreeStats(const QByteArray& packet, const HifiSockAddr& senderAddress);
+ void trackIncomingVoxelPacket(const QByteArray& packet, const HifiSockAddr& senderSockAddr, bool wasStatsPacket);
NodeToJurisdictionMap _voxelServerJurisdictions;
NodeToJurisdictionMap _particleServerJurisdictions;
@@ -500,6 +503,7 @@ private:
void checkVersion();
void displayUpdateDialog();
bool shouldSkipVersion(QString latestVersion);
+ void takeSnapshot();
};
#endif /* defined(__interface__Application__) */
diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp
index 47ab8b0aba..9fede84a93 100644
--- a/interface/src/Audio.cpp
+++ b/interface/src/Audio.cpp
@@ -22,7 +22,6 @@
#include
#include
-#include
#include
#include
#include
@@ -40,8 +39,9 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_
// Mute icon configration
static const int ICON_SIZE = 24;
-static const int ICON_LEFT = 20;
-static const int BOTTOM_PADDING = 110;
+static const int ICON_LEFT = 0;
+static const int ICON_TOP = 115;
+static const int ICON_TOP_MIRROR = 220;
Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* parent) :
AbstractAudioInterface(parent),
@@ -285,8 +285,8 @@ void Audio::start() {
void Audio::handleAudioInput() {
static char monoAudioDataPacket[MAX_PACKET_SIZE];
- static int numBytesPacketHeader = numBytesForPacketHeader((unsigned char*) &PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO);
- static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat) + NUM_BYTES_RFC4122_UUID;
+ static int numBytesPacketHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeMicrophoneAudioNoEcho);
+ static int leadingBytes = numBytesPacketHeader + sizeof(glm::vec3) + sizeof(glm::quat);
static int16_t* monoAudioSamples = (int16_t*) (monoAudioDataPacket + leadingBytes);
@@ -365,7 +365,7 @@ void Audio::handleAudioInput() {
NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
NodeList* nodeList = NodeList::getInstance();
- SharedNodePointer audioMixer = nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER);
+ SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
if (audioMixer && nodeList->getNodeActiveSocketOrPing(audioMixer.data())) {
MyAvatar* interfaceAvatar = Application::getInstance()->getAvatar();
@@ -375,16 +375,10 @@ void Audio::handleAudioInput() {
// we need the amount of bytes in the buffer + 1 for type
// + 12 for 3 floats for position + float for bearing + 1 attenuation byte
- PACKET_TYPE packetType = Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)
- ? PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO : PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO;
+ PacketType packetType = Menu::getInstance()->isOptionChecked(MenuOption::EchoServerAudio)
+ ? PacketTypeMicrophoneAudioWithEcho : PacketTypeMicrophoneAudioNoEcho;
- char* currentPacketPtr = monoAudioDataPacket + populateTypeAndVersion((unsigned char*) monoAudioDataPacket,
- packetType);
-
- // pack Source Data
- QByteArray rfcUUID = NodeList::getInstance()->getOwnerUUID().toRfc4122();
- memcpy(currentPacketPtr, rfcUUID.constData(), rfcUUID.size());
- currentPacketPtr += rfcUUID.size();
+ char* currentPacketPtr = monoAudioDataPacket + populatePacketHeader(monoAudioDataPacket, packetType);
// memcpy the three float positions
memcpy(currentPacketPtr, &headPosition, sizeof(headPosition));
@@ -433,7 +427,7 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) {
}
}
- _ringBuffer.parseData((unsigned char*) audioByteArray.data(), audioByteArray.size());
+ _ringBuffer.parseData(audioByteArray);
static float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate())
* (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount());
@@ -545,16 +539,16 @@ void Audio::toggleMute() {
}
void Audio::render(int screenWidth, int screenHeight) {
- if (_audioInput) {
+ if (_audioInput && _audioOutput) {
glLineWidth(2.0);
glBegin(GL_LINES);
- glColor3f(1,1,1);
+ glColor3f(.93f, .93f, .93f);
int startX = 20.0;
int currentX = startX;
- int topY = screenHeight - 40;
- int bottomY = screenHeight - 20;
- float frameWidth = 20.0;
+ int topY = screenHeight - 45;
+ int bottomY = screenHeight - 25;
+ float frameWidth = 23.0;
float halfY = topY + ((bottomY - topY) / 2.0);
// draw the lines for the base of the ring buffer
@@ -583,9 +577,9 @@ void Audio::render(int screenWidth, int screenHeight) {
float msLeftForAudioOutput = (secondsLeftForAudioOutput + secondsLeftForRingBuffer) * 1000;
if (_numFramesDisplayStarve == 0) {
- glColor3f(0, 1, 0);
+ glColor3f(0, .8f, .4f);
} else {
- glColor3f(0.5 + (_numFramesDisplayStarve / 20.0f), 0, 0);
+ glColor3f(0.5 + (_numFramesDisplayStarve / 20.0f), .2f, .4f);
_numFramesDisplayStarve--;
}
@@ -596,44 +590,44 @@ void Audio::render(int screenWidth, int screenHeight) {
}
glBegin(GL_QUADS);
- glVertex2f(startX, topY + 2);
- glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth, topY + 2);
- glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth, bottomY - 2);
- glVertex2f(startX, bottomY - 2);
+ glVertex2f(startX + 1, topY + 2);
+ glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 1, topY + 2);
+ glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 1, bottomY - 2);
+ glVertex2f(startX + 1, bottomY - 2);
glEnd();
// Show a yellow bar with the averaged msecs latency you are hearing (from time of packet receipt)
- glColor3f(1,1,0);
+ glColor3f(1, .8f, 0);
glBegin(GL_QUADS);
- glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 2, topY - 2);
- glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 2, topY - 2);
- glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 2, bottomY + 2);
- glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 2, bottomY + 2);
+ glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 1, topY - 2);
+ glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 3, topY - 2);
+ glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 3, bottomY + 2);
+ glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 1, bottomY + 2);
glEnd();
char out[40];
sprintf(out, "%3.0f\n", _averagedLatency);
- drawtext(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 10, topY - 9, 0.10f, 0, 1, 0, out, 1,1,0);
+ drawtext(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 10, topY - 9, 0.10f, 0, 1, 2, out, 1, .8f, 0);
// Show a red bar with the 'start' point of one frame plus the jitter buffer
- glColor3f(1, 0, 0);
+ glColor3f(1, .2f, .4f);
int jitterBufferPels = (1.f + (float)getJitterBufferSamples()
/ (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) * frameWidth;
sprintf(out, "%.0f\n", getJitterBufferSamples() / SAMPLE_RATE * 1000.f);
- drawtext(startX + jitterBufferPels - 5, topY - 9, 0.10f, 0, 1, 0, out, 1, 0, 0);
+ drawtext(startX + jitterBufferPels - 5, topY - 9, 0.10f, 0, 1, 2, out, 1, .2f, .4f);
sprintf(out, "j %.1f\n", _measuredJitter);
if (Menu::getInstance()->getAudioJitterBufferSamples() == 0) {
- drawtext(startX + jitterBufferPels - 5, bottomY + 12, 0.10f, 0, 1, 0, out, 1, 0, 0);
+ drawtext(startX + jitterBufferPels - 5, bottomY + 12, 0.10f, 0, 1, 2, out, 1, .2f, .4f);
} else {
- drawtext(startX, bottomY + 12, 0.10f, 0, 1, 0, out, 1, 0, 0);
+ drawtext(startX, bottomY + 12, 0.10f, 0, 1, 2, out, 1, .2f, .4f);
}
glBegin(GL_QUADS);
- glVertex2f(startX + jitterBufferPels - 2, topY - 2);
- glVertex2f(startX + jitterBufferPels + 2, topY - 2);
- glVertex2f(startX + jitterBufferPels + 2, bottomY + 2);
- glVertex2f(startX + jitterBufferPels - 2, bottomY + 2);
+ glVertex2f(startX + jitterBufferPels - 1, topY - 2);
+ glVertex2f(startX + jitterBufferPels + 3, topY - 2);
+ glVertex2f(startX + jitterBufferPels + 3, bottomY + 2);
+ glVertex2f(startX + jitterBufferPels - 1, bottomY + 2);
glEnd();
}
@@ -739,11 +733,13 @@ void Audio::handleAudioByteArray(const QByteArray& audioByteArray) {
void Audio::renderToolIcon(int screenHeight) {
- _iconBounds = QRect(ICON_LEFT, screenHeight - BOTTOM_PADDING, ICON_SIZE, ICON_SIZE);
+ int iconTop = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) ? ICON_TOP_MIRROR : ICON_TOP;
+
+ _iconBounds = QRect(ICON_LEFT, iconTop, ICON_SIZE, ICON_SIZE);
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, _micTextureId);
- glColor3f(1, 1, 1);
+ glColor3f(.93f, .93f, .93f);
glBegin(GL_QUADS);
glTexCoord2f(1, 1);
diff --git a/interface/src/BandwidthMeter.cpp b/interface/src/BandwidthMeter.cpp
index 94fb06a496..d8081e186f 100644
--- a/interface/src/BandwidthMeter.cpp
+++ b/interface/src/BandwidthMeter.cpp
@@ -14,21 +14,21 @@
namespace { // .cpp-local
int const AREA_WIDTH = -280; // Width of the area used. Aligned to the right when negative.
- int const AREA_HEIGHT = 40; // Height of the area used. Aligned to the bottom when negative.
- int const BORDER_DISTANCE_HORIZ = -20; // Distance to edge of screen (use negative value when width is negative).
- int const BORDER_DISTANCE_VERT = 40; // Distance to edge of screen (use negative value when height is negative).
+ int const AREA_HEIGHT = -40; // Height of the area used. Aligned to the bottom when negative.
+ int const BORDER_DISTANCE_HORIZ = -10; // Distance to edge of screen (use negative value when width is negative).
+ int const BORDER_DISTANCE_VERT = -15; // Distance to edge of screen (use negative value when height is negative).
int SPACING_VERT_BARS = 2; // Vertical distance between input and output bar
int SPACING_RIGHT_CAPTION_IN_OUT = 4; // IN/OUT <--> |######## : |
int SPACING_LEFT_CAPTION_UNIT = 4; // |######## : | <--> UNIT
int PADDING_HORIZ_VALUE = 2; // |<-->X.XX<:-># |
- unsigned const COLOR_TEXT = 0xe0e0e0e0; // ^ ^ ^ ^ ^ ^
+ unsigned const COLOR_TEXT = 0xedededff; // ^ ^ ^ ^ ^ ^
unsigned const COLOR_FRAME = 0xe0e0e0b0; // | | |
unsigned const COLOR_INDICATOR = 0xc0c0c0b0; // |
- char const* CAPTION_IN = "IN";
- char const* CAPTION_OUT = "OUT";
+ char const* CAPTION_IN = "In";
+ char const* CAPTION_OUT = "Out";
char const* CAPTION_UNIT = "Mbps";
double const UNIT_SCALE = 8000.0 / (1024.0 * 1024.0); // Bytes/ms -> Mbps
@@ -38,13 +38,13 @@ namespace { // .cpp-local
}
BandwidthMeter::ChannelInfo BandwidthMeter::_CHANNELS[] = {
- { "Audio" , "Kbps", 8000.0 / 1024.0, 0x40ff40d0 },
+ { "Audio" , "Kbps", 8000.0 / 1024.0, 0x33cc99ff },
{ "Avatars" , "Kbps", 8000.0 / 1024.0, 0xffef40c0 },
{ "Voxels" , "Kbps", 8000.0 / 1024.0, 0xd0d0d0a0 }
};
BandwidthMeter::BandwidthMeter() :
- _textRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT),
+ _textRenderer(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false),
_scaleMaxIndex(INITIAL_SCALE_MAXIMUM_INDEX) {
_channels = static_cast( malloc(sizeof(_CHANNELS)) );
diff --git a/interface/src/ControllerScriptingInterface.cpp b/interface/src/ControllerScriptingInterface.cpp
index fd27eb2428..4d7540c06c 100644
--- a/interface/src/ControllerScriptingInterface.cpp
+++ b/interface/src/ControllerScriptingInterface.cpp
@@ -10,6 +10,14 @@
#include "Application.h"
#include "ControllerScriptingInterface.h"
+ControllerScriptingInterface::ControllerScriptingInterface() :
+ _mouseCaptured(false),
+ _touchCaptured(false),
+ _wheelCaptured(false)
+{
+}
+
+
const PalmData* ControllerScriptingInterface::getPrimaryPalm() const {
int leftPalmIndex, rightPalmIndex;
@@ -179,5 +187,35 @@ glm::vec3 ControllerScriptingInterface::getSpatialControlNormal(int controlIndex
return glm::vec3(0); // bad index
}
+bool ControllerScriptingInterface::isKeyCaptured(QKeyEvent* event) const {
+ return isKeyCaptured(KeyEvent(*event));
+}
+bool ControllerScriptingInterface::isKeyCaptured(const KeyEvent& event) const {
+ // if we've captured some combination of this key it will be in the map
+ if (_capturedKeys.contains(event.key, event)) {
+ return true;
+ }
+ return false;
+}
+
+void ControllerScriptingInterface::captureKeyEvents(const KeyEvent& event) {
+ // if it's valid
+ if (event.isValid) {
+ // and not already captured
+ if (!isKeyCaptured(event)) {
+ // then add this KeyEvent record to the captured combos for this key
+ _capturedKeys.insert(event.key, event);
+ }
+ }
+}
+
+void ControllerScriptingInterface::releaseKeyEvents(const KeyEvent& event) {
+ if (event.isValid) {
+ // and not already captured
+ if (isKeyCaptured(event)) {
+ _capturedKeys.remove(event.key, event);
+ }
+ }
+}
diff --git a/interface/src/ControllerScriptingInterface.h b/interface/src/ControllerScriptingInterface.h
index 69daefa3fb..e84039bcb0 100644
--- a/interface/src/ControllerScriptingInterface.h
+++ b/interface/src/ControllerScriptingInterface.h
@@ -1,3 +1,4 @@
+
//
// ControllerScriptingInterface.h
// hifi
@@ -18,6 +19,27 @@ class PalmData;
class ControllerScriptingInterface : public AbstractControllerScriptingInterface {
Q_OBJECT
+public:
+ ControllerScriptingInterface();
+ void emitKeyPressEvent(QKeyEvent* event) { emit keyPressEvent(KeyEvent(*event)); }
+ void emitKeyReleaseEvent(QKeyEvent* event) { emit keyReleaseEvent(KeyEvent(*event)); }
+
+ void emitMouseMoveEvent(QMouseEvent* event) { emit mouseMoveEvent(MouseEvent(*event)); }
+ void emitMousePressEvent(QMouseEvent* event) { emit mousePressEvent(MouseEvent(*event)); }
+ void emitMouseReleaseEvent(QMouseEvent* event) { emit mouseReleaseEvent(MouseEvent(*event)); }
+
+ void emitTouchBeginEvent(QTouchEvent* event) { emit touchBeginEvent(*event); }
+ void emitTouchEndEvent(QTouchEvent* event) { emit touchEndEvent(*event); }
+ void emitTouchUpdateEvent(QTouchEvent* event) { emit touchUpdateEvent(*event); }
+ void emitWheelEvent(QWheelEvent* event) { emit wheelEvent(*event); }
+
+ bool isKeyCaptured(QKeyEvent* event) const;
+ bool isKeyCaptured(const KeyEvent& event) const;
+ bool isMouseCaptured() const { return _mouseCaptured; }
+ bool isTouchCaptured() const { return _touchCaptured; }
+ bool isWheelCaptured() const { return _wheelCaptured; }
+
+
public slots:
virtual bool isPrimaryButtonPressed() const;
virtual glm::vec2 getPrimaryJoystickPosition() const;
@@ -36,11 +58,28 @@ public slots:
virtual glm::vec3 getSpatialControlVelocity(int controlIndex) const;
virtual glm::vec3 getSpatialControlNormal(int controlIndex) const;
+ virtual void captureKeyEvents(const KeyEvent& event);
+ virtual void releaseKeyEvents(const KeyEvent& event);
+
+ virtual void captureMouseEvents() { _mouseCaptured = true; }
+ virtual void releaseMouseEvents() { _mouseCaptured = false; }
+
+ virtual void captureTouchEvents() { _touchCaptured = true; }
+ virtual void releaseTouchEvents() { _touchCaptured = false; }
+
+ virtual void captureWheelEvents() { _wheelCaptured = true; }
+ virtual void releaseWheelEvents() { _wheelCaptured = false; }
+
private:
const PalmData* getPrimaryPalm() const;
const PalmData* getPalm(int palmIndex) const;
int getNumberOfActivePalms() const;
const PalmData* getActivePalm(int palmIndex) const;
+
+ bool _mouseCaptured;
+ bool _touchCaptured;
+ bool _wheelCaptured;
+ QMultiMap _capturedKeys;
};
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 b78bd0c309..32d57352d9 100644
--- a/interface/src/DatagramProcessor.cpp
+++ b/interface/src/DatagramProcessor.cpp
@@ -26,52 +26,53 @@ void DatagramProcessor::processDatagrams() {
"DatagramProcessor::processDatagrams()");
HifiSockAddr senderSockAddr;
- ssize_t bytesReceived;
- static unsigned char incomingPacket[MAX_PACKET_SIZE];
+ static QByteArray incomingPacket;
Application* application = Application::getInstance();
+ NodeList* nodeList = NodeList::getInstance();
- while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams() &&
- (bytesReceived = NodeList::getInstance()->getNodeSocket().readDatagram((char*) incomingPacket,
- MAX_PACKET_SIZE,
- senderSockAddr.getAddressPointer(),
- senderSockAddr.getPortPointer()))) {
+ while (NodeList::getInstance()->getNodeSocket().hasPendingDatagrams()) {
+ incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
+ nodeList->getNodeSocket().readDatagram(incomingPacket.data(), incomingPacket.size(),
+ senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
_packetCount++;
- _byteCount += bytesReceived;
+ _byteCount += incomingPacket.size();
- if (packetVersionMatch(incomingPacket, senderSockAddr)) {
+ if (packetVersionMatch(incomingPacket)) {
// only process this packet if we have a match on the packet version
- switch (incomingPacket[0]) {
- case PACKET_TYPE_TRANSMITTER_DATA_V2:
+ switch (packetTypeForPacket(incomingPacket)) {
+ case PacketTypeTransmitterData:
// V2 = IOS transmitter app
- application->_myTransmitter.processIncomingData(incomingPacket, bytesReceived);
+ application->_myTransmitter.processIncomingData(reinterpret_cast(incomingPacket.data()),
+ incomingPacket.size());
break;
- case PACKET_TYPE_MIXED_AUDIO:
+ case PacketTypeMixedAudio:
QMetaObject::invokeMethod(&application->_audio, "addReceivedAudioToBuffer", Qt::QueuedConnection,
- Q_ARG(QByteArray, QByteArray((char*) incomingPacket, bytesReceived)));
+ Q_ARG(QByteArray, incomingPacket));
break;
- case PACKET_TYPE_PARTICLE_ADD_RESPONSE:
+ case PacketTypeParticleAddResponse:
// this will keep creatorTokenIDs to IDs mapped correctly
- Particle::handleAddParticleResponse(incomingPacket, bytesReceived);
+ Particle::handleAddParticleResponse(incomingPacket);
+ application->getParticles()->getTree()->handleAddParticleResponse(incomingPacket);
break;
- case PACKET_TYPE_PARTICLE_DATA:
- case PACKET_TYPE_PARTICLE_ERASE:
- case PACKET_TYPE_VOXEL_DATA:
- case PACKET_TYPE_VOXEL_ERASE:
- case PACKET_TYPE_OCTREE_STATS:
- case PACKET_TYPE_ENVIRONMENT_DATA: {
+ case PacketTypeParticleData:
+ case PacketTypeParticleErase:
+ case PacketTypeVoxelData:
+ case PacketTypeVoxelErase:
+ case PacketTypeOctreeStats:
+ case PacketTypeEnvironmentData: {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings),
"Application::networkReceive()... _voxelProcessor.queueReceivedPacket()");
bool wantExtraDebugging = application->getLogger()->extraDebugging();
- if (wantExtraDebugging && incomingPacket[0] == PACKET_TYPE_VOXEL_DATA) {
+ if (wantExtraDebugging && packetTypeForPacket(incomingPacket) == PacketTypeVoxelData) {
int numBytesPacketHeader = numBytesForPacketHeader(incomingPacket);
- unsigned char* dataAt = incomingPacket + numBytesPacketHeader;
+ unsigned char* dataAt = reinterpret_cast(incomingPacket.data()) + numBytesPacketHeader;
dataAt += sizeof(VOXEL_PACKET_FLAGS);
VOXEL_PACKET_SEQUENCE sequence = (*(VOXEL_PACKET_SEQUENCE*)dataAt);
dataAt += sizeof(VOXEL_PACKET_SEQUENCE);
@@ -80,52 +81,49 @@ void DatagramProcessor::processDatagrams() {
VOXEL_PACKET_SENT_TIME arrivedAt = usecTimestampNow();
int flightTime = arrivedAt - sentAt;
- printf("got PACKET_TYPE_VOXEL_DATA, sequence:%d flightTime:%d\n", sequence, flightTime);
+ 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, bytesReceived);
+ application->_voxelProcessor.queueReceivedPacket(senderSockAddr, incomingPacket);
break;
}
- case PACKET_TYPE_METAVOXEL_DATA:
- application->_metavoxels.processData(QByteArray((const char*) incomingPacket, bytesReceived),
- senderSockAddr);
+ case PacketTypeMetavoxelData:
+ application->_metavoxels.processData(incomingPacket, senderSockAddr);
break;
- case PACKET_TYPE_BULK_AVATAR_DATA:
- case PACKET_TYPE_KILL_AVATAR: {
+ case PacketTypeBulkAvatarData:
+ case PacketTypeKillAvatar: {
// update having heard from the avatar-mixer and record the bytes received
SharedNodePointer avatarMixer = NodeList::getInstance()->nodeWithAddress(senderSockAddr);
if (avatarMixer) {
avatarMixer->setLastHeardMicrostamp(usecTimestampNow());
- avatarMixer->recordBytesReceived(bytesReceived);
+ avatarMixer->recordBytesReceived(incomingPacket.size());
- QByteArray datagram(reinterpret_cast(incomingPacket), bytesReceived);
-
- if (incomingPacket[0] == PACKET_TYPE_BULK_AVATAR_DATA) {
+ if (packetTypeForPacket(incomingPacket) == PacketTypeBulkAvatarData) {
QMetaObject::invokeMethod(&application->getAvatarManager(), "processAvatarMixerDatagram",
- Q_ARG(const QByteArray&, datagram),
+ 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&, datagram));
+ Q_ARG(const QByteArray&, incomingPacket));
}
}
- application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(bytesReceived);
+ application->_bandwidthMeter.inputStream(BandwidthMeter::AVATARS).updateValue(incomingPacket.size());
break;
- }
- case PACKET_TYPE_DATA_SERVER_GET:
- case PACKET_TYPE_DATA_SERVER_PUT:
- case PACKET_TYPE_DATA_SERVER_SEND:
- case PACKET_TYPE_DATA_SERVER_CONFIRM:
- DataServerClient::processMessageFromDataServer(incomingPacket, bytesReceived);
+ }
+ case PacketTypeDataServerGet:
+ case PacketTypeDataServerPut:
+ case PacketTypeDataServerSend:
+ case PacketTypeDataServerConfirm:
+ DataServerClient::processMessageFromDataServer(incomingPacket);
break;
default:
- NodeList::getInstance()->processNodeData(senderSockAddr, incomingPacket, bytesReceived);
+ NodeList::getInstance()->processNodeData(senderSockAddr, incomingPacket);
break;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/interface/src/Environment.cpp b/interface/src/Environment.cpp
index 46c156f860..59f16fc5eb 100644
--- a/interface/src/Environment.cpp
+++ b/interface/src/Environment.cpp
@@ -148,32 +148,28 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3
return found;
}
-int Environment::parseData(const HifiSockAddr& senderAddress, unsigned char* sourceBuffer, int numBytes) {
+int Environment::parseData(const HifiSockAddr& senderAddress, const QByteArray& packet) {
// push past the packet header
- unsigned char* start = sourceBuffer;
-
- int numBytesPacketHeader = numBytesForPacketHeader(sourceBuffer);
- sourceBuffer += numBytesPacketHeader;
- numBytes -= numBytesPacketHeader;
+ int bytesRead = numBytesForPacketHeader(packet);
// get the lock for the duration of the call
QMutexLocker locker(&_mutex);
EnvironmentData newData;
- while (numBytes > 0) {
- int dataLength = newData.parseData(sourceBuffer, numBytes);
+ while (bytesRead < packet.size()) {
+ int dataLength = newData.parseData(reinterpret_cast(packet.data()) + bytesRead,
+ packet.size() - bytesRead);
// update the mapping by address/ID
_data[senderAddress][newData.getID()] = newData;
- sourceBuffer += dataLength;
- numBytes -= dataLength;
+ bytesRead += dataLength;
}
// remove the default mapping, if any
_data.remove(HifiSockAddr());
- return sourceBuffer - start;
+ return bytesRead;
}
ProgramObject* Environment::createSkyProgram(const char* from, int* locations) {
diff --git a/interface/src/Environment.h b/interface/src/Environment.h
index fc572c5e03..273ee54cee 100644
--- a/interface/src/Environment.h
+++ b/interface/src/Environment.h
@@ -34,7 +34,7 @@ public:
bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration);
- int parseData(const HifiSockAddr& senderSockAddr, unsigned char* sourceBuffer, int numBytes);
+ int parseData(const HifiSockAddr& senderSockAddr, const QByteArray& packet);
private:
diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp
index 4c96dfcfa5..81c626a46e 100644
--- a/interface/src/FileLogger.cpp
+++ b/interface/src/FileLogger.cpp
@@ -14,17 +14,20 @@
#include
#include
-FileLogger::FileLogger() : _logData(NULL) {
- setExtraDebugging(false);
- _fileName = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
- QDir logDir(_fileName);
- if (!logDir.exists(_fileName)) {
- logDir.mkdir(_fileName);
- }
+const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt";
+const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss";
+const QString LOGS_DIRECTORY = "Logs";
+FileLogger::FileLogger(QObject* parent) :
+ AbstractLoggerInterface(parent),
+ _logData(NULL)
+{
+ setExtraDebugging(false);
+
+ _fileName = FileUtils::standardPath(LOGS_DIRECTORY);
QHostAddress clientAddress = QHostAddress(getHostOrderLocalAddress());
QDateTime now = QDateTime::currentDateTime();
- _fileName.append(QString("/hifi-log_%1_%2.txt").arg(clientAddress.toString(), now.toString("yyyy-MM-dd_hh.mm.ss")));
+ _fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT)));
}
void FileLogger::addMessage(QString message) {
@@ -40,5 +43,5 @@ void FileLogger::addMessage(QString message) {
}
void FileLogger::locateLog() {
- FileUtils::LocateFile(_fileName);
+ FileUtils::locateFile(_fileName);
}
diff --git a/interface/src/FileLogger.h b/interface/src/FileLogger.h
index 6a17032ae2..35cafa4db7 100644
--- a/interface/src/FileLogger.h
+++ b/interface/src/FileLogger.h
@@ -16,7 +16,7 @@ class FileLogger : public AbstractLoggerInterface {
Q_OBJECT
public:
- FileLogger();
+ FileLogger(QObject* parent = NULL);
virtual void addMessage(QString);
virtual QStringList getLogData() { return _logData; };
diff --git a/interface/src/ImportDialog.cpp b/interface/src/ImportDialog.cpp
index 8851446f13..ac7853629c 100644
--- a/interface/src/ImportDialog.cpp
+++ b/interface/src/ImportDialog.cpp
@@ -6,212 +6,148 @@
// Copyright (c) 2013 High Fidelity, Inc. All rights reserved.
//
#include "ImportDialog.h"
-#include "Application.h"
#include
#include
-#include
+#include
+#include
+#include
+#include
-const QString WINDOW_NAME = QObject::tr("Import Voxels");
-const QString IMPORT_BUTTON_NAME = QObject::tr("Import");
-const QString IMPORT_TO_CLIPBOARD_CHECKBOX_STRING = QObject::tr("Import into clipboard");
-const QString PREVIEW_CHECKBOX_STRING = QObject::tr("Load preview");
-const QString IMPORT_FILE_TYPES = QObject::tr("Sparse Voxel Octree Files, "
- "Square PNG, "
- "Schematic Files "
- "(*.svo *.png *.schematic)");
+const QString WINDOW_NAME = QObject::tr("Import Voxels");
+const QString IMPORT_BUTTON_NAME = QObject::tr("Import");
+const QString IMPORT_INFO = QObject::tr("Import %1 as voxels");
+const QString CANCEL_BUTTON_NAME = QObject::tr("Cancel");
+const QString INFO_LABEL_TEXT = QObject::tr("
"
+ "This will load the selected file into Hifi and allow you "
+ "to place it with %1-V; you must be in select or "
+ "add mode (S or V keys will toggle mode) to place.