2763 lines · 116.2 KB · C++
RawDownload
                    }                }                 // Wait for the scripting thread to stop running, as                // flooding it with aborts/exceptions will persist it longer                static const auto MAX_SCRIPT_QUITTING_TIME = 50 * MSECS_PER_SECOND;                if (!workerThread->wait(MAX_SCRIPT_QUITTING_TIME)) {                    Q_ASSERT(false);                    workerThread->terminate();                }            }*/             if (shutdown) {                // NOTE: This will be called on the main application thread (among other threads) from stopAllScripts.                //       The thread will need to continue to process events, because                //       the scripts will likely need to marshall messages across to the main thread, e.g.                //       if they access Settings or Menu in any of their shutdown code. So:                // Process events for this thread, allowing invokeMethod calls to pass between threads.                QCoreApplication::processEvents();            }             // Avoid a pure busy wait            QThread::msleep(1);        }#endif         scriptInfoMessage("Script Engine has stopped:" + getFilename(), "", -1);    }} void ScriptManager::removeFromScriptEngines() {    Q_ASSERT(_scriptEngines);    _scriptEngines.toStrongRef()->removeScriptEngine(shared_from_this());} QString ScriptManager::getFilename() const {    QStringList fileNameParts = _fileNameString.split("/");    QString lastPart;    if (!fileNameParts.isEmpty()) {        lastPart = fileNameParts.last();    }    return lastPart;} QString ScriptManager::getAbsoluteFilename() const {    return _fileNameString;} bool ScriptManager::hasValidScriptSuffix(const QString& scriptFileName) {    QFileInfo fileInfo(scriptFileName);    QString scriptSuffixToLower = fileInfo.completeSuffix().toLower();    return scriptSuffixToLower.contains(QString("js"), Qt::CaseInsensitive);} void ScriptManager::loadURL(const QUrl& scriptURL, bool reload) {    if (_isRunning) {        return;    }     QUrl url = expandScriptUrl(scriptURL);    _fileNameString = url.toString();    _isReloading = reload;     // Check that script has a supported file extension    if (!hasValidScriptSuffix(_fileNameString)) {        scriptErrorMessage("File extension of file: " + _fileNameString + " is not a currently supported script type", _fileNameString, -1);        emit errorLoadingScript(_fileNameString);        return;    }     const auto maxRetries = 0; // for consistency with previous scriptCache->getScript() behavior    auto scriptCache = DependencyManager::get<ScriptCache>();    scriptCache->getScriptContents(url.toString(), [this](const QString& url, const QString& scriptContents, bool isURL, bool success, const QString&status) {        qCDebug(scriptengine) << "loadURL" << url << status << QThread::currentThread();        if (!success) {            scriptErrorMessage("ERROR Loading file (" + status + "):" + url, url, -1);            emit errorLoadingScript(_fileNameString);            return;        }         _scriptContents = scriptContents;         emit scriptLoaded(url);    }, reload, maxRetries);} void ScriptManager::scriptErrorMessage(const QString& message, const QString& fileName, int lineNumber) {    qCCritical(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message));    emit errorMessage(message, getFilename());    if (!currentEntityIdentifier.isInvalidID()) {        emit errorEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());    }} void ScriptManager::scriptWarningMessage(const QString& message, const QString& fileName, int lineNumber) {    qCWarning(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message));    emit warningMessage(message, getFilename());    if (!currentEntityIdentifier.isInvalidID()) {        emit warningEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());    }} void ScriptManager::scriptInfoMessage(const QString& message, const QString& fileName, int lineNumber) {    qCInfo(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message));    emit infoMessage(message, getFilename());    if (!currentEntityIdentifier.isInvalidID()) {        emit infoEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());    }} void ScriptManager::scriptPrintedMessage(const QString& message, const QString& fileName, int lineNumber) {    qCDebug(scriptengine, "[%s] %s", qUtf8Printable(getFilename()), qUtf8Printable(message));    emit printedMessage(message, getFilename());    if (!currentEntityIdentifier.isInvalidID()) {        emit printedEntityMessage(message, fileName, lineNumber, currentEntityIdentifier, isEntityServerScript());    }} void ScriptManager::clearDebugLogWindow() {    emit clearDebugWindow();} // Templated qScriptRegisterMetaType fails to compile with raw pointersusing ScriptableResourceRawPtr = ScriptableResource*; static ScriptValue scriptableResourceToScriptValue(ScriptEngine* engine,                                                    const ScriptableResourceRawPtr& resource) {    if (!resource) {        return ScriptValue();  // probably shutting down    }     // The first script to encounter this resource will track its memory.    // In this way, it will be more likely to GC.    // This fails in the case that the resource is used across many scripts, but    // in that case it would be too difficult to tell which one should track the memory, and    // this serves the common case (use in a single script).    auto data = resource->getResource();    auto manager = engine->manager();    if (data && manager && !resource->isInScript()) {        resource->setInScript(true);        QObject::connect(data.data(), &Resource::updateSize, manager, &ScriptManager::updateMemoryCost);    }     auto object = engine->newQObject(const_cast<ScriptableResourceRawPtr>(resource), ScriptEngine::ScriptOwnership);    return object;} static bool scriptableResourceFromScriptValue(const ScriptValue& value, ScriptableResourceRawPtr& resource) {    resource = static_cast<ScriptableResourceRawPtr>(value.toQObject());    return true;} /*@jsdoc * The <code>Resource</code> API provides values that define the possible loading states of a resource. * * @namespace Resource * * @hifi-interface * @hifi-client-entity * @hifi-avatar * @hifi-server-entity * @hifi-assignment-client * * @property {Resource.State} State - The possible loading states of a resource. <em>Read-only.</em> */static ScriptValue createScriptableResourcePrototype(ScriptManagerPointer manager) {    auto engine = manager->engine();    auto prototype = engine->newObject();     auto state = engine->newObject();    auto metaEnum = QMetaEnum::fromType<ScriptableResource::State>();    for (int i = 0; i < metaEnum.keyCount(); ++i) {        state.setProperty(metaEnum.key(i), metaEnum.value(i));    }     prototype.setProperty("State", state);     return prototype;} ScriptValue externalResourceBucketToScriptValue(ScriptEngine* engine, ExternalResource::Bucket const& in) {    return engine->newValue((int)in);} bool externalResourceBucketFromScriptValue(const ScriptValue& object, ExternalResource::Bucket& out) {    out = static_cast<ExternalResource::Bucket>(object.toInt32());    return true;} void ScriptManager::resetModuleCache(bool deleteScriptCache) {    if (QThread::currentThread() != thread()) {        executeOnScriptThread([=]() { resetModuleCache(deleteScriptCache); });        return;    }    auto jsRequire = _engine->globalObject().property("Script").property("require");    auto cache = jsRequire.property("cache");    auto cacheMeta = jsRequire.data();     if (deleteScriptCache) {        auto it = cache.newIterator();        while (it->hasNext()) {            it->next();            if (it->flags() & ScriptValue::SkipInEnumeration) {                continue;            }            qCDebug(scriptengine) << "resetModuleCache(true) -- staging " << it->name() << " for cache reset at next require";            cacheMeta.setProperty(it->name(), true);        }    }    cache = _engine->newObject();    if (!cacheMeta.isObject()) {        cacheMeta = _engine->newObject();        cacheMeta.setProperty("id", "Script.require.cacheMeta");        cacheMeta.setProperty("type", "cacheMeta");        jsRequire.setData(cacheMeta);    }    cache.setProperty("__created__", (double)QDateTime::currentMSecsSinceEpoch(), ScriptValue::SkipInEnumeration);#if DEBUG_JS_MODULES    cache.setProperty("__meta__", cacheMeta, READONLY_HIDDEN_PROP_FLAGS);#endif    jsRequire.setProperty("cache", cache, READONLY_PROP_FLAGS);} void ScriptManager::initMetaTypes() {    if (_areMetaTypesInitialized) {        return;    }    _areMetaTypesInitialized = true;    runStaticTypesInitializers(this);} void ScriptManager::init() {    if (_isInitialized) {        return; // only initialize once    }     _isInitialized = true;     if (_context != NETWORKLESS_TEST_SCRIPT) {        // This initializes a bunch of systems that want network access. We        // want to avoid it in test script mode.        runStaticInitializers(this);    }     auto scriptEngine = _engine.get();     if (_context != NETWORKLESS_TEST_SCRIPT) {        // For test scripts we want to minimize the amount of functionality available, for the least        // amount of dependencies and faster test system startup.         ScriptValue xmlHttpRequestConstructorValue = scriptEngine->newFunction(XMLHttpRequestClass::constructor);        scriptEngine->globalObject().setProperty("XMLHttpRequest", xmlHttpRequestConstructorValue);         ScriptValue webSocketConstructorValue = scriptEngine->newFunction(WebSocketClass::constructor);        scriptEngine->globalObject().setProperty("WebSocket", webSocketConstructorValue);    }     /*@jsdoc     * Prints a message to the program log and emits {@link Script.printedMessage}.     * The message logged is the message values separated by spaces.     * <p>Alternatively, you can use {@link Script.print} or one of the {@link console} API methods.</p>     * @function print     * @param {...*} [message] - The message values to print.     */    scriptEngine->globalObject().setProperty("print", scriptEngine->newFunction(debugPrint));     // NOTE: You do not want to end up creating new instances of singletons here. They will be on the ScriptManager thread    // and are likely to be unusable if we "reset" the ScriptManager by creating a new one (on a whole new thread).     scriptEngine->registerGlobalObject("Script", _scriptingInterface.get());     {        // set up Script.require.resolve and Script.require.cache        auto Script = scriptEngine->globalObject().property("Script");        auto require = Script.property("require");        auto resolve = Script.property("_requireResolve");        require.setProperty("resolve", resolve, READONLY_PROP_FLAGS);         scriptEngine->globalObject().setProperty("require", require, READONLY_PROP_FLAGS);        resetModuleCache();    }     scriptEngine->registerEnum("Script.ExternalPaths", QMetaEnum::fromType<ExternalResource::Bucket>());     scriptEngine->registerGlobalObject("Quat", &_quatLibrary);    scriptEngine->registerGlobalObject("Vec3", &_vec3Library);    scriptEngine->registerGlobalObject("Mat4", &_mat4Library);    scriptEngine->registerGlobalObject("Uuid", &_uuidLibrary);     if (_context != NETWORKLESS_TEST_SCRIPT) {        // This requires networking, we want to avoid the need for it in test scripts        scriptEngine->registerGlobalObject("Messages", DependencyManager::get<MessagesClient>().data());    }     scriptEngine->registerGlobalObject("console", &_consoleScriptingInterface);    scriptEngine->registerFunction("console", "info", ConsoleScriptingInterface::info, scriptEngine->currentContext()->argumentCount());    scriptEngine->registerFunction("console", "log", ConsoleScriptingInterface::log, scriptEngine->currentContext()->argumentCount());    scriptEngine->registerFunction("console", "debug", ConsoleScriptingInterface::debug, scriptEngine->currentContext()->argumentCount());    scriptEngine->registerFunction("console", "warn", ConsoleScriptingInterface::warn, scriptEngine->currentContext()->argumentCount());    scriptEngine->registerFunction("console", "error", ConsoleScriptingInterface::error, scriptEngine->currentContext()->argumentCount());    scriptEngine->registerFunction("console", "exception", ConsoleScriptingInterface::exception, scriptEngine->currentContext()->argumentCount());    scriptEngine->registerFunction("console", "assert", ConsoleScriptingInterface::assertion, scriptEngine->currentContext()->argumentCount());    scriptEngine->registerFunction("console", "group", ConsoleScriptingInterface::group, 1);    scriptEngine->registerFunction("console", "groupCollapsed", ConsoleScriptingInterface::groupCollapsed, 1);    scriptEngine->registerFunction("console", "groupEnd", ConsoleScriptingInterface::groupEnd, 0);     // constants    scriptEngine->globalObject().setProperty("TREE_SCALE", scriptEngine->newValue(TREE_SCALE));     if (_context != NETWORKLESS_TEST_SCRIPT) {        // Scriptable cache access        auto resourcePrototype = createScriptableResourcePrototype(shared_from_this());        scriptEngine->globalObject().setProperty("Resource", resourcePrototype);        scriptEngine->setDefaultPrototype(qMetaTypeId<ScriptableResource*>(), resourcePrototype);         scriptEngine->registerGlobalObject("Assets", _assetScriptingInterface);        scriptEngine->registerGlobalObject("Resources", DependencyManager::get<ResourceScriptingInterface>().data());         scriptEngine->registerGlobalObject("DebugDraw", &DebugDraw::getInstance());         scriptEngine->registerGlobalObject("UserActivityLogger", DependencyManager::get<UserActivityLoggerScriptingInterface>().data());    } #if DEV_BUILD || PR_BUILD    scriptEngine->registerGlobalObject("StackTest", new StackTestScriptingInterface(this));#endif     qCDebug(scriptengine) << "Engine initialized";} // registers a global object by namevoid ScriptManager::registerValue(const QString& valueName, ScriptValue value) {    _engine->globalObject().setProperty(valueName, value);} // Unregister the handlers for this eventName and entityID.void ScriptManager::removeEventHandler(const EntityItemID& entityID, const QString& eventName, const ScriptValue& handler) {    if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING        qCDebug(scriptengine) << "*** WARNING *** ScriptManager::removeEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "            "entityID:" << entityID << " eventName:" << eventName;#endif        QMetaObject::invokeMethod(this, "removeEventHandler",                                  Q_ARG(const EntityItemID&, entityID),                                  Q_ARG(const QString&, eventName),                                  Q_ARG(const ScriptValue&, handler));        return;    }#ifdef THREAD_DEBUGGING    qCDebug(scriptengine) << "ScriptManager::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;#endif     if (!_registeredHandlers.contains(entityID)) {        return;    }    RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];    CallbackList& handlersForEvent = handlersOnEntity[eventName];    // ScriptValue does not have operator==(), so we can't use QList::removeOne and friends. So iterate.    for (int i = 0; i < handlersForEvent.count(); ++i) {        if (handlersForEvent[i].function.equals(handler)) {            handlersForEvent.removeAt(i);            return; // Design choice: since comparison is relatively expensive, just remove the first matching handler.        }    }} // Unregister all event handlers for the specified entityID (i.e. the entity is being removed)void ScriptManager::removeAllEventHandlers(const EntityItemID& entityID) {    if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING        qCDebug(scriptengine) << "*** WARNING *** ScriptManager::removeAllEventHandlers() called on wrong thread [" << QThread::currentThread() << ", correct thread is " << thread() << " ], ignoring "            "entityID:" << entityID;#endif        return;    }     if (_registeredHandlers.contains(entityID)) {        _registeredHandlers.remove(entityID);    }} // Register the handler.void ScriptManager::addEventHandler(const EntityItemID& entityID, const QString& eventName, const ScriptValue& handler) {    if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING        qCDebug(scriptengine) << "*** WARNING *** ScriptManager::addEventHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "        "entityID:" << entityID << " eventName:" << eventName;#endif         QMetaObject::invokeMethod(this, "addEventHandler",                                  Q_ARG(const EntityItemID&, entityID),                                  Q_ARG(const QString&, eventName),                                  Q_ARG(const ScriptValue&, handler));        return;    }#ifdef THREAD_DEBUGGING    qCDebug(scriptengine) << "ScriptManager::addEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;#endif     if (_registeredHandlers.count() == 0) {        // First time any per-entity handler has been added in this script...        emit attachDefaultEventHandlers();    }    if (!_registeredHandlers.contains(entityID)) {        _registeredHandlers[entityID] = RegisteredEventHandlers();    }    CallbackList& handlersForEvent = _registeredHandlers[entityID][eventName];    CallbackData handlerData = { handler, currentEntityIdentifier, currentSandboxURL };    handlersForEvent << handlerData; // Note that the same handler can be added many times. See removeEntityEventHandler().} bool ScriptManager::isStopped() const {    if (_context == NETWORKLESS_TEST_SCRIPT) {        return false;    }     QSharedPointer<ScriptEngines> scriptEngines(_scriptEngines);    return !scriptEngines || scriptEngines->isStopped();} void ScriptManager::run() {    if (QThread::currentThread() != qApp->thread() && _context == Context::CLIENT_SCRIPT) {        // Flag that we're allowed to access local HTML files on UI created from C++ calls on this thread        // (because we're a client script)        hifi::scripting::setLocalAccessSafeThread(true);    }     if (QThread::currentThread() != _assetScriptingInterface->thread()) {        _assetScriptingInterface->moveToThread(QThread::currentThread());    }     auto filenameParts = _fileNameString.split("/");    auto name = filenameParts.size() > 0 ? filenameParts[filenameParts.size() - 1] : "unknown";    PROFILE_SET_THREAD_NAME("Script: " + name);     if (isStopped()) {        qCCritical(scriptengine) << "ScriptManager is stopped or ScriptEngines is not available, refusing to run script";        return; // bail early - avoid setting state in init(), as evaluate() will bail too    }     scriptInfoMessage("Script Engine starting:" + getFilename(), getFilename(), -1);     if (!_isInitialized) {        init();    }     _isRunning = true;    emit runningStateChanged();     {        PROFILE_RANGE(script, _fileNameString);        _returnValue = _engine->evaluate(_scriptContents, _fileNameString);         if (_engine->hasUncaughtException() && _abortOnUncaughtException) {             qCWarning(scriptengine) << "Engine has uncaught exception, stopping";            stop();            // V8TODO: Is clearing needed here?            //_engine->clearExceptions();        }    }#ifdef _WIN32    // VS13 does not sleep_until unless it uses the system_clock, see:    // https://www.reddit.com/r/cpp_questions/comments/3o71ic/sleep_until_not_working_with_a_time_pointsteady/    using clock = std::chrono::system_clock;#else    using clock = std::chrono::high_resolution_clock;#endif     clock::time_point startTime = clock::now();    int thisFrame = 0;     _lastUpdate = usecTimestampNow();     std::chrono::microseconds totalUpdates(0);     qCDebug(scriptengine) << "Waiting for finish";     // TODO: Integrate this with signals/slots instead of reimplementing throttling for ScriptManager    while (!_isFinished) {        //qCDebug(scriptengine) << "In script event loop";         auto beforeSleep = clock::now();         // Throttle to SCRIPT_FPS        // We'd like to try to keep the script at a solid SCRIPT_FPS update rate. And so we will        // calculate a sleepUntil to be the time from our start time until the original target        // sleepUntil for this frame. This approach will allow us to "catch up" in the event        // that some of our script udpates/frames take a little bit longer than the target average        // to execute.        // NOTE: if we go to variable SCRIPT_FPS, then we will need to reconsider this approach        const std::chrono::microseconds TARGET_SCRIPT_FRAME_DURATION(USECS_PER_SECOND / SCRIPT_FPS + 1);        clock::time_point targetSleepUntil(startTime + (thisFrame++ * TARGET_SCRIPT_FRAME_DURATION));         // However, if our sleepUntil is not at least our average update and timer execution time        // into the future it means our script is taking too long in its updates, and we want to        // punish the script a little bit. So we will force the sleepUntil to be at least our        // averageUpdate + averageTimerPerFrame time into the future.        auto averageUpdate = totalUpdates / thisFrame;        auto averageTimerPerFrame = _totalTimerExecution / thisFrame;

Lines 501–1000 of 2763. Previous lines