1266 lines · 52.5 KB · C++
RawDownload
        }    } else {        globalObject().setProperty(name, setterFunction, QScriptValue::PropertySetter);        globalObject().setProperty(name, getterFunction, QScriptValue::PropertyGetter);    }} // Unregister the handlers for this eventName and entityID.void ScriptEngine::removeEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {    if (QThread::currentThread() != thread()) {        #ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::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(QScriptValue, handler));        return;    }    #ifdef THREAD_DEBUGGING    qDebug() << "ScriptEngine::removeEventHandler() called on thread [" << QThread::currentThread() << "] entityID:" << entityID << " eventName : " << eventName;    #endif     if (!_registeredHandlers.contains(entityID)) {        return;    }    RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];    QScriptValueList& handlersForEvent = handlersOnEntity[eventName];    // QScriptValue 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].equals(handler)) {            handlersForEvent.removeAt(i);            return; // Design choice: since comparison is relatively expensive, just remove the first matching handler.        }    }}// Register the handler.void ScriptEngine::addEventHandler(const EntityItemID& entityID, const QString& eventName, QScriptValue handler) {    if (QThread::currentThread() != thread()) {        #ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::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(QScriptValue, handler));        return;    }    #ifdef THREAD_DEBUGGING    qDebug() << "ScriptEngine::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...        // Connect up ALL the handlers to the global entities object's signals.        // (We could go signal by signal, or even handler by handler, but I don't think the efficiency is worth the complexity.)        auto entities = DependencyManager::get<EntityScriptingInterface>();        connect(entities.data(), &EntityScriptingInterface::deletingEntity, this, [this](const EntityItemID& entityID) {            _registeredHandlers.remove(entityID);        });                // Two common cases of event handler, differing only in argument signature.        using SingleEntityHandler = std::function<void(const EntityItemID&)>;        auto makeSingleEntityHandler = [this](QString eventName) -> SingleEntityHandler {            return [this, eventName](const EntityItemID& entityItemID) {                forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this) });            };        };                using MouseHandler = std::function<void(const EntityItemID&, const MouseEvent&)>;        auto makeMouseHandler = [this](QString eventName) -> MouseHandler {            return [this, eventName](const EntityItemID& entityItemID, const MouseEvent& event) {                forwardHandlerCall(entityItemID, eventName, { entityItemID.toScriptValue(this), event.toScriptValue(this) });            };        };                using CollisionHandler = std::function<void(const EntityItemID&, const EntityItemID&, const Collision&)>;        auto makeCollisionHandler = [this](QString eventName) -> CollisionHandler {            return [this, eventName](const EntityItemID& idA, const EntityItemID& idB, const Collision& collision) {                forwardHandlerCall(idA, eventName, { idA.toScriptValue(this), idB.toScriptValue(this),                                                     collisionToScriptValue(this, collision) });            };        };                connect(entities.data(), &EntityScriptingInterface::enterEntity, this, makeSingleEntityHandler("enterEntity"));        connect(entities.data(), &EntityScriptingInterface::leaveEntity, this, makeSingleEntityHandler("leaveEntity"));         connect(entities.data(), &EntityScriptingInterface::mousePressOnEntity, this, makeMouseHandler("mousePressOnEntity"));        connect(entities.data(), &EntityScriptingInterface::mouseMoveOnEntity, this, makeMouseHandler("mouseMoveOnEntity"));        connect(entities.data(), &EntityScriptingInterface::mouseReleaseOnEntity, this, makeMouseHandler("mouseReleaseOnEntity"));         connect(entities.data(), &EntityScriptingInterface::clickDownOnEntity, this, makeMouseHandler("clickDownOnEntity"));        connect(entities.data(), &EntityScriptingInterface::holdingClickOnEntity, this, makeMouseHandler("holdingClickOnEntity"));        connect(entities.data(), &EntityScriptingInterface::clickReleaseOnEntity, this, makeMouseHandler("clickReleaseOnEntity"));         connect(entities.data(), &EntityScriptingInterface::hoverEnterEntity, this, makeMouseHandler("hoverEnterEntity"));        connect(entities.data(), &EntityScriptingInterface::hoverOverEntity, this, makeMouseHandler("hoverOverEntity"));        connect(entities.data(), &EntityScriptingInterface::hoverLeaveEntity, this, makeMouseHandler("hoverLeaveEntity"));         connect(entities.data(), &EntityScriptingInterface::collisionWithEntity, this, makeCollisionHandler("collisionWithEntity"));    }    if (!_registeredHandlers.contains(entityID)) {        _registeredHandlers[entityID] = RegisteredEventHandlers();    }    QScriptValueList& handlersForEvent = _registeredHandlers[entityID][eventName];    handlersForEvent << handler; // Note that the same handler can be added many times. See removeEntityEventHandler().}  QScriptValue ScriptEngine::evaluate(const QString& sourceCode, const QString& fileName, int lineNumber) {    if (_stoppingAllScripts) {        return QScriptValue(); // bail early    }     if (QThread::currentThread() != thread()) {        QScriptValue result;        #ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::evaluate() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "] "            "sourceCode:" << sourceCode << " fileName:" << fileName << "lineNumber:" << lineNumber;        #endif        QMetaObject::invokeMethod(this, "evaluate", Qt::BlockingQueuedConnection,            Q_RETURN_ARG(QScriptValue, result),            Q_ARG(const QString&, sourceCode),            Q_ARG(const QString&, fileName),            Q_ARG(int, lineNumber));        return result;    }        // Check syntax    const QScriptProgram program(sourceCode, fileName, lineNumber);    if (!hasCorrectSyntax(program)) {        return QScriptValue();    }     ++_evaluatesPending;    const auto result = QScriptEngine::evaluate(program);    --_evaluatesPending;        const auto hadUncaughtException = hadUncaughtExceptions(*this, program.fileName());    if (_wantSignals) {        emit evaluationFinished(result, hadUncaughtException);    }    return result;} void ScriptEngine::run() {    // TODO: can we add a short circuit for _stoppingAllScripts here? What does it mean to not start running if    // we're in the process of stopping?     if (!_isInitialized) {        init();    }        _isRunning = true;    _isFinished = false;    if (_wantSignals) {        emit runningStateChanged();    }     QScriptValue result = evaluate(_scriptContents, _fileNameString);     QElapsedTimer startTime;    startTime.start();     int thisFrame = 0;     auto nodeList = DependencyManager::get<NodeList>();    auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();     qint64 lastUpdate = usecTimestampNow();     while (!_isFinished) {        int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec        if (usecToSleep > 0) {            usleep(usecToSleep);        }         if (_isFinished) {            break;        }         QCoreApplication::processEvents();         if (_isFinished) {            break;        }         if (!_isFinished && entityScriptingInterface->getEntityPacketSender()->serversExist()) {            // release the queue of edit entity messages.            entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();             // since we're in non-threaded mode, call process so that the packets are sent            if (!entityScriptingInterface->getEntityPacketSender()->isThreaded()) {                entityScriptingInterface->getEntityPacketSender()->process();            }        }         qint64 now = usecTimestampNow();        float deltaTime = (float) (now - lastUpdate) / (float) USECS_PER_SECOND;         if (!_isFinished) {            if (_wantSignals) {                emit update(deltaTime);            }        }        lastUpdate = now;         // Debug and clear exceptions        hadUncaughtExceptions(*this, _fileNameString);    }     stopAllTimers(); // make sure all our timers are stopped if the script is ending    if (_wantSignals) {        emit scriptEnding();    }     if (entityScriptingInterface->getEntityPacketSender()->serversExist()) {        // release the queue of edit entity messages.        entityScriptingInterface->getEntityPacketSender()->releaseQueuedMessages();         // since we're in non-threaded mode, call process so that the packets are sent        if (!entityScriptingInterface->getEntityPacketSender()->isThreaded()) {            // wait here till the edit packet sender is completely done sending            while (entityScriptingInterface->getEntityPacketSender()->hasPacketsToSend()) {                entityScriptingInterface->getEntityPacketSender()->process();                QCoreApplication::processEvents();            }        } else {            // FIXME - do we need to have a similar "wait here" loop for non-threaded packet senders?        }    }     if (_wantSignals) {        emit finished(_fileNameString);    }     _isRunning = false;    if (_wantSignals) {        emit runningStateChanged();        emit doneRunning();    }     _doneRunningThisScript = true;} // NOTE: This is private because it must be called on the same thread that created the timers, which is why// we want to only call it in our own run "shutdown" processing.void ScriptEngine::stopAllTimers() {    QMutableHashIterator<QTimer*, QScriptValue> i(_timerFunctionMap);    while (i.hasNext()) {        i.next();        QTimer* timer = i.key();        stopTimer(timer);    }} void ScriptEngine::stop() {    if (!_isFinished) {        _isFinished = true;        if (_wantSignals) {            emit runningStateChanged();        }    }} // Other threads can invoke this through invokeMethod, which causes the callback to be asynchronously executed in this script's thread.void ScriptEngine::callAnimationStateHandler(QScriptValue callback, AnimVariantMap parameters, QStringList names, bool useNames, AnimVariantResultHandler resultHandler) {    if (QThread::currentThread() != thread()) {#ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::callAnimationStateHandler() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]  name:" << name;#endif        QMetaObject::invokeMethod(this, "callAnimationStateHandler",                                  Q_ARG(QScriptValue, callback),                                  Q_ARG(AnimVariantMap, parameters),                                  Q_ARG(QStringList, names),                                  Q_ARG(bool, useNames),                                  Q_ARG(AnimVariantResultHandler, resultHandler));        return;    }    QScriptValue javascriptParameters = parameters.animVariantMapToScriptValue(this, names, useNames);    QScriptValueList callingArguments;    callingArguments << javascriptParameters;    QScriptValue result = callback.call(QScriptValue(), callingArguments);    resultHandler(result);} void ScriptEngine::timerFired() {    QTimer* callingTimer = reinterpret_cast<QTimer*>(sender());    QScriptValue timerFunction = _timerFunctionMap.value(callingTimer);     if (!callingTimer->isActive()) {        // this timer is done, we can kill it        _timerFunctionMap.remove(callingTimer);        delete callingTimer;    }     // call the associated JS function, if it exists    if (timerFunction.isValid()) {        timerFunction.call();    }} QObject* ScriptEngine::setupTimerWithInterval(const QScriptValue& function, int intervalMS, bool isSingleShot) {    // create the timer, add it to the map, and start it    QTimer* newTimer = new QTimer(this);    newTimer->setSingleShot(isSingleShot);     connect(newTimer, &QTimer::timeout, this, &ScriptEngine::timerFired);     // make sure the timer stops when the script does    connect(this, &ScriptEngine::scriptEnding, newTimer, &QTimer::stop);     _timerFunctionMap.insert(newTimer, function);     newTimer->start(intervalMS);    return newTimer;} QObject* ScriptEngine::setInterval(const QScriptValue& function, int intervalMS) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.setInterval() while shutting down is ignored... parent script:" << getFilename();        return NULL; // bail early    }     return setupTimerWithInterval(function, intervalMS, false);} QObject* ScriptEngine::setTimeout(const QScriptValue& function, int timeoutMS) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.setTimeout() while shutting down is ignored... parent script:" << getFilename();        return NULL; // bail early    }     return setupTimerWithInterval(function, timeoutMS, true);} void ScriptEngine::stopTimer(QTimer *timer) {    if (_timerFunctionMap.contains(timer)) {        timer->stop();        _timerFunctionMap.remove(timer);        delete timer;    }} QUrl ScriptEngine::resolvePath(const QString& include) const {    QUrl url(include);    // first lets check to see if it's already a full URL    if (!url.scheme().isEmpty()) {        return url;    }     // we apparently weren't a fully qualified url, so, let's assume we're relative    // to the original URL of our script    QUrl parentURL;    if (_parentURL.isEmpty()) {        parentURL = QUrl(_fileNameString);    } else {        parentURL = QUrl(_parentURL);    }    // if the parent URL's scheme is empty, then this is probably a local file...    if (parentURL.scheme().isEmpty()) {        parentURL = QUrl::fromLocalFile(_fileNameString);    }     // at this point we should have a legitimate fully qualified URL for our parent    url = parentURL.resolved(url);    return url;} void ScriptEngine::print(const QString& message) {    if (_wantSignals) {        emit printedMessage(message);    }} // If a callback is specified, the included files will be loaded asynchronously and the callback will be called// when all of the files have finished loading.// If no callback is specified, the included files will be loaded synchronously and will block execution until// all of the files have finished loading.void ScriptEngine::include(const QStringList& includeFiles, QScriptValue callback) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.include() while shutting down is ignored..."                 << "includeFiles:" << includeFiles << "parent script:" << getFilename();        return; // bail early    }    QList<QUrl> urls;    for (QString file : includeFiles) {        QUrl thisURL { resolvePath(file) };        if (!_includedURLs.contains(thisURL)) {            urls.append(thisURL);            _includedURLs << thisURL;        }        else {            qCDebug(scriptengine) << "Script.include() ignoring previously included url:" << thisURL;        }    }     BatchLoader* loader = new BatchLoader(urls);     auto evaluateScripts = [=](const QMap<QUrl, QString>& data) {        for (QUrl url : urls) {            QString contents = data[url];            if (contents.isNull()) {                qCDebug(scriptengine) << "Error loading file: " << url << "line:" << __LINE__;            } else {                QScriptValue result = evaluate(contents, url.toString());            }        }         if (callback.isFunction()) {            QScriptValue(callback).call();        }         loader->deleteLater();    };     connect(loader, &BatchLoader::finished, this, evaluateScripts);     // If we are destroyed before the loader completes, make sure to clean it up    connect(this, &QObject::destroyed, loader, &QObject::deleteLater);     loader->start();     if (!callback.isFunction() && !loader->isFinished()) {        QEventLoop loop;        QObject::connect(loader, &BatchLoader::finished, &loop, &QEventLoop::quit);        loop.exec();    }} void ScriptEngine::include(const QString& includeFile, QScriptValue callback) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.include() while shutting down is ignored... "                 << "includeFile:" << includeFile << "parent script:" << getFilename();        return; // bail early    }     QStringList urls;    urls.append(includeFile);    include(urls, callback);} // NOTE: The load() command is similar to the include() command except that it loads the script// as a stand-alone script. To accomplish this, the ScriptEngine class just emits a signal which// the Application or other context will connect to in order to know to actually load the scriptvoid ScriptEngine::load(const QString& loadFile) {    if (_stoppingAllScripts) {        qCDebug(scriptengine) << "Script.load() while shutting down is ignored... "                 << "loadFile:" << loadFile << "parent script:" << getFilename();        return; // bail early    }     QUrl url = resolvePath(loadFile);    if (_isReloading) {        auto scriptCache = DependencyManager::get<ScriptCache>();        scriptCache->deleteScript(url.toString());        if (_wantSignals) {            emit reloadScript(url.toString(), false);        }    } else {        if (_wantSignals) {            emit loadScript(url.toString(), false);        }    }} // Look up the handler associated with eventName and entityID. If found, evalute the argGenerator thunk and call the handler with those argsvoid ScriptEngine::forwardHandlerCall(const EntityItemID& entityID, const QString& eventName, QScriptValueList eventHanderArgs) {    if (QThread::currentThread() != thread()) {        qDebug() << "*** ERROR *** ScriptEngine::forwardHandlerCall() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]";        assert(false);        return ;    }    if (!_registeredHandlers.contains(entityID)) {        return;    }    const RegisteredEventHandlers& handlersOnEntity = _registeredHandlers[entityID];    if (!handlersOnEntity.contains(eventName)) {        return;    }    QScriptValueList handlersForEvent = handlersOnEntity[eventName];    if (!handlersForEvent.isEmpty()) {        for (int i = 0; i < handlersForEvent.count(); ++i) {            handlersForEvent[i].call(QScriptValue(), eventHanderArgs);        }    }} // since all of these operations can be asynch we will always do the actual work in the response handler// for the downloadvoid ScriptEngine::loadEntityScript(const EntityItemID& entityID, const QString& entityScript, bool forceRedownload) {    if (QThread::currentThread() != thread()) {        #ifdef THREAD_DEBUGGING        qDebug() << "*** WARNING *** ScriptEngine::loadEntityScript() called on wrong thread [" << QThread::currentThread() << "], invoking on correct thread [" << thread() << "]  "            "entityID:" << entityID << "entityScript:" << entityScript <<"forceRedownload:" << forceRedownload;        #endif         QMetaObject::invokeMethod(this, "loadEntityScript",

Lines 501–1000 of 1266. Previous lines