From 99d1e17ae8a08fd41c340cbd23ede360da0e117f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 7 Oct 2015 19:18:59 -0700 Subject: [PATCH] Support reload-on-change for local scripts --- libraries/script-engine/src/ScriptEngine.cpp | 61 ++++++++++++++++++-- libraries/script-engine/src/ScriptEngine.h | 2 + 2 files changed, 57 insertions(+), 6 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index b8a97f0902..3906d68c42 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -11,12 +11,13 @@ #include #include +#include #include #include #include #include -#include -#include +#include +#include #include #include @@ -942,6 +943,7 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co #endif auto scriptCache = DependencyManager::get(); + bool isFileUrl = isURL && scriptOrURL.startsWith("file://"); // first check the syntax of the script contents QScriptSyntaxCheckResult syntaxCheck = QScriptEngine::checkSyntax(contents); @@ -950,7 +952,9 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co qCDebug(scriptengine) << " " << syntaxCheck.errorMessage() << ":" << syntaxCheck.errorLineNumber() << syntaxCheck.errorColumnNumber(); qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; - scriptCache->addScriptToBadScriptList(scriptOrURL); + if (!isFileUrl) { + scriptCache->addScriptToBadScriptList(scriptOrURL); + } return; // done processing script } @@ -965,14 +969,21 @@ void ScriptEngine::entityScriptContentAvailable(const EntityItemID& entityID, co qCDebug(scriptengine) << "ScriptEngine::loadEntityScript() entity:" << entityID; qCDebug(scriptengine) << " NOT CONSTRUCTOR"; qCDebug(scriptengine) << " SCRIPT:" << scriptOrURL; - scriptCache->addScriptToBadScriptList(scriptOrURL); + if (!isFileUrl) { + scriptCache->addScriptToBadScriptList(scriptOrURL); + } return; // done processing script } - QScriptValue entityScriptConstructor = evaluate(contents); + int64_t lastModified = 0; + if (isFileUrl) { + QString file = QUrl(scriptOrURL).toLocalFile(); + lastModified = (quint64)QFileInfo(file).lastModified().toMSecsSinceEpoch(); + } + QScriptValue entityScriptConstructor = evaluate(contents); QScriptValue entityScriptObject = entityScriptConstructor.construct(); - EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject }; + EntityScriptDetails newDetails = { scriptOrURL, entityScriptObject, lastModified }; _entityScripts[entityID] = newDetails; if (isURL) { setParentURL(""); @@ -1022,6 +1033,41 @@ void ScriptEngine::unloadAllEntityScripts() { _entityScripts.clear(); } +void ScriptEngine::refreshFileScript(const EntityItemID& entityID) { + if (!_entityScripts.contains(entityID)) { + return; + } + + static bool recurseGuard = false; + if (recurseGuard) { + return; + } + recurseGuard = true; + + EntityScriptDetails details = _entityScripts[entityID]; + // Check to see if a file based script needs to be reloaded (easier debugging) + if (details.lastModified > 0) { + QString filePath = QUrl(details.scriptText).toLocalFile(); + auto lastModified = QFileInfo(filePath).lastModified().toMSecsSinceEpoch(); + if (lastModified > details.lastModified) { + qDebug() << "Reloading modified script " << details.scriptText; + + QFile file(filePath); + file.open(QIODevice::ReadOnly); + QString scriptContents = QTextStream(&file).readAll(); + this->unloadEntityScript(entityID); + this->entityScriptContentAvailable(entityID, details.scriptText, scriptContents, true, true); + if (!_entityScripts.contains(entityID)) { + qWarning() << "Reload script " << details.scriptText << " failed"; + } else { + details = _entityScripts[entityID]; + } + } + } + recurseGuard = false; +} + + void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING @@ -1039,6 +1085,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS "entityID:" << entityID << "methodName:" << methodName; #endif + refreshFileScript(entityID); if (_entityScripts.contains(entityID)) { EntityScriptDetails details = _entityScripts[entityID]; QScriptValue entityScript = details.scriptObject; // previously loaded @@ -1069,6 +1116,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent"; #endif + refreshFileScript(entityID); if (_entityScripts.contains(entityID)) { EntityScriptDetails details = _entityScripts[entityID]; QScriptValue entityScript = details.scriptObject; // previously loaded @@ -1101,6 +1149,7 @@ void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QS "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; #endif + refreshFileScript(entityID); if (_entityScripts.contains(entityID)) { EntityScriptDetails details = _entityScripts[entityID]; QScriptValue entityScript = details.scriptObject; // previously loaded diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 3cfeb3447e..3ff397e98d 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -45,6 +45,7 @@ class EntityScriptDetails { public: QString scriptText; QScriptValue scriptObject; + int64_t lastModified; }; class ScriptEngine : public QScriptEngine, public ScriptUser, public EntitiesScriptEngineProvider { @@ -171,6 +172,7 @@ private: bool evaluatePending() const { return _evaluatesPending > 0; } void timerFired(); void stopAllTimers(); + void refreshFileScript(const EntityItemID& entityID); void setParentURL(const QString& parentURL) { _parentURL = parentURL; }