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 <SimpleEntitySimulation.h>
#include <ResourceCache.h> #include <ResourceCache.h>
#include <ScriptCache.h> #include <ScriptCache.h>
#include <EntityEditFilters.h>
#include "EntityServer.h" #include "EntityServer.h"
#include "EntityServerConsts.h" #include "EntityServerConsts.h"
@ -292,97 +293,33 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
} else { } else {
tree->setEntityScriptSourceWhitelist(""); tree->setEntityScriptSourceWhitelist("");
} }
_entityEditFilters = new EntityEditFilters();
if (readOptionString("entityEditFilter", settingsSectionObject, _entityEditFilter) && !_entityEditFilter.isEmpty()) { std::static_pointer_cast<EntityTree>(tree)->setEntityEditFilters(_entityEditFilters);
// Tell the tree that we have a filter, so that it doesn't accept edits until we have a filter function set up. QString filterURL;
std::static_pointer_cast<EntityTree>(_tree)->setHasEntityFilter(true); if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) {
// Now fetch script from file asynchronously. // connect the filterAdded signal, and block edits until you hear back
QUrl scriptURL(_entityEditFilter); connect(_entityEditFilters, &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded);
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) _entityEditFilters->rejectAll(true);
if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { _entityEditFilters->addFilter(EntityItemID(), filterURL);
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";
} }
} }
// Copied from ScriptEngine.cpp. We should make this a class method for reuse. void EntityServer::entityFilterAdded(EntityItemID id, bool success) {
// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point. if(id.isInvalidID()) {
static bool hasCorrectSyntax(const QScriptProgram& program) { // this is the domain-wide entity filter, which we want to stop
const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode()); // the world for
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) { if (success) {
const auto error = syntaxCheck.errorMessage(); _entityEditFilters->rejectAll(false);
const auto line = QString::number(syntaxCheck.errorLineNumber()); } else {
const auto column = QString::number(syntaxCheck.errorColumnNumber()); qDebug() << "entity edit filter unsuccessfully added, stopping...";
const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column); stop();
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 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) { void EntityServer::nodeAdded(SharedNodePointer node) {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);

View file

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

View file

@ -16,12 +16,55 @@
#include "EntityEditFilters.h" #include "EntityEditFilters.h"
QScriptValue EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd) { bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType) {
qDebug() << "in EntityEditFilters"; qCDebug(entities) << "in EntityEditFilters";
return QScriptValue();
// 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); QScriptEngine* engine = _filterScriptEngineMap.value(entityID);
if (engine) { if (engine) {
_filterScriptEngineMap.remove(entityID); _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); QUrl scriptURL(filterURL);
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) // 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; return;
} }
// first remove any existing info for this entity // first remove any existing info for this entity
removeEntityFilter(entityID); removeFilter(entityID);
auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL); auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL);
if (!scriptRequest) { if (!scriptRequest) {
@ -53,11 +96,11 @@ void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterUR
return; return;
} }
// Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own. // 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() // FIXME: handle atp rquests setup here. See Agent::requestScript()
qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString()); qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString());
scriptRequest->send(); 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; return false;
} }
void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) { void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
qDebug() << "script request completed"; qDebug() << "script request completed for entity " << entityID;
auto scriptRequest = qobject_cast<ResourceRequest*>(sender()); auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
const QString urlString = scriptRequest->getUrl().toString(); const QString urlString = scriptRequest->getUrl().toString();
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) { if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
@ -123,7 +166,9 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) {
return; return;
} }
_filterFunctionMap.insert(entityID, pair); _filterFunctionMap.insert(entityID, pair);
qDebug() << "script request filter processed"; qDebug() << "script request filter processed for entity id " << entityID;
emit filterAdded(entityID, true);
return; return;
} }
} }
@ -134,5 +179,5 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) {
} else { } else {
qCritical() << "Failed to create script request."; qCritical() << "Failed to create script request.";
} }
emit filterAdded(entityID, false);
} }

View file

