//// 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/QTimer>#include <QtCore/QThread>#include <QtNetwork/QNetworkRequest>#include <QtNetwork/QNetworkReply>#include <QScriptEngine> #include <AudioConstants.h>#include <AudioEffectOptions.h>#include <AvatarData.h>#include <Bitstream.h>#include <CollisionInfo.h>#include <EntityScriptingInterface.h>#include <NetworkAccessManager.h>#include <NodeList.h>#include <PacketHeaders.h>#include <UUID.h> #include "AnimationObject.h"#include "ArrayBufferViewClass.h"#include "BatchLoader.h"#include "DataViewClass.h"#include "EventTypes.h"#include "MenuItemProperties.h"#include "ScriptAudioInjector.h"#include "ScriptEngine.h"#include "TypedArrays.h"#include "XMLHttpRequestClass.h" #include "MIDIEvent.h" #ifdef WANT_DEBUG_SCRIPT_ENDING#define DEBUG_SCRIPT_ENDING(X) X#else#define DEBUG_SCRIPT_ENDING(x)#endif EntityScriptingInterface ScriptEngine::_entityScriptingInterface; static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ qDebug() << "script:print()<<" << context->argument(0).toString(); QString message = context->argument(0).toString() .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, AbstractInputController* const &in) { return engine->newQObject(in);} void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) { out = qobject_cast<AbstractInputController*>(object.toQObject());} ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, AbstractControllerScriptingInterface* controllerScriptingInterface) : _scriptContents(scriptContents), _isFinished(false), _isRunning(false), _isInitialized(false), _isAvatar(false), _avatarIdentityTimer(NULL), _avatarBillboardTimer(NULL), _timerFunctionMap(), _isListeningToAudioStream(false), _avatarSound(NULL), _numAvatarSoundSentBytes(0), _controllerScriptingInterface(controllerScriptingInterface), _avatarData(NULL), _scriptName(), _fileNameString(fileNameString), _quatLibrary(), _vec3Library(), _uuidLibrary(), _isUserLoaded(false), _arrayBufferClass(new ArrayBufferClass(this)){ _allScriptsMutex.lock(); _allKnownScriptEngines.insert(this); _allScriptsMutex.unlock();} ScriptEngine::~ScriptEngine() { DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::~ScriptEngine() ------- BEGIN ------- script:" << getFilename();) // 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(); } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::~ScriptEngine() ------- END ------- script:" << getFilename();)} QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines;QMutex ScriptEngine::_allScriptsMutex;bool ScriptEngine::_stoppingAllScripts = false;bool ScriptEngine::_doneRunningThisScript = false; void ScriptEngine::stopAllScripts(QObject* application) { DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() ------- BEGIN -------";) _allScriptsMutex.lock(); _stoppingAllScripts = true; QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines); while (i.hasNext()) { ScriptEngine* scriptEngine = i.next(); QString scriptName = scriptEngine->getFilename(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() considering:" << scriptName << "isRunning():" << scriptEngine->isRunning() << "evaluatePending():" << scriptEngine->evaluatePending();) // 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()) { QEventLoop loop; QObject::connect(scriptEngine, &ScriptEngine::evaluationFinished, &loop, &QEventLoop::quit); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() while(evaluatePending()) STARTING loop.exec() script:" << scriptName;) loop.exec(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() while(evaluatePending()) AFTER loop.exec() script:" << scriptName;) } } scriptEngine->disconnect(application); scriptEngine->stop(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() -- WAITING for waitTillDoneRunning() script:" << scriptName;) scriptEngine->waitTillDoneRunning(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() -- AFTER waitTillDoneRunning() script:" << scriptName;) i.remove(); } else { } } _stoppingAllScripts = false; _allScriptsMutex.unlock(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() ------- DONE -------";)} void ScriptEngine::waitTillDoneRunning() { QString scriptName = getFilename(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() ------- BEGIN ------- script:" << scriptName;) if (_isRunning) { DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() SETTING _doneRunningThisScript = false ################################### line:" << __LINE__ << " script:" << getFilename() << "[" << this << "]";) _doneRunningThisScript = false; _isWaitingForDoneRunning = true; // What can we do here??? // we will be calling this on the main Application thread, inside of stopAllScripts() // we want the application thread to continue to process events, because the script will // likely need to marshall messages across to the main thread. while (!_doneRunningThisScript && _isRunning) { // and run a QEventLoop??? QEventLoop loop; QObject::connect(this, &ScriptEngine::doneRunning, &loop, &QEventLoop::quit); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() STARTING loop.exec() script:" << scriptName;) DEBUG_SCRIPT_ENDING(qDebug() << " _doneRunningThisScript:" << _doneRunningThisScript;) DEBUG_SCRIPT_ENDING(qDebug() << " _isRunning:" << _isRunning;) DEBUG_SCRIPT_ENDING(qDebug() << " _isWaitingForDoneRunning:" << _isWaitingForDoneRunning;) DEBUG_SCRIPT_ENDING(qDebug() << " _isFinished:" << _isFinished;) DEBUG_SCRIPT_ENDING(qDebug() << " _isInitialized:" << _isInitialized;) DEBUG_SCRIPT_ENDING(qDebug() << " _evaluatesPending:" << _evaluatesPending;) loop.exec(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() AFTER loop.exec() script:" << scriptName;) // process events DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() STARTING QCoreApplication::processEvents() script:" << scriptName;) QCoreApplication::processEvents(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() AFTER QCoreApplication::processEvents() script:" << scriptName;) if (_doneRunningThisScript) { DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() _doneRunningThisScript after processEvents... breaking!!! script:" << scriptName;) break; } } _isWaitingForDoneRunning = false; } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() ------- DONE ------- script:" << scriptName;)} QString ScriptEngine::getFilename() const { QStringList fileNameParts = _fileNameString.split("/"); QString lastPart; if (!fileNameParts.isEmpty()) { lastPart = fileNameParts.last(); } return lastPart; } void ScriptEngine::setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; if (_isAvatar && !_avatarIdentityTimer) { // set up the avatar timers _avatarIdentityTimer = new QTimer(this); _avatarBillboardTimer = new QTimer(this); // connect our slot connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket); connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket); // start the timers _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); _avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); } if (!_isAvatar) { delete _avatarIdentityTimer; _avatarIdentityTimer = NULL; delete _avatarBillboardTimer; _avatarBillboardTimer = NULL; }} void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) { _avatarData = avatarData; // remove the old Avatar property, if it exists globalObject().setProperty(objectName, QScriptValue()); // give the script engine the new Avatar script property registerGlobalObject(objectName, _avatarData);} void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) { // remove the old Avatar property, if it exists globalObject().setProperty(objectName, QScriptValue()); // give the script engine the new avatar hash map registerGlobalObject(objectName, avatarHashMap);} bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { return false; } _scriptContents = scriptContents; _fileNameString = fileNameString; return true;} void ScriptEngine::loadURL(const QUrl& scriptURL) { if (_isRunning) { return; } _fileNameString = scriptURL.toString(); QUrl url(scriptURL); // if the scheme length is one or lower, maybe they typed in a file, let's try const int WINDOWS_DRIVE_LETTER_SIZE = 1; if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { url = QUrl::fromLocalFile(_fileNameString); } // ok, let's see if it's valid... and if so, load it if (url.isValid()) { if (url.scheme() == "file") { _fileNameString = url.toLocalFile(); QFile scriptFile(_fileNameString); if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { qDebug() << "ScriptEngine loading file:" << _fileNameString; QTextStream in(&scriptFile); _scriptContents = in.readAll(); emit scriptLoaded(_fileNameString); } else { qDebug() << "ERROR Loading file:" << _fileNameString; emit errorLoadingScript(_fileNameString); } } else { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); connect(reply, &QNetworkReply::finished, this, &ScriptEngine::handleScriptDownload); } }} void ScriptEngine::handleScriptDownload() { QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { _scriptContents = reply->readAll(); emit scriptLoaded(_fileNameString); } else { qDebug() << "ERROR Loading file:" << reply->url().toString(); emit errorLoadingScript(_fileNameString); } reply->deleteLater();} void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once } _isInitialized = true; _entityScriptingInterface.init(); // register various meta-types registerMetaTypes(this); registerMIDIMetaTypes(this); registerEventTypes(this); registerMenuItemProperties(this); registerAnimationTypes(this); registerAvatarTypes(this); registerAudioMetaTypes(this); Bitstream::registerTypes(this); qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValue); qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); qScriptRegisterMetaType(this, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); 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 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); registerGlobalObject("Script", this); registerGlobalObject("Audio", &AudioScriptingInterface::getInstance()); registerGlobalObject("Controller", _controllerScriptingInterface); registerGlobalObject("Entities", &_entityScriptingInterface); registerGlobalObject("Quat", &_quatLibrary); registerGlobalObject("Vec3", &_vec3Library); registerGlobalObject("Uuid", &_uuidLibrary); registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data()); // constants globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); globalObject().setProperty("COLLISION_GROUP_ENVIRONMENT", newVariant(QVariant(COLLISION_GROUP_ENVIRONMENT))); globalObject().setProperty("COLLISION_GROUP_AVATARS", newVariant(QVariant(COLLISION_GROUP_AVATARS))); globalObject().setProperty("AVATAR_MOTION_OBEY_LOCAL_GRAVITY", newVariant(QVariant(AVATAR_MOTION_OBEY_LOCAL_GRAVITY))); globalObject().setProperty("AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY", newVariant(QVariant(AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY))); } QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { if (object) { QScriptValue value = newQObject(object); globalObject().setProperty(name, value); return value; } return QScriptValue::NullValue;} void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { registerFunction(globalObject(), name, fun, numArguments);} void ScriptEngine::registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { QScriptValue scriptFun = newFunction(fun, numArguments); parent.setProperty(name, scriptFun);} void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, QScriptEngine::FunctionSignature setter, QScriptValue object) { QScriptValue setterFunction = newFunction(setter, 1); QScriptValue getterFunction = newFunction(getter); if (!object.isNull()) { 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); }} void ScriptEngine::evaluate() { if (_stoppingAllScripts) { qDebug() << "ScriptEngine::evaluate() while shutting down is ignored..."; qDebug() << " parent script:" << getFilename() << "[" << this << "]"; return; // bail early } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate() -- BEGIN script:" << getFilename() << "[" << this << "]";) if (!_isInitialized) { init(); } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate() -- about to call own evaluate(program) script:" << getFilename() << "[" << this << "]";) QScriptValue result = evaluate(_scriptContents); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate() -- AFTER own evaluate(program) script:" << getFilename() << "[" << this << "]";) // TODO: why do we check this twice? It seems like the call to clearExcpetions() in the lower level evaluate call // will cause this code to never actually run... if (hasUncaughtException()) { int line = uncaughtExceptionLineNumber(); DEBUG_SCRIPT_ENDING(qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();) emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString()); clearExceptions(); }} QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { if (_stoppingAllScripts) { qDebug() << "ScriptEngine::evaluate(program) while shutting down is ignored..."; qDebug() << " parent script:" << getFilename() << "[" << this << "]"; return QScriptValue(); // bail early } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- BEGIN script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) _evaluatesPending++; DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- after _evaluatesPending++ script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- BEFORE QScriptEngine::evaluate() script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- AFTER QScriptEngine::evaluate() script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) if (hasUncaughtException()) { int line = uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString(); } _evaluatesPending--; DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- after _evaluatesPending-- script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- ABOUT TO emit evaluationFinished() script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) emit evaluationFinished(result, hasUncaughtException()); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- AFTER emit evaluationFinished() script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) clearExceptions(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- DONE script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) 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? DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::run() line:" << __LINE__ << " script:" << getFilename() << "[" << this << "]";) if (!_isInitialized) {Lines 1–500 of 947. Load more
// // 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/QTimer> #include <QtCore/QThread> #include <QtNetwork/QNetworkRequest> #include <QtNetwork/QNetworkReply> #include <QScriptEngine> #include <AudioConstants.h> #include <AudioEffectOptions.h> #include <AvatarData.h> #include <Bitstream.h> #include <CollisionInfo.h> #include <EntityScriptingInterface.h> #include <NetworkAccessManager.h> #include <NodeList.h> #include <PacketHeaders.h> #include <UUID.h> #include "AnimationObject.h" #include "ArrayBufferViewClass.h" #include "BatchLoader.h" #include "DataViewClass.h" #include "EventTypes.h" #include "MenuItemProperties.h" #include "ScriptAudioInjector.h" #include "ScriptEngine.h" #include "TypedArrays.h" #include "XMLHttpRequestClass.h" #include "MIDIEvent.h" #ifdef WANT_DEBUG_SCRIPT_ENDING #define DEBUG_SCRIPT_ENDING(X) X #else #define DEBUG_SCRIPT_ENDING(x) #endif EntityScriptingInterface ScriptEngine::_entityScriptingInterface; static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ qDebug() << "script:print()<<" << context->argument(0).toString(); QString message = context->argument(0).toString() .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, AbstractInputController* const &in) { return engine->newQObject(in); } void inputControllerFromScriptValue(const QScriptValue &object, AbstractInputController* &out) { out = qobject_cast<AbstractInputController*>(object.toQObject()); } ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, AbstractControllerScriptingInterface* controllerScriptingInterface) : _scriptContents(scriptContents), _isFinished(false), _isRunning(false), _isInitialized(false), _isAvatar(false), _avatarIdentityTimer(NULL), _avatarBillboardTimer(NULL), _timerFunctionMap(), _isListeningToAudioStream(false), _avatarSound(NULL), _numAvatarSoundSentBytes(0), _controllerScriptingInterface(controllerScriptingInterface), _avatarData(NULL), _scriptName(), _fileNameString(fileNameString), _quatLibrary(), _vec3Library(), _uuidLibrary(), _isUserLoaded(false), _arrayBufferClass(new ArrayBufferClass(this)) { _allScriptsMutex.lock(); _allKnownScriptEngines.insert(this); _allScriptsMutex.unlock(); } ScriptEngine::~ScriptEngine() { DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::~ScriptEngine() ------- BEGIN ------- script:" << getFilename();) // 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(); } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::~ScriptEngine() ------- END ------- script:" << getFilename();) } QSet<ScriptEngine*> ScriptEngine::_allKnownScriptEngines; QMutex ScriptEngine::_allScriptsMutex; bool ScriptEngine::_stoppingAllScripts = false; bool ScriptEngine::_doneRunningThisScript = false; void ScriptEngine::stopAllScripts(QObject* application) { DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() ------- BEGIN -------";) _allScriptsMutex.lock(); _stoppingAllScripts = true; QMutableSetIterator<ScriptEngine*> i(_allKnownScriptEngines); while (i.hasNext()) { ScriptEngine* scriptEngine = i.next(); QString scriptName = scriptEngine->getFilename(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() considering:" << scriptName << "isRunning():" << scriptEngine->isRunning() << "evaluatePending():" << scriptEngine->evaluatePending();) // 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()) { QEventLoop loop; QObject::connect(scriptEngine, &ScriptEngine::evaluationFinished, &loop, &QEventLoop::quit); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() while(evaluatePending()) STARTING loop.exec() script:" << scriptName;) loop.exec(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() while(evaluatePending()) AFTER loop.exec() script:" << scriptName;) } } scriptEngine->disconnect(application); scriptEngine->stop(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() -- WAITING for waitTillDoneRunning() script:" << scriptName;) scriptEngine->waitTillDoneRunning(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() -- AFTER waitTillDoneRunning() script:" << scriptName;) i.remove(); } else { } } _stoppingAllScripts = false; _allScriptsMutex.unlock(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::stopAllScripts() ------- DONE -------";) } void ScriptEngine::waitTillDoneRunning() { QString scriptName = getFilename(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() ------- BEGIN ------- script:" << scriptName;) if (_isRunning) { DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() SETTING _doneRunningThisScript = false ################################### line:" << __LINE__ << " script:" << getFilename() << "[" << this << "]";) _doneRunningThisScript = false; _isWaitingForDoneRunning = true; // What can we do here??? // we will be calling this on the main Application thread, inside of stopAllScripts() // we want the application thread to continue to process events, because the script will // likely need to marshall messages across to the main thread. while (!_doneRunningThisScript && _isRunning) { // and run a QEventLoop??? QEventLoop loop; QObject::connect(this, &ScriptEngine::doneRunning, &loop, &QEventLoop::quit); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() STARTING loop.exec() script:" << scriptName;) DEBUG_SCRIPT_ENDING(qDebug() << " _doneRunningThisScript:" << _doneRunningThisScript;) DEBUG_SCRIPT_ENDING(qDebug() << " _isRunning:" << _isRunning;) DEBUG_SCRIPT_ENDING(qDebug() << " _isWaitingForDoneRunning:" << _isWaitingForDoneRunning;) DEBUG_SCRIPT_ENDING(qDebug() << " _isFinished:" << _isFinished;) DEBUG_SCRIPT_ENDING(qDebug() << " _isInitialized:" << _isInitialized;) DEBUG_SCRIPT_ENDING(qDebug() << " _evaluatesPending:" << _evaluatesPending;) loop.exec(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() AFTER loop.exec() script:" << scriptName;) // process events DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() STARTING QCoreApplication::processEvents() script:" << scriptName;) QCoreApplication::processEvents(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() AFTER QCoreApplication::processEvents() script:" << scriptName;) if (_doneRunningThisScript) { DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() _doneRunningThisScript after processEvents... breaking!!! script:" << scriptName;) break; } } _isWaitingForDoneRunning = false; } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::waitTillDoneRunning() ------- DONE ------- script:" << scriptName;) } QString ScriptEngine::getFilename() const { QStringList fileNameParts = _fileNameString.split("/"); QString lastPart; if (!fileNameParts.isEmpty()) { lastPart = fileNameParts.last(); } return lastPart; } void ScriptEngine::setIsAvatar(bool isAvatar) { _isAvatar = isAvatar; if (_isAvatar && !_avatarIdentityTimer) { // set up the avatar timers _avatarIdentityTimer = new QTimer(this); _avatarBillboardTimer = new QTimer(this); // connect our slot connect(_avatarIdentityTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarIdentityPacket); connect(_avatarBillboardTimer, &QTimer::timeout, this, &ScriptEngine::sendAvatarBillboardPacket); // start the timers _avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS); _avatarBillboardTimer->start(AVATAR_BILLBOARD_PACKET_SEND_INTERVAL_MSECS); } if (!_isAvatar) { delete _avatarIdentityTimer; _avatarIdentityTimer = NULL; delete _avatarBillboardTimer; _avatarBillboardTimer = NULL; } } void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectName) { _avatarData = avatarData; // remove the old Avatar property, if it exists globalObject().setProperty(objectName, QScriptValue()); // give the script engine the new Avatar script property registerGlobalObject(objectName, _avatarData); } void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) { // remove the old Avatar property, if it exists globalObject().setProperty(objectName, QScriptValue()); // give the script engine the new avatar hash map registerGlobalObject(objectName, avatarHashMap); } bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { return false; } _scriptContents = scriptContents; _fileNameString = fileNameString; return true; } void ScriptEngine::loadURL(const QUrl& scriptURL) { if (_isRunning) { return; } _fileNameString = scriptURL.toString(); QUrl url(scriptURL); // if the scheme length is one or lower, maybe they typed in a file, let's try const int WINDOWS_DRIVE_LETTER_SIZE = 1; if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { url = QUrl::fromLocalFile(_fileNameString); } // ok, let's see if it's valid... and if so, load it if (url.isValid()) { if (url.scheme() == "file") { _fileNameString = url.toLocalFile(); QFile scriptFile(_fileNameString); if (scriptFile.open(QFile::ReadOnly | QFile::Text)) { qDebug() << "ScriptEngine loading file:" << _fileNameString; QTextStream in(&scriptFile); _scriptContents = in.readAll(); emit scriptLoaded(_fileNameString); } else { qDebug() << "ERROR Loading file:" << _fileNameString; emit errorLoadingScript(_fileNameString); } } else { QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(url); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); QNetworkReply* reply = networkAccessManager.get(networkRequest); connect(reply, &QNetworkReply::finished, this, &ScriptEngine::handleScriptDownload); } } } void ScriptEngine::handleScriptDownload() { QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { _scriptContents = reply->readAll(); emit scriptLoaded(_fileNameString); } else { qDebug() << "ERROR Loading file:" << reply->url().toString(); emit errorLoadingScript(_fileNameString); } reply->deleteLater(); } void ScriptEngine::init() { if (_isInitialized) { return; // only initialize once } _isInitialized = true; _entityScriptingInterface.init(); // register various meta-types registerMetaTypes(this); registerMIDIMetaTypes(this); registerEventTypes(this); registerMenuItemProperties(this); registerAnimationTypes(this); registerAvatarTypes(this); registerAudioMetaTypes(this); Bitstream::registerTypes(this); qScriptRegisterMetaType(this, EntityItemPropertiesToScriptValue, EntityItemPropertiesFromScriptValue); qScriptRegisterMetaType(this, EntityItemIDtoScriptValue, EntityItemIDfromScriptValue); qScriptRegisterMetaType(this, RayToEntityIntersectionResultToScriptValue, RayToEntityIntersectionResultFromScriptValue); 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 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); registerGlobalObject("Script", this); registerGlobalObject("Audio", &AudioScriptingInterface::getInstance()); registerGlobalObject("Controller", _controllerScriptingInterface); registerGlobalObject("Entities", &_entityScriptingInterface); registerGlobalObject("Quat", &_quatLibrary); registerGlobalObject("Vec3", &_vec3Library); registerGlobalObject("Uuid", &_uuidLibrary); registerGlobalObject("AnimationCache", DependencyManager::get<AnimationCache>().data()); // constants globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); globalObject().setProperty("COLLISION_GROUP_ENVIRONMENT", newVariant(QVariant(COLLISION_GROUP_ENVIRONMENT))); globalObject().setProperty("COLLISION_GROUP_AVATARS", newVariant(QVariant(COLLISION_GROUP_AVATARS))); globalObject().setProperty("AVATAR_MOTION_OBEY_LOCAL_GRAVITY", newVariant(QVariant(AVATAR_MOTION_OBEY_LOCAL_GRAVITY))); globalObject().setProperty("AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY", newVariant(QVariant(AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY))); } QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { if (object) { QScriptValue value = newQObject(object); globalObject().setProperty(name, value); return value; } return QScriptValue::NullValue; } void ScriptEngine::registerFunction(const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { registerFunction(globalObject(), name, fun, numArguments); } void ScriptEngine::registerFunction(QScriptValue parent, const QString& name, QScriptEngine::FunctionSignature fun, int numArguments) { QScriptValue scriptFun = newFunction(fun, numArguments); parent.setProperty(name, scriptFun); } void ScriptEngine::registerGetterSetter(const QString& name, QScriptEngine::FunctionSignature getter, QScriptEngine::FunctionSignature setter, QScriptValue object) { QScriptValue setterFunction = newFunction(setter, 1); QScriptValue getterFunction = newFunction(getter); if (!object.isNull()) { 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); } } void ScriptEngine::evaluate() { if (_stoppingAllScripts) { qDebug() << "ScriptEngine::evaluate() while shutting down is ignored..."; qDebug() << " parent script:" << getFilename() << "[" << this << "]"; return; // bail early } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate() -- BEGIN script:" << getFilename() << "[" << this << "]";) if (!_isInitialized) { init(); } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate() -- about to call own evaluate(program) script:" << getFilename() << "[" << this << "]";) QScriptValue result = evaluate(_scriptContents); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate() -- AFTER own evaluate(program) script:" << getFilename() << "[" << this << "]";) // TODO: why do we check this twice? It seems like the call to clearExcpetions() in the lower level evaluate call // will cause this code to never actually run... if (hasUncaughtException()) { int line = uncaughtExceptionLineNumber(); DEBUG_SCRIPT_ENDING(qDebug() << "Uncaught exception at (" << _fileNameString << ") line" << line << ":" << result.toString();) emit errorMessage("Uncaught exception at (" + _fileNameString + ") line" + QString::number(line) + ":" + result.toString()); clearExceptions(); } } QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { if (_stoppingAllScripts) { qDebug() << "ScriptEngine::evaluate(program) while shutting down is ignored..."; qDebug() << " parent script:" << getFilename() << "[" << this << "]"; return QScriptValue(); // bail early } DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- BEGIN script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) _evaluatesPending++; DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- after _evaluatesPending++ script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- BEFORE QScriptEngine::evaluate() script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) QScriptValue result = QScriptEngine::evaluate(program, fileName, lineNumber); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- AFTER QScriptEngine::evaluate() script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) if (hasUncaughtException()) { int line = uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at (" << _fileNameString << " : " << fileName << ") line" << line << ": " << result.toString(); } _evaluatesPending--; DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- after _evaluatesPending-- script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- ABOUT TO emit evaluationFinished() script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) emit evaluationFinished(result, hasUncaughtException()); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- AFTER emit evaluationFinished() script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) clearExceptions(); DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::evaluate(program) -- DONE script:" << getFilename() << "_evaluatesPending:" << _evaluatesPending << "[" << this << "]";) 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? DEBUG_SCRIPT_ENDING(qDebug() << "ScriptEngine::run() line:" << __LINE__ << " script:" << getFilename() << "[" << this << "]";) if (!_isInitialized) {