912 lines · 36.8 KB · C++
RawDownload
        clearExceptions();    }} QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) {    if (_stoppingAllScripts) {        return QScriptValue(); // bail early    }     _evaluatesPending++;    QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber);    if (hasUncaughtException()) {        int line = uncaughtExceptionLineNumber();        qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString();    }    _evaluatesPending--;    emit evaluationFinished(result, hasUncaughtException());    clearExceptions();    return result;} void ScriptEngine::sendAvatarIdentityPacket() {    if (_isAvatar && _avatarData) {        _avatarData->sendIdentityPacket();    }} void ScriptEngine::sendAvatarBillboardPacket() {    if (_isAvatar && _avatarData) {        _avatarData->sendBillboardPacket();    }} void ScriptEngine::run() {    // TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if    // we're in the process of stopping?     if (!_isInitialized) {        init();    }    _isRunning = true;    _isFinished = false;    emit runningStateChanged();     QScriptValue result = evaluate(_scriptContents);     QElapsedTimer startTime;    startTime.start();     int thisFrame = 0;     auto nodeList = DependencyManager::get<NodeList>();    auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();     qint64 lastUpdate = usecTimestampNow();     while (!_isFinished) {        int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec        if (usecToSleep > 0) {            usleep(usecToSleep);        }         if (_isFinished) {            break;        }         QCoreApplication::processEvents();         if (_isFinished) {            break;        }         if (!_isFinished && entityScriptingInterface->getEntityPacketSender()->serversExist()) {            // release the queue of edit entity messages.            entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();             // since we're in non-threaded mode, call process so that the packets are sent            if (!entityScriptingInterface->getEntityPacketSender()->isThreaded()) {                entityScriptingInterface->getEntityPacketSender()->process();            }        }         if (!_isFinished && _isAvatar && _avatarData) {             const int SCRIPT_AUDIO_BUFFER_SAMPLES = floor(((SCRIPT_DATA_CALLBACK_USECS * AudioConstants::SAMPLE_RATE)                                                           / (1000 * 1000)) + 0.5);            const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);             QByteArray avatarPacket = nodeList->byteArrayWithPopulatedHeader(PacketTypeAvatarData);            avatarPacket.append(_avatarData->toByteArray());             nodeList->broadcastToNodes(avatarPacket, NodeSet() << NodeType::AvatarMixer);             if (_isListeningToAudioStream || _avatarSound) {                // if we have an avatar audio stream then send it out to our audio-mixer                bool silentFrame = true;                 int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;                const int16_t* nextSoundOutput = NULL;                 if (_avatarSound) {                     const QByteArray& soundByteArray = _avatarSound->getByteArray();                    nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()                                                                       + _numAvatarSoundSentBytes);                     int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES                        ? SCRIPT_AUDIO_BUFFER_BYTES                        : soundByteArray.size() - _numAvatarSoundSentBytes;                    numAvailableSamples = numAvailableBytes / sizeof(int16_t);                      // check if the all of the _numAvatarAudioBufferSamples to be sent are silence                    for (int i = 0; i < numAvailableSamples; ++i) {                        if (nextSoundOutput[i] != 0) {                            silentFrame = false;                            break;                        }                    }                     _numAvatarSoundSentBytes += numAvailableBytes;                    if (_numAvatarSoundSentBytes == soundByteArray.size()) {                        // we're done with this sound object - so set our pointer back to NULL                        // and our sent bytes back to zero                        _avatarSound = NULL;                        _numAvatarSoundSentBytes = 0;                    }                }                                QByteArray audioPacket = nodeList->byteArrayWithPopulatedHeader(silentFrame                                                                                ? PacketTypeSilentAudioFrame                                                                                : PacketTypeMicrophoneAudioNoEcho);                 QDataStream packetStream(&audioPacket, QIODevice::Append);                 // pack a placeholder value for sequence number for now, will be packed when destination node is known                int numPreSequenceNumberBytes = audioPacket.size();                packetStream << (quint16) 0;                 if (silentFrame) {                    if (!_isListeningToAudioStream) {                        // if we have a silent frame and we're not listening then just send nothing and break out of here                        break;                    }                     // write the number of silent samples so the audio-mixer can uphold timing                    packetStream.writeRawData(reinterpret_cast<const char*>(&SCRIPT_AUDIO_BUFFER_SAMPLES), sizeof(int16_t));                     // use the orientation and position of this avatar for the source of this audio                    packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));                    glm::quat headOrientation = _avatarData->getHeadOrientation();                    packetStream.writeRawData(reinterpret_cast<const char*>(&headOrientation), sizeof(glm::quat));                 } else if (nextSoundOutput) {                    // assume scripted avatar audio is mono and set channel flag to zero                    packetStream << (quint8)0;                     // use the orientation and position of this avatar for the source of this audio                    packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));                    glm::quat headOrientation = _avatarData->getHeadOrientation();                    packetStream.writeRawData(reinterpret_cast<const char*>(&headOrientation), sizeof(glm::quat));                     // write the raw audio data                    packetStream.writeRawData(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples * sizeof(int16_t));                }                                // write audio packet to AudioMixer nodes                auto nodeList = DependencyManager::get<NodeList>();                nodeList->eachNode([this, &nodeList, &audioPacket, &numPreSequenceNumberBytes](const SharedNodePointer& node){                    // only send to nodes of type AudioMixer                    if (node->getType() == NodeType::AudioMixer) {                        // pack sequence number                        quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;                        memcpy(audioPacket.data() + numPreSequenceNumberBytes, &sequence, sizeof(quint16));                                                // send audio packet                        nodeList->writeDatagram(audioPacket, node);                    }                });            }        }         qint64 now = usecTimestampNow();        float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;         if (hasUncaughtException()) {            int line = uncaughtExceptionLineNumber();            qCDebug(scriptengine) << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << uncaughtException().toString();            emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + uncaughtException().toString());            clearExceptions();        }         if (!_isFinished) {            emit update(deltaTime);        }        lastUpdate = now;            }     stopAllTimers(); // make sure all our timers are stopped if the script is ending    emit scriptEnding();     // kill the avatar identity timer    delete _avatarIdentityTimer;     if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {        // release the queue of edit entity messages.        entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();         // since we're in non-threaded mode, call process so that the packets are sent        if (!entityScriptingInterface->getEntityPacketSender()->isThreaded()) {            entityScriptingInterface->getEntityPacketSender()->process();        }    }     // If we were on a thread, then wait till it's done    if (thread()) {        thread()->quit();    }     emit finished(_fileNameString);     _isRunning = false;    emit runningStateChanged();     emit doneRunning();     _doneRunningThisScript = true;} // NOTE: This is private because it must be called on the same thread that created the timers, which is why// we want to only call it in our own run "shutdown" processing.void ScriptEngine::stopAllTimers() {    QMutableHashIterator<QTimer*, QScriptValue> i(_timerFunctionMap);    while (i.hasNext()) {        i.next();        QTimer* timer = i.key();        stopTimer(timer);    }} void ScriptEngine::stop() {    if (!_isFinished) {        _isFinished = true;        emit runningStateChanged();    }} void ScriptEngine::timerFired() {    QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());    QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);        if (!callingTimer->isActive()) {        // this timer is done, we can kill it        _timerFunctionMap.remove(callingTimer);        delete callingTimer;    }        // call the associated JS function, if it exists    if (timerFunction.isValid()) {        timerFunction.call();    }} QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {    // create the timer, add it to the map, and start it    QTimer* newTimer = new QTimer(this);    newTimer->setSingleShot(isSingleShot);     connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired);     // make sure the timer stops when the script does    connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);     _timerFunctionMap.insert(newTimer, function);     newTimer->start(intervalMS);    return newTimer;} QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();        return NULL; // bail early    }     return setupTimerWithInterval(function, intervalMS, false);} QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();        return NULL; // bail early    }     return setupTimerWithInterval(function, timeoutMS, true);} void ScriptEngine::stopTimer(QTimer *timer) {    if (_timerFunctionMap.contains(timer)) {        timer->stop();        _timerFunctionMap.remove(timer);        delete timer;    }} QUrl ScriptEngine::resolvePath(const QString& include) const {    QUrl url(include);    // first lets check to see if it's already a full URL    if (!url.scheme().isEmpty()) {        return url;    }     // we apparently weren't a fully qualified url, so, let's assume we're relative    // to the original URL of our script    QUrl parentURL;    if (_parentURL.isEmpty()) {        parentURL = QUrl(_fileNameString);    } else {        parentURL = QUrl(_parentURL);    }    // if the parent URL's scheme is empty, then this is probably a local file...    if (parentURL.scheme().isEmpty()) {        parentURL = QUrl::fromLocalFile(_fileNameString);    }     // at this point we should have a legitimate fully qualified URL for our parent    url = parentURL.resolved(url);    return url;} void ScriptEngine::print(const QString& message) {    emit printedMessage(message);} // If a callback is specified, the included files will be loaded asynchronously and the callback will be called// when all of the files have finished loading.// If no callback is specified, the included files will be loaded synchronously and will block execution until// all of the files have finished loading.void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.include() while shutting down is ignored..."                 << "includeFiles:" << includeFiles << "parent script:" << getFilename();        return; // bail early    }    QList<QUrl> urls;    for (QString file : includeFiles) {        urls.append(resolvePath(file));    }     BatchLoader* loader = new BatchLoader(urls);        auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {        for (QUrl url : urls) {            QString contents = data[url];            if (contents.isNull()) {                qCDebug(scriptengine) << "Error loading file: " << url << "line:" << __LINE__;            } else {                QScriptValue result = evaluate(contents, url.toString());            }        }         if (callback.isFunction()) {            QScriptValue(callback).call();        }         loader->deleteLater();    };     connect(loader, &BatchLoader::finished, this, evaluateScripts);     // If we are destroyed before the loader completes, make sure to clean it up    connect(this, &QObject::destroyed, loader, &QObject::deleteLater);     loader->start();     if (!callback.isFunction() && !loader->isFinished()) {        QEventLoop loop;        QObject::connect(loader, &BatchLoader::finished, &loop, &QEventLoop::quit);        loop.exec();    }} void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.include() while shutting down is ignored... "                  << "includeFile:" << includeFile << "parent script:" << getFilename();        return; // bail early    }     QStringList urls;    urls.append(includeFile);    include(urls, callback);} // NOTE: The load() command is similar to the include() command except that it loads the script// as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which// the Application or other context will connect to in order to know to actually load the scriptvoid ScriptEngine::load(const QString& loadFile) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.load() while shutting down is ignored... "                 << "loadFile:" << loadFile << "parent script:" << getFilename();        return; // bail early    }     QUrl url = resolvePath(loadFile);    emit loadScript(url.toString(), false);} void ScriptEngine::nodeKilled(SharedNodePointer node) {    _outgoingScriptAudioSequenceNumbers.remove(node->getUUID());}

Lines 501–912 of 912. Previous lines