diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp new file mode 100644 index 0000000000..a74ba091b5 --- /dev/null +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -0,0 +1,138 @@ +// +// EntityEditFilters.cpp +// libraries/entities/src +// +// Created by David Kelly on 2/7/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#include + +#include + +#include "EntityEditFilters.h" + +QScriptValue EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd) { + qDebug() << "in EntityEditFilters"; + return QScriptValue(); +} + +void EntityEditFilters::removeEntityFilter(EntityItemID& entityID) { + QScriptEngine* engine = _filterScriptEngineMap.value(entityID); + if (engine) { + _filterScriptEngineMap.remove(entityID); + delete engine; + } + FilterFunctionPair* pair = _filterFunctionMap.value(entityID); + if (pair) { + _filterFunctionMap.remove(entityID); + delete pair; + } +} + +void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterURL) { + QUrl scriptURL(filterURL); + + // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) + if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { + qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; + scriptRequestFinished(entityID); + return; + } + // first remove any existing info for this entity + removeEntityFilter(entityID); + + auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL); + if (!scriptRequest) { + qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString(); + scriptRequestFinished(entityID); + return; + } + // Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own. + connect(scriptRequest, &ResourceRequest::finished, this, [this, &entityID]{ EntityEditFilters::scriptRequestFinished(entityID);} ); + // FIXME: handle atp rquests setup here. See Agent::requestScript() + qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString()); + scriptRequest->send(); + qDebug() << "script request sent"; + +} + +// Copied from ScriptEngine.cpp. We should make this a class method for reuse. +// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point. +static bool hasCorrectSyntax(const QScriptProgram& program) { + const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); + if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { + const auto error = syntaxCheck.errorMessage(); + const auto line = QString::number(syntaxCheck.errorLineNumber()); + const auto column = QString::number(syntaxCheck.errorColumnNumber()); + const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column); + qCritical() << qPrintable(message); + return false; + } + return true; +} +static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) { + if (engine.hasUncaughtException()) { + const auto backtrace = engine.uncaughtExceptionBacktrace(); + const auto exception = engine.uncaughtException().toString(); + const auto line = QString::number(engine.uncaughtExceptionLineNumber()); + engine.clearExceptions(); + + static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3"; + auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line); + if (!backtrace.empty()) { + static const auto lineSeparator = "\n "; + message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator)); + } + qCritical() << qPrintable(message); + return true; + } + return false; +} + +void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) { + qDebug() << "script request completed"; + auto scriptRequest = qobject_cast(sender()); + const QString urlString = scriptRequest->getUrl().toString(); + if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) { + auto scriptContents = scriptRequest->getData(); + qInfo() << "Downloaded script:" << scriptContents; + QScriptProgram program(scriptContents, urlString); + if (hasCorrectSyntax(program)) { + // create a QScriptEngine for this script + QScriptEngine* engine = new QScriptEngine(); + engine->evaluate(scriptContents); + if (!hadUncaughtExceptions(*engine, urlString)) { + // put the engine in the engine map (so we don't leak them, etc...) + _filterScriptEngineMap.insert(entityID, engine); + + // define the uncaughtException function + FilterFunctionPair* pair = new FilterFunctionPair(); + QScriptEngine& engineRef = *engine; + pair->second = [this, &engineRef, &urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; + + // now get the filter function + auto global = engine->globalObject(); + pair->first = global.property("filter"); + if (!pair->first.isFunction()) { + qDebug() << "Filter function specified but not found. Ignoring filter"; + return; + } + _filterFunctionMap.insert(entityID, pair); + qDebug() << "script request filter processed"; + return; + } + } + } else if (scriptRequest) { + qCritical() << "Failed to download script at" << urlString; + // See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure. + qCritical() << "ResourceRequest error was" << scriptRequest->getResult(); + } else { + qCritical() << "Failed to create script request."; + } + +} diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h new file mode 100644 index 0000000000..678b3ad0d1 --- /dev/null +++ b/libraries/entities/src/EntityEditFilters.h @@ -0,0 +1,46 @@ +// +// EntityEditFilters.h +// libraries/entities/src +// +// Created by David Kelly on 2/7/2017. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// +#ifndef hifi_EntityEditFilters_h +#define hifi_EntityEditFilters_h + +#include +#include +#include +#include +#include + +#include + +#include "EntityItemID.h" +#include "EntityItemProperties.h" + + +typedef QPair> FilterFunctionPair; + +class EntityEditFilters : public QObject { + Q_OBJECT +public: + EntityEditFilters() {}; + + void addEntityFilter(EntityItemID& entityID, QString filterURL); + void removeEntityFilter(EntityItemID& entityID); + + QScriptValue filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd); + +private slots: + void scriptRequestFinished(EntityItemID& entityID); + +private: + QMap _filterFunctionMap; + QMap _filterScriptEngineMap; +}; + +#endif //hifi_EntityEditFilters_h