@ -21,7 +21,7 @@
#include "EntityItemID.h" #include "EntityItemID.h"
#include "EntityItemProperties.h" #include "EntityItemProperties.h"
#include "EntityTree.h"
typedef QPair<QScriptValue, std::function<bool()>> FilterFunctionPair; typedef QPair<QScriptValue, std::function<bool()>> FilterFunctionPair;
@ -30,15 +30,21 @@ class EntityEditFilters : public QObject {
public: public:
EntityEditFilters() {}; EntityEditFilters() {};
void addEntityFilter(EntityItemID& entityID, QString filterURL); void addFilter(EntityItemID& entityID, QString filterURL);
void removeEntityFilter(EntityItemID& entityID); 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: private slots:
void scriptRequestFinished(EntityItemID& entityID); void scriptRequestFinished(EntityItemID entityID);
private: private:
bool _rejectAll {false};
QScriptValue _nullObjectForFilter{};
QMap<EntityItemID, FilterFunctionPair*> _filterFunctionMap; QMap<EntityItemID, FilterFunctionPair*> _filterFunctionMap;
QMap<EntityItemID, QScriptEngine*> _filterScriptEngineMap; QMap<EntityItemID, QScriptEngine*> _filterScriptEngineMap;
}; };

View file

@ -24,6 +24,7 @@
#include "EntitiesLogging.h" #include "EntitiesLogging.h"
#include "RecurseOctreeToMapOperator.h" #include "RecurseOctreeToMapOperator.h"
#include "LogHandler.h" #include "LogHandler.h"
#include "EntityEditFilters.h"
static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; 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 const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour
@ -61,6 +62,9 @@ EntityTree::EntityTree(bool shouldReaverage) :
EntityTree::~EntityTree() { EntityTree::~EntityTree() {
eraseAllOctreeElements(false); eraseAllOctreeElements(false);
if (_entityEditFilters) {
delete _entityEditFilters;
}
} }
void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) {
@ -940,8 +944,8 @@ void EntityTree::initEntityEditFilterEngine(QScriptEngine* engine, std::function
_hasEntityEditFilter = true; _hasEntityEditFilter = true;
} }
bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) { bool EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) {
if (!_entityEditFilterEngine) { if (!_entityEditFilterEngine && !_entityEditFilters) {
propertiesOut = propertiesIn; propertiesOut = propertiesIn;
wasChanged = false; // not changed wasChanged = false; // not changed
if (_hasEntityEditFilter) { if (_hasEntityEditFilter) {
@ -950,28 +954,9 @@ bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItem
} }
return true; // allowed return true; // allowed
} }
auto oldProperties = propertiesIn.getDesiredProperties(); bool accepted = true;
auto specifiedProperties = propertiesIn.getChangedProperties(); if (_entityEditFilters) {
propertiesIn.setDesiredProperties(specifiedProperties); accepted = _entityEditFilters->filter(existingEntity->getPosition(), propertiesIn, propertiesOut, wasChanged, filterType);
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;
} }
return accepted; return accepted;
@ -1076,11 +1061,16 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
// an existing entity... handle appropriately // an existing entity... handle appropriately
if (validEditPacket) { if (validEditPacket) {
// search for the entity by EntityItemID
startLookup = usecTimestampNow();
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
endLookup = usecTimestampNow();
startFilter = usecTimestampNow(); startFilter = usecTimestampNow();
bool wasChanged = false; bool wasChanged = false;
// Having (un)lock rights bypasses the filter, unless it's a physics result. // Having (un)lock rights bypasses the filter, unless it's a physics result.
FilterType filterType = isPhysics ? FilterType::Physics : (isAdd ? FilterType::Add : FilterType::Edit); 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) { if (!allowed) {
auto timestamp = properties.getLastEdited(); auto timestamp = properties.getLastEdited();
properties = EntityItemProperties(); properties = EntityItemProperties();
@ -1093,10 +1083,6 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c
} }
endFilter = usecTimestampNow(); endFilter = usecTimestampNow();
// search for the entity by EntityItemID
startLookup = usecTimestampNow();
EntityItemPointer existingEntity = findEntityByEntityItemID(entityItemID);
endLookup = usecTimestampNow();
if (existingEntity && !isAdd) { if (existingEntity && !isAdd) {
if (suppressDisallowedScript) { if (suppressDisallowedScript) {

View file

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