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: -
sudo apt-get install build-essential cmake git libcurl4-openssl-dev libqt5scripttools5 libqt5svg5-dev libqt5webkit5-dev libqt5location5 qtlocation5-dev qtdeclarative5-dev qtscript5-dev qtsensors5-dev qtmultimedia5-dev qtquick1-5-dev libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev
+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/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 84714259e5..2694bf83e2 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -101,6 +101,9 @@ void Agent::run() { // 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 146cb71df4..8b2038a8b0 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -21,9 +21,14 @@ class Agent : public ThreadedAssignment { Q_OBJECT + + Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar) public: Agent(const QByteArray& packet); + void setIsAvatar(bool isAvatar) { _scriptEngine.setIsAvatar(isAvatar); } + bool isAvatar() const { return _scriptEngine.isAvatar(); } + public slots: void run(); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index cdf0da43de..b2a5555e36 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -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() { diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp index ef587c9b6d..5bf0417f22 100644 --- a/assignment-client/src/AssignmentFactory.cpp +++ b/assignment-client/src/AssignmentFactory.cpp @@ -19,10 +19,11 @@ #include "metavoxels/MetavoxelServer.h" ThreadedAssignment* AssignmentFactory::unpackAssignment(const QByteArray& packet) { - int headerBytes = numBytesForPacketHeader(packet); - + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + quint8 packedType; - memcpy(&packedType, packet.data() + headerBytes, sizeof(packedType)); + packetStream >> packedType; Assignment::Type unpackedType = (Assignment::Type) packedType; diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 014008d35e..358e507fc4 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -70,8 +70,7 @@ void broadcastAvatarData() { QByteArray avatarByteArray; avatarByteArray.append(otherNode->getUUID().toRfc4122()); - - AvatarData *nodeData = (AvatarData *)otherNode->getLinkedData(); + AvatarData* nodeData = (AvatarData*) otherNode->getLinkedData(); avatarByteArray.append(nodeData->toByteArray()); if (avatarByteArray.size() + mixedAvatarByteArray.size() > MAX_PACKET_SIZE) { diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 6e4a4cdff4..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(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,8 +68,145 @@ 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() { @@ -148,13 +259,13 @@ void DomainServer::readAvailableDatagrams() { << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::MetavoxelServer; - Assignment* matchingStaticAssignment = NULL; + 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)) - || checkInWithUUIDMatchesExistingNode(nodePublicAddress, - nodeLocalAddress, - nodeUUID))) + || (matchingStaticAssignment = matchingStaticAssignmentForCheckIn(nodeUUID, nodeType)) + || nodeList->getInstance()->nodeWithUUID(nodeUUID)) { SharedNodePointer checkInNode = nodeList->addOrUpdateNode(nodeUUID, nodeType, @@ -167,19 +278,13 @@ void DomainServer::readAvailableDatagrams() { 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); } - quint8 numInterestTypes = 0; packetStream >> numInterestTypes; @@ -210,34 +315,29 @@ void DomainServer::readAvailableDatagrams() { } } else if (requestType == PacketTypeRequestAssignment) { - if (_assignmentQueue.size() > 0) { - // construct the requested assignment from the packet data - Assignment requestAssignment(receivedPacket); + // 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; - qDebug("Received a request for assignment type %i from %s.", - requestAssignment.getType(), qPrintable(senderSockAddr.getAddress().toString())); + // give this assignment out, either the type matches or the requestor said they will take any + assignmentPacket.resize(numAssignmentPacketHeaderBytes); - Assignment* assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); + QDataStream assignmentStream(&assignmentPacket, QIODevice::Append); - if (assignmentToDeploy) { - // 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; - - nodeList->getNodeSocket().writeDatagram(assignmentPacket, - senderSockAddr.getAddress(), senderSockAddr.getPort()); - - if (assignmentToDeploy->getNumberOfInstances() == 0) { - // there are no more instances of this script to send out, delete it - delete assignmentToDeploy; - } - } + 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; } } } @@ -281,70 +381,6 @@ QJsonObject jsonObjectForNode(Node* node) { 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"; @@ -373,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)->getPool().isEmpty()) { - queuedAssignmentJSON[JSON_KEY_POOL] = (*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; @@ -428,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"; @@ -466,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)) { @@ -504,307 +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); } } -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())); - - voxelServerAssignment.setPayload(config.toUtf8()); - - 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())); - - particleServerAssignment.setPayload(config.toLocal8Bit()); - - 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(QByteArray(_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, NodeType_t 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)->getPool().isEmpty() && requestAssignment.getPool().isEmpty(); - bool assignmentPoolsMatch = (*assignment)->getPool() == requestAssignment.getPool(); - + 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 26cdc765ba..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,37 +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, NodeType_t 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); + + 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 index c1b33b24a5..43eb516cee 100644 --- a/examples/controllerExample.js +++ b/examples/controllerExample.js @@ -48,6 +48,15 @@ function checkController() { 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"); } @@ -73,18 +82,106 @@ function touchEndEvent(event) { } // register the call back so it fires before each data send -Agent.willSendVisualDataCallback.connect(checkController); +Script.willSendVisualDataCallback.connect(checkController); // Map keyPress and mouse move events to our callbacks Controller.keyPressEvent.connect(keyPressEvent); -var AKeyEvent = { +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 -Controller.captureKeyEvents(AKeyEvent); +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); @@ -102,4 +199,4 @@ function scriptEnding() { Controller.releaseTouchEvents(); } -Agent.scriptEnding.connect(scriptEnding); +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 61e32c4d55..152bb18fca 100644 --- a/examples/editParticleExample.js +++ b/examples/editParticleExample.js @@ -44,7 +44,6 @@ var particleID = Particles.addParticle(originalProperties); function moveParticle() { if (count >= moveUntil) { - //Agent.stop(); // delete it... if (count == moveUntil) { @@ -54,8 +53,8 @@ function moveParticle() { // stop it... if (count >= stopAfter) { - print("calling Agent.stop()"); - Agent.stop(); + print("calling Script.stop()"); + Script.stop(); } count++; @@ -86,5 +85,5 @@ function moveParticle() { // 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 index 79fad76a1b..f404019bc1 100644 --- a/examples/lookWithMouse.js +++ b/examples/lookWithMouse.js @@ -70,5 +70,5 @@ MyAvatar.bodyPitch = 0; MyAvatar.bodyRoll = 0; // would be nice to change to update -Agent.willSendVisualDataCallback.connect(update); -Agent.scriptEnding.connect(scriptEnding); +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 index 9f19069ee9..e95cc0c2bf 100644 --- a/examples/particleModelExample.js +++ b/examples/particleModelExample.js @@ -37,8 +37,8 @@ var ballParticleID = Particles.addParticle(ballProperties); function endAfterAWhile() { // stop it... if (count >= stopAfter) { - print("calling Agent.stop()"); - Agent.stop(); + print("calling Script.stop()"); + Script.stop(); } print("count =" + count); @@ -47,5 +47,5 @@ function endAfterAWhile() { // register the call back so it fires before each data send -Agent.willSendVisualDataCallback.connect(endAfterAWhile); +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/src/Application.cpp b/interface/src/Application.cpp index 09e4c1ca62..a8e5a540fd 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -117,11 +117,10 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _frameCount(0), _fps(120.0f), _justStarted(true), - _voxelImporter(_window), + _voxelImporter(NULL), _wantToKillLocalVoxels(false), _audioScope(256, 200, true), - _avatarManager(), - _myAvatar(NULL), + _myAvatar(), _profile(QString()), _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), _mouseX(0), @@ -1090,7 +1089,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(); @@ -1410,10 +1409,10 @@ void Application::wheelEvent(QWheelEvent* event) { void Application::sendPingPackets() { QByteArray pingPacket = NodeList::getInstance()->constructPingPacket(); - getInstance()->controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer - << NodeType::ParticleServer - << NodeType::AudioMixer << NodeType::AvatarMixer - << NodeType::MetavoxelServer); + controlledBroadcastToNodes(pingPacket, NodeSet() << NodeType::VoxelServer + << NodeType::ParticleServer + << NodeType::AudioMixer << NodeType::AvatarMixer + << NodeType::MetavoxelServer); } // Every second, check the frame rates and other stuff @@ -1670,7 +1669,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."); @@ -1810,8 +1814,6 @@ void Application::init() { _sharedVoxelSystem.changeTree(&_clipboard); delete tmpTree; - _voxelImporter.init(_settings); - _environment.init(); _glowEffect.init(); @@ -1875,6 +1877,17 @@ void Application::init() { _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); _palette.addAction(Menu::getInstance()->getActionForOption(MenuOption::VoxelDeleteMode), 0, 1); @@ -3842,18 +3855,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); diff --git a/interface/src/Application.h b/interface/src/Application.h index 111110296f..bf9981a160 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -264,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(); @@ -353,7 +353,7 @@ private: VoxelSystem _voxels; VoxelTree _clipboard; // if I copy/paste - VoxelImporter _voxelImporter; + VoxelImporter* _voxelImporter; VoxelSystem _sharedVoxelSystem; ViewFrustum _sharedVoxelSystemViewFrustum; diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index e42947bf8c..9fede84a93 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -539,7 +539,7 @@ void Audio::toggleMute() { } void Audio::render(int screenWidth, int screenHeight) { - if (_audioInput) { + if (_audioInput && _audioOutput) { glLineWidth(2.0); glBegin(GL_LINES); glColor3f(.93f, .93f, .93f); diff --git a/interface/src/ImportDialog.cpp b/interface/src/ImportDialog.cpp index 28e39f1abd..ac7853629c 100644 --- a/interface/src/ImportDialog.cpp +++ b/interface/src/ImportDialog.cpp @@ -231,10 +231,6 @@ void ImportDialog::setLayout() { widget = findChild("treeView"); widget->setAttribute(Qt::WA_MacShowFocusRect, false); - // remove reference to treeView - widget = NULL; - widget->deleteLater(); - switchToResourcesParentIfRequired(); QFile styleSheet("resources/styles/import_dialog.qss"); if (styleSheet.open(QIODevice::ReadOnly)) { diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 8fd268938e..67eaa8782c 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -116,10 +116,10 @@ Menu::Menu() : this, SLOT(goToLocation())); addActionToQMenuAndActionHash(fileMenu, - MenuOption::GoToUser, + MenuOption::GoTo, Qt::Key_At, this, - SLOT(goToUser())); + SLOT(goTo())); addDisabledActionAndSeparator(fileMenu, "Settings"); @@ -910,6 +910,60 @@ void Menu::goToDomain() { sendFakeEnterEvent(); } +void Menu::goTo() { + + QInputDialog gotoDialog(Application::getInstance()->getWindow()); + gotoDialog.setWindowTitle("Go to"); + gotoDialog.setLabelText("Destination:"); + QString destination = Application::getInstance()->getProfile()->getUsername(); + gotoDialog.setTextValue(destination); + gotoDialog.setWindowFlags(Qt::Sheet); + gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height()); + + int dialogReturn = gotoDialog.exec(); + if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { + + destination = gotoDialog.textValue(); + + QStringList coordinateItems = destination.split(QRegExp("_|,"), QString::SkipEmptyParts); + + const int NUMBER_OF_COORDINATE_ITEMS = 3; + const int X_ITEM = 0; + const int Y_ITEM = 1; + const int Z_ITEM = 2; + if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) { + + double x = replaceLastOccurrence('-', '.', coordinateItems[X_ITEM].trimmed()).toDouble(); + double y = replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM].trimmed()).toDouble(); + double z = replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM].trimmed()).toDouble(); + + glm::vec3 newAvatarPos(x, y, z); + + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + glm::vec3 avatarPos = myAvatar->getPosition(); + if (newAvatarPos != avatarPos) { + // send a node kill request, indicating to other clients that they should play the "disappeared" effect + MyAvatar::sendKillAvatar(); + + qDebug("Going To Location: %f, %f, %f...", x, y, z); + myAvatar->setPosition(newAvatarPos); + } + + } else { + // there's a username entered by the user, make a request to the data-server + DataServerClient::getValuesForKeysAndUserString( + QStringList() + << DataServerKey::Domain + << DataServerKey::Position + << DataServerKey::Orientation, + destination, Application::getInstance()->getProfile()); + + } + } + + sendFakeEnterEvent(); +} + void Menu::goToLocation() { MyAvatar* myAvatar = Application::getInstance()->getAvatar(); glm::vec3 avatarPos = myAvatar->getPosition(); @@ -954,26 +1008,6 @@ void Menu::goToLocation() { sendFakeEnterEvent(); } -void Menu::goToUser() { - QInputDialog userDialog(Application::getInstance()->getWindow()); - userDialog.setWindowTitle("Go to User"); - userDialog.setLabelText("Destination user:"); - QString username = Application::getInstance()->getProfile()->getUsername(); - userDialog.setTextValue(username); - userDialog.setWindowFlags(Qt::Sheet); - userDialog.resize(userDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, userDialog.size().height()); - - int dialogReturn = userDialog.exec(); - if (dialogReturn == QDialog::Accepted && !userDialog.textValue().isEmpty()) { - // there's a username entered by the user, make a request to the data-server - DataServerClient::getValuesForKeysAndUserString( - QStringList() << DataServerKey::Domain << DataServerKey::Position << DataServerKey::Orientation, - userDialog.textValue(), Application::getInstance()->getProfile()); - } - - sendFakeEnterEvent(); -} - void Menu::pasteToVoxel() { QInputDialog pasteToOctalCodeDialog(Application::getInstance()->getWindow()); pasteToOctalCodeDialog.setWindowTitle("Paste to Voxel"); @@ -1137,3 +1171,14 @@ void Menu::updateFrustumRenderModeAction() { } } +QString Menu::replaceLastOccurrence(QChar search, QChar replace, QString string) { + int lastIndex; + lastIndex = string.lastIndexOf(search); + if (lastIndex > 0) { + lastIndex = string.lastIndexOf(search); + string.replace(lastIndex, 1, replace); + } + + return string; +} + diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 03149ce07c..5e49ca6fd1 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -92,7 +92,7 @@ public slots: void saveSettings(QSettings* settings = NULL); void importSettings(); void exportSettings(); - void goToUser(); + void goTo(); void pasteToVoxel(); private slots: @@ -152,6 +152,7 @@ private: QAction* _useVoxelShader; int _maxVoxelPacketsPerSecond; QMenu* _activeScriptsMenu; + QString replaceLastOccurrence(QChar search, QChar replace, QString string); }; namespace MenuOption { @@ -209,7 +210,7 @@ namespace MenuOption { const QString GlowMode = "Cycle Glow Mode"; const QString GoToDomain = "Go To Domain..."; const QString GoToLocation = "Go To Location..."; - const QString GoToUser = "Go To User..."; + const QString GoTo = "Go To..."; const QString ImportVoxels = "Import Voxels"; const QString ImportVoxelsClipboard = "Import Voxels to Clipboard"; const QString IncreaseAvatarSize = "Increase Avatar Size"; diff --git a/interface/src/VoxelImporter.cpp b/interface/src/VoxelImporter.cpp index 0011b0abb7..653d04cee4 100644 --- a/interface/src/VoxelImporter.cpp +++ b/interface/src/VoxelImporter.cpp @@ -24,15 +24,15 @@ private: const QString SETTINGS_GROUP_NAME = "VoxelImport"; const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings"; -VoxelImporter::VoxelImporter(QWidget* parent) - : QObject(parent), - _voxelTree(true), - _importDialog(parent), - _currentTask(NULL), - _nextTask(NULL) { - - connect(&_importDialog, SIGNAL(currentChanged(QString)), SLOT(preImport())); - connect(&_importDialog, SIGNAL(accepted()), SLOT(import())); +VoxelImporter::VoxelImporter(QWidget* parent) : + QObject(parent), + _voxelTree(true), + _importDialog(parent), + _currentTask(NULL), + _nextTask(NULL) +{ + connect(&_importDialog, &QFileDialog::currentChanged, this, &VoxelImporter::preImport); + connect(&_importDialog, &QFileDialog::accepted, this, &VoxelImporter::import); } void VoxelImporter::saveSettings(QSettings* settings) { diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 78b8bad3ed..d98d30cf6a 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -162,13 +162,13 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const int bytesRead = numBytesForPacketHeader(datagram); QByteArray dummyAvatarByteArray = byteArrayWithPopluatedHeader(PacketTypeAvatarData); - int numDummyByteArrayHeaderBytes = dummyAvatarByteArray.size(); + int numDummyHeaderBytes = dummyAvatarByteArray.size(); + int numDummyHeaderBytesWithoutUUID = numDummyHeaderBytes - NUM_BYTES_RFC4122_UUID; // enumerate over all of the avatars in this packet // only add them if mixerWeakPointer points to something (meaning that mixer is still around) while (bytesRead < datagram.size() && mixerWeakPointer.data()) { QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID)); - // TODO: skip the data if nodeUUID is same as MY_AVATAR_KEY AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); @@ -189,15 +189,13 @@ void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const } // copy the rest of the packet to the avatarData holder so we can read the next Avatar from there - dummyAvatarByteArray.resize(numDummyByteArrayHeaderBytes); + dummyAvatarByteArray.resize(numDummyHeaderBytesWithoutUUID); // make this Avatar's UUID the UUID in the packet and tack the remaining data onto the end - dummyAvatarByteArray.replace(numDummyByteArrayHeaderBytes - NUM_BYTES_RFC4122_UUID, - NUM_BYTES_RFC4122_UUID + datagram.size() - bytesRead, - datagram.mid(bytesRead)); + dummyAvatarByteArray.append(datagram.mid(bytesRead)); // have the matching (or new) avatar parse the data from the packet - bytesRead += matchingAvatar->parseData(dummyAvatarByteArray); + bytesRead += matchingAvatar->parseData(dummyAvatarByteArray) - numDummyHeaderBytesWithoutUUID; } } diff --git a/interface/src/avatar/Profile.cpp b/interface/src/avatar/Profile.cpp index 74f47bd658..902a0ea12a 100644 --- a/interface/src/avatar/Profile.cpp +++ b/interface/src/avatar/Profile.cpp @@ -53,7 +53,10 @@ void Profile::setUUID(const QUuid& uuid) { // when the UUID is changed we need set it appropriately on the NodeList instance NodeList::getInstance()->setOwnerUUID(uuid); - } + + // ask for a window title update so the new UUID is presented + Application::getInstance()->updateWindowTitle(); + } } void Profile::setFaceModelURL(const QUrl& faceModelURL) { diff --git a/interface/src/renderer/FBXReader.cpp b/interface/src/renderer/FBXReader.cpp index 8a7f839b70..d128206c80 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/interface/src/renderer/FBXReader.cpp @@ -170,6 +170,7 @@ public: const QByteArray& getDatum() const { return _datum; } void pushBackToken(int token) { _pushedBackToken = token; } + void ungetChar(char ch) { _device->ungetChar(ch); } private: @@ -221,7 +222,7 @@ int Tokenizer::nextToken() { _datum.append(ch); while (_device->getChar(&ch)) { if (QChar(ch).isSpace() || ch == ';' || ch == ':' || ch == '{' || ch == '}' || ch == ',' || ch == '\"') { - _device->ungetChar(ch); // read until we encounter a special character, then replace it + ungetChar(ch); // read until we encounter a special character, then replace it break; } _datum.append(ch); @@ -257,9 +258,17 @@ FBXNode parseTextFBXNode(Tokenizer& tokenizer) { expectingDatum = true; } else if (token == Tokenizer::DATUM_TOKEN && expectingDatum) { - node.properties.append(tokenizer.getDatum()); - expectingDatum = false; - + QByteArray datum = tokenizer.getDatum(); + if ((token = tokenizer.nextToken()) == ':') { + tokenizer.ungetChar(':'); + tokenizer.pushBackToken(Tokenizer::DATUM_TOKEN); + return node; + + } else { + tokenizer.pushBackToken(token); + node.properties.append(datum); + expectingDatum = false; + } } else { tokenizer.pushBackToken(token); return node; @@ -377,6 +386,9 @@ glm::mat4 createMat4(const QVector& doubleVector) { } QVector getIntVector(const QVariantList& properties, int index) { + if (index >= properties.size()) { + return QVector(); + } QVector vector = properties.at(index).value >(); if (!vector.isEmpty()) { return vector; @@ -388,6 +400,9 @@ QVector getIntVector(const QVariantList& properties, int index) { } QVector getDoubleVector(const QVariantList& properties, int index) { + if (index >= properties.size()) { + return QVector(); + } QVector vector = properties.at(index).value >(); if (!vector.isEmpty()) { return vector; @@ -1106,6 +1121,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QVector modelIDs; QSet remainingModels; for (QHash::const_iterator model = models.constBegin(); model != models.constEnd(); model++) { + // make sure the parent is in the child map + QString parent = parentMap.value(model.key()); + if (!childMap.contains(parent, model.key())) { + childMap.insert(parent, model.key()); + } remainingModels.insert(model.key()); } while (!remainingModels.isEmpty()) { diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a8906f9167..98afa76107 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -172,105 +172,102 @@ int AvatarData::parseData(const QByteArray& packet) { _handData = new HandData(this); } - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); + // increment to push past the packet header + const unsigned char* startPosition = reinterpret_cast(packet.data()); + const unsigned char* sourceBuffer = startPosition + numBytesForPacketHeader(packet); - packetStream.readRawData(reinterpret_cast(&_position), sizeof(_position)); + // Body world position + memcpy(&_position, sourceBuffer, sizeof(float) * 3); + sourceBuffer += sizeof(float) * 3; // Body rotation (NOTE: This needs to become a quaternion to save two bytes) - uint16_t twoByteHolder; - packetStream >> twoByteHolder; - unpackFloatAngleFromTwoByte(&twoByteHolder, &_bodyYaw); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyYaw); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyPitch); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &_bodyRoll); - packetStream >> twoByteHolder; - unpackFloatAngleFromTwoByte(&twoByteHolder, &_bodyPitch); + // Body scale + sourceBuffer += unpackFloatRatioFromTwoByte(sourceBuffer, _targetScale); - packetStream >> twoByteHolder; - unpackFloatAngleFromTwoByte(&twoByteHolder, &_bodyRoll); - - // body scale - packetStream >> twoByteHolder; - unpackFloatRatioFromTwoByte(reinterpret_cast(&twoByteHolder), _targetScale); - // Head rotation (NOTE: This needs to become a quaternion to save two bytes) float headYaw, headPitch, headRoll; - - packetStream >> twoByteHolder; - unpackFloatAngleFromTwoByte(&twoByteHolder, &headYaw); - - packetStream >> twoByteHolder; - unpackFloatAngleFromTwoByte(&twoByteHolder, &headPitch); - - packetStream >> twoByteHolder; - unpackFloatAngleFromTwoByte(&twoByteHolder, &headRoll); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headYaw); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headPitch); + sourceBuffer += unpackFloatAngleFromTwoByte((uint16_t*) sourceBuffer, &headRoll); _headData->setYaw(headYaw); _headData->setPitch(headPitch); _headData->setRoll(headRoll); - + // Head position relative to pelvis - packetStream >> _headData->_leanSideways; - packetStream >> _headData->_leanForward; - + memcpy(&_headData->_leanSideways, sourceBuffer, sizeof(_headData->_leanSideways)); + sourceBuffer += sizeof(float); + memcpy(&_headData->_leanForward, sourceBuffer, sizeof(_headData->_leanForward)); + sourceBuffer += sizeof(_headData->_leanForward); + // Hand Position - is relative to body position glm::vec3 handPositionRelative; - packetStream.readRawData(reinterpret_cast(&handPositionRelative), sizeof(handPositionRelative)); + memcpy(&handPositionRelative, sourceBuffer, sizeof(float) * 3); _handPosition = _position + handPositionRelative; + sourceBuffer += sizeof(float) * 3; + + // Lookat Position + memcpy(&_headData->_lookAtPosition, sourceBuffer, sizeof(_headData->_lookAtPosition)); + sourceBuffer += sizeof(_headData->_lookAtPosition); - packetStream.readRawData(reinterpret_cast(&_headData->_lookAtPosition), sizeof(_headData->_lookAtPosition)); - // Instantaneous audio loudness (used to drive facial animation) //sourceBuffer += unpackFloatFromByte(sourceBuffer, _audioLoudness, MAX_AUDIO_LOUDNESS); - packetStream >> _headData->_audioLoudness; - - // the rest is a chat message + memcpy(&_headData->_audioLoudness, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + + // the rest is a chat message + int chatMessageSize = *sourceBuffer++; + _chatMessage = string((char*)sourceBuffer, chatMessageSize); + sourceBuffer += chatMessageSize * sizeof(char); - quint8 chatMessageSize; - packetStream >> chatMessageSize; - _chatMessage = string(packet.data() + packetStream.device()->pos(), chatMessageSize); - packetStream.skipRawData(chatMessageSize); - // voxel sending features... unsigned char bitItems = 0; - packetStream >> bitItems; + bitItems = (unsigned char)*sourceBuffer++; // key state, stored as a semi-nibble in the bitItems _keyState = (KeyState)getSemiNibbleAt(bitItems,KEY_STATE_START_BIT); - + // hand state, stored as a semi-nibble in the bitItems _handState = getSemiNibbleAt(bitItems,HAND_STATE_START_BIT); - + _headData->_isFaceshiftConnected = oneAtBit(bitItems, IS_FACESHIFT_CONNECTED); - + _isChatCirclingEnabled = oneAtBit(bitItems, IS_CHAT_CIRCLING_ENABLED); - + // If it is connected, pack up the data if (_headData->_isFaceshiftConnected) { - packetStream >> _headData->_leftEyeBlink; - packetStream >> _headData->_rightEyeBlink; - packetStream >> _headData->_averageLoudness; - packetStream >> _headData->_browAudioLift; + memcpy(&_headData->_leftEyeBlink, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); - quint8 numBlendshapeCoefficients; - packetStream >> numBlendshapeCoefficients; + memcpy(&_headData->_rightEyeBlink, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); - _headData->_blendshapeCoefficients.resize(numBlendshapeCoefficients); - packetStream.readRawData(reinterpret_cast(_headData->_blendshapeCoefficients.data()), - numBlendshapeCoefficients * sizeof(float)); + memcpy(&_headData->_averageLoudness, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + + memcpy(&_headData->_browAudioLift, sourceBuffer, sizeof(float)); + sourceBuffer += sizeof(float); + + _headData->_blendshapeCoefficients.resize(*sourceBuffer++); + memcpy(_headData->_blendshapeCoefficients.data(), sourceBuffer, + _headData->_blendshapeCoefficients.size() * sizeof(float)); + sourceBuffer += _headData->_blendshapeCoefficients.size() * sizeof(float); } // pupil dilation - quint8 pupilByte; - packetStream >> pupilByte; - unpackFloatFromByte(&pupilByte, _headData->_pupilDilation, 1.0f); + sourceBuffer += unpackFloatFromByte(sourceBuffer, _headData->_pupilDilation, 1.0f); // leap hand data - if (packetStream.device()->pos() < packet.size()) { + if (sourceBuffer - startPosition < packet.size()) { // check passed, bytes match - packetStream.skipRawData(_handData->decodeRemoteData(packet.mid(packetStream.device()->pos()))); + sourceBuffer += _handData->decodeRemoteData(packet.mid(sourceBuffer - startPosition)); } - - return packetStream.device()->pos(); + + return sourceBuffer - startPosition; } void AvatarData::setClampedTargetScale(float targetScale) { diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index d947b894aa..5a923eea93 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -160,31 +160,21 @@ int HandData::encodeRemoteData(unsigned char* destinationBuffer) { } int HandData::decodeRemoteData(const QByteArray& dataByteArray) { - QDataStream packetStream(dataByteArray); - - quint8 numHands; - packetStream >> numHands; + const unsigned char* startPosition; + const unsigned char* sourceBuffer = startPosition = reinterpret_cast(dataByteArray.data()); + unsigned int numHands = *sourceBuffer++; for (unsigned int handIndex = 0; handIndex < numHands; ++handIndex) { if (handIndex >= getNumPalms()) addNewPalm(); PalmData& palm = getPalms()[handIndex]; - + glm::vec3 handPosition; glm::vec3 handNormal; - qint16 twoByteHolder; + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, handPosition, fingerVectorRadix); + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, handNormal, fingerVectorRadix); + unsigned int numFingers = *sourceBuffer++; - packetStream >> twoByteHolder; - unpackFloatVec3FromSignedTwoByteFixed(reinterpret_cast(&twoByteHolder), - handPosition, fingerVectorRadix); - - packetStream >> twoByteHolder; - unpackFloatVec3FromSignedTwoByteFixed(reinterpret_cast(&twoByteHolder), - handNormal, fingerVectorRadix); - - quint8 numFingers; - packetStream >> numFingers; - palm.setRawPosition(handPosition); palm.setRawNormal(handNormal); palm.setActive(true); @@ -195,18 +185,12 @@ int HandData::decodeRemoteData(const QByteArray& dataByteArray) { for (unsigned int fingerIndex = 0; fingerIndex < numFingers; ++fingerIndex) { if (fingerIndex < palm.getNumFingers()) { FingerData& finger = palm.getFingers()[fingerIndex]; - + glm::vec3 tipPosition; glm::vec3 rootPosition; + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, tipPosition, fingerVectorRadix); + sourceBuffer += unpackFloatVec3FromSignedTwoByteFixed(sourceBuffer, rootPosition, fingerVectorRadix); - packetStream >> twoByteHolder; - unpackFloatVec3FromSignedTwoByteFixed(reinterpret_cast(&twoByteHolder), tipPosition, - fingerVectorRadix); - - packetStream >> twoByteHolder; - unpackFloatVec3FromSignedTwoByteFixed(reinterpret_cast(&twoByteHolder), rootPosition, - fingerVectorRadix); - finger.setRawTipPosition(tipPosition); finger.setRawRootPosition(rootPosition); finger.setActive(true); @@ -225,14 +209,11 @@ int HandData::decodeRemoteData(const QByteArray& dataByteArray) { } // One byte for error checking safety. - unsigned char requiredLength = packetStream.device()->pos(); - - unsigned char checkLength; - packetStream >> checkLength; - + unsigned char requiredLength = (unsigned char)(sourceBuffer - startPosition); + unsigned char checkLength = *sourceBuffer++; assert(checkLength == requiredLength); - - return packetStream.device()->pos(); + + return sourceBuffer - startPosition; } void HandData::setFingerTrailLength(unsigned int length) { diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index 849bf593cd..a217555a78 100755 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -63,7 +63,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& p QFile localFile(filePath); localFile.open(QIODevice::ReadOnly); - QString localFileString(localFile.readAll()); + QByteArray localFileData = localFile.readAll(); QFileInfo localFileInfo(filePath); @@ -77,6 +77,7 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& p int matchPosition = 0; + QString localFileString(localFileData); while ((matchPosition = includeRegExp.indexIn(localFileString, matchPosition)) != -1) { // check if this is a file or vitual include @@ -105,9 +106,11 @@ bool HTTPManager::handleHTTPRequest(HTTPConnection* connection, const QString& p // push the match position forward so we can check the next match matchPosition += includeRegExp.matchedLength(); } + + localFileData = localFileString.toLocal8Bit(); } - connection->respond(HTTPConnection::StatusCode200, localFileString.toLocal8Bit(), + connection->respond(HTTPConnection::StatusCode200, localFileData, qPrintable(mimeDatabase.mimeTypeForFile(filePath).name())); } else { diff --git a/libraries/octree-server/src/OctreeServer.cpp b/libraries/octree-server/src/OctreeServer.cpp index 73522565f9..6e58a0a987 100644 --- a/libraries/octree-server/src/OctreeServer.cpp +++ b/libraries/octree-server/src/OctreeServer.cpp @@ -433,7 +433,7 @@ void OctreeServer::setArguments(int argc, char** argv) { void OctreeServer::parsePayload() { if (getPayload().size() > 0) { - QString config((const char*) _payload); + QString config(_payload); // Now, parse the config QStringList configList = config.split(" "); diff --git a/libraries/octree/src/JurisdictionListener.cpp b/libraries/octree/src/JurisdictionListener.cpp index fc8ea008d1..d2c24021ac 100644 --- a/libraries/octree/src/JurisdictionListener.cpp +++ b/libraries/octree/src/JurisdictionListener.cpp @@ -45,8 +45,7 @@ bool JurisdictionListener::queueJurisdictionRequest() { NodeList* nodeList = NodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (nodeList->getNodeActiveSocketOrPing(node.data()) && - node->getType() == getNodeType()) { + if (nodeList->getNodeActiveSocketOrPing(node.data()) && node->getType() == getNodeType()) { const HifiSockAddr* nodeAddress = node->getActiveSocket(); _packetSender.queuePacketForSending(*nodeAddress, QByteArray(reinterpret_cast(bufferOut), sizeOut)); nodeCount++; diff --git a/libraries/octree/src/JurisdictionSender.cpp b/libraries/octree/src/JurisdictionSender.cpp index 52990397db..854496e40f 100644 --- a/libraries/octree/src/JurisdictionSender.cpp +++ b/libraries/octree/src/JurisdictionSender.cpp @@ -64,7 +64,7 @@ bool JurisdictionSender::process() { _nodesRequestingJurisdictions.pop(); SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(nodeUUID); - if (node->getActiveSocket() != NULL) { + if (node && node->getActiveSocket() != NULL) { const HifiSockAddr* nodeAddress = node->getActiveSocket(); _packetSender.queuePacketForSending(*nodeAddress, QByteArray(reinterpret_cast(bufferOut), sizeOut)); nodeCount++; diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index 51b73a9ed2..5c67715ab9 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -24,7 +24,6 @@ static const float fingerVectorRadix = 4; // bits of precision when converting f OctreeQuery::OctreeQuery() : NodeData(), - _uuid(), _cameraPosition(0,0,0), _cameraOrientation(), _cameraFov(0.0f), @@ -53,11 +52,6 @@ int OctreeQuery::getBroadcastData(unsigned char* destinationBuffer) { // that can pack any type given the number of bytes // and return the number of bytes to push the pointer - // UUID - QByteArray uuidByteArray = _uuid.toRfc4122(); - memcpy(destinationBuffer, uuidByteArray.constData(), uuidByteArray.size()); - destinationBuffer += uuidByteArray.size(); - // camera details memcpy(destinationBuffer, &_cameraPosition, sizeof(_cameraPosition)); destinationBuffer += sizeof(_cameraPosition); @@ -103,13 +97,6 @@ int OctreeQuery::parseData(const QByteArray& packet) { const unsigned char* startPosition = reinterpret_cast(packet.data()); const unsigned char* sourceBuffer = startPosition + numBytesPacketHeader; - // push past the node session UUID - sourceBuffer += NUM_BYTES_RFC4122_UUID; - - // user UUID - _uuid = QUuid::fromRfc4122(QByteArray((char*) sourceBuffer, NUM_BYTES_RFC4122_UUID)); - sourceBuffer += NUM_BYTES_RFC4122_UUID; - // camera details memcpy(&_cameraPosition, sourceBuffer, sizeof(_cameraPosition)); sourceBuffer += sizeof(_cameraPosition); diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index acb4b7cb32..800d30b7cd 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -57,9 +57,6 @@ public: int getBroadcastData(unsigned char* destinationBuffer); int parseData(const QByteArray& packet); - QUuid& getUUID() { return _uuid; } - void setUUID(const QUuid& uuid) { _uuid = uuid; } - // getters for camera details const glm::vec3& getCameraPosition() const { return _cameraPosition; } const glm::quat& getCameraOrientation() const { return _cameraOrientation; } @@ -101,8 +98,6 @@ public slots: void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _boundaryLevelAdjust = boundaryLevelAdjust; } protected: - QUuid _uuid; - // camera details for the avatar glm::vec3 _cameraPosition; glm::quat _cameraOrientation; diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index 1f6829e29a..bb190aa32f 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -13,6 +13,8 @@ #include #include // usecTimestampNow() #include +#include + // This is not ideal, but adding script-engine as a linked library, will cause a circular reference // I'm open to other potential solutions. Could we change cmake to allow libraries to reference each others @@ -830,7 +832,7 @@ void Particle::update(const quint64& now) { bool shouldDie = (getAge() > getLifetime()) || getShouldDie() || (!isInHand && isStopped && isReallyOld); setShouldDie(shouldDie); - runUpdateScript(); // allow the javascript to alter our state + executeUpdateScripts(); // allow the javascript to alter our state // If the ball is in hand, it doesn't move or have gravity effect it if (!isInHand) { @@ -852,106 +854,62 @@ void Particle::update(const quint64& now) { } } -void Particle::runUpdateScript() { +void Particle::startParticleScriptContext(ScriptEngine& engine, ParticleScriptObject& particleScriptable) { + if (_voxelEditSender) { + engine.getVoxelsScriptingInterface()->setPacketSender(_voxelEditSender); + } + if (_particleEditSender) { + engine.getParticlesScriptingInterface()->setPacketSender(_particleEditSender); + } + + // Add the "this" Particle object + engine.registerGlobalObject("Particle", &particleScriptable); + engine.evaluate(); +} + +void Particle::endParticleScriptContext(ScriptEngine& engine, ParticleScriptObject& particleScriptable) { + if (_voxelEditSender) { + _voxelEditSender->releaseQueuedMessages(); + } + if (_particleEditSender) { + _particleEditSender->releaseQueuedMessages(); + } +} + +void Particle::executeUpdateScripts() { + // Only run this particle script if there's a script attached directly to the particle. if (!_script.isEmpty()) { - ScriptEngine engine(_script); // no menu or controller interface... - - if (_voxelEditSender) { - engine.getVoxelsScriptingInterface()->setPacketSender(_voxelEditSender); - } - if (_particleEditSender) { - engine.getParticlesScriptingInterface()->setPacketSender(_particleEditSender); - } - - // Add the Particle object + ScriptEngine engine(_script); ParticleScriptObject particleScriptable(this); - engine.registerGlobalObject("Particle", &particleScriptable); - - // init and evaluate the script, but return so we can emit the collision - engine.evaluate(); - + startParticleScriptContext(engine, particleScriptable); particleScriptable.emitUpdate(); - - // it seems like we may need to send out particle edits if the state of our particle was changed. - - if (_voxelEditSender) { - _voxelEditSender->releaseQueuedMessages(); - } - if (_particleEditSender) { - _particleEditSender->releaseQueuedMessages(); - } + endParticleScriptContext(engine, particleScriptable); } } void Particle::collisionWithParticle(Particle* other) { + // Only run this particle script if there's a script attached directly to the particle. if (!_script.isEmpty()) { - ScriptEngine engine(_script); // no menu or controller interface... - - if (_voxelEditSender) { - engine.getVoxelsScriptingInterface()->setPacketSender(_voxelEditSender); - } - if (_particleEditSender) { - engine.getParticlesScriptingInterface()->setPacketSender(_particleEditSender); - } - - // Add the Particle object + ScriptEngine engine(_script); ParticleScriptObject particleScriptable(this); - engine.registerGlobalObject("Particle", &particleScriptable); - - // init and evaluate the script, but return so we can emit the collision - engine.evaluate(); - + startParticleScriptContext(engine, particleScriptable); ParticleScriptObject otherParticleScriptable(other); particleScriptable.emitCollisionWithParticle(&otherParticleScriptable); - - // it seems like we may need to send out particle edits if the state of our particle was changed. - - if (_voxelEditSender) { - _voxelEditSender->releaseQueuedMessages(); - } - if (_particleEditSender) { - _particleEditSender->releaseQueuedMessages(); - } + endParticleScriptContext(engine, particleScriptable); } } void Particle::collisionWithVoxel(VoxelDetail* voxelDetails) { + // Only run this particle script if there's a script attached directly to the particle. if (!_script.isEmpty()) { - - ScriptEngine engine(_script); // no menu or controller interface... - - // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so - // we can use the same ones as our context. - if (_voxelEditSender) { - engine.getVoxelsScriptingInterface()->setPacketSender(_voxelEditSender); - } - if (_particleEditSender) { - engine.getParticlesScriptingInterface()->setPacketSender(_particleEditSender); - } - - // Add the Particle object + ScriptEngine engine(_script); ParticleScriptObject particleScriptable(this); - engine.registerGlobalObject("Particle", &particleScriptable); - - // init and evaluate the script, but return so we can emit the collision - engine.evaluate(); - - VoxelDetailScriptObject voxelDetailsScriptable(voxelDetails); - particleScriptable.emitCollisionWithVoxel(&voxelDetailsScriptable); - - // it seems like we may need to send out particle edits if the state of our particle was changed. - - if (_voxelEditSender) { - _voxelEditSender->releaseQueuedMessages(); - } - if (_particleEditSender) { - _particleEditSender->releaseQueuedMessages(); - } + startParticleScriptContext(engine, particleScriptable); + particleScriptable.emitCollisionWithVoxel(*voxelDetails); + endParticleScriptContext(engine, particleScriptable); } } - - void Particle::setAge(float age) { quint64 ageInUsecs = age * USECS_PER_SECOND; _created = usecTimestampNow() - ageInUsecs; diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index debb073ebd..925227055f 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -20,13 +20,16 @@ #include #include -class VoxelsScriptingInterface; -class ParticlesScriptingInterface; -class VoxelEditPacketSender; +class Particle; class ParticleEditPacketSender; class ParticleProperties; -class Particle; +class ParticlesScriptingInterface; +class ParticleScriptObject; class ParticleTree; +class ScriptEngine; +class VoxelEditPacketSender; +class VoxelsScriptingInterface; +struct VoxelDetail; const uint32_t NEW_PARTICLE = 0xFFFFFFFF; const uint32_t UNKNOWN_TOKEN = 0xFFFFFFFF; @@ -227,7 +230,8 @@ public: const glm::vec3& getModelTranslation() const { return _modelTranslation; } const glm::quat& getModelRotation() const { return _modelRotation; } float getModelScale() const { return _modelScale; } - + + ParticleID getParticleID() const { return ParticleID(getID(), getCreatorTokenID(), getID() != UNKNOWN_PARTICLE_ID); } ParticleProperties getProperties() const; /// The last updated/simulated time of this particle from the time perspective of the authoritative server/source @@ -318,11 +322,9 @@ protected: static VoxelEditPacketSender* _voxelEditSender; static ParticleEditPacketSender* _particleEditSender; - void runUpdateScript(); - static QScriptValue vec3toScriptValue(QScriptEngine *engine, const glm::vec3 &vec3); - static void vec3FromScriptValue(const QScriptValue &object, glm::vec3 &vec3); - static QScriptValue xColorToScriptValue(QScriptEngine *engine, const xColor& color); - static void xColorFromScriptValue(const QScriptValue &object, xColor& color); + void startParticleScriptContext(ScriptEngine& engine, ParticleScriptObject& particleScriptable); + void endParticleScriptContext(ScriptEngine& engine, ParticleScriptObject& particleScriptable); + void executeUpdateScripts(); void setAge(float age); @@ -366,10 +368,11 @@ class ParticleScriptObject : public QObject { Q_OBJECT public: ParticleScriptObject(Particle* particle) { _particle = particle; } + //~ParticleScriptObject() { qDebug() << "~ParticleScriptObject() this=" << this; } void emitUpdate() { emit update(); } void emitCollisionWithParticle(QObject* other) { emit collisionWithParticle(other); } - void emitCollisionWithVoxel(QObject* voxel) { emit collisionWithVoxel(voxel); } + void emitCollisionWithVoxel(const VoxelDetail& voxel) { emit collisionWithVoxel(voxel); } public slots: unsigned int getID() const { return _particle->getID(); } @@ -414,7 +417,7 @@ public slots: signals: void update(); - void collisionWithVoxel(QObject* voxel); + void collisionWithVoxel(const VoxelDetail& voxel); void collisionWithParticle(QObject* other); private: diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index 275ac542a2..b5fe827ebb 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -68,6 +68,17 @@ void ParticleCollisionSystem::checkParticle(Particle* particle) { updateCollisionWithAvatars(particle); } +void ParticleCollisionSystem::emitGlobalParicleCollisionWithVoxel(Particle* particle, VoxelDetail* voxelDetails) { + ParticleID particleID = particle->getParticleID(); + emit particleCollisionWithVoxel(particleID, *voxelDetails); +} + +void ParticleCollisionSystem::emitGlobalParicleCollisionWithParticle(Particle* particleA, Particle* particleB) { + ParticleID idA = particleA->getParticleID(); + ParticleID idB = particleB->getParticleID(); + emit particleCollisionWithParticle(idA, idB); +} + void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { glm::vec3 center = particle->getPosition() * (float)(TREE_SCALE); float radius = particle->getRadius() * (float)(TREE_SCALE); @@ -83,6 +94,9 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { // let the particles run their collision scripts if they have them particle->collisionWithVoxel(voxelDetails); + // let the global script run their collision scripts for particles if they have them + emitGlobalParicleCollisionWithVoxel(particle, voxelDetails); + updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); collisionInfo._penetration /= (float)(TREE_SCALE); particle->applyHardCollision(collisionInfo); @@ -110,6 +124,7 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) if (glm::dot(relativeVelocity, penetration) > 0.0f) { particleA->collisionWithParticle(particleB); particleB->collisionWithParticle(particleA); + emitGlobalParicleCollisionWithParticle(particleA, particleB); glm::vec3 axis = glm::normalize(penetration); glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis; diff --git a/libraries/particles/src/ParticleCollisionSystem.h b/libraries/particles/src/ParticleCollisionSystem.h index 4a61693fa6..9baf9bfd05 100644 --- a/libraries/particles/src/ParticleCollisionSystem.h +++ b/libraries/particles/src/ParticleCollisionSystem.h @@ -31,7 +31,8 @@ class VoxelTree; const glm::vec3 NO_ADDED_VELOCITY = glm::vec3(0); -class ParticleCollisionSystem { +class ParticleCollisionSystem : public QObject { +Q_OBJECT public: ParticleCollisionSystem(ParticleEditPacketSender* packetSender = NULL, ParticleTree* particles = NULL, VoxelTree* voxels = NULL, AbstractAudioInterface* audio = NULL, @@ -51,9 +52,14 @@ public: void queueParticlePropertiesUpdate(Particle* particle); void updateCollisionSound(Particle* particle, const glm::vec3 &penetration, float frequency); +signals: + void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel); + void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB); + private: static bool updateOperation(OctreeElement* element, void* extraData); - + void emitGlobalParicleCollisionWithVoxel(Particle* particle, VoxelDetail* voxelDetails); + void emitGlobalParicleCollisionWithParticle(Particle* particleA, Particle* particleB); ParticleEditPacketSender* _packetSender; ParticleTree* _particles; diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index a7472d98d1..51150722d0 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -190,7 +190,6 @@ void ParticleTree::handleAddParticleResponse(const QByteArray& packet) { int numBytesPacketHeader = numBytesForPacketHeader(packet); const unsigned char* dataAt = reinterpret_cast(packet.data()) + numBytesPacketHeader; - dataAt += numBytesPacketHeader; uint32_t creatorTokenID; memcpy(&creatorTokenID, dataAt, sizeof(creatorTokenID)); diff --git a/libraries/particles/src/ParticlesScriptingInterface.h b/libraries/particles/src/ParticlesScriptingInterface.h index dc5793ae30..729561571e 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.h +++ b/libraries/particles/src/ParticlesScriptingInterface.h @@ -53,7 +53,19 @@ public slots: /// finds particles within the search sphere specified by the center point and radius /// this function will not find any particles in script engine contexts which don't have access to particles QVector findParticles(const glm::vec3& center, float radius) const; + + /// inbound slots for external collision systems + void forwardParticleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel) { + emit particleCollisionWithVoxel(particleID, voxel); + } + + void forwardParticleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB) { + emit particleCollisionWithParticle(idA, idB); + } +signals: + void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel); + void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB); private: void queueParticleMessage(PacketType packetType, ParticleID particleID, const ParticleProperties& properties); diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index 5c4ab7f2a7..ff27282b73 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -8,22 +8,72 @@ // Used to register meta-types with Qt for very various event types so that they can be exposed to our // scripting engine +#include #include "EventTypes.h" KeyEvent::KeyEvent() { key = 0; + text = QString(""); isShifted = false; isMeta = false; + isControl = false; isValid = false; } KeyEvent::KeyEvent(const QKeyEvent& event) { key = event.key(); + text = event.text(); isShifted = event.modifiers().testFlag(Qt::ShiftModifier); - isMeta = event.modifiers().testFlag(Qt::ControlModifier); + isMeta = event.modifiers().testFlag(Qt::MetaModifier); + isControl = event.modifiers().testFlag(Qt::ControlModifier); + isAlt = event.modifiers().testFlag(Qt::AltModifier); + isKeypad = event.modifiers().testFlag(Qt::KeypadModifier); isValid = true; + + // handle special text for special characters... + if (key == Qt::Key_F1) { + text = "F1"; + } else if (key == Qt::Key_F2) { + text = "F2"; + } else if (key == Qt::Key_F3) { + text = "F3"; + } else if (key == Qt::Key_F4) { + text = "F4"; + } else if (key == Qt::Key_F5) { + text = "F5"; + } else if (key == Qt::Key_F6) { + text = "F6"; + } else if (key == Qt::Key_F7) { + text = "F7"; + } else if (key == Qt::Key_F8) { + text = "F8"; + } else if (key == Qt::Key_F9) { + text = "F9"; + } else if (key == Qt::Key_F10) { + text = "F10"; + } else if (key == Qt::Key_F11) { + text = "F11"; + } else if (key == Qt::Key_F12) { + text = "F12"; + } else if (key == Qt::Key_Up) { + text = "UP"; + } else if (key == Qt::Key_Down) { + text = "DOWN"; + } else if (key == Qt::Key_Left) { + text = "LEFT"; + } else if (key == Qt::Key_Right) { + text = "RIGHT"; + } else if (key == Qt::Key_Escape) { + text = "ESC"; + } else if (key == Qt::Key_Tab) { + text = "TAB"; + } else if (key == Qt::Key_Delete) { + text = "DELETE"; + } else if (key == Qt::Key_Backspace) { + text = "BACKSPACE"; + } } MouseEvent::MouseEvent(const QMouseEvent& event) { @@ -65,16 +115,113 @@ void registerEventTypes(QScriptEngine* engine) { QScriptValue keyEventToScriptValue(QScriptEngine* engine, const KeyEvent& event) { QScriptValue obj = engine->newObject(); obj.setProperty("key", event.key); + obj.setProperty("text", event.text); obj.setProperty("isShifted", event.isShifted); obj.setProperty("isMeta", event.isMeta); + obj.setProperty("isControl", event.isControl); + obj.setProperty("isAlt", event.isAlt); + obj.setProperty("isKeypad", event.isKeypad); return obj; } void keyEventFromScriptValue(const QScriptValue &object, KeyEvent& event) { - event.key = object.property("key").toVariant().toInt(); - event.isShifted = object.property("isShifted").toVariant().toBool(); + + event.isValid = false; // assume the worst event.isMeta = object.property("isMeta").toVariant().toBool(); - event.isValid = object.property("key").isValid(); + event.isControl = object.property("isControl").toVariant().toBool(); + event.isAlt = object.property("isAlt").toVariant().toBool(); + event.isKeypad = object.property("isKeypad").toVariant().toBool(); + + QScriptValue key = object.property("key"); + if (key.isValid()) { + event.key = key.toVariant().toInt(); + event.text = QString(QChar(event.key)); + event.isValid = true; + } else { + QScriptValue text = object.property("text"); + if (text.isValid()) { + event.text = object.property("text").toVariant().toString(); + + // if the text is a special command, then map it here... + // TODO: come up with more elegant solution here, a map? is there a Qt function that gives nice names for keys? + if (event.text.toUpper() == "F1") { + event.key = Qt::Key_F1; + } else if (event.text.toUpper() == "F2") { + event.key = Qt::Key_F2; + } else if (event.text.toUpper() == "F3") { + event.key = Qt::Key_F3; + } else if (event.text.toUpper() == "F4") { + event.key = Qt::Key_F4; + } else if (event.text.toUpper() == "F5") { + event.key = Qt::Key_F5; + } else if (event.text.toUpper() == "F6") { + event.key = Qt::Key_F6; + } else if (event.text.toUpper() == "F7") { + event.key = Qt::Key_F7; + } else if (event.text.toUpper() == "F8") { + event.key = Qt::Key_F8; + } else if (event.text.toUpper() == "F9") { + event.key = Qt::Key_F9; + } else if (event.text.toUpper() == "F10") { + event.key = Qt::Key_F10; + } else if (event.text.toUpper() == "F11") { + event.key = Qt::Key_F11; + } else if (event.text.toUpper() == "F12") { + event.key = Qt::Key_F12; + } else if (event.text.toUpper() == "UP") { + event.key = Qt::Key_Up; + event.isKeypad = true; + } else if (event.text.toUpper() == "DOWN") { + event.key = Qt::Key_Down; + event.isKeypad = true; + } else if (event.text.toUpper() == "LEFT") { + event.key = Qt::Key_Left; + event.isKeypad = true; + } else if (event.text.toUpper() == "RIGHT") { + event.key = Qt::Key_Right; + event.isKeypad = true; + } else if (event.text.toUpper() == "ESC") { + event.key = Qt::Key_Escape; + } else if (event.text.toUpper() == "TAB") { + event.key = Qt::Key_Tab; + } else if (event.text.toUpper() == "DELETE") { + event.key = Qt::Key_Delete; + } else if (event.text.toUpper() == "BACKSPACE") { + event.key = Qt::Key_Backspace; + } else { + event.key = event.text.at(0).unicode(); + } + event.isValid = true; + } + } + + QScriptValue isShifted = object.property("isShifted"); + if (isShifted.isValid()) { + event.isShifted = isShifted.toVariant().toBool(); + } else { + // if no isShifted was included, get it from the text + QChar character = event.text.at(0); + if (character.isLetter() && character.isUpper()) { + event.isShifted = true; + } else { + // if it's a symbol, then attempt to detect shifted-ness + if (QString("~!@#$%^&*()_+{}|:\"<>?").contains(character)) { + event.isShifted = true; + } + } + } + + + const bool wantDebug = false; + if (wantDebug) { + qDebug() << "event.key=" << event.key + << " event.text=" << event.text + << " event.isShifted=" << event.isShifted + << " event.isControl=" << event.isControl + << " event.isMeta=" << event.isMeta + << " event.isAlt=" << event.isAlt + << " event.isKeypad=" << event.isKeypad; + } } QScriptValue mouseEventToScriptValue(QScriptEngine* engine, const MouseEvent& event) { diff --git a/libraries/script-engine/src/EventTypes.h b/libraries/script-engine/src/EventTypes.h index c3764b2619..81cba03cea 100644 --- a/libraries/script-engine/src/EventTypes.h +++ b/libraries/script-engine/src/EventTypes.h @@ -24,10 +24,19 @@ public: KeyEvent(); KeyEvent(const QKeyEvent& event); inline bool operator==(const KeyEvent& other) const { - return other.key == key && other.isShifted == isShifted && other.isMeta == isMeta; } + return other.key == key + && other.isShifted == isShifted + && other.isControl == isControl + && other.isMeta == isMeta + && other.isAlt == isAlt + && other.isKeypad == isKeypad; } int key; + QString text; bool isShifted; + bool isControl; bool isMeta; + bool isAlt; + bool isKeypad; bool isValid; }; diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 71e450d4ad..9a7a9197b0 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -41,6 +42,7 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng ScriptEngine::ScriptEngine(const QString& scriptContents, bool wantMenuItems, const QString& fileNameString, AbstractMenuInterface* menu, AbstractControllerScriptingInterface* controllerScriptingInterface) : + _isAvatar(false), _dataServerScriptingInterface(), _avatarData(NULL) { @@ -114,10 +116,12 @@ void ScriptEngine::init() { _voxelsScriptingInterface.init(); _particlesScriptingInterface.init(); - // register meta-type for glm::vec3 conversions + // register various meta-types registerMetaTypes(&_engine); - + registerVoxelMetaTypes(&_engine); + //registerParticleMetaTypes(&_engine); registerEventTypes(&_engine); + qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); @@ -129,7 +133,7 @@ void ScriptEngine::init() { QScriptValue injectionOptionValue = _engine.scriptValueFromQMetaObject(); _engine.globalObject().setProperty("AudioInjectionOptions", injectionOptionValue); - registerGlobalObject("Agent", this); + registerGlobalObject("Script", this); registerGlobalObject("Audio", &_audioScriptingInterface); registerGlobalObject("Controller", _controllerScriptingInterface); registerGlobalObject("Data", &_dataServerScriptingInterface); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index ef55e14109..8f29379266 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -31,8 +31,6 @@ const QString NO_SCRIPT(""); class ScriptEngine : public QObject { Q_OBJECT - - Q_PROPERTY(bool isAvatar READ isAvatar WRITE setIsAvatar) public: ScriptEngine(const QString& scriptContents = NO_SCRIPT, bool wantMenuItems = false, const QString& scriptMenuName = QString(""), AbstractMenuInterface* menu = NULL, @@ -41,10 +39,10 @@ public: ~ScriptEngine(); /// Access the VoxelsScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener - VoxelsScriptingInterface* getVoxelsScriptingInterface() { return &_voxelsScriptingInterface; } + static VoxelsScriptingInterface* getVoxelsScriptingInterface() { return &_voxelsScriptingInterface; } /// Access the ParticlesScriptingInterface in order to initialize it with a custom packet sender and jurisdiction listener - ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; } + static ParticlesScriptingInterface* getParticlesScriptingInterface() { return &_particlesScriptingInterface; } /// Access the DataServerScriptingInterface for access to its underlying UUID const DataServerScriptingInterface& getDataServerScriptingInterface() { return _dataServerScriptingInterface; } diff --git a/libraries/shared/src/Assignment.cpp b/libraries/shared/src/Assignment.cpp index 2bff4ddcd0..83296991f3 100644 --- a/libraries/shared/src/Assignment.cpp +++ b/libraries/shared/src/Assignment.cpp @@ -14,9 +14,6 @@ #include "Assignment.h" -const char IPv4_ADDRESS_DESIGNATOR = 4; -const char IPv6_ADDRESS_DESIGNATOR = 6; - Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) { switch (nodeType) { case NodeType::AudioMixer: @@ -55,6 +52,7 @@ Assignment::Assignment() : } Assignment::Assignment(Assignment::Command command, Assignment::Type type, const QString& pool, Assignment::Location location) : + _uuid(), _command(command), _type(type), _pool(pool), @@ -85,20 +83,7 @@ Assignment::Assignment(const QByteArray& packet) : QDataStream packetStream(packet); packetStream.skipRawData(numBytesForPacketHeader(packet)); - uchar assignmentType; - packetStream >> assignmentType; - _type = (Assignment::Type) assignmentType; - - if (_command != Assignment::RequestCommand) { - // read the GUID for this assignment - packetStream >> _uuid; - } - - packetStream >> _pool; - - if (!packetStream.atEnd()) { - _payload = packet.mid(packetStream.device()->pos()); - } + packetStream >> *this; } #ifdef WIN32 @@ -159,18 +144,26 @@ const char* Assignment::getTypeName() const { QDebug operator<<(QDebug debug, const Assignment &assignment) { debug.nospace() << "UUID: " << qPrintable(assignment.getUUID().toString()) << ", Type: " << assignment.getType(); - return debug.nospace(); + + if (!assignment.getPool().isEmpty()) { + debug << ", Pool: " << assignment.getPool(); + } + + return debug.space(); } QDataStream& operator<<(QDataStream &out, const Assignment& assignment) { - out << (quint8) assignment._type; - - // pack the UUID for this assignment, if this is an assignment create or deploy - if (assignment._command != Assignment::RequestCommand) { - out << assignment._uuid; - } - - out << assignment._pool << assignment._payload; + out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload; return out; } + +QDataStream& operator>>(QDataStream &in, Assignment& assignment) { + quint8 packedType; + in >> packedType; + assignment._type = (Assignment::Type) packedType; + + in >> assignment._uuid >> assignment._pool >> assignment._payload; + + return in; +} diff --git a/libraries/shared/src/Assignment.h b/libraries/shared/src/Assignment.h index 418ea7fe09..dd34751b57 100644 --- a/libraries/shared/src/Assignment.h +++ b/libraries/shared/src/Assignment.h @@ -20,7 +20,6 @@ #include "NodeList.h" const int MAX_PAYLOAD_BYTES = 1024; -const int MAX_ASSIGNMENT_POOL_BYTES = 64 + sizeof('\0'); const QString emptyPool = QString(); @@ -76,7 +75,7 @@ public: Assignment::Location getLocation() const { return _location; } const QByteArray& getPayload() const { return _payload; } - void setPayload(const QByteArray& payload) { _payload = payload; } + void setPayload(const QByteArray& payload) { _payload = payload.left(MAX_PAYLOAD_BYTES); } void setPool(const QString& pool) { _pool = pool; }; const QString& getPool() const { return _pool; } @@ -92,6 +91,7 @@ public: friend QDebug operator<<(QDebug debug, const Assignment& assignment); friend QDataStream& operator<<(QDataStream &out, const Assignment& assignment); + friend QDataStream& operator>>(QDataStream &in, Assignment& assignment); protected: QUuid _uuid; /// the 16 byte UUID for this assignment diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp index c5ad709fa4..cd9356ab15 100644 --- a/libraries/shared/src/NodeList.cpp +++ b/libraries/shared/src/NodeList.cpp @@ -119,13 +119,13 @@ void NodeList::timePingReply(const QByteArray& packet) { if (matchingNode) { QDataStream packetStream(packet); - packetStream.device()->seek(numBytesForPacketHeader(packet)); + packetStream.skipRawData(numBytesForPacketHeader(packet)); - qint64 ourOriginalTime, othersReplyTime; + quint64 ourOriginalTime, othersReplyTime; packetStream >> ourOriginalTime >> othersReplyTime; - qint64 now = usecTimestampNow(); + quint64 now = usecTimestampNow(); int pingTime = now - ourOriginalTime; int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight @@ -554,8 +554,11 @@ QByteArray NodeList::constructPingPacket() { } QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { + QDataStream pingPacketStream(pingPacket); + pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); + quint64 timeFromOriginalPing; - memcpy(&timeFromOriginalPing, pingPacket.data() + numBytesForPacketHeader(pingPacket), sizeof(timeFromOriginalPing)); + pingPacketStream >> timeFromOriginalPing; QByteArray replyPacket = byteArrayWithPopluatedHeader(PacketTypePingReply); QDataStream packetStream(&replyPacket, QIODevice::Append); @@ -621,8 +624,7 @@ SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, } } -unsigned NodeList::broadcastToNodes(const QByteArray& packet, - const NodeSet& destinationNodeTypes) { +unsigned NodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) { unsigned n = 0; foreach (const SharedNodePointer& node, getNodeHash()) { diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/shared/src/PacketHeaders.cpp index 5d2dd52a2f..3fd51949f9 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/shared/src/PacketHeaders.cpp @@ -80,7 +80,7 @@ bool packetVersionMatch(const QByteArray& packet) { // currently this just checks if the version in the packet matches our return from versionForPacketType // may need to be expanded in the future for types and versions that take > than 1 byte - if (packet[1] == versionForPacketType(packetTypeForPacket(packet)) || packet[0] == PacketTypeStunResponse) { + if (packet[1] == versionForPacketType(packetTypeForPacket(packet)) || packetTypeForPacket(packet) == PacketTypeStunResponse) { return true; } else { PacketType mismatchType = packetTypeForPacket(packet); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index de667c9ed8..b198add7c2 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -22,6 +22,7 @@ Q_DECLARE_METATYPE(glm::vec2) Q_DECLARE_METATYPE(glm::quat) Q_DECLARE_METATYPE(xColor) + void registerMetaTypes(QScriptEngine* engine); QScriptValue vec3toScriptValue(QScriptEngine* engine, const glm::vec3 &vec3); diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 4788582102..3b869e0ece 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -231,7 +231,6 @@ void sharedMessageHandler(QtMsgType type, const QMessageLogContext& context, con fprintf(stdout, "%s", message.toLocal8Bit().constData()); } - unsigned char* pointToOctalCode(float x, float y, float z, float s) { return pointToVoxel(x, y, z, s); } diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index f47a9eea1e..c982ab3596 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -96,20 +96,9 @@ bool cmdOptionExists(int argc, const char * argv[],const char* option); void sharedMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message); -struct VoxelDetail { - float x; - float y; - float z; - float s; - unsigned char red; - unsigned char green; - unsigned char blue; -}; - unsigned char* pointToVoxel(float x, float y, float z, float s, unsigned char r = 0, unsigned char g = 0, unsigned char b = 0); unsigned char* pointToOctalCode(float x, float y, float z, float s); - #ifdef _WIN32 void usleep(int waitTime); #endif diff --git a/libraries/voxels/src/VoxelDetail.cpp b/libraries/voxels/src/VoxelDetail.cpp new file mode 100644 index 0000000000..d4ab5cafcb --- /dev/null +++ b/libraries/voxels/src/VoxelDetail.cpp @@ -0,0 +1,37 @@ +// +// VoxelDetail.cpp +// hifi +// +// Created by Brad Hefta-Gaub on 1/29/2014 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +#include "VoxelDetail.h" + +void registerVoxelMetaTypes(QScriptEngine* engine) { + qScriptRegisterMetaType(engine, voxelDetailToScriptValue, voxelDetailFromScriptValue); +} + +QScriptValue voxelDetailToScriptValue(QScriptEngine* engine, const VoxelDetail& voxelDetail) { + QScriptValue obj = engine->newObject(); + obj.setProperty("x", voxelDetail.x * (float)TREE_SCALE); + obj.setProperty("y", voxelDetail.y * (float)TREE_SCALE); + obj.setProperty("z", voxelDetail.z * (float)TREE_SCALE); + obj.setProperty("s", voxelDetail.s * (float)TREE_SCALE); + obj.setProperty("red", voxelDetail.red); + obj.setProperty("green", voxelDetail.green); + obj.setProperty("blue", voxelDetail.blue); + return obj; +} + +void voxelDetailFromScriptValue(const QScriptValue &object, VoxelDetail& voxelDetail) { + voxelDetail.x = object.property("x").toVariant().toFloat() / (float)TREE_SCALE; + voxelDetail.y = object.property("y").toVariant().toFloat() / (float)TREE_SCALE; + voxelDetail.z = object.property("z").toVariant().toFloat() / (float)TREE_SCALE; + voxelDetail.s = object.property("s").toVariant().toFloat() / (float)TREE_SCALE; + voxelDetail.red = object.property("red").toVariant().toInt(); + voxelDetail.green = object.property("green").toVariant().toInt(); + voxelDetail.blue = object.property("blue").toVariant().toInt(); +} + + + diff --git a/libraries/voxels/src/VoxelDetail.h b/libraries/voxels/src/VoxelDetail.h new file mode 100644 index 0000000000..ca1ff3940b --- /dev/null +++ b/libraries/voxels/src/VoxelDetail.h @@ -0,0 +1,36 @@ +// +// VoxelDetail.h +// hifi +// +// Created by Brad Hefta-Gaub on 1/29/2014 +// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// +// + +#ifndef __hifi__VoxelDetail__ +#define __hifi__VoxelDetail__ + +#include + +#include +#include "VoxelConstants.h" + +struct VoxelDetail { + float x; + float y; + float z; + float s; + unsigned char red; + unsigned char green; + unsigned char blue; +}; + +Q_DECLARE_METATYPE(VoxelDetail) + +void registerVoxelMetaTypes(QScriptEngine* engine); + +QScriptValue voxelDetailToScriptValue(QScriptEngine* engine, const VoxelDetail& color); +void voxelDetailFromScriptValue(const QScriptValue &object, VoxelDetail& color); + + +#endif /* defined(__hifi__VoxelDetail__) */ \ No newline at end of file diff --git a/libraries/voxels/src/VoxelEditPacketSender.cpp b/libraries/voxels/src/VoxelEditPacketSender.cpp index 3ea4ca3626..fea75abf23 100644 --- a/libraries/voxels/src/VoxelEditPacketSender.cpp +++ b/libraries/voxels/src/VoxelEditPacketSender.cpp @@ -70,14 +70,14 @@ bool createVoxelEditMessage(PacketType command, short int sequence, // cleanup delete[] voxelData; } - + if (success) { // finally, copy the result to the output bufferOut = new unsigned char[actualMessageSize]; sizeOut = actualMessageSize; memcpy(bufferOut, messageBuffer, actualMessageSize); } - + delete[] messageBuffer; // clean up our temporary buffer return success; } diff --git a/libraries/voxels/src/VoxelEditPacketSender.h b/libraries/voxels/src/VoxelEditPacketSender.h index 79d9f8c757..90085635b0 100644 --- a/libraries/voxels/src/VoxelEditPacketSender.h +++ b/libraries/voxels/src/VoxelEditPacketSender.h @@ -12,6 +12,7 @@ #define __shared__VoxelEditPacketSender__ #include +#include "VoxelDetail.h" /// Utility for processing, packing, queueing and sending of outbound edit voxel messages. class VoxelEditPacketSender : public OctreeEditPacketSender { diff --git a/libraries/voxels/src/VoxelTreeElement.h b/libraries/voxels/src/VoxelTreeElement.h index 86732f4b66..028d2456eb 100644 --- a/libraries/voxels/src/VoxelTreeElement.h +++ b/libraries/voxels/src/VoxelTreeElement.h @@ -91,5 +91,4 @@ protected: nodeColor _currentColor; /// Client only, false color of this voxel, 4 bytes }; - #endif /* defined(__hifi__VoxelTreeElement__) */ \ No newline at end of file diff --git a/libraries/voxels/src/VoxelsScriptingInterface.h b/libraries/voxels/src/VoxelsScriptingInterface.h index dc53840cb1..877383e0b0 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.h +++ b/libraries/voxels/src/VoxelsScriptingInterface.h @@ -57,20 +57,4 @@ private: void queueVoxelAdd(PacketType addPacketType, VoxelDetail& addVoxelDetails); }; -class VoxelDetailScriptObject : public QObject { - Q_OBJECT -public: - VoxelDetailScriptObject(VoxelDetail* voxelDetail) { _voxelDetail = voxelDetail; } - -public slots: - /// position in meter units - glm::vec3 getPosition() const { return glm::vec3(_voxelDetail->x, _voxelDetail->y, _voxelDetail->z) * (float)TREE_SCALE; } - xColor getColor() const { xColor color = { _voxelDetail->red, _voxelDetail->green, _voxelDetail->blue }; return color; } - /// scale in meter units - float getScale() const { return _voxelDetail->s * (float)TREE_SCALE; } - -private: - VoxelDetail* _voxelDetail; -}; - #endif /* defined(__hifi__VoxelsScriptingInterface__) */