From ff7c9d354694e6b74dd9d10d82e7b4b3c85368c1 Mon Sep 17 00:00:00 2001 From: David Kelly Date: Wed, 8 Feb 2017 15:36:16 -0700 Subject: [PATCH] Working like before Single entity script running properly. Now, need to add the zone filters and execute them. --- .../src/entities/EntityServer.cpp | 105 ++++-------------- assignment-client/src/entities/EntityServer.h | 7 +- libraries/entities/src/EntityEditFilters.cpp | 69 ++++++++++-- libraries/entities/src/EntityEditFilters.h | 16 ++- libraries/entities/src/EntityTree.cpp | 44 +++----- libraries/entities/src/EntityTree.h | 9 +- 6 files changed, 112 insertions(+), 138 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 02dc552dae..9af9618b92 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #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(_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(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(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(_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(_tree); diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index f142145d5f..0edbc45ce1 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -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 message, SharedNodePointer senderNode); - void scriptRequestFinished(); private: SimpleEntitySimulationPointer _entitySimulation; @@ -77,9 +77,8 @@ private: QReadWriteLock _viewerSendingStatsLock; QMap> _viewerSendingStats; - - QString _entityEditFilter{}; - QScriptEngine _entityEditFilterEngine{}; + + EntityEditFilters* _entityEditFilters{}; }; #endif // hifi_EntityServer_h diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index a74ba091b5..2afcd93b63 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -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(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); } diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index 678b3ad0d1..ad6722819b 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -21,7 +21,7 @@ #include "EntityItemID.h" #include "EntityItemProperties.h" - +#include "EntityTree.h" typedef QPair> 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 _filterFunctionMap; QMap _filterScriptEngineMap; }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 4e92b2a572..5e2fc0f1f9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -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) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 7d7948844e..34a52ce1b2 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -24,8 +24,8 @@ typedef std::shared_ptr EntityTreePointer; #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" -#include "EntityEditFilters.h" +class EntityEditFilters; class Model; using ModelPointer = std::shared_ptr; using ModelWeakPointer = std::weak_ptr; @@ -274,7 +274,8 @@ public: void initEntityEditFilterEngine(QScriptEngine* engine, std::function 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 _entityEditFilterHadUncaughtExceptions; QStringList _entityScriptSourceWhitelist; - EntityEditFilters* _entityEditFilters; + EntityEditFilters* _entityEditFilters{}; }; #endif // hifi_EntityTree_h