Working like before

Single entity script running properly.  Now, need to add the zone
filters and execute them.
This commit is contained in:
David Kelly 2017-02-08 15:36:16 -07:00
parent c08adc9faa
commit ff7c9d3546
6 changed files with 112 additions and 138 deletions

View file

@ -15,6 +15,7 @@
#include <SimpleEntitySimulation.h>
#include <ResourceCache.h>
#include <ScriptCache.h>
#include <EntityEditFilters.h>
#include "EntityServer.h"
#include "EntityServerConsts.h"
@ -292,97 +293,33 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
} else {
tree->setEntityScriptSourceWhitelist("");
}
_entityEditFilters = new EntityEditFilters();
if (readOptionString("entityEditFilter", settingsSectionObject, _entityEditFilter) && !_entityEditFilter.isEmpty()) {
// Tell the tree that we have a filter, so that it doesn't accept edits until we have a filter function set up.
std::static_pointer_cast<EntityTree>(_tree)->setHasEntityFilter(true);
// Now fetch script from file asynchronously.
QUrl scriptURL(_entityEditFilter);
// 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();
return;
}
auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL);
if (!scriptRequest) {
qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString();
scriptRequestFinished();
return;
}
// Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own.
connect(scriptRequest, &ResourceRequest::finished, this, &EntityServer::scriptRequestFinished);
// FIXME: handle atp rquests setup here. See Agent::requestScript()
qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString());
scriptRequest->send();
qDebug() << "script request sent";
std::static_pointer_cast<EntityTree>(tree)->setEntityEditFilters(_entityEditFilters);
QString filterURL;
if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) {
// connect the filterAdded signal, and block edits until you hear back
connect(_entityEditFilters, &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded);
_entityEditFilters->rejectAll(true);
_entityEditFilters->addFilter(EntityItemID(), filterURL);
}
}
// 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;
void EntityServer::entityFilterAdded(EntityItemID id, bool success) {
if(id.isInvalidID()) {
// this is the domain-wide entity filter, which we want to stop
// the world for
if (success) {
_entityEditFilters->rejectAll(false);
} else {
qDebug() << "entity edit filter unsuccessfully added, stopping...";
stop();
}
}
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 EntityServer::scriptRequestFinished() {
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)) {
_entityEditFilterEngine.evaluate(scriptContents);
if (!hadUncaughtExceptions(_entityEditFilterEngine, urlString)) {
std::static_pointer_cast<EntityTree>(_tree)->initEntityEditFilterEngine(&_entityEditFilterEngine, [this]() {
return hadUncaughtExceptions(_entityEditFilterEngine, _entityEditFilter);
});
scriptRequest->deleteLater();
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.";
}
// Hard stop of the assignment client on failure. We don't want anyone to think they have a filter in place when they don't.
// Alas, only indications will be the above logging with assignment client restarting repeatedly, and clients will not see any entities.
qDebug() << "script request failure causing stop";
stop();
}
void EntityServer::nodeAdded(SharedNodePointer node) {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);

View file

@ -63,13 +63,13 @@ public slots:
virtual void nodeAdded(SharedNodePointer node) override;
virtual void nodeKilled(SharedNodePointer node) override;
void pruneDeletedEntities();
void entityFilterAdded(EntityItemID id, bool success);
protected:
virtual OctreePointer createTree() override;
private slots:
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
void scriptRequestFinished();
private:
SimpleEntitySimulationPointer _entitySimulation;
@ -77,9 +77,8 @@ private:
QReadWriteLock _viewerSendingStatsLock;
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
QString _entityEditFilter{};
QScriptEngine _entityEditFilterEngine{};
EntityEditFilters* _entityEditFilters{};
};
#endif // hifi_EntityServer_h

View file

