and the new filter class

This commit is contained in:
David Kelly 2017-02-08 10:51:28 -07:00
parent 4821180dd3
commit c08adc9faa
2 changed files with 184 additions and 0 deletions

View file

@ -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 <QUrl>
#include <ResourceManager.h>
#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<ResourceRequest*>(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.";
}
}

View file

@ -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 <QObject>
#include <QMap>
#include <QScriptValue>
#include <QScriptEngine>
#include <glm/glm.hpp>
#include <functional>
#include "EntityItemID.h"
#include "EntityItemProperties.h"
typedef QPair<QScriptValue, std::function<bool()>> 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<EntityItemID, FilterFunctionPair*> _filterFunctionMap;
QMap<EntityItemID, QScriptEngine*> _filterScriptEngineMap;
};
#endif //hifi_EntityEditFilters_h