Update ScriptEngine to store full running status off entity scripts

This commit is contained in:
Ryan Huffman 2017-01-19 13:25:11 -08:00
parent 89d1242925
commit cbb55a06a0
3 changed files with 93 additions and 42 deletions

View file

@ -11,8 +11,11 @@
#include "EntityScriptServer.h"
#include "EntityScriptUtils.h"
#include <AudioConstants.h>
#include <AudioInjectorManager.h>
#include <src/entities/AssignmentParentFinder.h>
#include <EntityScriptingInterface.h>
#include <MessagesClient.h>
#include <plugins/CodecPlugin.h>
@ -23,8 +26,6 @@
#include <SoundCache.h>
#include <WebSocketServerClass.h>
#include "../entities/AssignmentParentFinder.h"
const size_t UUID_LENGTH_BYTES = 16;
int EntityScriptServer::_entitiesScriptEngineCount = 0;
@ -75,16 +76,21 @@ void EntityScriptServer::handleEntityScriptGetStatusPacket(QSharedPointer<Receiv
MessageID messageID;
message->readPrimitive(&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>();
nodeList->sendPacket(std::move(replyPacket), *senderNode);
nodeList->sendPacketList(std::move(replyPacketList), *senderNode);
}
void EntityScriptServer::run() {

View file

@ -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<ScriptEngine> 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()) {

View file

@ -29,6 +29,7 @@
#include <LimitedNodeList.h>
#include <EntityItemID.h>
#include <EntitiesScriptEngineProvider.h>
#include <EntityScriptUtils.h>
#include "PointerEvent.h"
#include "ArrayBufferClass.h"
@ -58,10 +59,15 @@ typedef QHash<QString, CallbackList> 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<EntityItemID, RegisteredEventHandlers> _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<void()> operation);
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
QString _scriptContents;
QString _parentURL;
std::atomic<bool> _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<EntityItemID, RegisteredEventHandlers> _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<void()> operation);
void callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args);
std::function<bool()> _emitScriptUpdates{ [](){ return true; } };
std::recursive_mutex _lock;