#ifdef DEBUG_ENGINE_STATE _debugDump( "---- CURRENT STATE OF ENGINE: --------------------------", globalObject(), "--------------------------------------------------------" );#endif // DEBUG_ENGINE_STATE} void ScriptManager::refreshFileScript(const EntityItemID& entityID) { if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !hasEntityScriptDetails(entityID)) { return; } static bool recurseGuard = false; if (recurseGuard) { return; } recurseGuard = true; EntityScriptDetails details; { QWriteLocker locker { &_entityScriptsLock }; 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) { scriptInfoMessage("Reloading modified script " + details.scriptText, filePath, -1); loadEntityScript(entityID, details.scriptText, true); } } recurseGuard = false;} // Execute operation in the appropriate context for (the possibly empty) entityID.// Even if entityID is supplied as currentEntityIdentifier, this still documents the source// of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different// global values for different entity scripts).void ScriptManager::doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation) { EntityItemID oldIdentifier = currentEntityIdentifier; QUrl oldSandboxURL = currentSandboxURL; currentEntityIdentifier = entityID; currentSandboxURL = sandboxURL; #if DEBUG_CURRENT_ENTITY ScriptValue oldData = this->globalObject().property("debugEntityID"); this->globalObject().setProperty("debugEntityID", entityID.toScriptValue(this)); // Make the entityID available to javascript as a global. operation(); this->globalObject().setProperty("debugEntityID", oldData);#else operation();#endif currentEntityIdentifier = oldIdentifier; currentSandboxURL = oldSandboxURL;} void ScriptManager::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, const ScriptValue& function, const ScriptValue& thisObject, const ScriptValueList& args) { auto operation = [&]() { function.call(thisObject, args); }; doWithEnvironment(entityID, sandboxURL, operation);} void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params, const QUuid& remoteCallerID) { if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName;#endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, methodName), Q_ARG(const QStringList&, params), Q_ARG(const QUuid&, remoteCallerID)); return; }#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName;#endif if (HIFI_AUTOREFRESH_FILE_SCRIPTS && methodName != "unload") { refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { EntityScriptDetails details; { QWriteLocker locker { &_entityScriptsLock }; details = _entityScripts[entityID]; } ScriptValue entityScript = details.scriptObject; // previously loaded // If this is a remote call, we need to check to see if the function is remotely callable // we do this by checking for the existance of the 'remotelyCallable' property on the // entityScript. And we confirm that the method name is included. If this fails, the // function will not be called. bool callAllowed = false; if (remoteCallerID == QUuid()) { callAllowed = true; } else { if (entityScript.property("remotelyCallable").isArray()) { auto callables = entityScript.property("remotelyCallable"); auto callableCount = callables.property("length").toInteger(); for (int i = 0; i < callableCount; i++) { auto callable = callables.property(i).toString(); if (callable == methodName) { callAllowed = true; break; } } } if (!callAllowed) { qCDebug(scriptengine) << "Method [" << methodName << "] not remotely callable."; } } if (callAllowed && entityScript.property(methodName).isFunction()) { auto scriptEngine = engine().get(); ScriptValueList args; args << EntityItemIDtoScriptValue(scriptEngine, entityID); args << scriptValueFromSequence(scriptEngine, params); ScriptValue oldData = scriptEngine->globalObject().property("Script").property("remoteCallerID"); scriptEngine->globalObject().property("Script").setProperty("remoteCallerID", remoteCallerID.toString()); // Make the remoteCallerID available to javascript as a global. callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); scriptEngine->globalObject().property("Script").setProperty("remoteCallerID", oldData); } }} void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event) { if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent";#endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, methodName), Q_ARG(const PointerEvent&, event)); return; }#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent";#endif if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { EntityScriptDetails details; { QWriteLocker locker { &_entityScriptsLock }; details = _entityScripts[entityID]; } ScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { auto scriptEngine = engine().get(); ScriptValueList args; args << EntityItemIDtoScriptValue(scriptEngine, entityID); args << event.toScriptValue(scriptEngine); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } }} void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";#endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, methodName), Q_ARG(const EntityItemID&, otherID), Q_ARG(const Collision&, collision)); return; }#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision";#endif if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { EntityScriptDetails details; { QWriteLocker locker { &_entityScriptsLock }; details = _entityScripts[entityID]; } ScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { auto scriptEngine = engine().get(); ScriptValueList args; args << EntityItemIDtoScriptValue(scriptEngine, entityID); args << EntityItemIDtoScriptValue(scriptEngine, otherID); args << collisionToScriptValue(scriptEngine, collision); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } }} QString ScriptManager::getExternalPath(ExternalResource::Bucket bucket, const QString& path) { return ExternalResource::getInstance()->getUrl(bucket, path);} QString ScriptManager::formatException(const ScriptValue& exception, bool includeExtendedDetails) { if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { return QString(); } QString note{ "UncaughtException" }; QString result; if (!exception.isObject()) { return result; } const auto message = exception.toString(); const auto fileName = exception.property("fileName").toString(); const auto lineNumber = exception.property("lineNumber").toString(); const auto stacktrace = exception.property("stack").toString(); if (includeExtendedDetails) { // Display additional exception / troubleshooting hints that can be added via the custom Error .detail property // Example difference: // [UncaughtExceptions] Error: Can't find variable: foobar in atp:/myentity.js\n... // [UncaughtException (construct {1eb5d3fa-23b1-411c-af83-163af7220e3f})] Error: Can't find variable: foobar in atp:/myentity.js\n... if (exception.property("detail").isValid()) { note += " " + exception.property("detail").toString(); } } result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber); if (!stacktrace.isEmpty()) { result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace); } return result;} ScriptValue ScriptManager::evaluate(const QString& program, const QString& fileName) { return _engine->evaluate(program, fileName);} void ScriptManager::requestGarbageCollection() { _engine->requestCollectGarbage();} void ScriptManager::logBacktrace(const QString &title) { _engine->logBacktrace(title);}Lines 2501–2763 of 2763. Previous lines
#ifdef DEBUG_ENGINE_STATE _debugDump( "---- CURRENT STATE OF ENGINE: --------------------------", globalObject(), "--------------------------------------------------------" ); #endif // DEBUG_ENGINE_STATE } void ScriptManager::refreshFileScript(const EntityItemID& entityID) { if (!HIFI_AUTOREFRESH_FILE_SCRIPTS || !hasEntityScriptDetails(entityID)) { return; } static bool recurseGuard = false; if (recurseGuard) { return; } recurseGuard = true; EntityScriptDetails details; { QWriteLocker locker { &_entityScriptsLock }; 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) { scriptInfoMessage("Reloading modified script " + details.scriptText, filePath, -1); loadEntityScript(entityID, details.scriptText, true); } } recurseGuard = false; } // Execute operation in the appropriate context for (the possibly empty) entityID. // Even if entityID is supplied as currentEntityIdentifier, this still documents the source // of the code being executed (e.g., if we ever sandbox different entity scripts, or provide different // global values for different entity scripts). void ScriptManager::doWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, std::function<void()> operation) { EntityItemID oldIdentifier = currentEntityIdentifier; QUrl oldSandboxURL = currentSandboxURL; currentEntityIdentifier = entityID; currentSandboxURL = sandboxURL; #if DEBUG_CURRENT_ENTITY ScriptValue oldData = this->globalObject().property("debugEntityID"); this->globalObject().setProperty("debugEntityID", entityID.toScriptValue(this)); // Make the entityID available to javascript as a global. operation(); this->globalObject().setProperty("debugEntityID", oldData); #else operation(); #endif currentEntityIdentifier = oldIdentifier; currentSandboxURL = oldSandboxURL; } void ScriptManager::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, const ScriptValue& function, const ScriptValue& thisObject, const ScriptValueList& args) { auto operation = [&]() { function.call(thisObject, args); }; doWithEnvironment(entityID, sandboxURL, operation); } void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params, const QUuid& remoteCallerID) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName; #endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, methodName), Q_ARG(const QStringList&, params), Q_ARG(const QUuid&, remoteCallerID)); return; } #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName; #endif if (HIFI_AUTOREFRESH_FILE_SCRIPTS && methodName != "unload") { refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { EntityScriptDetails details; { QWriteLocker locker { &_entityScriptsLock }; details = _entityScripts[entityID]; } ScriptValue entityScript = details.scriptObject; // previously loaded // If this is a remote call, we need to check to see if the function is remotely callable // we do this by checking for the existance of the 'remotelyCallable' property on the // entityScript. And we confirm that the method name is included. If this fails, the // function will not be called. bool callAllowed = false; if (remoteCallerID == QUuid()) { callAllowed = true; } else { if (entityScript.property("remotelyCallable").isArray()) { auto callables = entityScript.property("remotelyCallable"); auto callableCount = callables.property("length").toInteger(); for (int i = 0; i < callableCount; i++) { auto callable = callables.property(i).toString(); if (callable == methodName) { callAllowed = true; break; } } } if (!callAllowed) { qCDebug(scriptengine) << "Method [" << methodName << "] not remotely callable."; } } if (callAllowed && entityScript.property(methodName).isFunction()) { auto scriptEngine = engine().get(); ScriptValueList args; args << EntityItemIDtoScriptValue(scriptEngine, entityID); args << scriptValueFromSequence(scriptEngine, params); ScriptValue oldData = scriptEngine->globalObject().property("Script").property("remoteCallerID"); scriptEngine->globalObject().property("Script").setProperty("remoteCallerID", remoteCallerID.toString()); // Make the remoteCallerID available to javascript as a global. callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); scriptEngine->globalObject().property("Script").setProperty("remoteCallerID", oldData); } } } void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "event: mouseEvent"; #endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, methodName), Q_ARG(const PointerEvent&, event)); return; } #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "event: pointerEvent"; #endif if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { EntityScriptDetails details; { QWriteLocker locker { &_entityScriptsLock }; details = _entityScripts[entityID]; } ScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { auto scriptEngine = engine().get(); ScriptValueList args; args << EntityItemIDtoScriptValue(scriptEngine, entityID); args << event.toScriptValue(scriptEngine); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } } } void ScriptManager::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptManager::callEntityScriptMethod() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; #endif QMetaObject::invokeMethod(this, "callEntityScriptMethod", Q_ARG(const EntityItemID&, entityID), Q_ARG(const QString&, methodName), Q_ARG(const EntityItemID&, otherID), Q_ARG(const Collision&, collision)); return; } #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptManager::callEntityScriptMethod() called on correct thread [" << thread() << "] " "entityID:" << entityID << "methodName:" << methodName << "otherID:" << otherID << "collision: collision"; #endif if (HIFI_AUTOREFRESH_FILE_SCRIPTS) { refreshFileScript(entityID); } if (isEntityScriptRunning(entityID)) { EntityScriptDetails details; { QWriteLocker locker { &_entityScriptsLock }; details = _entityScripts[entityID]; } ScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { auto scriptEngine = engine().get(); ScriptValueList args; args << EntityItemIDtoScriptValue(scriptEngine, entityID); args << EntityItemIDtoScriptValue(scriptEngine, otherID); args << collisionToScriptValue(scriptEngine, collision); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } } } QString ScriptManager::getExternalPath(ExternalResource::Bucket bucket, const QString& path) { return ExternalResource::getInstance()->getUrl(bucket, path); } QString ScriptManager::formatException(const ScriptValue& exception, bool includeExtendedDetails) { if (!_engine->IS_THREADSAFE_INVOCATION(__FUNCTION__)) { return QString(); } QString note{ "UncaughtException" }; QString result; if (!exception.isObject()) { return result; } const auto message = exception.toString(); const auto fileName = exception.property("fileName").toString(); const auto lineNumber = exception.property("lineNumber").toString(); const auto stacktrace = exception.property("stack").toString(); if (includeExtendedDetails) { // Display additional exception / troubleshooting hints that can be added via the custom Error .detail property // Example difference: // [UncaughtExceptions] Error: Can't find variable: foobar in atp:/myentity.js\n... // [UncaughtException (construct {1eb5d3fa-23b1-411c-af83-163af7220e3f})] Error: Can't find variable: foobar in atp:/myentity.js\n... if (exception.property("detail").isValid()) { note += " " + exception.property("detail").toString(); } } result = QString(SCRIPT_EXCEPTION_FORMAT).arg(note, message, fileName, lineNumber); if (!stacktrace.isEmpty()) { result += QString("\n[Backtrace]%1%2").arg(SCRIPT_BACKTRACE_SEP).arg(stacktrace); } return result; } ScriptValue ScriptManager::evaluate(const QString& program, const QString& fileName) { return _engine->evaluate(program, fileName); } void ScriptManager::requestGarbageCollection() { _engine->requestCollectGarbage(); } void ScriptManager::logBacktrace(const QString &title) { _engine->logBacktrace(title); }