Brad Hefta-GaubCR feedbackbf8cdab10 years agoHistory
1373 lines · 59.5 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 <QtScript/QScriptValueIterator>#include <QtCore/QStringList> #include <AudioConstants.h>#include <AudioEffectOptions.h>#include <AvatarData.h>#include <EntityScriptingInterface.h>#include <MessagesClient.h>#include <NetworkAccessManager.h>#include <ResourceScriptingInterface.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 "RecordingScriptingInterface.h"#include "ScriptEngines.h" #include "MIDIEvent.h" std::atomic<bool> ScriptEngine::_stoppingAllScripts { false }; static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; 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).noquote() << "script:print()<<" << message;  // noquote() so that \n is treated as newline     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());} Q_DECLARE_METATYPE(controller::InputController*)//static int inputControllerPointerId = qRegisterMetaType<controller::InputController*>(); 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(SCRIPT_EXCEPTION_FORMAT).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),    _timerFunctionMap(),    _wantSignals(wantSignals),    _fileNameString(fileNameString),    _arrayBufferClass(new ArrayBufferClass(this)){    DependencyManager::get<ScriptEngines>()->addScriptEngine(this);     connect(this, &QScriptEngine::signalHandlerException, this, [this](const QScriptValue& exception) {        hadUncaughtExceptions(*this, _fileNameString);    });} ScriptEngine::~ScriptEngine() {    qCDebug(scriptengine) << "Script Engine shutting down (destructor) for script:" << getFilename();    auto scriptEngines = DependencyManager::get<ScriptEngines>();    if (scriptEngines) {        scriptEngines->removeScriptEngine(this);    } else {        qCWarning(scriptengine) << "Script destroyed after ScriptEngines!";    }} void ScriptEngine::disconnectNonEssentialSignals() {    disconnect();    QThread* receiver;    // Ensure the thread should be running, and does exist    if (_isRunning && _isThreaded && (receiver = thread())) {        connect(this, &ScriptEngine::doneRunning, receiver, &QThread::quit);    }} void ScriptEngine::runInThread() {    Q_ASSERT_X(!_isThreaded, "ScriptEngine::runInThread()", "runInThread should not be called more than once");     if (_isThreaded) {        qCWarning(scriptengine) << "ScriptEngine already running in thread: " << getFilename();        return;    }     _isThreaded = true;    QThread* workerThread = new QThread(); // thread is not owned, so we need to manage the delete    QString scriptEngineName = QString("Script Thread:") + getFilename();    workerThread->setObjectName(scriptEngineName);     // NOTE: If you connect any essential signals for proper shutdown or cleanup of    // the script engine, make sure to add code to "reconnect" them to the    // disconnectNonEssentialSignals() method     // 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);     moveToThread(workerThread);     // Starts an event loop, and emits workerThread->started()    workerThread->start();} 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 && _isThreaded) {         // 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 (thread()->isRunning()) {            // 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;    }     QUrl url = expandScriptUrl(scriptURL);    _fileNameString = url.toString();    _isReloading = reload;     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(url.toString());    }} // 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("Mat4", &_mat4Library);    registerGlobalObject("Uuid", &_uuidLibrary);    registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data());    registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().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);     auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();    registerGlobalObject("Recording", recordingInterface.data());     registerGlobalObject("Assets", &_assetScriptingInterface);    registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());} 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);            object.setProperty(name, getterFunction, QScriptValue::PropertyGetter);        }    } else {        globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);        globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);    }} // Unregister the handlers for this eventName and entityID.void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {    if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "            "entityID:" << entityID << " eventName:" << eventName;#endif        QMetaObject::invokeMethod(this, "removeEventHandler",                                  Q_ARG(const EntityItemID&, entityID),                                  Q_ARG(const QString&, eventName),                                  Q_ARG(QScriptValue, handler));        return;    }#ifdef THREAD_DEBUGGING    qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;#endif     if (!_registeredHandlers.contains(entityID)) {        return;    }    RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];    CallbackList& handlersForEvent = handlersOnEntity[eventName];    // QScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate.

Lines 1–500 of 1373.