diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 9af9618b92..c3045f599c 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -294,16 +294,15 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio tree->setEntityScriptSourceWhitelist(""); } - _entityEditFilters = new EntityEditFilters(); - - std::static_pointer_cast(tree)->setEntityEditFilters(_entityEditFilters); + auto entityEditFilters = tree->createEntityEditFilters(); + 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); + connect(entityEditFilters, &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); - _entityEditFilters->rejectAll(true); - _entityEditFilters->addFilter(EntityItemID(), filterURL); + entityEditFilters->rejectAll(true); + entityEditFilters->addFilter(EntityItemID(), filterURL); } } @@ -311,8 +310,10 @@ 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 + auto entityEditFilters = qobject_cast(sender()); if (success) { - _entityEditFilters->rejectAll(false); + qDebug() << "entity edit filter for " << id << "added successfully"; + entityEditFilters->rejectAll(false); } else { qDebug() << "entity edit filter unsuccessfully added, stopping..."; stop(); diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index 0edbc45ce1..325435fe7e 100644 --- a/assignment-client/src/entities/EntityServer.h +++ b/assignment-client/src/entities/EntityServer.h @@ -77,8 +77,6 @@ private: QReadWriteLock _viewerSendingStatsLock; QMap> _viewerSendingStats; - - EntityEditFilters* _entityEditFilters{}; }; #endif // hifi_EntityServer_h diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 2afcd93b63..dccb49d4f5 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -13,58 +13,99 @@ #include #include - #include "EntityEditFilters.h" +QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { + QList zones; + _lock.lockForRead(); + qCDebug(entities) << "looking at " << _filterFunctionMap.size() << "possible zones"; + for (auto it = _filterFunctionMap.begin(); it != _filterFunctionMap.end(); it++) { + auto id = it.key(); + if (!id.isInvalidID()) { + // for now, look it up in the tree (soon we need to cache or similar?) + EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); + auto zone = std::dynamic_pointer_cast(itemPtr); + if (zone && zone->containsPoint(position)) { + zones.append(id); + } + } else { + zones.append(id); + } + } + _lock.unlock(); + return zones; +} + 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) { + qCDebug(entities) << "rejecting all edits"; return false; } - bool accepted = true; + // get the ids of all the zones (plus the global entity edit filter) that the position + // lies within + auto zoneIDs = getZonesByPosition(position); - 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) { + // temp debugging -- remove! + qCDebug(entities) << "zones:"; + for (auto zoneID : zoneIDs) { + qCDebug(entities) << zoneID << ","; + } + + auto oldProperties = propertiesIn.getDesiredProperties(); + auto specifiedProperties = propertiesIn.getChangedProperties(); + propertiesIn.setDesiredProperties(specifiedProperties); + for (auto it = zoneIDs.begin(); it != zoneIDs.end(); it++) { + qCDebug(entities) << "applying filter for zone" << *it; - 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); + // get the filter pair, etc... + _lock.lockForRead(); + FilterFunctionPair* pair = _filterFunctionMap.value(*it); + QScriptEngine* engine = _filterScriptEngineMap.value(*it); + _lock.unlock(); + + qCDebug(entities) << "pair: " << (qint64) pair << ", engine" << (qint64)engine; + if (pair != nullptr && engine != nullptr) { - 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 inputValues = propertiesIn.copyToScriptValue(engine, false, true, true); + propertiesIn.setDesiredProperties(oldProperties); - 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; + 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(); + } + + + if (result.isObject()){ + qCDebug(entities) << "filter result accepted"; + // make propertiesIn reflect the changes, for next filter... + propertiesIn.copyFromScriptValue(result, false); + + // and update propertiesOut too. #TODO: this could be more efficient... + 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); + } else { + // an edit was rejected, so we stop here and return false + qCDebug(entities) << "Edit rejected by " << *it; + } } } - return accepted; + // if we made it here, + return true; } void EntityEditFilters::removeFilter(EntityItemID& entityID) { + QWriteLocker writeLock(&_lock); QScriptEngine* engine = _filterScriptEngineMap.value(entityID); if (engine) { _filterScriptEngineMap.remove(entityID); @@ -78,6 +119,7 @@ void EntityEditFilters::removeFilter(EntityItemID& entityID) { } 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) @@ -151,8 +193,10 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { engine->evaluate(scriptContents); if (!hadUncaughtExceptions(*engine, urlString)) { // put the engine in the engine map (so we don't leak them, etc...) + _lock.lockForWrite(); _filterScriptEngineMap.insert(entityID, engine); - + _lock.unlock(); + // define the uncaughtException function FilterFunctionPair* pair = new FilterFunctionPair(); QScriptEngine& engineRef = *engine; @@ -160,12 +204,20 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { // now get the filter function auto global = engine->globalObject(); + auto entitiesObject = engine->newObject(); + entitiesObject.setProperty("ADD_FILTER_TYPE", EntityTree::FilterType::Add); + entitiesObject.setProperty("EDIT_FILTER_TYPE", EntityTree::FilterType::Edit); + entitiesObject.setProperty("PHYSICS_FILTER_TYPE", EntityTree::FilterType::Physics); + global.setProperty("Entities", entitiesObject); pair->first = global.property("filter"); if (!pair->first.isFunction()) { qDebug() << "Filter function specified but not found. Ignoring filter"; return; } - _filterFunctionMap.insert(entityID, pair); + _lock.lockForWrite(); + _filterFunctionMap.insert(entityID, pair); + _lock.unlock(); + qDebug() << "script request filter processed for entity id " << entityID; emit filterAdded(entityID, true); diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h index ad6722819b..b4d31a54e8 100644 --- a/libraries/entities/src/EntityEditFilters.h +++ b/libraries/entities/src/EntityEditFilters.h @@ -29,6 +29,7 @@ class EntityEditFilters : public QObject { Q_OBJECT public: EntityEditFilters() {}; + EntityEditFilters(EntityTreePointer tree ): _tree(tree) {}; void addFilter(EntityItemID& entityID, QString filterURL); void removeFilter(EntityItemID& entityID); @@ -43,8 +44,13 @@ private slots: void scriptRequestFinished(EntityItemID entityID); private: + QList getZonesByPosition(glm::vec3& position); + + EntityTreePointer _tree {}; bool _rejectAll {false}; QScriptValue _nullObjectForFilter{}; + + QReadWriteLock _lock; QMap _filterFunctionMap; QMap _filterScriptEngineMap; }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 5e2fc0f1f9..6a2a52f958 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -62,9 +62,6 @@ EntityTree::EntityTree(bool shouldReaverage) : EntityTree::~EntityTree() { eraseAllOctreeElements(false); - if (_entityEditFilters) { - delete _entityEditFilters; - } } void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { @@ -1751,3 +1748,8 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { } return entity->getJointNames(); } + +EntityEditFilters* EntityTree::createEntityEditFilters() { + _entityEditFilters = new EntityEditFilters(getThisPointer()); + return _entityEditFilters; +} diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 34a52ce1b2..03e0f30b0b 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -275,7 +275,9 @@ public: void initEntityEditFilterEngine(QScriptEngine* engine, std::function entityEditFilterHadUncaughtExceptions); void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; } - void setEntityEditFilters(EntityEditFilters* entityEditFilters) { _entityEditFilters = entityEditFilters; } + EntityEditFilters* createEntityEditFilters(); + EntityEditFilters* getEntityEditFilters() { return _entityEditFilters; } + static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; public slots: diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 7de3f6b68f..61b3800b50 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -19,6 +19,9 @@ #include "EntityTree.h" #include "EntityTreeElement.h" #include "ZoneEntityItem.h" +#include "EntityEditFilters.h" + +#include bool ZoneEntityItem::_zonesArePickable = false; bool ZoneEntityItem::_drawZoneBoundaries = false; @@ -221,12 +224,24 @@ bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return _zonesArePickable; } +void ZoneEntityItem::setFilterURL(QString url) { + _filterURL = url; + if (getTree()) { + auto entityEditFilters = getTree()->getEntityEditFilters(); + if (entityEditFilters) { + qCDebug(entities) << "adding filter " << url << "for zone" << getEntityItemID(); + entityEditFilters->addFilter(getEntityItemID(), url); + } + } +} + bool ZoneEntityItem::containsPoint(glm::vec3& position) { // use _shapeType shortly // for now bounding box just so I can get end-to-end working bool success; AABox box = getAABox(success); if (success) { + qCDebug(entities) << "box: " << box; return box.contains(position); } // just return false if no AABox diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 07520001f7..f804ac8dbb 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -75,7 +75,7 @@ public: bool getGhostingAllowed() const { return _ghostingAllowed; } void setGhostingAllowed(bool value) { _ghostingAllowed = value; } QString getFilterURL() const { return _filterURL; } - void setFilterURL(const QString url) { _filterURL = url; } + void setFilterURL(const QString url); bool containsPoint(glm::vec3& position); virtual bool supportsDetailedRayIntersection() const override { return true; }