From cbb55a06a09664b8c8ae688fabad7aac6034f513 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 19 Jan 2017 13:25:11 -0800 Subject: [PATCH] Update ScriptEngine to store full running status off entity scripts --- .../src/scripts/EntityScriptServer.cpp | 24 +++++--- libraries/script-engine/src/ScriptEngine.cpp | 51 +++++++++++++--- libraries/script-engine/src/ScriptEngine.h | 60 +++++++++++-------- 3 files changed, 93 insertions(+), 42 deletions(-) diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 5b638f6671..388e04437c 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -11,8 +11,11 @@ #include "EntityScriptServer.h" +#include "EntityScriptUtils.h" + #include #include +#include #include #include #include @@ -23,8 +26,6 @@ #include #include -#include "../entities/AssignmentParentFinder.h" - const size_t UUID_LENGTH_BYTES = 16; int EntityScriptServer::_entitiesScriptEngineCount = 0; @@ -75,16 +76,21 @@ void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointerreadPrimitive(&messageID); auto entityID = QUuid::fromRfc4122(message->read(UUID_LENGTH_BYTES)); - - // TODO(Huffman) Get Status - qDebug() << "Getting script status of: " << entityID; - auto replyPacket = NLPacket::create(PacketType::EntityScriptGetStatusReply, -1, true); - replyPacket->writePrimitive(messageID); - replyPacket->writeString("running"); + auto replyPacketList = NLPacketList::create(PacketType::EntityScriptGetStatusReply, QByteArray(), true, true); + replyPacketList->writePrimitive(messageID); + + EntityScriptDetails details; + if (_entitiesScriptEngine->getEntityScriptDetails(entityID, details)) { + replyPacketList->writePrimitive(true); + replyPacketList->writePrimitive(details.status); + replyPacketList->writeString(details.errorInfo); + } else { + replyPacketList->writePrimitive(false); + } auto nodeList = DependencyManager::get(); - nodeList->sendPacket(std::move(replyPacket), *senderNode); + nodeList->sendPacketList(std::move(replyPacketList), *senderNode); } void EntityScriptServer::run() { diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2c4512a000..4f6796a2a5 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1374,6 +1374,15 @@ void ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QStrin } } +bool ScriptEngine::getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const { + auto it = _entityScripts.constFind(entityID); + if (it == _entityScripts.constEnd()) { + return false; + } + details = it.value(); + return true; +} + // since all of these operations can be asynch we will always do the actual work in the response handler // for the download void ScriptEngine::loadEntityScript(QWeakPointer theEngine, const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) { @@ -1421,11 +1430,24 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); auto fileName = isURL ? scriptOrURL : "EmbeddedEntityScript"; + EntityScriptDetails newDetails; + newDetails.scriptText = isURL ? contents : scriptOrURL; + + if (!success) { + newDetails.status = ERROR_LOADING_SCRIPT; + newDetails.errorInfo = "Failed to load script"; + _entityScripts[entityID] = newDetails; + return; + } + QScriptProgram program(contents, fileName); if (!hasCorrectSyntax(program, this)) { if (!isFileUrl) { scriptCache->addScriptToBadScriptList(scriptOrURL); } + newDetails.status = ERROR_RUNNING_SCRIPT; + newDetails.errorInfo = "Bad syntax"; + _entityScripts[entityID] = newDetails; return; // done processing script } @@ -1451,6 +1473,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co testConstructor = sandbox.evaluate(program); } if (hadUncaughtExceptions(sandbox, program.fileName(), this)) { + newDetails.status = ERROR_RUNNING_SCRIPT; + newDetails.errorInfo = "Exception"; + _entityScripts[entityID] = newDetails; + return; } @@ -1473,6 +1499,10 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co scriptCache->addScriptToBadScriptList(scriptOrURL); } + newDetails.status = ERROR_RUNNING_SCRIPT; + newDetails.errorInfo = "Could not find constructor"; + _entityScripts[entityID] = newDetails; + return; // done processing script } @@ -1489,8 +1519,11 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co }; doWithEnvironment(entityID, sandboxURL, initialization); - EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified, sandboxURL }; + newDetails.scriptObject = entityScriptObject; + newDetails.lastModified = lastModified; + newDetails.definingSandboxURL = sandboxURL; _entityScripts[entityID] = newDetails; + if (isURL) { setParentURL(""); } @@ -1516,7 +1549,9 @@ void ScriptEngine::unloadEntityScript(const EntityItemID& entityID) { #endif if (_entityScripts.contains(entityID)) { - callEntityScriptMethod(entityID, "unload"); + if (_entityScripts[entityID].status == RUNNING) { + callEntityScriptMethod(entityID, "unload"); + } _entityScripts.remove(entityID); stopAllTimersForEntityScript(entityID); } @@ -1535,7 +1570,9 @@ void ScriptEngine::unloadAllEntityScripts() { qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; #endif foreach(const EntityItemID& entityID, _entityScripts.keys()) { - callEntityScriptMethod(entityID, "unload"); + if (_entityScripts[entityID].status == RUNNING) { + callEntityScriptMethod(entityID, "unload"); + } } _entityScripts.clear(); @@ -1574,7 +1611,7 @@ void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { QString scriptContents = QTextStream(&file).readAll(); this->unloadEntityScript(entityID); this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true); - if (!_entityScripts.contains(entityID)) { + if (!_entityScripts.contains(entityID) || _entityScripts[entityID].status != RUNNING) { scriptWarningMessage("Reload script " + details.scriptText + " failed"); } else { details = _entityScripts[entityID]; @@ -1633,7 +1670,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS #endif refreshFileScript(entityID); - if (_entityScripts.contains(entityID)) { + if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) { EntityScriptDetails details = _entityScripts[entityID]; QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { @@ -1665,7 +1702,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS #endif refreshFileScript(entityID); - if (_entityScripts.contains(entityID)) { + if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) { EntityScriptDetails details = _entityScripts[entityID]; QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { @@ -1698,7 +1735,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS #endif refreshFileScript(entityID); - if (_entityScripts.contains(entityID)) { + if (_entityScripts.contains(entityID) && _entityScripts[entityID].status == RUNNING) { EntityScriptDetails details = _entityScripts[entityID]; QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index d9a1249b1c..e266ff3bc9 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "PointerEvent.h" #include "ArrayBufferClass.h" @@ -58,10 +59,15 @@ typedef QHash RegisteredEventHandlers; class EntityScriptDetails { public: - QString scriptText; - QScriptValue scriptObject; - int64_t lastModified; - QUrl definingSandboxURL; + EntityScriptStatus status { RUNNING }; + + // If status indicates an error, this contains a human-readable string giving more information about the error. + QString errorInfo { "" }; + + QString scriptText { "" }; + QScriptValue scriptObject { QScriptValue() }; + int64_t lastModified { 0 }; + QUrl definingSandboxURL { QUrl() }; }; class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { @@ -178,6 +184,8 @@ public: void scriptWarningMessage(const QString& message); void scriptInfoMessage(const QString& message); + bool getEntityScriptDetails(const EntityItemID& entityID, EntityScriptDetails &details) const; + public slots: void callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler); void updateMemoryCost(const qint64&); @@ -200,6 +208,28 @@ signals: void doneRunning(); protected: + void init(); + + bool evaluatePending() const { return _evaluatesPending > 0; } + void timerFired(); + void stopAllTimers(); + void stopAllTimersForEntityScript(const EntityItemID& entityID); + void refreshFileScript(const EntityItemID& entityID); + + void setParentURL(const QString& parentURL) { _parentURL = parentURL; } + + QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); + void stopTimer(QTimer* timer); + + QHash _registeredHandlers; + void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs); + Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); + + EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. + QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty. + void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation); + void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); + QString _scriptContents; QString _parentURL; std::atomic _isFinished { false }; @@ -215,19 +245,6 @@ protected: bool _debuggable { false }; qint64 _lastUpdate; - void init(); - - bool evaluatePending() const { return _evaluatesPending > 0; } - void timerFired(); - void stopAllTimers(); - void stopAllTimersForEntityScript(const EntityItemID& entityID); - void refreshFileScript(const EntityItemID& entityID); - - void setParentURL(const QString& parentURL) { _parentURL = parentURL; } - - QObject* setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot); - void stopTimer(QTimer* timer); - QString _fileNameString; Quat _quatLibrary; Vec3 _vec3Library; @@ -240,15 +257,6 @@ protected: AssetScriptingInterface _assetScriptingInterface{ this }; - QHash _registeredHandlers; - void forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs); - Q_INVOKABLE void entityScriptContentAvailable(const EntityItemID& entityID, const QString& scriptOrURL, const QString& contents, bool isURL, bool success); - - EntityItemID currentEntityIdentifier {}; // Contains the defining entity script entity id during execution, if any. Empty for interface script execution. - QUrl currentSandboxURL {}; // The toplevel url string for the entity script that loaded the code being executed, else empty. - void doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function operation); - void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args); - std::function _emitScriptUpdates{ [](){ return true; } }; std::recursive_mutex _lock;