1274 lines · 52.8 KB · C++
RawDownload
////  ScriptEngine.cpp//  libraries/script-engine/src////  Created by Brad Hefta-Gaub on 12/14/13.//  Copyright 2013 High Fidelity, Inc.////  Distributed under the Apache License, Version 2.0.//  See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// #include <QtCore/QCoreApplication>#include <QtCore/QEventLoop>#include <QtCore/QFileInfo>#include <QtCore/QTimer>#include <QtCore/QThread>#include <QtNetwork/QNetworkRequest>#include <QtNetwork/QNetworkReply>#include <QtScript/QScriptEngine>#include <QtScript/QScriptValue>#include <QtCore/QStringList> #include <AudioConstants.h>#include <AudioEffectOptions.h>#include <AvatarData.h>#include <EntityScriptingInterface.h>#include <NetworkAccessManager.h>#include <NodeList.h>#include <udt/PacketHeaders.h>#include <UUID.h> #include <controllers/ScriptingInterface.h>#include <AnimationObject.h> #include "ArrayBufferViewClass.h"#include "BatchLoader.h"#include "DataViewClass.h"#include "EventTypes.h"#include "MenuItemProperties.h"#include "ScriptAudioInjector.h"#include "ScriptCache.h"#include "ScriptEngineLogging.h"#include "ScriptEngine.h"#include "TypedArrays.h"#include "XMLHttpRequestClass.h"#include "WebSocketClass.h" #include "SceneScriptingInterface.h" #include "MIDIEvent.h" Q_DECLARE_METATYPE(QScriptEngine::FunctionSignature)static int functionSignatureMetaID = qRegisterMetaType<QScriptEngine::FunctionSignature>(); static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){    QString message = "";    for (int i = 0; i < context->argumentCount(); i++) {        if (i > 0) {            message += " ";        }        message += context->argument(i).toString();    }    qCDebug(scriptengine) << "script:print()<<" << message;     message = message.replace("\\", "\\\\")                     .replace("\n", "\\n")                     .replace("\r", "\\r")                     .replace("'", "\\'");    engine->evaluate("Script.print('" + message + "')");     return QScriptValue();} QScriptValue avatarDataToScriptValue(QScriptEngine* engine, AvatarData* const &in) {    return engine->newQObject(in);} void avatarDataFromScriptValue(const QScriptValue &object, AvatarData* &out) {    out = qobject_cast<AvatarData*>(object.toQObject());} QScriptValue inputControllerToScriptValue(QScriptEngine *engine, controller::InputController* const &in) {    return engine->newQObject(in);} void inputControllerFromScriptValue(const QScriptValue &object, controller::InputController* &out) {    out = qobject_cast<controller::InputController*>(object.toQObject());}   static bool hasCorrectSyntax(const QScriptProgram& program) {    const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());    if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {        const auto error = syntaxCheck.errorMessage();        const auto line = QString::number(syntaxCheck.errorLineNumber());        const auto column = QString::number(syntaxCheck.errorColumnNumber());        const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column);        qCWarning(scriptengine) << qPrintable(message);        return false;    }    return true;} static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) {    if (engine.hasUncaughtException()) {        const auto backtrace = engine.uncaughtExceptionBacktrace();        const auto exception = engine.uncaughtException().toString();        const auto line = QString::number(engine.uncaughtExceptionLineNumber());        engine.clearExceptions();                auto message = QString("[UncaughtException] %1 in %2:%3").arg(exception, fileName, line);        if (!backtrace.empty()) {            static const auto lineSeparator = "\n    ";            message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));        }        qCWarning(scriptengine) << qPrintable(message);        return true;    }    return false;} ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, bool wantSignals) :    _scriptContents(scriptContents),    _isFinished(false),    _isRunning(false),    _isInitialized(false),    _timerFunctionMap(),    _wantSignals(wantSignals),    _fileNameString(fileNameString),    _isUserLoaded(false),    _isReloading(false),    _arrayBufferClass(new ArrayBufferClass(this)){    _allScriptsMutex.lock();    _allKnownScriptEngines.insert(this);    _allScriptsMutex.unlock();} ScriptEngine::~ScriptEngine() {    // If we're not already in the middle of stopping all scripts, then we should remove ourselves    // from the list of running scripts. We don't do this if we're in the process of stopping all scripts    // because that method removes scripts from its list as it iterates them    if (!_stoppingAllScripts) {        _allScriptsMutex.lock();        _allKnownScriptEngines.remove(this);        _allScriptsMutex.unlock();    }} void ScriptEngine::runInThread() {    QThread* workerThread = new QThread(); // thread is not owned, so we need to manage the delete    QString scriptEngineName = QString("Script Thread:") + getFilename();    workerThread->setObjectName(scriptEngineName);     // when the worker thread is started, call our engine's run..    connect(workerThread, &QThread::started, this, &ScriptEngine::run);     // tell the thread to stop when the script engine is done    connect(this, &ScriptEngine::doneRunning, workerThread, &QThread::quit);     // when the thread is finished, add thread to the deleteLater queue    connect(workerThread, &QThread::finished, workerThread, &QThread::deleteLater);     // when the thread is finished, add scriptEngine to the deleteLater queue    connect(workerThread, &QThread::finished, this, &ScriptEngine::deleteLater);     moveToThread(workerThread);     // Starts an event loop, and emits workerThread->started()    workerThread->start();} QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;QMutex ScriptEngine::_allScriptsMutex;bool ScriptEngine::_stoppingAllScripts = false;bool ScriptEngine::_doneRunningThisScript = false; void ScriptEngine::stopAllScripts(QObject* application) {    _allScriptsMutex.lock();    _stoppingAllScripts = true;     QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines);    while (i.hasNext()) {        ScriptEngine* scriptEngine = i.next();         QString scriptName = scriptEngine->getFilename();         // NOTE: typically all script engines are running. But there's at least one known exception to this, the        // "entities sandbox" which is only used to evaluate entities scripts to test their validity before using        // them. We don't need to stop scripts that aren't running.        if (scriptEngine->isRunning()) {             // If the script is running, but still evaluating then we need to wait for its evaluation step to            // complete. After that we can handle the stop process appropriately            if (scriptEngine->evaluatePending()) {                while (scriptEngine->evaluatePending()) {                     // This event loop allows any started, but not yet finished evaluate() calls to complete                    // we need to let these complete so that we can be guaranteed that the script engine isn't                    // in a partially setup state, which can confuse our shutdown unwinding.                    QEventLoop loop;                    QObject::connect(scriptEngine, &ScriptEngine::evaluationFinished, &loop, &QEventLoop::quit);                    loop.exec();                }            }             // We disconnect any script engine signals from the application because we don't want to do any            // extra stopScript/loadScript processing that the Application normally does when scripts start            // and stop. We can safely short circuit this because we know we're in the "quitting" process            scriptEngine->disconnect(application);             // Calling stop on the script engine will set it's internal _isFinished state to true, and result            // in the ScriptEngine gracefully ending it's run() method.            scriptEngine->stop();             // We need to wait for the engine to be done running before we proceed, because we don't            // want any of the scripts final "scriptEnding()" or pending "update()" methods from accessing            // any application state after we leave this stopAllScripts() method            scriptEngine->waitTillDoneRunning();             // If the script is stopped, we can remove it from our set            i.remove();        }    }    _stoppingAllScripts = false;    _allScriptsMutex.unlock();}  void ScriptEngine::waitTillDoneRunning() {    // If the script never started running or finished running before we got here, we don't need to wait for it    if (_isRunning) {         _doneRunningThisScript = false; // NOTE: this is static, we serialize our waiting for scripts to finish         // NOTE: waitTillDoneRunning() will be called on the main Application thread, inside of stopAllScripts()        // we want the application thread to continue to process events, because the scripts will likely need to        // marshall messages across to the main thread. For example if they access Settings or Meny in any of their        // shutdown code.        while (!_doneRunningThisScript) {             // process events for the main application thread, allowing invokeMethod calls to pass between threads            QCoreApplication::processEvents();        }    }} QString ScriptEngine::getFilename() const {    QStringList fileNameParts = _fileNameString.split("/");    QString lastPart;    if (!fileNameParts.isEmpty()) {        lastPart = fileNameParts.last();    }    return lastPart;}  // FIXME - switch this to the new model of ScriptCache callbacksvoid ScriptEngine::loadURL(const QUrl& scriptURL, bool reload) {    if (_isRunning) {        return;    }     _fileNameString = scriptURL.toString();    _isReloading = reload;     QUrl url(scriptURL);     bool isPending;    auto scriptCache = DependencyManager::get<ScriptCache>();    scriptCache->getScript(url, this, isPending, reload);} // FIXME - switch this to the new model of ScriptCache callbacksvoid ScriptEngine::scriptContentsAvailable(const QUrl& url, const QString& scriptContents) {    _scriptContents = scriptContents;    if (_wantSignals) {        emit scriptLoaded(_fileNameString);    }} // FIXME - switch this to the new model of ScriptCache callbacksvoid ScriptEngine::errorInLoadingScript(const QUrl& url) {    qCDebug(scriptengine) << "ERROR Loading file:" << url.toString() << "line:" << __LINE__;    if (_wantSignals) {        emit errorLoadingScript(_fileNameString); // ??    }} // Even though we never pass AnimVariantMap directly to and from javascript, the queued invokeMethod of// callAnimationStateHandler requires that the type be registered.// These two are meaningful, if we ever do want to use them...static QScriptValue animVarMapToScriptValue(QScriptEngine* engine, const AnimVariantMap& parameters) {    QStringList unused;    return parameters.animVariantMapToScriptValue(engine, unused, false);}static void animVarMapFromScriptValue(const QScriptValue& value, AnimVariantMap& parameters) {    parameters.animVariantMapFromScriptValue(value);}// ... while these two are not. But none of the four are ever used.static QScriptValue resultHandlerToScriptValue(QScriptEngine* engine, const AnimVariantResultHandler& resultHandler) {    qCCritical(scriptengine) << "Attempt to marshall result handler to javascript";    assert(false);    return QScriptValue();}static void resultHandlerFromScriptValue(const QScriptValue& value, AnimVariantResultHandler& resultHandler) {    qCCritical(scriptengine) << "Attempt to marshall result handler from javascript";    assert(false);} void ScriptEngine::init() {    if (_isInitialized) {        return; // only initialize once    }     _isInitialized = true;        auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();    entityScriptingInterface->init();     // register various meta-types    registerMetaTypes(this);    registerMIDIMetaTypes(this);    registerEventTypes(this);    registerMenuItemProperties(this);    registerAnimationTypes(this);    registerAvatarTypes(this);    registerAudioMetaTypes(this);     qScriptRegisterMetaType(this, EntityPropertyFlagsToScriptValue, EntityPropertyFlagsFromScriptValue);    qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValueHonorReadOnly);    qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue);    qScriptRegisterMetaType(this, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue);    qScriptRegisterSequenceMetaType<QVector<QUuid>>(this);    qScriptRegisterSequenceMetaType<QVector<EntityItemID>>(this);     qScriptRegisterSequenceMetaType<QVector<glm::vec2> >(this);    qScriptRegisterSequenceMetaType<QVector<glm::quat> >(this);    qScriptRegisterSequenceMetaType<QVector<QString> >(this);     QScriptValue xmlHttpRequestConstructorValue = newFunction(XMLHttpRequestClass::constructor);    globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue);     QScriptValue webSocketConstructorValue = newFunction(WebSocketClass::constructor);    globalObject().setProperty("WebSocket", webSocketConstructorValue);     QScriptValue printConstructorValue = newFunction(debugPrint);    globalObject().setProperty("print", printConstructorValue);     QScriptValue audioEffectOptionsConstructorValue = newFunction(AudioEffectOptions::constructor);    globalObject().setProperty("AudioEffectOptions", audioEffectOptionsConstructorValue);     qScriptRegisterMetaType(this, injectorToScriptValue, injectorFromScriptValue);    qScriptRegisterMetaType(this, inputControllerToScriptValue, inputControllerFromScriptValue);    qScriptRegisterMetaType(this, avatarDataToScriptValue, avatarDataFromScriptValue);    qScriptRegisterMetaType(this, animationDetailsToScriptValue, animationDetailsFromScriptValue);    qScriptRegisterMetaType(this, webSocketToScriptValue, webSocketFromScriptValue);    qScriptRegisterMetaType(this, qWSCloseCodeToScriptValue, qWSCloseCodeFromScriptValue);    qScriptRegisterMetaType(this, wscReadyStateToScriptValue, wscReadyStateFromScriptValue);     registerGlobalObject("Script", this);    registerGlobalObject("Audio", &AudioScriptingInterface::getInstance());    registerGlobalObject("Entities", entityScriptingInterface.data());    registerGlobalObject("Quat", &_quatLibrary);    registerGlobalObject("Vec3", &_vec3Library);    registerGlobalObject("Uuid", &_uuidLibrary);    registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());    qScriptRegisterMetaType(this, animVarMapToScriptValue, animVarMapFromScriptValue);    qScriptRegisterMetaType(this, resultHandlerToScriptValue, resultHandlerFromScriptValue);     // constants    globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE)));     auto scriptingInterface = DependencyManager::get<controller::ScriptingInterface>();    registerGlobalObject("Controller", scriptingInterface.data());    UserInputMapper::registerControllerTypes(this);} void ScriptEngine::registerValue(const QString& valueName, QScriptValue value) {    if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::registerValue() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";#endif        QMetaObject::invokeMethod(this, "registerValue",            Q_ARG(const QString&, valueName),            Q_ARG(QScriptValue, value));        return;    }     QStringList pathToValue = valueName.split(".");    int partsToGo = pathToValue.length();    QScriptValue partObject = globalObject();     for (const auto& pathPart : pathToValue) {        partsToGo--;        if (!partObject.property(pathPart).isValid()) {            if (partsToGo > 0) {                //QObject *object = new QObject;                QScriptValue partValue = newArray(); //newQObject(object, QScriptEngine::ScriptOwnership);                qDebug() << "partValue[" << pathPart<<"].isArray() :" << partValue.isArray();                partObject.setProperty(pathPart, partValue);            } else {                partObject.setProperty(pathPart, value);            }        }        partObject = partObject.property(pathPart);    }} void ScriptEngine::registerGlobalObject(const QString& name, QObject* object) {    if (QThread::currentThread() != thread()) {        #ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::registerGlobalObject() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]  name:" << name;        #endif        QMetaObject::invokeMethod(this, "registerGlobalObject",            Q_ARG(const QString&, name),            Q_ARG(QObject*, object));        return;    }    #ifdef THREAD_DEBUGGING    qDebug() << "ScriptEngine::registerGlobalObject() called on thread [" << QThread::currentThread() << "] name:" << name;    #endif     if (!globalObject().property(name).isValid()) {        if (object) {            QScriptValue value = newQObject(object);            globalObject().setProperty(name, value);        } else {            globalObject().setProperty(name, QScriptValue());        }    }} void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {    if (QThread::currentThread() != thread()) {        #ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] name:" << name;        #endif        QMetaObject::invokeMethod(this, "registerFunction",            Q_ARG(const QString&, name),            Q_ARG(QScriptEngine::FunctionSignature, functionSignature),            Q_ARG(int, numArguments));        return;    }    #ifdef THREAD_DEBUGGING    qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] name:" << name;    #endif     QScriptValue scriptFun = newFunction(functionSignature, numArguments);    globalObject().setProperty(name, scriptFun);} void ScriptEngine::registerFunction(const QString& parent, const QString& name, QScriptEngine::FunctionSignature functionSignature, int numArguments) {    if (QThread::currentThread() != thread()) {        #ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::registerFunction() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] parent:" << parent << "name:" << name;        #endif        QMetaObject::invokeMethod(this, "registerFunction",            Q_ARG(const QString&, name),            Q_ARG(QScriptEngine::FunctionSignature, functionSignature),            Q_ARG(int, numArguments));        return;    }    #ifdef THREAD_DEBUGGING    qDebug() << "ScriptEngine::registerFunction() called on thread [" << QThread::currentThread() << "] parent:" << parent << "name:" << name;    #endif     QScriptValue object = globalObject().property(parent);    if (object.isValid()) {        QScriptValue scriptFun = newFunction(functionSignature, numArguments);        object.setProperty(name, scriptFun);    }} void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter,                            QScriptEngine::FunctionSignature setter, const QString& parent) {    if (QThread::currentThread() != thread()) {        #ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::registerGetterSetter() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "                    " name:" << name << "parent:" << parent;        #endif        QMetaObject::invokeMethod(this, "registerGetterSetter",            Q_ARG(const QString&, name),            Q_ARG(QScriptEngine::FunctionSignature, getter),            Q_ARG(QScriptEngine::FunctionSignature, setter),            Q_ARG(const QString&, parent));        return;    }    #ifdef THREAD_DEBUGGING    qDebug() << "ScriptEngine::registerGetterSetter() called on thread [" << QThread::currentThread() << "] name:" << name << "parent:" << parent;    #endif     QScriptValue setterFunction = newFunction(setter, 1);    QScriptValue getterFunction = newFunction(getter);     if (!parent.isNull() && !parent.isEmpty()) {        QScriptValue object = globalObject().property(parent);        if (object.isValid()) {            object.setProperty(name, setterFunction, QScriptValue::PropertySetter);

Lines 1–500 of 1274.