diff --git a/CMakeLists.txt b/CMakeLists.txt index 7a664c4ce4..50ac76adc3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,9 +13,8 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CMAKE_AUTOMOC ON) add_subdirectory(animation-server) +add_subdirectory(assignment-client) add_subdirectory(assignment-server) -add_subdirectory(avatar-mixer) -add_subdirectory(audio-mixer) add_subdirectory(domain-server) add_subdirectory(eve) add_subdirectory(interface) diff --git a/avatar-mixer/CMakeLists.txt b/assignment-client/CMakeLists.txt similarity index 71% rename from avatar-mixer/CMakeLists.txt rename to assignment-client/CMakeLists.txt index 33f603e228..5f20c86ca9 100644 --- a/avatar-mixer/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 2.8) -set(TARGET_NAME "avatar-mixer") +set(TARGET_NAME assignment-client) set(ROOT_DIR ..) set(MACRO_DIR ${ROOT_DIR}/cmake/macros) @@ -8,15 +8,11 @@ set(MACRO_DIR ${ROOT_DIR}/cmake/macros) # setup for find modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") -# setup the project include(${MACRO_DIR}/SetupHifiProject.cmake) setup_hifi_project(${TARGET_NAME} TRUE) -# include glm -include(${MACRO_DIR}/IncludeGLM.cmake) -include_glm(${TARGET_NAME} ${ROOT_DIR}) - -# link required hifi libraries +# link in the shared library include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) +link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) link_hifi_library(avatars ${TARGET_NAME} ${ROOT_DIR}) \ No newline at end of file diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp new file mode 100644 index 0000000000..f3d6b8c0cd --- /dev/null +++ b/assignment-client/src/main.cpp @@ -0,0 +1,88 @@ +// +// main.cpp +// assignment-client +// +// Created by Stephen Birarda on 8/22/13. +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// + +#include +#include + +#include +#include +#include +#include +#include +#include + +const int ASSIGNMENT_REQUEST_INTERVAL_USECS = 1 * 1000 * 1000; + +int main(int argc, char* const argv[]) { + + setvbuf(stdout, NULL, _IOLBF, 0); + + // create a NodeList as an unassigned client + NodeList* nodeList = NodeList::createInstance(NODE_TYPE_UNASSIGNED); + + // change the timeout on the nodelist socket to be as often as we want to re-request + nodeList->getNodeSocket()->setBlockingReceiveTimeoutInUsecs(ASSIGNMENT_REQUEST_INTERVAL_USECS); + + unsigned char packetData[MAX_PACKET_SIZE]; + ssize_t receivedBytes = 0; + + // loop the parameters to see if we were passed a pool + int parameter = -1; + const char ALLOWED_PARAMETERS[] = "p::"; + const char POOL_PARAMETER_CHAR = 'p'; + + char* assignmentPool = NULL; + + while ((parameter = getopt(argc, argv, ALLOWED_PARAMETERS)) != -1) { + if (parameter == POOL_PARAMETER_CHAR) { + // copy the passed assignment pool + int poolLength = strlen(optarg); + assignmentPool = new char[poolLength + sizeof(char)]; + strcpy(assignmentPool, optarg); + } + } + + // create a request assignment, accept all assignments, pass the desired pool (if it exists) + Assignment requestAssignment(Assignment::Request, Assignment::All, assignmentPool); + + while (true) { + // if we're here we have no assignment, so send a request + qDebug() << "Sending an assignment request -" << requestAssignment; + nodeList->sendAssignment(requestAssignment); + + while (nodeList->getNodeSocket()->receive(packetData, &receivedBytes)) { + if (packetData[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT && packetVersionMatch(packetData)) { + + // construct the deployed assignment from the packet data + Assignment deployedAssignment(packetData, receivedBytes); + + qDebug() << "Received an assignment - " << deployedAssignment << "\n"; + + // switch our nodelist DOMAIN_IP to the ip receieved in the assignment + if (deployedAssignment.getDomainSocket()->sa_family == AF_INET) { + in_addr domainSocketAddr = ((sockaddr_in*) deployedAssignment.getDomainSocket())->sin_addr; + nodeList->setDomainIP(inet_ntoa(domainSocketAddr)); + + qDebug() << "Changed domain IP to " << inet_ntoa(domainSocketAddr); + } + + if (deployedAssignment.getType() == Assignment::AudioMixer) { + AudioMixer::run(); + } else { + AvatarMixer::run(); + } + + qDebug() << "Assignment finished or never started - waiting for new assignment"; + + // reset our NodeList by switching back to unassigned and clearing the list + nodeList->setOwnerType(NODE_TYPE_UNASSIGNED); + nodeList->clear(); + } + } + } +} \ No newline at end of file diff --git a/assignment-server/src/main.cpp b/assignment-server/src/main.cpp index be902ed5b0..dbbd6b2607 100644 --- a/assignment-server/src/main.cpp +++ b/assignment-server/src/main.cpp @@ -12,19 +12,17 @@ #include +#include #include #include #include const int MAX_PACKET_SIZE_BYTES = 1400; - -struct Assignment { - QString scriptFilename; -}; +const long long NUM_DEFAULT_ASSIGNMENT_STALENESS_USECS = 10 * 1000 * 1000; int main(int argc, const char* argv[]) { - std::queue assignmentQueue; + std::deque assignmentQueue; sockaddr_in senderSocket; unsigned char senderData[MAX_PACKET_SIZE_BYTES] = {}; @@ -33,46 +31,78 @@ int main(int argc, const char* argv[]) { UDPSocket serverSocket(ASSIGNMENT_SERVER_PORT); unsigned char assignmentPacket[MAX_PACKET_SIZE_BYTES]; - int numSendHeaderBytes = populateTypeAndVersion(assignmentPacket, PACKET_TYPE_SEND_ASSIGNMENT); + int numSendHeaderBytes = populateTypeAndVersion(assignmentPacket, PACKET_TYPE_DEPLOY_ASSIGNMENT); while (true) { if (serverSocket.receive((sockaddr*) &senderSocket, &senderData, &receivedBytes)) { - - int numHeaderBytes = numBytesForPacketHeader(senderData); - - if (senderData[0] == PACKET_TYPE_REQUEST_ASSIGNMENT) { - qDebug() << "Assignment request received.\n"; - // grab the FI assignment in the queue, if it exists + if (senderData[0] == PACKET_TYPE_REQUEST_ASSIGNMENT) { + // construct the requested assignment from the packet data + Assignment requestAssignment(senderData, receivedBytes); + + qDebug() << "Received request for assignment:" << requestAssignment; + qDebug() << "Current queue size is" << assignmentQueue.size(); + + // make sure there are assignments in the queue at all if (assignmentQueue.size() > 0) { - Assignment firstAssignment = assignmentQueue.front(); - assignmentQueue.pop(); - QString scriptURL = QString("http://base8-compute.s3.amazonaws.com/%1").arg(firstAssignment.scriptFilename); + std::deque::iterator assignment = assignmentQueue.begin(); - qDebug() << "Sending assignment with URL" << scriptURL << "\n"; - - int scriptURLBytes = scriptURL.size(); - memcpy(assignmentPacket + numSendHeaderBytes, scriptURL.toLocal8Bit().constData(), scriptURLBytes); - - // send the assignment - serverSocket.send((sockaddr*) &senderSocket, assignmentPacket, numHeaderBytes + scriptURLBytes); + // enumerate assignments until we find one to give this client (if possible) + while (assignment != assignmentQueue.end()) { + + // if this assignment is stale then get rid of it and check the next one + if (usecTimestampNow() - usecTimestamp(&((*assignment)->getTime())) + >= NUM_DEFAULT_ASSIGNMENT_STALENESS_USECS) { + delete *assignment; + assignment = assignmentQueue.erase(assignment); + + continue; + } + + bool eitherHasPool = ((*assignment)->getPool() || requestAssignment.getPool()); + bool bothHavePool = ((*assignment)->getPool() && requestAssignment.getPool()); + + // make sure there is a pool match for the created and requested assignment + // or that neither has a designated pool + if ((eitherHasPool && bothHavePool + && strcmp((*assignment)->getPool(), requestAssignment.getPool()) == 0) + || !eitherHasPool) { + + int numAssignmentBytes = (*assignment)->packToBuffer(assignmentPacket + numSendHeaderBytes); + + // send the assignment + serverSocket.send((sockaddr*) &senderSocket, + assignmentPacket, + numSendHeaderBytes + numAssignmentBytes); + + + // delete this assignment now that it has been sent out + delete *assignment; + // remove it from the deque and make the iterator the next assignment + assignmentQueue.erase(assignment); + + // stop looping - we've handed out an assignment + break; + } else { + // push forward the iterator + assignment++; + } + } } - } else if (senderData[0] == PACKET_TYPE_SEND_ASSIGNMENT) { - Assignment newAssignment; + } else if (senderData[0] == PACKET_TYPE_CREATE_ASSIGNMENT && packetVersionMatch(senderData)) { + // construct the create assignment from the packet data + Assignment* createdAssignment = new Assignment(senderData, receivedBytes); - senderData[receivedBytes] = '\0'; - newAssignment.scriptFilename = QString((const char*)senderData + numHeaderBytes); + qDebug() << "Received a created assignment:" << *createdAssignment; + qDebug() << "Current queue size is" << assignmentQueue.size(); + + // assignment server is on a public server + // assume that the address we now have for the sender is the public address/port + // and store that with the assignment so it can be given to the requestor later + createdAssignment->setDomainSocket((sockaddr*) &senderSocket); - qDebug() << "Added an assignment with script with filename" << newAssignment.scriptFilename << "\n"; - // add this assignment to the queue - - // we're not a queue right now, only keep one assignment - if (assignmentQueue.size() > 0) { - assignmentQueue.pop(); - } - - assignmentQueue.push(newAssignment); + assignmentQueue.push_back(createdAssignment); } } } diff --git a/audio-mixer/.gitignore b/audio-mixer/.gitignore deleted file mode 100644 index f69e105ae9..0000000000 --- a/audio-mixer/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -socket -sftp-config.json -.DS_Store -*.raw \ No newline at end of file diff --git a/audio-mixer/CMakeLists.txt b/audio-mixer/CMakeLists.txt deleted file mode 100644 index 472327de42..0000000000 --- a/audio-mixer/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -cmake_minimum_required(VERSION 2.8) - -set(ROOT_DIR ..) -set(MACRO_DIR ${ROOT_DIR}/cmake/macros) - -# setup for find modules -set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") - -set(TARGET_NAME audio-mixer) - -include(${MACRO_DIR}/SetupHifiProject.cmake) -setup_hifi_project(${TARGET_NAME} TRUE) - -# set up the external glm library -include(${MACRO_DIR}/IncludeGLM.cmake) -include_glm(${TARGET_NAME} ${ROOT_DIR}) - -# link the shared hifi library -include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) -link_hifi_library(audio ${TARGET_NAME} ${ROOT_DIR}) - -# link the stk library -set(STK_ROOT_DIR ${ROOT_DIR}/externals/stk) -find_package(STK REQUIRED) -target_link_libraries(${TARGET_NAME} ${STK_LIBRARIES}) -include_directories(${STK_INCLUDE_DIRS}) \ No newline at end of file diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake index 455ca89701..8b2bcb542c 100644 --- a/cmake/macros/SetupHifiProject.cmake +++ b/cmake/macros/SetupHifiProject.cmake @@ -2,7 +2,7 @@ MACRO(SETUP_HIFI_PROJECT TARGET INCLUDE_QT) project(${TARGET}) # grab the implemenation and header files - file(GLOB TARGET_SRCS src/*.cpp src/*.h) + file(GLOB TARGET_SRCS src/*.cpp src/*.h src/*.c) # add the executable add_executable(${TARGET} ${TARGET_SRCS}) diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 51eea6b838..882fa85098 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -17,6 +17,7 @@ // M - Audio Mixer // +#include #include #include #include @@ -24,6 +25,7 @@ #include #include +#include "Assignment.h" #include "NodeList.h" #include "NodeTypes.h" #include "Logstash.h" @@ -46,14 +48,13 @@ unsigned char* addNodeToBroadcastPacket(unsigned char* currentPosition, Node* no return currentPosition; } -int main(int argc, const char * argv[]) -{ +int main(int argc, char* const argv[]) { NodeList* nodeList = NodeList::createInstance(NODE_TYPE_DOMAIN, DOMAIN_LISTEN_PORT); // If user asks to run in "local" mode then we do NOT replace the IP // with the EC2 IP. Otherwise, we will replace the IP like we used to // this allows developers to run a local domain without recompiling the // domain server - bool isLocalMode = cmdOptionExists(argc, argv, "--local"); + bool isLocalMode = cmdOptionExists(argc, (const char**) argv, "--local"); if (isLocalMode) { printf("NOTE: Running in local mode!\n"); } else { @@ -84,7 +85,56 @@ int main(int argc, const char * argv[]) timeval lastStatSendTime = {}; + // loop the parameters to see if we were passed a pool for assignment + int parameter = -1; + const char ALLOWED_PARAMETERS[] = "p::-local::"; + const char POOL_PARAMETER_CHAR = 'p'; + + char* assignmentPool = NULL; + + while ((parameter = getopt(argc, argv, ALLOWED_PARAMETERS)) != -1) { + if (parameter == POOL_PARAMETER_CHAR) { + // copy the passed assignment pool + int poolLength = strlen(optarg); + assignmentPool = new char[poolLength + sizeof(char)]; + strcpy(assignmentPool, optarg); + } + } + + // use a map to keep track of iterations of silence for assignment creation requests + const int ASSIGNMENT_SILENCE_MAX_ITERATIONS = 5; + std::map assignmentSilenceCount; + + // as a domain-server we will always want an audio mixer and avatar mixer + // setup the create assignments for those + Assignment audioAssignment(Assignment::Create, Assignment::AudioMixer, assignmentPool); + Assignment avatarAssignment(Assignment::Create, Assignment::AvatarMixer, assignmentPool); + while (true) { + + if (!nodeList->soloNodeOfType(NODE_TYPE_AUDIO_MIXER)) { + if (assignmentSilenceCount[&audioAssignment] == ASSIGNMENT_SILENCE_MAX_ITERATIONS) { + nodeList->sendAssignment(audioAssignment); + assignmentSilenceCount[&audioAssignment] = 0; + } else { + assignmentSilenceCount[&audioAssignment]++; + } + } else { + assignmentSilenceCount[&audioAssignment] = 0; + } + + if (!nodeList->soloNodeOfType(NODE_TYPE_AVATAR_MIXER)) { + if (assignmentSilenceCount[&avatarAssignment] == ASSIGNMENT_SILENCE_MAX_ITERATIONS) { + nodeList->sendAssignment(avatarAssignment); + assignmentSilenceCount[&avatarAssignment] = 0; + } else { + assignmentSilenceCount[&avatarAssignment]++; + } + } else { + assignmentSilenceCount[&avatarAssignment] = 0; + } + + if (nodeList->getNodeSocket()->receive((sockaddr *)&nodePublicAddress, packetData, &receivedBytes) && (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY || packetData[0] == PACKET_TYPE_DOMAIN_LIST_REQUEST) && packetVersionMatch(packetData)) { @@ -117,68 +167,71 @@ int main(int argc, const char * argv[]) nodeType, nodeList->getLastNodeID()); - if (newNode->getNodeID() == nodeList->getLastNodeID()) { - nodeList->increaseNodeID(); - } - - currentBufferPos = broadcastPacket + numHeaderBytes; - startPointer = currentBufferPos; - - unsigned char* nodeTypesOfInterest = packetData + numBytesSenderHeader + sizeof(NODE_TYPE) + // if addOrUpdateNode returns NULL this was a solo node we already have, don't talk back to it + if (newNode) { + if (newNode->getNodeID() == nodeList->getLastNodeID()) { + nodeList->increaseNodeID(); + } + + currentBufferPos = broadcastPacket + numHeaderBytes; + startPointer = currentBufferPos; + + unsigned char* nodeTypesOfInterest = packetData + numBytesSenderHeader + sizeof(NODE_TYPE) + numBytesSocket + sizeof(unsigned char); - int numInterestTypes = *(nodeTypesOfInterest - 1); - - if (numInterestTypes > 0) { - // if the node has sent no types of interest, assume they want nothing but their own ID back - for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { - if (!node->matches((sockaddr*) &nodePublicAddress, (sockaddr*) &nodeLocalAddress, nodeType) && + int numInterestTypes = *(nodeTypesOfInterest - 1); + + if (numInterestTypes > 0) { + // if the node has sent no types of interest, assume they want nothing but their own ID back + for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { + if (!node->matches((sockaddr*) &nodePublicAddress, (sockaddr*) &nodeLocalAddress, nodeType) && memchr(nodeTypesOfInterest, node->getType(), numInterestTypes)) { - // this is not the node themselves - // and this is an node of a type in the passed node types of interest - // or the node did not pass us any specific types they are interested in - - if (memchr(SOLO_NODE_TYPES, node->getType(), sizeof(SOLO_NODE_TYPES)) == NULL) { - // this is an node of which there can be multiple, just add them to the packet - // 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)); - } - - } else { - // solo node, we need to only send newest - if (newestSoloNodes[node->getType()] == NULL || - newestSoloNodes[node->getType()]->getWakeMicrostamp() < node->getWakeMicrostamp()) { - // we have to set the newer solo node to add it to the broadcast later - newestSoloNodes[node->getType()] = &(*node); + // this is not the node themselves + // and this is an node of a type in the passed node types of interest + // or the node did not pass us any specific types they are interested in + + if (memchr(SOLO_NODE_TYPES, node->getType(), sizeof(SOLO_NODE_TYPES)) == NULL) { + // this is an node of which there can be multiple, just add them to the packet + // 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)); + } + + } else { + // solo node, we need to only send newest + if (newestSoloNodes[node->getType()] == NULL || + newestSoloNodes[node->getType()]->getWakeMicrostamp() < node->getWakeMicrostamp()) { + // we have to set the newer solo node to add it to the broadcast later + newestSoloNodes[node->getType()] = &(*node); + } } } } + + for (std::map::iterator soloNode = newestSoloNodes.begin(); + soloNode != newestSoloNodes.end(); + soloNode++) { + // this is the newest alive solo node, add them to the packet + currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, soloNode->second); + } } - for (std::map::iterator soloNode = newestSoloNodes.begin(); - soloNode != newestSoloNodes.end(); - soloNode++) { - // this is the newest alive solo node, add them to the packet - currentBufferPos = addNodeToBroadcastPacket(currentBufferPos, soloNode->second); + // update last receive to now + uint64_t timeNow = usecTimestampNow(); + newNode->setLastHeardMicrostamp(timeNow); + + if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY + && memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { + newNode->setWakeMicrostamp(timeNow); } + + // add the node ID to the end of the pointer + currentBufferPos += packNodeId(currentBufferPos, newNode->getNodeID()); + + // send the constructed list back to this node + nodeList->getNodeSocket()->send(destinationSocket, + broadcastPacket, + (currentBufferPos - startPointer) + numHeaderBytes); } - - // update last receive to now - uint64_t timeNow = usecTimestampNow(); - newNode->setLastHeardMicrostamp(timeNow); - - if (packetData[0] == PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY - && memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { - newNode->setWakeMicrostamp(timeNow); - } - - // add the node ID to the end of the pointer - currentBufferPos += packNodeId(currentBufferPos, newNode->getNodeID()); - - // send the constructed list back to this node - nodeList->getNodeSocket()->send(destinationSocket, - broadcastPacket, - (currentBufferPos - startPointer) + numHeaderBytes); } if (Logstash::shouldSendStats()) { diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index 6070649060..6c458149bc 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -8,6 +8,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cm set(TARGET_NAME audio) +# set up the external glm library include(${MACRO_DIR}/SetupHifiLibrary.cmake) setup_hifi_library(${TARGET_NAME}) @@ -15,4 +16,10 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} ${ROOT_DIR}) include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) \ No newline at end of file +link_hifi_library(shared ${TARGET_NAME} ${ROOT_DIR}) + +# link the stk library +set(STK_ROOT_DIR ${ROOT_DIR}/externals/stk) +find_package(STK REQUIRED) +target_link_libraries(${TARGET_NAME} ${STK_LIBRARIES}) +include_directories(${STK_INCLUDE_DIRS}) \ No newline at end of file diff --git a/audio-mixer/src/main.cpp b/libraries/audio/src/AudioMixer.cpp similarity index 89% rename from audio-mixer/src/main.cpp rename to libraries/audio/src/AudioMixer.cpp index 6e912d7c21..c6387ce02a 100644 --- a/audio-mixer/src/main.cpp +++ b/libraries/audio/src/AudioMixer.cpp @@ -1,9 +1,9 @@ // -// main.cpp -// mixer +// AudioMixer.cpp +// hifi // -// Created by Stephen Birarda on 2/1/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Stephen Birarda on 8/22/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. // #include @@ -40,11 +40,13 @@ #include #include -#include +#include "AudioRingBuffer.h" #include "AvatarAudioRingBuffer.h" #include "InjectedAudioRingBuffer.h" +#include "AudioMixer.h" + const unsigned short MIXER_LISTEN_PORT = 55443; const short JITTER_BUFFER_MSECS = 12; @@ -65,36 +67,21 @@ void attachNewBufferToNode(Node *newNode) { } } -bool wantLocalDomain = false; - -int main(int argc, const char* argv[]) { - setvbuf(stdout, NULL, _IOLBF, 0); +void AudioMixer::run() { - NodeList* nodeList = NodeList::createInstance(NODE_TYPE_AUDIO_MIXER, MIXER_LISTEN_PORT); - - // Handle Local Domain testing with the --local command line - const char* local = "--local"; - ::wantLocalDomain = cmdOptionExists(argc, argv,local); - if (::wantLocalDomain) { - printf("Local Domain MODE!\n"); - nodeList->setDomainIPToLocalhost(); - } - - const char* domainIP = getCmdOption(argc, argv, "--domain"); - if (domainIP) { - NodeList::getInstance()->setDomainHostname(domainIP); - } + NodeList *nodeList = NodeList::getInstance(); + nodeList->setOwnerType(NODE_TYPE_AUDIO_MIXER); ssize_t receivedBytes = 0; nodeList->linkedDataCreateCallback = attachNewBufferToNode; nodeList->startSilentNodeRemovalThread(); - + unsigned char* packetData = new unsigned char[MAX_PACKET_SIZE]; - + sockaddr* nodeAddress = new sockaddr; - + // make sure our node socket is non-blocking nodeList->getNodeSocket()->setBlocking(false); @@ -123,6 +110,10 @@ int main(int argc, const char* argv[]) { } while (true) { + if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + break; + } + if (Logstash::shouldSendStats()) { gettimeofday(&beginSendTime, NULL); } @@ -174,155 +165,155 @@ int main(int argc, const char* argv[]) { float attenuationCoefficient = 1.0f; int numSamplesDelay = 0; float weakChannelAmplitudeRatio = 1.0f; - + stk::TwoPole* otherNodeTwoPole = NULL; - + // only do axis/distance attenuation when in normal mode if (otherNode != node && nodeRingBuffer->getListeningMode() == AudioRingBuffer::NORMAL) { - + glm::vec3 listenerPosition = nodeRingBuffer->getPosition(); glm::vec3 relativePosition = otherNodeBuffer->getPosition() - nodeRingBuffer->getPosition(); glm::quat inverseOrientation = glm::inverse(nodeRingBuffer->getOrientation()); - + float distanceSquareToSource = glm::dot(relativePosition, relativePosition); float radius = 0.0f; - + if (otherNode->getType() == NODE_TYPE_AUDIO_INJECTOR) { InjectedAudioRingBuffer* injectedBuffer = (InjectedAudioRingBuffer*) otherNodeBuffer; radius = injectedBuffer->getRadius(); attenuationCoefficient *= injectedBuffer->getAttenuationRatio(); } - + if (radius == 0 || (distanceSquareToSource > radius * radius)) { // this is either not a spherical source, or the listener is outside the sphere - + if (radius > 0) { // this is a spherical source - the distance used for the coefficient // needs to be the closest point on the boundary to the source - + // ovveride the distance to the node with the distance to the point on the // boundary of the sphere distanceSquareToSource -= (radius * radius); - + } else { // calculate the angle delivery for off-axis attenuation glm::vec3 rotatedListenerPosition = glm::inverse(otherNodeBuffer->getOrientation()) - * relativePosition; - + * relativePosition; + float angleOfDelivery = glm::angle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedListenerPosition)); - + const float MAX_OFF_AXIS_ATTENUATION = 0.2f; const float OFF_AXIS_ATTENUATION_FORMULA_STEP = (1 - MAX_OFF_AXIS_ATTENUATION) / 2.0f; - + float offAxisCoefficient = MAX_OFF_AXIS_ATTENUATION + - (OFF_AXIS_ATTENUATION_FORMULA_STEP * (angleOfDelivery / 90.0f)); - + (OFF_AXIS_ATTENUATION_FORMULA_STEP * (angleOfDelivery / 90.0f)); + // multiply the current attenuation coefficient by the calculated off axis coefficient attenuationCoefficient *= offAxisCoefficient; } - + glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition; - + const float DISTANCE_SCALE = 2.5f; const float GEOMETRIC_AMPLITUDE_SCALAR = 0.3f; const float DISTANCE_LOG_BASE = 2.5f; const float DISTANCE_SCALE_LOG = logf(DISTANCE_SCALE) / logf(DISTANCE_LOG_BASE); - + // calculate the distance coefficient using the distance to this node float distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR, - DISTANCE_SCALE_LOG + - (0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1); + DISTANCE_SCALE_LOG + + (0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1); distanceCoefficient = std::min(1.0f, distanceCoefficient); - + // multiply the current attenuation coefficient by the distance coefficient attenuationCoefficient *= distanceCoefficient; - + // project the rotated source position vector onto the XZ plane rotatedSourcePosition.y = 0.0f; - + // produce an oriented angle about the y-axis bearingRelativeAngleToSource = glm::orientedAngle(glm::vec3(0.0f, 0.0f, -1.0f), glm::normalize(rotatedSourcePosition), glm::vec3(0.0f, 1.0f, 0.0f)); - + const float PHASE_AMPLITUDE_RATIO_AT_90 = 0.5; - + // figure out the number of samples of delay and the ratio of the amplitude // in the weak channel for audio spatialization float sinRatio = fabsf(sinf(glm::radians(bearingRelativeAngleToSource))); numSamplesDelay = PHASE_DELAY_AT_90 * sinRatio; weakChannelAmplitudeRatio = 1 - (PHASE_AMPLITUDE_RATIO_AT_90 * sinRatio); - + // grab the TwoPole object for this source, add it if it doesn't exist TwoPoleNodeMap& nodeTwoPoles = nodeRingBuffer->getTwoPoles(); TwoPoleNodeMap::iterator twoPoleIterator = nodeTwoPoles.find(otherNode->getNodeID()); - + if (twoPoleIterator == nodeTwoPoles.end()) { // setup the freeVerb effect for this source for this client otherNodeTwoPole = nodeTwoPoles[otherNode->getNodeID()] = new stk::TwoPole; } else { otherNodeTwoPole = twoPoleIterator->second; } - + // calculate the reasonance for this TwoPole based on angle to source float TWO_POLE_CUT_OFF_FREQUENCY = 800.0f; float TWO_POLE_MAX_FILTER_STRENGTH = 0.4f; - + otherNodeTwoPole->setResonance(TWO_POLE_CUT_OFF_FREQUENCY, - TWO_POLE_MAX_FILTER_STRENGTH - * fabsf(bearingRelativeAngleToSource) / 180.0f, - true); + TWO_POLE_MAX_FILTER_STRENGTH + * fabsf(bearingRelativeAngleToSource) / 180.0f, + true); } } - + int16_t* sourceBuffer = otherNodeBuffer->getNextOutput(); - + int16_t* goodChannel = (bearingRelativeAngleToSource > 0.0f) - ? clientSamples - : clientSamples + BUFFER_LENGTH_SAMPLES_PER_CHANNEL; + ? clientSamples + : clientSamples + BUFFER_LENGTH_SAMPLES_PER_CHANNEL; int16_t* delayedChannel = (bearingRelativeAngleToSource > 0.0f) - ? clientSamples + BUFFER_LENGTH_SAMPLES_PER_CHANNEL - : clientSamples; - + ? clientSamples + BUFFER_LENGTH_SAMPLES_PER_CHANNEL + : clientSamples; + int16_t* delaySamplePointer = otherNodeBuffer->getNextOutput() == otherNodeBuffer->getBuffer() - ? otherNodeBuffer->getBuffer() + RING_BUFFER_LENGTH_SAMPLES - numSamplesDelay - : otherNodeBuffer->getNextOutput() - numSamplesDelay; - + ? otherNodeBuffer->getBuffer() + RING_BUFFER_LENGTH_SAMPLES - numSamplesDelay + : otherNodeBuffer->getNextOutput() - numSamplesDelay; + for (int s = 0; s < BUFFER_LENGTH_SAMPLES_PER_CHANNEL; s++) { // load up the stkFrameBuffer with this source's samples stkFrameBuffer[s] = (stk::StkFloat) sourceBuffer[s]; } - + // perform the TwoPole effect on the stkFrameBuffer if (otherNodeTwoPole) { otherNodeTwoPole->tick(stkFrameBuffer); } - + for (int s = 0; s < BUFFER_LENGTH_SAMPLES_PER_CHANNEL; s++) { if (s < numSamplesDelay) { // pull the earlier sample for the delayed channel int earlierSample = delaySamplePointer[s] * attenuationCoefficient * weakChannelAmplitudeRatio; - + delayedChannel[s] = glm::clamp(delayedChannel[s] + earlierSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); } - + int16_t currentSample = stkFrameBuffer[s] * attenuationCoefficient; - + goodChannel[s] = glm::clamp(goodChannel[s] + currentSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); - + if (s + numSamplesDelay < BUFFER_LENGTH_SAMPLES_PER_CHANNEL) { int sumSample = delayedChannel[s + numSamplesDelay] - + (currentSample * weakChannelAmplitudeRatio); + + (currentSample * weakChannelAmplitudeRatio); delayedChannel[s + numSamplesDelay] = glm::clamp(sumSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); } - + if (s >= BUFFER_LENGTH_SAMPLES_PER_CHANNEL - PHASE_DELAY_AT_90) { // this could be a delayed sample on the next pass // so store the affected back in the ARB @@ -356,11 +347,11 @@ int main(int argc, const char* argv[]) { packetVersionMatch(packetData)) { if (packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_NO_ECHO || packetData[0] == PACKET_TYPE_MICROPHONE_AUDIO_WITH_ECHO) { - + unsigned char* currentBuffer = packetData + numBytesForPacketHeader(packetData); uint16_t sourceID; memcpy(&sourceID, currentBuffer, sizeof(sourceID)); - + Node* avatarNode = nodeList->addOrUpdateNode(nodeAddress, nodeAddress, NODE_TYPE_AGENT, @@ -377,7 +368,7 @@ int main(int argc, const char* argv[]) { for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { if (node->getLinkedData()) { - + InjectedAudioRingBuffer* ringBuffer = (InjectedAudioRingBuffer*) node->getLinkedData(); if (memcmp(ringBuffer->getStreamIdentifier(), packetData + numBytesForPacketHeader(packetData), @@ -400,8 +391,8 @@ int main(int argc, const char* argv[]) { // give the new audio data to the matching injector node nodeList->updateNodeWithData(matchingInjector, packetData, receivedBytes); - } else if (packetData[0] == PACKET_TYPE_PING) { - + } else if (packetData[0] == PACKET_TYPE_PING || packetData[0] == PACKET_TYPE_DOMAIN) { + // If the packet is a ping, let processNodeData handle it. nodeList->processNodeData(nodeAddress, packetData, receivedBytes); } @@ -414,7 +405,7 @@ int main(int argc, const char* argv[]) { gettimeofday(&endSendTime, NULL); float percentageOfMaxElapsed = ((float) (usecTimestamp(&endSendTime) - usecTimestamp(&beginSendTime)) - / BUFFER_SEND_INTERVAL_USECS) * 100.0f; + / BUFFER_SEND_INTERVAL_USECS) * 100.0f; sumFrameTimePercentages += percentageOfMaxElapsed; @@ -429,6 +420,4 @@ int main(int argc, const char* argv[]) { std::cout << "Took too much time, not sleeping!\n"; } } - - return 0; -} +} \ No newline at end of file diff --git a/libraries/audio/src/AudioMixer.h b/libraries/audio/src/AudioMixer.h new file mode 100644 index 0000000000..6318e756dc --- /dev/null +++ b/libraries/audio/src/AudioMixer.h @@ -0,0 +1,17 @@ +// +// AudioMixer.h +// hifi +// +// Created by Stephen Birarda on 8/22/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__AudioMixer__ +#define __hifi__AudioMixer__ + +class AudioMixer { +public: + static void run(); +}; + +#endif /* defined(__hifi__AudioMixer__) */ diff --git a/audio-mixer/src/AvatarAudioRingBuffer.cpp b/libraries/audio/src/AvatarAudioRingBuffer.cpp similarity index 100% rename from audio-mixer/src/AvatarAudioRingBuffer.cpp rename to libraries/audio/src/AvatarAudioRingBuffer.cpp diff --git a/audio-mixer/src/AvatarAudioRingBuffer.h b/libraries/audio/src/AvatarAudioRingBuffer.h similarity index 100% rename from audio-mixer/src/AvatarAudioRingBuffer.h rename to libraries/audio/src/AvatarAudioRingBuffer.h diff --git a/audio-mixer/src/InjectedAudioRingBuffer.cpp b/libraries/audio/src/InjectedAudioRingBuffer.cpp similarity index 100% rename from audio-mixer/src/InjectedAudioRingBuffer.cpp rename to libraries/audio/src/InjectedAudioRingBuffer.cpp diff --git a/audio-mixer/src/InjectedAudioRingBuffer.h b/libraries/audio/src/InjectedAudioRingBuffer.h similarity index 97% rename from audio-mixer/src/InjectedAudioRingBuffer.h rename to libraries/audio/src/InjectedAudioRingBuffer.h index f5caef5f75..e1df9ac5b9 100644 --- a/audio-mixer/src/InjectedAudioRingBuffer.h +++ b/libraries/audio/src/InjectedAudioRingBuffer.h @@ -9,7 +9,7 @@ #ifndef __hifi__InjectedAudioRingBuffer__ #define __hifi__InjectedAudioRingBuffer__ -#include +#include "AudioInjector.h" #include "PositionalAudioRingBuffer.h" diff --git a/audio-mixer/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp similarity index 100% rename from audio-mixer/src/PositionalAudioRingBuffer.cpp rename to libraries/audio/src/PositionalAudioRingBuffer.cpp diff --git a/audio-mixer/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h similarity index 98% rename from audio-mixer/src/PositionalAudioRingBuffer.h rename to libraries/audio/src/PositionalAudioRingBuffer.h index 6c7ee9ce3f..b43cd60660 100644 --- a/audio-mixer/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -12,7 +12,7 @@ #include #include -#include +#include "AudioRingBuffer.h" class PositionalAudioRingBuffer : public AudioRingBuffer { public: diff --git a/avatar-mixer/src/main.cpp b/libraries/avatars/src/AvatarMixer.cpp similarity index 83% rename from avatar-mixer/src/main.cpp rename to libraries/avatars/src/AvatarMixer.cpp index 0677ffbc7e..92e27a64af 100644 --- a/avatar-mixer/src/main.cpp +++ b/libraries/avatars/src/AvatarMixer.cpp @@ -1,44 +1,26 @@ // -// main.cpp -// Avatar Mixer +// AvatarMixer.cpp +// hifi // -// Created by Leonardo Murillo on 03/25/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved +// Created by Stephen Birarda on 9/5/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// +// Original avatar-mixer main created by Leonardo Murillo on 03/25/13. // // The avatar mixer receives head, hand and positional data from all connected // nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms. -// -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include -#include #include -#include -#include -#include +#include #include "AvatarData.h" -const int AVATAR_LISTEN_PORT = 55444; +#include "AvatarMixer.h" unsigned char* addNodeToBroadcastPacket(unsigned char *currentPosition, Node *nodeToAdd) { currentPosition += packNodeId(currentPosition, nodeToAdd->getNodeID()); - + AvatarData *nodeData = (AvatarData *)nodeToAdd->getLinkedData(); currentPosition += nodeData->getBroadcastData(currentPosition); @@ -55,7 +37,7 @@ void attachAvatarDataToNode(Node* newNode) { // 1) use the view frustum to cull those avatars that are out of view. Since avatar data doesn't need to be present // if the avatar is not in view or in the keyhole. // 2) after culling for view frustum, sort order the avatars by distance, send the closest ones first. -// 3) if we need to rate limit the amount of data we send, we can use a distance weighted "semi-random" function to +// 3) if we need to rate limit the amount of data we send, we can use a distance weighted "semi-random" function to // 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(NodeList* nodeList, sockaddr* nodeAddress) { @@ -66,13 +48,13 @@ void broadcastAvatarData(NodeList* nodeList, sockaddr* nodeAddress) { unsigned char* currentBufferPosition = broadcastPacket + numHeaderBytes; int packetLength = currentBufferPosition - broadcastPacket; int packetsSent = 0; - + // send back a packet with other active node data to this node for (NodeList::iterator node = nodeList->begin(); node != nodeList->end(); node++) { if (node->getLinkedData() && !socketMatch(nodeAddress, node->getActiveSocket())) { unsigned char* avatarDataEndpoint = addNodeToBroadcastPacket((unsigned char*)&avatarDataBuffer[0], &*node); int avatarDataLength = avatarDataEndpoint - (unsigned char*)&avatarDataBuffer; - + if (avatarDataLength + packetLength <= MAX_PACKET_SIZE) { memcpy(currentBufferPosition, &avatarDataBuffer[0], avatarDataLength); packetLength += avatarDataLength; @@ -98,17 +80,9 @@ void broadcastAvatarData(NodeList* nodeList, sockaddr* nodeAddress) { nodeList->getNodeSocket()->send(nodeAddress, broadcastPacket, currentBufferPosition - broadcastPacket); } -int main(int argc, const char* argv[]) { - - NodeList* nodeList = NodeList::createInstance(NODE_TYPE_AVATAR_MIXER, AVATAR_LISTEN_PORT); - setvbuf(stdout, NULL, _IOLBF, 0); - - // Handle Local Domain testing with the --local command line - const char* local = "--local"; - if (cmdOptionExists(argc, argv, local)) { - printf("Local Domain MODE!\n"); - nodeList->setDomainIPToLocalhost(); - } +void AvatarMixer::run() { + NodeList* nodeList = NodeList::getInstance(); + nodeList->setOwnerType(NODE_TYPE_AVATAR_MIXER); nodeList->linkedDataCreateCallback = attachAvatarDataToNode; @@ -119,16 +93,19 @@ int main(int argc, const char* argv[]) { unsigned char* packetData = new unsigned char[MAX_PACKET_SIZE]; - uint16_t nodeID = 0; Node* avatarNode = NULL; timeval lastDomainServerCheckIn = {}; // we only need to hear back about avatar nodes from the DS - NodeList::getInstance()->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1); + nodeList->setNodeTypesOfInterest(&NODE_TYPE_AGENT, 1); while (true) { + if (NodeList::getInstance()->getNumNoReplyDomainCheckIns() == MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + break; + } + // send a check in packet to the domain server if DOMAIN_SERVER_CHECK_IN_USECS has elapsed if (usecTimestampNow() - usecTimestamp(&lastDomainServerCheckIn) >= DOMAIN_SERVER_CHECK_IN_USECS) { gettimeofday(&lastDomainServerCheckIn, NULL); @@ -162,9 +139,6 @@ int main(int argc, const char* argv[]) { } } break; - case PACKET_TYPE_DOMAIN: - // ignore the DS packet, for now nodes are added only when they communicate directly with us - break; default: // hand this off to the NodeList nodeList->processNodeData(nodeAddress, packetData, receivedBytes); @@ -174,6 +148,4 @@ int main(int argc, const char* argv[]) { } nodeList->stopSilentNodeRemovalThread(); - - return 0; -} +} \ No newline at end of file diff --git a/libraries/avatars/src/AvatarMixer.h b/libraries/avatars/src/AvatarMixer.h new file mode 100644 index 0000000000..cf3f2245fe --- /dev/null +++ b/libraries/avatars/src/AvatarMixer.h @@ -0,0 +1,19 @@ +// +// AvatarMixer.h +// hifi +// +// Created by Stephen Birarda on 9/5/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__AvatarMixer__ +#define __hifi__AvatarMixer__ + +#include + +class AvatarMixer { +public: + static void run(); +}; + +#endif /* defined(__hifi__AvatarMixer__) */ diff --git a/libraries/shared/src/Assignment.cpp b/libraries/shared/src/Assignment.cpp new file mode 100644 index 0000000000..3c6e432f2c --- /dev/null +++ b/libraries/shared/src/Assignment.cpp @@ -0,0 +1,138 @@ +// +// Assignment.cpp +// hifi +// +// Created by Stephen Birarda on 8/22/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#include + +#include "PacketHeaders.h" + +#include "Assignment.h" + +const char IPv4_ADDRESS_DESIGNATOR = 4; +const char IPv6_ADDRESS_DESIGNATOR = 6; + +Assignment::Assignment(Assignment::Direction direction, Assignment::Type type, const char* pool) : + _direction(direction), + _type(type), + _pool(NULL), + _domainSocket(NULL) +{ + // set the create time on this assignment + gettimeofday(&_time, NULL); + + // copy the pool, if we got one + if (pool) { + int poolLength = strlen(pool); + + // create the char array and make it large enough for string and null termination + _pool = new char[poolLength + sizeof(char)]; + strcpy(_pool, pool); + } +} + +Assignment::Assignment(const unsigned char* dataBuffer, int numBytes) : + _pool(NULL), + _domainSocket(NULL) +{ + // set the create time on this assignment + gettimeofday(&_time, NULL); + + int numBytesRead = 0; + + if (dataBuffer[0] == PACKET_TYPE_REQUEST_ASSIGNMENT) { + _direction = Assignment::Request; + } else if (dataBuffer[0] == PACKET_TYPE_CREATE_ASSIGNMENT) { + _direction = Assignment::Create; + } else if (dataBuffer[0] == PACKET_TYPE_DEPLOY_ASSIGNMENT) { + _direction = Assignment::Deploy; + } + + numBytesRead += numBytesForPacketHeader(dataBuffer); + + memcpy(&_type, dataBuffer + numBytesRead, sizeof(Assignment::Type)); + numBytesRead += sizeof(Assignment::Type); + + if (dataBuffer[numBytesRead] != 0) { + int poolLength = strlen((const char*) dataBuffer + numBytesRead); + _pool = new char[poolLength + sizeof(char)]; + strcpy(_pool, (char*) dataBuffer + numBytesRead); + numBytesRead += poolLength + sizeof(char); + } else { + numBytesRead += sizeof(char); + } + + if (numBytes > numBytesRead) { + if (dataBuffer[numBytesRead++] == IPv4_ADDRESS_DESIGNATOR) { + // IPv4 address + sockaddr_in destinationSocket = {}; + memcpy(&destinationSocket, dataBuffer + numBytesRead, sizeof(sockaddr_in)); + destinationSocket.sin_family = AF_INET; + setDomainSocket((sockaddr*) &destinationSocket); + } else { + // IPv6 address + sockaddr_in6 destinationSocket = {}; + memcpy(&destinationSocket, dataBuffer + numBytesRead, sizeof(sockaddr_in6)); + setDomainSocket((sockaddr*) &destinationSocket); + } + } +} + +Assignment::~Assignment() { + delete _domainSocket; + delete _pool; +} + +int Assignment::packToBuffer(unsigned char* buffer) { + int numPackedBytes = 0; + + memcpy(buffer + numPackedBytes, &_type, sizeof(_type)); + numPackedBytes += sizeof(_type); + + if (_pool) { + int poolLength = strlen(_pool); + strcpy((char*) buffer + numPackedBytes, _pool); + + numPackedBytes += poolLength + sizeof(char); + } else { + buffer[numPackedBytes] = '\0'; + numPackedBytes += sizeof(char); + } + + if (_domainSocket) { + buffer[numPackedBytes++] = (_domainSocket->sa_family == AF_INET) ? IPv4_ADDRESS_DESIGNATOR : IPv6_ADDRESS_DESIGNATOR; + + int numSocketBytes = (_domainSocket->sa_family == AF_INET) ? sizeof(sockaddr_in) : sizeof(sockaddr_in6); + + memcpy(buffer + numPackedBytes, _domainSocket, numSocketBytes); + numPackedBytes += numSocketBytes; + } + + return numPackedBytes; +} + +void Assignment::setDomainSocket(const sockaddr* domainSocket) { + + if (_domainSocket) { + // delete the old _domainSocket if it exists + delete _domainSocket; + _domainSocket = NULL; + } + + // create a new sockaddr or sockaddr_in depending on what type of address this is + if (domainSocket->sa_family == AF_INET) { + _domainSocket = (sockaddr*) new sockaddr_in; + memcpy(_domainSocket, domainSocket, sizeof(sockaddr_in)); + } else { + _domainSocket = (sockaddr*) new sockaddr_in6; + memcpy(_domainSocket, domainSocket, sizeof(sockaddr_in6)); + } +} + +QDebug operator<<(QDebug debug, const Assignment &assignment) { + debug << "T:" << assignment.getType() << "P:" << assignment.getPool(); + return debug.nospace(); +} \ No newline at end of file diff --git a/libraries/shared/src/Assignment.h b/libraries/shared/src/Assignment.h new file mode 100644 index 0000000000..720169e55b --- /dev/null +++ b/libraries/shared/src/Assignment.h @@ -0,0 +1,54 @@ +// +// Assignment.h +// hifi +// +// Created by Stephen Birarda on 8/22/13. +// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Assignment__ +#define __hifi__Assignment__ + +#include "NodeList.h" + +class Assignment { +public: + + enum Type { + AudioMixer, + AvatarMixer, + All + }; + + enum Direction { + Create, + Deploy, + Request + }; + + Assignment(Assignment::Direction direction, Assignment::Type type, const char* pool = NULL); + Assignment(const unsigned char* dataBuffer, int numBytes); + + ~Assignment(); + + Assignment::Direction getDirection() const { return _direction; } + Assignment::Type getType() const { return _type; } + const char* getPool() const { return _pool; } + const timeval& getTime() const { return _time; } + + const sockaddr* getDomainSocket() { return _domainSocket; } + void setDomainSocket(const sockaddr* domainSocket); + + int packToBuffer(unsigned char* buffer); + +private: + Assignment::Direction _direction; + Assignment::Type _type; + char* _pool; + sockaddr* _domainSocket; + timeval _time; +}; + +QDebug operator<<(QDebug debug, const Assignment &assignment); + +#endif /* defined(__hifi__Assignment__) */ diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index 8850d34e87..c650190a4a 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -13,6 +13,7 @@ #include +#include "Assignment.h" #include "NodeList.h" #include "NodeTypes.h" #include "PacketHeaders.h" @@ -63,7 +64,8 @@ NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) : _ownerType(newOwnerType), _nodeTypesOfInterest(NULL), _ownerID(UNKNOWN_NODE_ID), - _lastNodeID(UNKNOWN_NODE_ID + 1) + _lastNodeID(UNKNOWN_NODE_ID + 1), + _numNoReplyDomainCheckIns(0) { memcpy(_domainHostname, DEFAULT_DOMAIN_HOSTNAME, sizeof(DEFAULT_DOMAIN_HOSTNAME)); memcpy(_domainIP, DEFAULT_DOMAIN_IP, sizeof(DEFAULT_DOMAIN_IP)); @@ -328,9 +330,15 @@ void NodeList::sendDomainServerCheckIn() { } _nodeSocket.send(_domainIP, DEFAULT_DOMAINSERVER_PORT, checkInPacket, checkInPacketSize); + + // increment the count of un-replied check-ins + _numNoReplyDomainCheckIns++; } int NodeList::processDomainServerList(unsigned char* packetData, size_t dataBytes) { + // this is a packet from the domain server, reset the count of un-replied check-ins + _numNoReplyDomainCheckIns = 0; + int readNodes = 0; char nodeType; @@ -366,13 +374,21 @@ int NodeList::processDomainServerList(unsigned char* packetData, size_t dataByte return readNodes; } -void NodeList::sendAssignmentRequest() { - const char ASSIGNMENT_SERVER_HOSTNAME[] = "assignment.highfidelity.io"; +const char ASSIGNMENT_SERVER_HOSTNAME[] = "assignment.highfidelity.io"; +const sockaddr_in assignmentServerSocket = socketForHostnameAndHostOrderPort(ASSIGNMENT_SERVER_HOSTNAME, + ASSIGNMENT_SERVER_PORT); + +void NodeList::sendAssignment(Assignment& assignment) { + unsigned char assignmentPacket[MAX_PACKET_SIZE]; - static sockaddr_in assignmentServerSocket = socketForHostname(ASSIGNMENT_SERVER_HOSTNAME); - assignmentServerSocket.sin_port = htons(ASSIGNMENT_SERVER_PORT); + PACKET_TYPE assignmentPacketType = assignment.getDirection() == Assignment::Create + ? PACKET_TYPE_CREATE_ASSIGNMENT + : PACKET_TYPE_REQUEST_ASSIGNMENT; - _nodeSocket.send((sockaddr*) &assignmentServerSocket, &PACKET_TYPE_REQUEST_ASSIGNMENT, 1); + int numHeaderBytes = populateTypeAndVersion(assignmentPacket, assignmentPacketType); + int numAssignmentBytes = assignment.packToBuffer(assignmentPacket + numHeaderBytes); + + _nodeSocket.send((sockaddr*) &assignmentServerSocket, assignmentPacket, numHeaderBytes + numAssignmentBytes); } Node* NodeList::addOrUpdateNode(sockaddr* publicSocket, sockaddr* localSocket, char nodeType, uint16_t nodeId) { @@ -385,9 +401,14 @@ Node* NodeList::addOrUpdateNode(sockaddr* publicSocket, sockaddr* localSocket, c break; } } - } + } if (node == end()) { + // if we already had this node AND it's a solo type then bust out of here + if (soloNodeOfType(nodeType)) { + return NULL; + } + // we didn't have this node, so add them Node* newNode = new Node(publicSocket, localSocket, nodeType, nodeId); diff --git a/libraries/shared/src/NodeList.h b/libraries/shared/src/NodeList.h index 9e9f72767d..8afe8e38d3 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/shared/src/NodeList.h @@ -28,7 +28,6 @@ const int MAX_NUM_NODES = 10000; const int NODES_PER_BUCKET = 100; const int MAX_PACKET_SIZE = 1500; -const unsigned short int NODE_SOCKET_LISTEN_PORT = 40103; const int NODE_SILENCE_THRESHOLD_USECS = 2 * 1000000; const int DOMAIN_SERVER_CHECK_IN_USECS = 1 * 1000000; @@ -43,6 +42,9 @@ extern const int DEFAULT_DOMAINSERVER_PORT; const int UNKNOWN_NODE_ID = 0; +const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; + +class Assignment; class NodeListIterator; // Callers who want to hook add/kill callbacks should implement this class @@ -55,7 +57,7 @@ public: class NodeList { public: - static NodeList* createInstance(char ownerType, unsigned short int socketListenPort = NODE_SOCKET_LISTEN_PORT); + static NodeList* createInstance(char ownerType, unsigned short int socketListenPort = 0); static NodeList* getInstance(); typedef NodeListIterator iterator; @@ -88,6 +90,8 @@ public: int size() { return _numNodes; } int getNumAliveNodes() const; + int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; } + void clear(); void setNodeTypesOfInterest(const char* nodeTypesOfInterest, int numNodeTypesOfInterest); @@ -95,7 +99,7 @@ public: void sendDomainServerCheckIn(); int processDomainServerList(unsigned char *packetData, size_t dataBytes); - void sendAssignmentRequest(); + void sendAssignment(Assignment& assignment); Node* nodeWithAddress(sockaddr *senderAddress); Node* nodeWithID(uint16_t nodeID); @@ -146,6 +150,7 @@ private: uint16_t _lastNodeID; pthread_t removeSilentNodesThread; pthread_t checkInWithDomainServerThread; + int _numNoReplyDomainCheckIns; void handlePingReply(sockaddr *nodeAddress); void timePingReply(sockaddr *nodeAddress, unsigned char *packetData); diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index 8eb82cea4d..1cfb17f63d 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -67,7 +67,7 @@ int numBytesForPacketVersion(const unsigned char* packetVersion) { } } -int numBytesForPacketHeader(unsigned char* packetHeader) { +int numBytesForPacketHeader(const unsigned char* packetHeader) { // int numBytesType = numBytesForPacketType(packetHeader); // return numBytesType + numBytesForPacketVersion(packetHeader + numBytesType); diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/shared/src/PacketHeaders.h index 2f4f8d2196..2aaabc4aa8 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/shared/src/PacketHeaders.h @@ -36,7 +36,8 @@ const PACKET_TYPE PACKET_TYPE_ENVIRONMENT_DATA = 'e'; const PACKET_TYPE PACKET_TYPE_DOMAIN_LIST_REQUEST = 'L'; const PACKET_TYPE PACKET_TYPE_DOMAIN_REPORT_FOR_DUTY = 'C'; const PACKET_TYPE PACKET_TYPE_REQUEST_ASSIGNMENT = 'r'; -const PACKET_TYPE PACKET_TYPE_SEND_ASSIGNMENT = 's'; +const PACKET_TYPE PACKET_TYPE_CREATE_ASSIGNMENT = 's'; +const PACKET_TYPE PACKET_TYPE_DEPLOY_ASSIGNMENT = 'd'; const PACKET_TYPE PACKET_TYPE_VOXEL_STATS = '#'; const PACKET_TYPE PACKET_TYPE_VOXEL_JURISDICTION = 'J'; const PACKET_TYPE PACKET_TYPE_VOXEL_JURISDICTION_REQUEST = 'j'; @@ -48,7 +49,7 @@ PACKET_VERSION versionForPacketType(PACKET_TYPE type); bool packetVersionMatch(unsigned char* packetHeader); int populateTypeAndVersion(unsigned char* destinationHeader, PACKET_TYPE type); -int numBytesForPacketHeader(unsigned char* packetHeader); +int numBytesForPacketHeader(const unsigned char* packetHeader); const int MAX_PACKET_HEADER_BYTES = sizeof(PACKET_TYPE) + sizeof(PACKET_VERSION); @@ -57,6 +58,6 @@ const int MAX_PACKET_HEADER_BYTES = sizeof(PACKET_TYPE) + sizeof(PACKET_VERSION) #define ADD_SCENE_COMMAND "add scene" #define TEST_COMMAND "a message" -const int ASSIGNMENT_SERVER_PORT = 7007; +const unsigned short ASSIGNMENT_SERVER_PORT = 7007; #endif diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index c34663c077..359a154c4f 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -26,7 +26,7 @@ #include "PacketHeaders.h" #include "SharedUtil.h" -uint64_t usecTimestamp(timeval *time) { +uint64_t usecTimestamp(const timeval *time) { return (time->tv_sec * 1000000 + time->tv_usec); } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index 82850d8de3..77c9cdd040 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -38,7 +38,7 @@ static const float DECIMETER = 0.1f; static const float CENTIMETER = 0.01f; static const float MILLIIMETER = 0.001f; -uint64_t usecTimestamp(timeval *time); +uint64_t usecTimestamp(const timeval *time); uint64_t usecTimestampNow(); float randFloat(); diff --git a/libraries/shared/src/UDPSocket.cpp b/libraries/shared/src/UDPSocket.cpp index 39c4dc2e62..ab2460dd7f 100644 --- a/libraries/shared/src/UDPSocket.cpp +++ b/libraries/shared/src/UDPSocket.cpp @@ -6,6 +6,7 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // +#include #include #include #include @@ -118,14 +119,18 @@ unsigned short loadBufferWithSocketInfo(char* addressBuffer, sockaddr* socket) { } } -sockaddr_in socketForHostname(const char* hostname) { +sockaddr_in socketForHostnameAndHostOrderPort(const char* hostname, unsigned short port) { struct hostent* pHostInfo; - sockaddr_in newSocket; + sockaddr_in newSocket = {}; if ((pHostInfo = gethostbyname(hostname))) { memcpy(&newSocket.sin_addr, pHostInfo->h_addr_list[0], pHostInfo->h_length); } + if (port != 0) { + newSocket.sin_port = htons(port); + } + return newSocket; } @@ -162,11 +167,8 @@ UDPSocket::UDPSocket(unsigned short int listeningPort) : _listeningPort = ntohs(bind_address.sin_port); } - // set timeout on socket recieve to 0.5 seconds - struct timeval tv; - tv.tv_sec = 0; - tv.tv_usec = 500000; - setsockopt(handle, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof tv); + const int DEFAULT_BLOCKING_SOCKET_TIMEOUT_USECS = 0.5 * 1000000; + setBlockingReceiveTimeoutInUsecs(DEFAULT_BLOCKING_SOCKET_TIMEOUT_USECS); qDebug("Created UDP socket listening on port %hu.\n", _listeningPort); } @@ -223,6 +225,11 @@ void UDPSocket::setBlocking(bool blocking) { #endif } +void UDPSocket::setBlockingReceiveTimeoutInUsecs(int timeoutUsecs) { + struct timeval tv = {timeoutUsecs / 1000000, timeoutUsecs % 1000000}; + setsockopt(handle, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv)); +} + // Receive data on this socket with retrieving address of sender bool UDPSocket::receive(void* receivedData, ssize_t* receivedBytes) const { return receive((sockaddr*) &senderAddress, receivedData, receivedBytes); diff --git a/libraries/shared/src/UDPSocket.h b/libraries/shared/src/UDPSocket.h index 34f8ee0b06..1b627dbd41 100644 --- a/libraries/shared/src/UDPSocket.h +++ b/libraries/shared/src/UDPSocket.h @@ -22,12 +22,17 @@ class UDPSocket { public: UDPSocket(unsigned short int listeningPort); ~UDPSocket(); + bool init(); unsigned short int getListeningPort() const { return _listeningPort; } + void setBlocking(bool blocking); bool isBlocking() const { return blocking; } + void setBlockingReceiveTimeoutInUsecs(int timeoutUsecs); + int send(sockaddr* destAddress, const void* data, size_t byteLength) const; int send(char* destAddress, int destPort, const void* data, size_t byteLength) const; + bool receive(void* receivedData, ssize_t* receivedBytes) const; bool receive(sockaddr* recvAddress, void* receivedData, ssize_t* receivedBytes) const; private: @@ -42,6 +47,6 @@ int packSocket(unsigned char* packStore, sockaddr* socketToPack); int unpackSocket(unsigned char* packedData, sockaddr* unpackDestSocket); int getLocalAddress(); unsigned short loadBufferWithSocketInfo(char* addressBuffer, sockaddr* socket); -sockaddr_in socketForHostname(const char* hostname); +sockaddr_in socketForHostnameAndHostOrderPort(const char* hostname, unsigned short port = 0); #endif /* defined(__interface__UDPSocket__) */