diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 87eb5ee1ab..fe43e6f730 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -548,9 +548,6 @@ AudioMixerClientData::IgnoreZone& AudioMixerClientData::IgnoreZoneMemo::get(unsi _zone = box; unsigned int oldFrame = _frame.exchange(frame, std::memory_order_release); Q_UNUSED(oldFrame); - - // check the precondition - assert(oldFrame == 0 || frame == (oldFrame + 1)); } } diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 1047e10570..8d76cda2f1 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -44,7 +44,7 @@ public: AvatarAudioStream* getAvatarAudioStream(); // returns whether self (this data's node) should ignore node, memoized by frame - // precondition: frame is monotonically increasing after first call + // precondition: frame is increasing after first call (including overflow wrap) bool shouldIgnore(SharedNodePointer self, SharedNodePointer node, unsigned int frame); // the following methods should be called from the AudioMixer assignment thread ONLY @@ -131,7 +131,7 @@ private: // returns an ignore zone, memoized by frame (lockless if the zone is already memoized) // preconditions: - // - frame is monotonically increasing after first call + // - frame is increasing after first call (including overflow wrap) // - there are no references left from calls to getIgnoreZone(frame - 1) IgnoreZone& get(unsigned int frame); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 02dc552dae..425bea2c38 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" @@ -71,6 +72,7 @@ OctreePointer EntityServer::createTree() { DependencyManager::registerInheritance(); DependencyManager::set(tree); + DependencyManager::set(std::static_pointer_cast(tree)); return tree; } @@ -292,96 +294,26 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio } else { tree->setEntityScriptSourceWhitelist(""); } - - 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"; + + auto entityEditFilters = DependencyManager::get(); + + QString filterURL; + if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) { + // connect the filterAdded signal, and block edits until you hear back + connect(entityEditFilters.data(), &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded); + + 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; - } - 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)); +void EntityServer::entityFilterAdded(EntityItemID id, bool success) { + if (id.isInvalidID()) { + if (success) { + qDebug() << "entity edit filter for " << id << "added successfully"; + } else { + qDebug() << "entity edit filter unsuccessfully added, all edits will be rejected for those without lock rights."; } - 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) { diff --git a/assignment-client/src/entities/EntityServer.h b/assignment-client/src/entities/EntityServer.h index f142145d5f..325435fe7e 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,6 @@ private: QReadWriteLock _viewerSendingStatsLock; QMap> _viewerSendingStats; - - QString _entityEditFilter{}; - QScriptEngine _entityEditFilterEngine{}; }; #endif // hifi_EntityServer_h diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 746e599d4e..2ce537a5a0 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -20,7 +20,7 @@ endif () symlink_or_copy_directory_beside_target(${_SHOULD_SYMLINK_RESOURCES} "${CMAKE_CURRENT_SOURCE_DIR}/resources" "resources") # link the shared hifi libraries -link_hifi_libraries(embedded-webserver networking shared) +link_hifi_libraries(embedded-webserver networking shared avatars) # find OpenSSL find_package(OpenSSL REQUIRED) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 7e49dc55a4..23f663817d 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -29,7 +29,7 @@ #include #include #include - +#include //for KillAvatarReason #include "DomainServerNodeData.h" const QString SETTINGS_DESCRIPTION_RELATIVE_PATH = "/resources/describe-settings.json"; @@ -728,6 +728,13 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointerwrite(nodeUUID.toRfc4122()); + packet->writePrimitive(KillAvatarReason::NoReason); + + // send to avatar mixer, it sends the kill to everyone else + limitedNodeList->broadcastToNodes(std::move(packet), NodeSet() << NodeType::AvatarMixer); if (newPermissions) { qDebug() << "Removing connect permission for node" << uuidStringWithoutCurlyBraces(matchingNode->getUUID()) @@ -735,9 +742,12 @@ void DomainServerSettingsManager::processNodeKickRequestPacket(QSharedPointergetJointTransform(cluster.jointIndex); -#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS) +#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); state.clusterMatrices[j] = out; @@ -155,7 +155,7 @@ void CauterizedModel::updateClusterMatrices() { if (_cauterizeBoneSet.find(cluster.jointIndex) != _cauterizeBoneSet.end()) { jointMatrix = cauterizeMatrix; } -#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS) +#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); state.clusterMatrices[j] = out; diff --git a/interface/src/avatar/SoftAttachmentModel.cpp b/interface/src/avatar/SoftAttachmentModel.cpp index da7ca0b87d..6ed54afb27 100644 --- a/interface/src/avatar/SoftAttachmentModel.cpp +++ b/interface/src/avatar/SoftAttachmentModel.cpp @@ -60,7 +60,7 @@ void SoftAttachmentModel::updateClusterMatrices() { } else { jointMatrix = _rig->getJointTransform(cluster.jointIndex); } -#if GLM_ARCH & GLM_ARCH_SSE2 +#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); state.clusterMatrices[j] = out; diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index ba97f70518..5638cacabc 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -50,7 +50,7 @@ glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const { } AnimPose AnimPose::operator*(const AnimPose& rhs) const { -#if GLM_ARCH & GLM_ARCH_SSE2 +#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) glm::mat4 result; glm::mat4 lhsMat = *this; glm::mat4 rhsMat = rhs; diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp new file mode 100644 index 0000000000..d62495d95e --- /dev/null +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -0,0 +1,237 @@ +// +// 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 + +#include +#include "EntityEditFilters.h" + +QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { + QList zones; + QList missingZones; + _lock.lockForRead(); + auto zoneIDs = _filterDataMap.keys(); + _lock.unlock(); + for (auto id : zoneIDs) { + 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) { + // TODO: maybe remove later? + removeFilter(id); + } else if (zone->contains(position)) { + zones.append(id); + } + } else { + // the null id is the global filter we put in the domain server's + // advanced entity server settings + zones.append(id); + } + } + return zones; +} + +bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, + EntityTree::FilterType filterType, EntityItemID& itemID) { + + // get the ids of all the zones (plus the global entity edit filter) that the position + // lies within + auto zoneIDs = getZonesByPosition(position); + for (auto id : zoneIDs) { + if (!itemID.isInvalidID() && id == itemID) { + continue; + } + + // get the filter pair, etc... + _lock.lockForRead(); + FilterData filterData = _filterDataMap.value(id); + _lock.unlock(); + + if (filterData.valid()) { + if (filterData.rejectAll) { + return false; + } + auto oldProperties = propertiesIn.getDesiredProperties(); + auto specifiedProperties = propertiesIn.getChangedProperties(); + propertiesIn.setDesiredProperties(specifiedProperties); + QScriptValue inputValues = propertiesIn.copyToScriptValue(filterData.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 = filterData.filterFn.call(_nullObjectForFilter, args); + if (filterData.uncaughtExceptions()) { + return false; + } + + if (result.isObject()){ + // 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 { + return false; + } + } + } + // if we made it here, + return true; +} + +void EntityEditFilters::removeFilter(EntityItemID entityID) { + QWriteLocker writeLock(&_lock); + FilterData filterData = _filterDataMap.value(entityID); + if (filterData.valid()) { + delete filterData.engine; + } + _filterDataMap.remove(entityID); +} + +void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { + + QUrl scriptURL(filterURL); + + // setting it to an empty string is same as removing + if (filterURL.size() == 0) { + removeFilter(entityID); + return; + } + + // 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 + removeFilter(entityID); + + // reject all edits until we load the script + FilterData filterData; + filterData.rejectAll = true; + + _lock.lockForWrite(); + _filterDataMap.insert(entityID, filterData); + _lock.unlock(); + + 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 for entity " << entityID; +} + +// 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 for entity " << entityID; + 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)) { + // 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...) + FilterData filterData; + filterData.engine = engine; + filterData.rejectAll = false; + + // define the uncaughtException function + QScriptEngine& engineRef = *engine; + filterData.uncaughtExceptions = [this, &engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; + + // 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); + filterData.filterFn = global.property("filter"); + if (!filterData.filterFn.isFunction()) { + qDebug() << "Filter function specified but not found. Will reject all edits for those without lock rights."; + delete engine; + filterData.rejectAll=true; + } + + + _lock.lockForWrite(); + _filterDataMap.insert(entityID, filterData); + _lock.unlock(); + + qDebug() << "script request filter processed for entity id " << entityID; + + emit filterAdded(entityID, true); + 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."; + } + emit filterAdded(entityID, false); +} diff --git a/libraries/entities/src/EntityEditFilters.h b/libraries/entities/src/EntityEditFilters.h new file mode 100644 index 0000000000..6aeb347603 --- /dev/null +++ b/libraries/entities/src/EntityEditFilters.h @@ -0,0 +1,65 @@ +// +// 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 +#include +#include +#include +#include + +#include + +#include "EntityItemID.h" +#include "EntityItemProperties.h" +#include "EntityTree.h" + +class EntityEditFilters : public QObject, public Dependency { + Q_OBJECT +public: + struct FilterData { + QScriptValue filterFn; + std::function uncaughtExceptions; + QScriptEngine* engine; + bool rejectAll; + + FilterData(): engine(nullptr), rejectAll(false) {}; + bool valid() { return (rejectAll || (engine != nullptr && filterFn.isFunction() && uncaughtExceptions)); } + }; + + EntityEditFilters() {}; + EntityEditFilters(EntityTreePointer tree ): _tree(tree) {}; + + void addFilter(EntityItemID entityID, QString filterURL); + void removeFilter(EntityItemID entityID); + + bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, + EntityTree::FilterType filterType, EntityItemID& entityID); + +signals: + void filterAdded(EntityItemID id, bool success); + +private slots: + void scriptRequestFinished(EntityItemID entityID); + +private: + QList getZonesByPosition(glm::vec3& position); + + EntityTreePointer _tree {}; + bool _rejectAll {false}; + QScriptValue _nullObjectForFilter{}; + + QReadWriteLock _lock; + QMap _filterDataMap; +}; + +#endif //hifi_EntityEditFilters_h diff --git a/libraries/entities/src/EntityItemID.cpp b/libraries/entities/src/EntityItemID.cpp index 1462a4ef88..5f07019db4 100644 --- a/libraries/entities/src/EntityItemID.cpp +++ b/libraries/entities/src/EntityItemID.cpp @@ -19,6 +19,7 @@ #include "RegisteredMetaTypes.h" #include "EntityItemID.h" +int entityItemIDTypeID = qRegisterMetaType(); EntityItemID::EntityItemID() : QUuid() { diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 21018d8afa..ea81df3801 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -332,6 +332,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_FLYING_ALLOWED, flyingAllowed); CHECK_PROPERTY_CHANGE(PROP_GHOSTING_ALLOWED, ghostingAllowed); + CHECK_PROPERTY_CHANGE(PROP_FILTER_URL, filterURL); CHECK_PROPERTY_CHANGE(PROP_CLIENT_ONLY, clientOnly); CHECK_PROPERTY_CHANGE(PROP_OWNING_AVATAR_ID, owningAvatarID); @@ -509,6 +510,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FLYING_ALLOWED, flyingAllowed); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GHOSTING_ALLOWED, ghostingAllowed); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_FILTER_URL, filterURL); } // Web only @@ -751,6 +753,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(flyingAllowed, bool, setFlyingAllowed); COPY_PROPERTY_FROM_QSCRIPTVALUE(ghostingAllowed, bool, setGhostingAllowed); + COPY_PROPERTY_FROM_QSCRIPTVALUE(filterURL, QString, setFilterURL); COPY_PROPERTY_FROM_QSCRIPTVALUE(clientOnly, bool, setClientOnly); COPY_PROPERTY_FROM_QSCRIPTVALUE(owningAvatarID, QUuid, setOwningAvatarID); @@ -879,6 +882,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(flyingAllowed); COPY_PROPERTY_IF_CHANGED(ghostingAllowed); + COPY_PROPERTY_IF_CHANGED(filterURL); COPY_PROPERTY_IF_CHANGED(clientOnly); COPY_PROPERTY_IF_CHANGED(owningAvatarID); @@ -1063,6 +1067,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue ADD_PROPERTY_TO_MAP(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool); ADD_PROPERTY_TO_MAP(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool); + ADD_PROPERTY_TO_MAP(PROP_FILTER_URL, FilterURL, filterURL, QString); ADD_PROPERTY_TO_MAP(PROP_DPI, DPI, dpi, uint16_t); @@ -1311,6 +1316,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed()); + APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, properties.getFilterURL()); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1605,6 +1611,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_FILTER_URL, QString, setFilterURL); } if (properties.getType() == EntityTypes::PolyVox) { @@ -1808,6 +1815,7 @@ void EntityItemProperties::markAllChanged() { _flyingAllowedChanged = true; _ghostingAllowedChanged = true; + _filterURLChanged = true; _clientOnlyChanged = true; _owningAvatarIDChanged = true; @@ -2150,7 +2158,9 @@ QList EntityItemProperties::listChangedProperties() { if (ghostingAllowedChanged()) { out += "ghostingAllowed"; } - + if (filterURLChanged()) { + out += "filterURL"; + } if (dpiChanged()) { out += "dpi"; } diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 1961feaf1d..419740e4ea 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -215,6 +215,7 @@ public: DEFINE_PROPERTY(PROP_FLYING_ALLOWED, FlyingAllowed, flyingAllowed, bool, ZoneEntityItem::DEFAULT_FLYING_ALLOWED); DEFINE_PROPERTY(PROP_GHOSTING_ALLOWED, GhostingAllowed, ghostingAllowed, bool, ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED); + DEFINE_PROPERTY(PROP_FILTER_URL, FilterURL, filterURL, QString, ZoneEntityItem::DEFAULT_FILTER_URL); DEFINE_PROPERTY(PROP_CLIENT_ONLY, ClientOnly, clientOnly, bool, false); DEFINE_PROPERTY_REF(PROP_OWNING_AVATAR_ID, OwningAvatarID, owningAvatarID, QUuid, UNKNOWN_ENTITY_ID); @@ -458,6 +459,7 @@ inline QDebug operator<<(QDebug debug, const EntityItemProperties& properties) { DEBUG_PROPERTY_IF_CHANGED(debug, properties, FlyingAllowed, flyingAllowed, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, GhostingAllowed, ghostingAllowed, ""); + DEBUG_PROPERTY_IF_CHANGED(debug, properties, FilterURL, filterURL, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, ClientOnly, clientOnly, ""); DEBUG_PROPERTY_IF_CHANGED(debug, properties, OwningAvatarID, owningAvatarID, ""); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index b77d3cc077..b3cfc143c2 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -185,6 +185,8 @@ enum EntityPropertyList { PROP_SERVER_SCRIPTS, + PROP_FILTER_URL, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 427f6b4af0..d7471474a6 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 @@ -923,55 +924,14 @@ void EntityTree::fixupTerseEditLogging(EntityItemProperties& properties, QList entityEditFilterHadUncaughtExceptions) { - _entityEditFilterEngine = engine; - _entityEditFilterHadUncaughtExceptions = entityEditFilterHadUncaughtExceptions; - auto global = _entityEditFilterEngine->globalObject(); - _entityEditFilterFunction = global.property("filter"); - if (!_entityEditFilterFunction.isFunction()) { - qCDebug(entities) << "Filter function specified but not found. Will reject all edits."; - _entityEditFilterEngine = nullptr; // So that we don't try to call it. See filterProperties. - } - auto entitiesObject = _entityEditFilterEngine->newObject(); - entitiesObject.setProperty("ADD_FILTER_TYPE", FilterType::Add); - entitiesObject.setProperty("EDIT_FILTER_TYPE", FilterType::Edit); - entitiesObject.setProperty("PHYSICS_FILTER_TYPE", FilterType::Physics); - global.setProperty("Entities", entitiesObject); - _hasEntityEditFilter = true; -} -bool EntityTree::filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) { - if (!_entityEditFilterEngine) { - propertiesOut = propertiesIn; - wasChanged = false; // not changed - if (_hasEntityEditFilter) { - qCDebug(entities) << "Rejecting properties because filter has not been set."; - return false; - } - 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 EntityTree::filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType) { + bool accepted = true; + auto entityEditFilters = DependencyManager::get(); + if (entityEditFilters) { + auto position = existingEntity ? existingEntity->getPosition() : propertiesIn.getPosition(); + auto entityID = existingEntity ? existingEntity->getEntityItemID() : EntityItemID(); + accepted = entityEditFilters->filter(position, propertiesIn, propertiesOut, wasChanged, filterType, entityID); } return accepted; @@ -1076,11 +1036,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 +1058,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) { @@ -1767,3 +1728,4 @@ QStringList EntityTree::getJointNames(const QUuid& entityID) const { } return entity->getJointNames(); } + diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 5dad282d3b..63f7bbfd66 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -25,6 +25,7 @@ typedef std::shared_ptr EntityTreePointer; #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" +class EntityEditFilters; class Model; using ModelPointer = std::shared_ptr; using ModelWeakPointer = std::weak_ptr; @@ -271,9 +272,6 @@ public: void notifyNewCollisionSoundURL(const QString& newCollisionSoundURL, const EntityItemID& entityID); - void initEntityEditFilterEngine(QScriptEngine* engine, std::function entityEditFilterHadUncaughtExceptions); - void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; } - static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; public slots: @@ -362,13 +360,8 @@ 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{}; - QScriptValue _nullObjectForFilter{}; - std::function _entityEditFilterHadUncaughtExceptions; - QStringList _entityScriptSourceWhitelist; }; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 3e21497d63..37b3be99a3 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -19,6 +19,7 @@ #include "EntityTree.h" #include "EntityTreeElement.h" #include "ZoneEntityItem.h" +#include "EntityEditFilters.h" bool ZoneEntityItem::_zonesArePickable = false; bool ZoneEntityItem::_drawZoneBoundaries = false; @@ -28,7 +29,7 @@ const ShapeType ZoneEntityItem::DEFAULT_SHAPE_TYPE = SHAPE_TYPE_BOX; const QString ZoneEntityItem::DEFAULT_COMPOUND_SHAPE_URL = ""; const bool ZoneEntityItem::DEFAULT_FLYING_ALLOWED = true; const bool ZoneEntityItem::DEFAULT_GHOSTING_ALLOWED = true; - +const QString ZoneEntityItem::DEFAULT_FILTER_URL = ""; EntityItemPointer ZoneEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity { new ZoneEntityItem(entityID) }; @@ -61,6 +62,7 @@ EntityItemProperties ZoneEntityItem::getProperties(EntityPropertyFlags desiredPr COPY_ENTITY_PROPERTY_TO_PROPERTIES(flyingAllowed, getFlyingAllowed); COPY_ENTITY_PROPERTY_TO_PROPERTIES(ghostingAllowed, getGhostingAllowed); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(filterURL, getFilterURL); return properties; } @@ -79,6 +81,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); bool somethingChangedInSkybox = _skyboxProperties.setProperties(properties); @@ -128,6 +131,7 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); + READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL); return bytesRead; } @@ -147,6 +151,7 @@ EntityPropertyFlags ZoneEntityItem::getEntityProperties(EncodeBitstreamParams& p requestedProperties += PROP_FLYING_ALLOWED; requestedProperties += PROP_GHOSTING_ALLOWED; + requestedProperties += PROP_FILTER_URL; return requestedProperties; } @@ -177,6 +182,7 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); + APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL()); } void ZoneEntityItem::debugDump() const { @@ -215,3 +221,13 @@ bool ZoneEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const return _zonesArePickable; } + +void ZoneEntityItem::setFilterURL(QString url) { + _filterURL = url; + if (DependencyManager::isSet()) { + auto entityEditFilters = DependencyManager::get(); + qCDebug(entities) << "adding filter " << url << "for zone" << getEntityItemID(); + entityEditFilters->addFilter(getEntityItemID(), url); + } +} + diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 3084d71f46..2bef95e452 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -74,6 +74,8 @@ public: void setFlyingAllowed(bool value) { _flyingAllowed = value; } bool getGhostingAllowed() const { return _ghostingAllowed; } void setGhostingAllowed(bool value) { _ghostingAllowed = value; } + QString getFilterURL() const { return _filterURL; } + void setFilterURL(const QString url); virtual bool supportsDetailedRayIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -87,6 +89,7 @@ public: static const QString DEFAULT_COMPOUND_SHAPE_URL; static const bool DEFAULT_FLYING_ALLOWED; static const bool DEFAULT_GHOSTING_ALLOWED; + static const QString DEFAULT_FILTER_URL; protected: KeyLightPropertyGroup _keyLightProperties; @@ -101,6 +104,7 @@ protected: bool _flyingAllowed { DEFAULT_FLYING_ALLOWED }; bool _ghostingAllowed { DEFAULT_GHOSTING_ALLOWED }; + QString _filterURL { DEFAULT_FILTER_URL }; static bool _drawZoneBoundaries; static bool _zonesArePickable; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 855499c0e7..ddbc30d020 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return VERSION_ENTITIES_PHYSICS_PACKET; + return VERSION_ENTITIES_ZONE_FILTERS; case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::JsonFilter); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index e198a486f7..de3d0369b5 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -204,6 +204,7 @@ const PacketVersion VERSION_ENTITIES_ARROW_ACTION = 64; const PacketVersion VERSION_ENTITIES_LAST_EDITED_BY = 65; const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66; const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67; +const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68; enum class EntityQueryPacketVersion: PacketVersion { JsonFilter = 18 diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 665e063a6e..adfffe2614 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1178,7 +1178,7 @@ void Model::updateClusterMatrices() { for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); auto jointMatrix = _rig->getJointTransform(cluster.jointIndex); -#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MACOS) +#if (GLM_ARCH & GLM_ARCH_SSE2) && !(defined Q_OS_MAC) glm::mat4 out, inverseBindMatrix = cluster.inverseBindMatrix; glm_mat4_mul((glm_vec4*)&jointMatrix, (glm_vec4*)&inverseBindMatrix, (glm_vec4*)&out); state.clusterMatrices[j] = out; diff --git a/scripts/system/html/entityProperties.html b/scripts/system/html/entityProperties.html index e563758782..b11127b26c 100644 --- a/scripts/system/html/entityProperties.html +++ b/scripts/system/html/entityProperties.html @@ -450,6 +450,11 @@ +
+
+ + +
diff --git a/scripts/system/html/js/entityProperties.js b/scripts/system/html/js/entityProperties.js index 957cea4528..8879c0f34e 100644 --- a/scripts/system/html/js/entityProperties.js +++ b/scripts/system/html/js/entityProperties.js @@ -697,6 +697,7 @@ function loaded() { var elZoneFlyingAllowed = document.getElementById("property-zone-flying-allowed"); var elZoneGhostingAllowed = document.getElementById("property-zone-ghosting-allowed"); + var elZoneFilterURL = document.getElementById("property-zone-filter-url"); var elPolyVoxSections = document.querySelectorAll(".poly-vox-section"); allSections.push(elPolyVoxSections); @@ -1032,6 +1033,7 @@ function loaded() { elZoneFlyingAllowed.checked = properties.flyingAllowed; elZoneGhostingAllowed.checked = properties.ghostingAllowed; + elZoneFilterURL.value = properties.filterURL; showElements(document.getElementsByClassName('skybox-section'), elZoneBackgroundMode.value == 'skybox'); } else if (properties.type == "PolyVox") { @@ -1387,7 +1389,8 @@ function loaded() { elZoneFlyingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('flyingAllowed')); elZoneGhostingAllowed.addEventListener('change', createEmitCheckedPropertyUpdateFunction('ghostingAllowed')); - + elZoneFilterURL.addEventListener('change', createEmitTextPropertyUpdateFunction('filterURL')); + var voxelVolumeSizeChangeFunction = createEmitVec3PropertyUpdateFunction( 'voxelVolumeSize', elVoxelVolumeSizeX, elVoxelVolumeSizeY, elVoxelVolumeSizeZ); elVoxelVolumeSizeX.addEventListener('change', voxelVolumeSizeChangeFunction);