stopAllTimersForEntityScript(entityID); { // FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests processDeferredEntityLoads(scriptText, entityID); } }} void ScriptEngine::unloadAllEntityScripts() { if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::unloadAllEntityScripts() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";#endif QMetaObject::invokeMethod(this, "unloadAllEntityScripts"); return; }#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]";#endif QList<EntityItemID> keys; { QReadLocker locker{ &_entityScriptsLock }; keys = _entityScripts.keys(); } foreach(const EntityItemID& entityID, keys) { unloadEntityScript(entityID); } { QWriteLocker locker{ &_entityScriptsLock }; _entityScripts.clear(); } emit entityScriptDetailsUpdated(); _occupiedScriptURLs.clear(); #ifdef DEBUG_ENGINE_STATE _debugDump( "---- CURRENT STATE OF ENGINE: --------------------------", globalObject(), "--------------------------------------------------------" );#endif // DEBUG_ENGINE_STATE} void ScriptEngine::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); 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 ScriptEngine::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 QScriptValue 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 maybeEmitUncaughtException(!entityID.isNull() ? entityID.toString() : __FUNCTION__); currentEntityIdentifier = oldIdentifier; currentSandboxURL = oldSandboxURL;} void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args) { auto operation = [&]() { function.call(thisObject, args); }; doWithEnvironment(entityID, sandboxURL, operation);} void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params, const QUuid& remoteCallerID) { if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::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) << "ScriptEngine::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]; } QScriptValue 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) { qDebug() << "Method [" << methodName << "] not remotely callable."; } } if (callAllowed && entityScript.property(methodName).isFunction()) { QScriptValueList args; args << entityID.toScriptValue(this); args << qScriptValueFromSequence(this, params); QScriptValue oldData = this->globalObject().property("Script").property("remoteCallerID"); this->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); this->globalObject().property("Script").setProperty("remoteCallerID", oldData); } }} void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event) { if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::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) << "ScriptEngine::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]; } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; args << entityID.toScriptValue(this); args << event.toScriptValue(this); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } }} void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::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) << "ScriptEngine::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]; } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; args << entityID.toScriptValue(this); args << otherID.toScriptValue(this); args << collisionToScriptValue(this, collision); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } }} Lines 2501–2746 of 2746. Previous lines
stopAllTimersForEntityScript(entityID); { // FIXME: shouldn't have to do this here, but currently something seems to be firing unloads moments after firing initial load requests processDeferredEntityLoads(scriptText, entityID); } } } void ScriptEngine::unloadAllEntityScripts() { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::unloadAllEntityScripts() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]"; #endif QMetaObject::invokeMethod(this, "unloadAllEntityScripts"); return; } #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptEngine::unloadAllEntityScripts() called on correct thread [" << thread() << "]"; #endif QList<EntityItemID> keys; { QReadLocker locker{ &_entityScriptsLock }; keys = _entityScripts.keys(); } foreach(const EntityItemID& entityID, keys) { unloadEntityScript(entityID); } { QWriteLocker locker{ &_entityScriptsLock }; _entityScripts.clear(); } emit entityScriptDetailsUpdated(); _occupiedScriptURLs.clear(); #ifdef DEBUG_ENGINE_STATE _debugDump( "---- CURRENT STATE OF ENGINE: --------------------------", globalObject(), "--------------------------------------------------------" ); #endif // DEBUG_ENGINE_STATE } void ScriptEngine::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); 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 ScriptEngine::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 QScriptValue 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 maybeEmitUncaughtException(!entityID.isNull() ? entityID.toString() : __FUNCTION__); currentEntityIdentifier = oldIdentifier; currentSandboxURL = oldSandboxURL; } void ScriptEngine::callWithEnvironment(const EntityItemID& entityID, const QUrl& sandboxURL, QScriptValue function, QScriptValue thisObject, QScriptValueList args) { auto operation = [&]() { function.call(thisObject, args); }; doWithEnvironment(entityID, sandboxURL, operation); } void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const QStringList& params, const QUuid& remoteCallerID) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::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) << "ScriptEngine::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]; } QScriptValue 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) { qDebug() << "Method [" << methodName << "] not remotely callable."; } } if (callAllowed && entityScript.property(methodName).isFunction()) { QScriptValueList args; args << entityID.toScriptValue(this); args << qScriptValueFromSequence(this, params); QScriptValue oldData = this->globalObject().property("Script").property("remoteCallerID"); this->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); this->globalObject().property("Script").setProperty("remoteCallerID", oldData); } } } void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const PointerEvent& event) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::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) << "ScriptEngine::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]; } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; args << entityID.toScriptValue(this); args << event.toScriptValue(this); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } } } void ScriptEngine::callEntityScriptMethod(const EntityItemID& entityID, const QString& methodName, const EntityItemID& otherID, const Collision& collision) { if (QThread::currentThread() != thread()) { #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "*** WARNING *** ScriptEngine::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) << "ScriptEngine::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]; } QScriptValue entityScript = details.scriptObject; // previously loaded if (entityScript.property(methodName).isFunction()) { QScriptValueList args; args << entityID.toScriptValue(this); args << otherID.toScriptValue(this); args << collisionToScriptValue(this, collision); callWithEnvironment(entityID, details.definingSandboxURL, entityScript.property(methodName), entityScript, args); } } }