@ -16,12 +16,55 @@
#include "EntityEditFilters.h"
QScriptValue EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd) {
qDebug() << "in EntityEditFilters";
return QScriptValue();
bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType) {
qCDebug(entities) << "in EntityEditFilters";
// allows us to start entity server and reject all edits until filter is setup
if (_rejectAll) {
return false;
}
bool accepted = true;
qCDebug(entities) << "_filterFunctionMap.size:" << _filterFunctionMap.size();
//first off lets call the filter (if any) that is domain-wide (EntityItemID())
FilterFunctionPair* pair = _filterFunctionMap.value(EntityItemID());
QScriptEngine* engine = _filterScriptEngineMap.value(EntityItemID());
qCDebug(entities) << "pair: " << (qint64) pair << ", engine" << (qint64)engine;
if (pair != nullptr && engine != nullptr) {
qCDebug(entities) << "attempting to call filter";
auto oldProperties = propertiesIn.getDesiredProperties();
auto specifiedProperties = propertiesIn.getChangedProperties();
propertiesIn.setDesiredProperties(specifiedProperties);
QScriptValue inputValues = propertiesIn.copyToScriptValue(engine, false, true, true);
propertiesIn.setDesiredProperties(oldProperties);
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
QScriptValueList args;
args << inputValues;
args << filterType;
QScriptValue result = pair->first.call(_nullObjectForFilter, args);
if (pair->second()) {
result = QScriptValue();
}
accepted = result.isObject();
if (accepted) {
qCDebug(entities) << "filter result accepted";
// call rest of them soon
propertiesOut.copyFromScriptValue(result, false);
// Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON.
auto out = QJsonValue::fromVariant(result.toVariant());
wasChanged = in != out;
}
}
return accepted;
}
void EntityEditFilters::removeEntityFilter(EntityItemID& entityID) {
void EntityEditFilters::removeFilter(EntityItemID& entityID) {
QScriptEngine* engine = _filterScriptEngineMap.value(entityID);
if (engine) {
_filterScriptEngineMap.remove(entityID);
@ -34,7 +77,7 @@ void EntityEditFilters::removeEntityFilter(EntityItemID& entityID) {
}
}
void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterURL) {
void EntityEditFilters::addFilter(EntityItemID& entityID, QString filterURL) {
QUrl scriptURL(filterURL);
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp)
@ -44,7 +87,7 @@ void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterUR
return;
}
// first remove any existing info for this entity
removeEntityFilter(entityID);
removeFilter(entityID);
auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL);
if (!scriptRequest) {
@ -53,11 +96,11 @@ void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterUR
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);} );
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";
qDebug() << "script request sent for entity " << entityID;
}
@ -94,8 +137,8 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName
return false;
}
void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) {
qDebug() << "script request completed";
void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
qDebug() << "script request completed for entity " << entityID;
auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
const QString urlString = scriptRequest->getUrl().toString();
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
@ -123,7 +166,9 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) {
return;
}
_filterFunctionMap.insert(entityID, pair);
qDebug() << "script request filter processed";
qDebug() << "script request filter processed for entity id " << entityID;
emit filterAdded(entityID, true);
return;
}
}
@ -134,5 +179,5 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) {
} else {
qCritical() << "Failed to create script request.";
}
emit filterAdded(entityID, false);
}

View file

@ -21,7 +21,7 @@
#include "EntityItemID.h"
#include "EntityItemProperties.h"
#include "EntityTree.h"
typedef QPair<QScriptValue, std::function<bool()>> FilterFunctionPair;
@ -30,15 +30,21 @@ class EntityEditFilters : public QObject {
public:
EntityEditFilters() {};
void addEntityFilter(EntityItemID& entityID, QString filterURL);
void removeEntityFilter(EntityItemID& entityID);
void addFilter(EntityItemID& entityID, QString filterURL);
void removeFilter(EntityItemID& entityID);
QScriptValue filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd);
bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType);
void rejectAll(bool state) {_rejectAll = state; }
signals:
void filterAdded(EntityItemID id, bool success);
private slots:
void scriptRequestFinished(EntityItemID& entityID);
void scriptRequestFinished(EntityItemID entityID);
private:
bool _rejectAll {false};
QScriptValue _nullObjectForFilter{};
QMap<EntityItemID, FilterFunctionPair*> _filterFunctionMap;
QMap<EntityItemID, QScriptEngine*> _filterScriptEngineMap;
};

View file

@ -24,6 +24,7 @@
#include "EntitiesLogging.h"
#include "RecurseOctreeToMapOperator.h"
#include "LogHandler.h"
#include "EntityEditFilters.h"
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50;
const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour
@ -61,6 +62,9 @@ EntityTree::EntityTree(bool shouldReaverage) :
EntityTree::~EntityTree() {
eraseAllOctreeElements(false);
if (_entityEditFilters) {
delete _entityEditFilters;
}
}
void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) {
@ -940,8 +944,8 @@ void EntityTree::initEntityEditFilterEngine(QScriptEngine* engine, std::function
_hasEntityEditFilter = true;
}
bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) {
if (!_entityEditFilterEngine) {
bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) {
if (!_entityEditFilterEngine && !_entityEditFilters) {
propertiesOut = propertiesIn;
wasChanged = false; // not changed
if (_hasEntityEditFilter) {
@ -950,28 +954,9 @@ bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItem
}
return true; // allowed
}
auto oldProperties = propertiesIn.getDesiredProperties();
auto specifiedProperties = propertiesIn.getChangedProperties();
propertiesIn.setDesiredProperties(specifiedProperties);
QScriptValue inputValues = propertiesIn.copyToScriptValue(_entityEditFilterEngine, false, true, true);
propertiesIn.setDesiredProperties(oldProperties);
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
QScriptValueList args;
args << inputValues;
args << filterType;
QScriptValue result = _entityEditFilterFunction.call(_nullObjectForFilter, args);
if (_entityEditFilterHadUncaughtExceptions()) {
result = QScriptValue();
}
bool accepted = result.isObject(); // filters should return null or false to completely reject edit or add
if (accepted) {
propertiesOut.copyFromScriptValue(result, false);
// Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON.
auto out = QJsonValue::fromVariant(result.toVariant());
wasChanged = in != out;
bool accepted = true;
if (_entityEditFilters) {
accepted = _entityEditFilters->filter(existingEntity->getPosition(), propertiesIn, propertiesOut, wasChanged, filterType);
}
return accepted;
@ -1076,11 +1061,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
// an existing entity... handle appropriately
if (validEditPacket) {
// search for the entity by EntityItemID
startLookup = usecTimestampNow();
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
endLookup = usecTimestampNow();
startFilter = usecTimestampNow();
bool wasChanged = false;
// Having (un)lock rights bypasses the filter, unless it's a physics result.
FilterType filterType = isPhysics ? FilterType::Physics : (isAdd ? FilterType::Add : FilterType::Edit);
bool allowed = (!isPhysics && senderNode->isAllowedEditor()) || filterProperties(properties, properties, wasChanged, filterType);
bool allowed = (!isPhysics && senderNode->isAllowedEditor()) || filterProperties(existingEntity, properties, properties, wasChanged, filterType);
if (!allowed) {
auto timestamp = properties.getLastEdited();
properties = EntityItemProperties();
@ -1093,10 +1083,6 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
}
endFilter = usecTimestampNow();
// search for the entity by EntityItemID
startLookup = usecTimestampNow();
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
endLookup = usecTimestampNow();
if (existingEntity && !isAdd) {
if (suppressDisallowedScript) {

View file

@ -24,8 +24,8 @@ typedef std::shared_ptr<EntityTree> EntityTreePointer;
#include "EntityTreeElement.h"
#include "DeleteEntityOperator.h"
#include "EntityEditFilters.h"
class EntityEditFilters;
class Model;
using ModelPointer = std::shared_ptr<Model>;
using ModelWeakPointer = std::weak_ptr<Model>;
@ -274,7 +274,8 @@ public:
void initEntityEditFilterEngine(QScriptEngine* engine, std::function<bool()> entityEditFilterHadUncaughtExceptions);
void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; }
void setEntityEditFilters(EntityEditFilters* entityEditFilters) { _entityEditFilters = entityEditFilters; }
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
public slots:
@ -363,7 +364,7 @@ protected:
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };
bool filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
bool _hasEntityEditFilter{ false };
QScriptEngine* _entityEditFilterEngine{};
QScriptValue _entityEditFilterFunction{};
@ -371,7 +372,7 @@ protected:
std::function<bool()> _entityEditFilterHadUncaughtExceptions;
QStringList _entityScriptSourceWhitelist;
EntityEditFilters* _entityEditFilters;
EntityEditFilters* _entityEditFilters{};
};
#endif // hifi_EntityTree_h