mirror of
https://github.com/lubosz/overte.git
synced 2025-04-16 09:29:16 +02:00
Working like before
Single entity script running properly. Now, need to add the zone filters and execute them.
This commit is contained in:
parent
c08adc9faa
commit
ff7c9d3546
6 changed files with 112 additions and 138 deletions
|
@ -15,6 +15,7 @@
|
|||
#include <SimpleEntitySimulation.h>
|
||||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <EntityEditFilters.h>
|
||||
|
||||
#include "EntityServer.h"
|
||||
#include "EntityServerConsts.h"
|
||||
|
@ -292,97 +293,33 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
} else {
|
||||
tree->setEntityScriptSourceWhitelist("");
|
||||
}
|
||||
|
||||
_entityEditFilters = new EntityEditFilters();
|
||||
|
||||
if (readOptionString("entityEditFilter", settingsSectionObject, _entityEditFilter) && !_entityEditFilter.isEmpty()) {
|
||||
// Tell the tree that we have a filter, so that it doesn't accept edits until we have a filter function set up.
|
||||
std::static_pointer_cast<EntityTree>(_tree)->setHasEntityFilter(true);
|
||||
// Now fetch script from file asynchronously.
|
||||
QUrl scriptURL(_entityEditFilter);
|
||||
|
||||
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp)
|
||||
if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) {
|
||||
qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer.";
|
||||
scriptRequestFinished();
|
||||
return;
|
||||
}
|
||||
auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL);
|
||||
if (!scriptRequest) {
|
||||
qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString();
|
||||
scriptRequestFinished();
|
||||
return;
|
||||
}
|
||||
// Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own.
|
||||
connect(scriptRequest, &ResourceRequest::finished, this, &EntityServer::scriptRequestFinished);
|
||||
// FIXME: handle atp rquests setup here. See Agent::requestScript()
|
||||
qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString());
|
||||
scriptRequest->send();
|
||||
qDebug() << "script request sent";
|
||||
std::static_pointer_cast<EntityTree>(tree)->setEntityEditFilters(_entityEditFilters);
|
||||
QString filterURL;
|
||||
if (readOptionString("entityEditFilter", settingsSectionObject, filterURL) && !filterURL.isEmpty()) {
|
||||
// connect the filterAdded signal, and block edits until you hear back
|
||||
connect(_entityEditFilters, &EntityEditFilters::filterAdded, this, &EntityServer::entityFilterAdded);
|
||||
|
||||
_entityEditFilters->rejectAll(true);
|
||||
_entityEditFilters->addFilter(EntityItemID(), filterURL);
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from ScriptEngine.cpp. We should make this a class method for reuse.
|
||||
// Note: I've deliberately stopped short of using ScriptEngine instead of QScriptEngine, as that is out of project scope at this point.
|
||||
static bool hasCorrectSyntax(const QScriptProgram& program) {
|
||||
const auto syntaxCheck = QScriptEngine::checkSyntax(program.sourceCode());
|
||||
if (syntaxCheck.state() != QScriptSyntaxCheckResult::Valid) {
|
||||
const auto error = syntaxCheck.errorMessage();
|
||||
const auto line = QString::number(syntaxCheck.errorLineNumber());
|
||||
const auto column = QString::number(syntaxCheck.errorColumnNumber());
|
||||
const auto message = QString("[SyntaxError] %1 in %2:%3(%4)").arg(error, program.fileName(), line, column);
|
||||
qCritical() << qPrintable(message);
|
||||
return false;
|
||||
void EntityServer::entityFilterAdded(EntityItemID id, bool success) {
|
||||
if(id.isInvalidID()) {
|
||||
// this is the domain-wide entity filter, which we want to stop
|
||||
// the world for
|
||||
if (success) {
|
||||
_entityEditFilters->rejectAll(false);
|
||||
} else {
|
||||
qDebug() << "entity edit filter unsuccessfully added, stopping...";
|
||||
stop();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName) {
|
||||
if (engine.hasUncaughtException()) {
|
||||
const auto backtrace = engine.uncaughtExceptionBacktrace();
|
||||
const auto exception = engine.uncaughtException().toString();
|
||||
const auto line = QString::number(engine.uncaughtExceptionLineNumber());
|
||||
engine.clearExceptions();
|
||||
|
||||
static const QString SCRIPT_EXCEPTION_FORMAT = "[UncaughtException] %1 in %2:%3";
|
||||
auto message = QString(SCRIPT_EXCEPTION_FORMAT).arg(exception, fileName, line);
|
||||
if (!backtrace.empty()) {
|
||||
static const auto lineSeparator = "\n ";
|
||||
message += QString("\n[Backtrace]%1%2").arg(lineSeparator, backtrace.join(lineSeparator));
|
||||
}
|
||||
qCritical() << qPrintable(message);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void EntityServer::scriptRequestFinished() {
|
||||
qDebug() << "script request completed";
|
||||
auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
|
||||
const QString urlString = scriptRequest->getUrl().toString();
|
||||
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
|
||||
auto scriptContents = scriptRequest->getData();
|
||||
qInfo() << "Downloaded script:" << scriptContents;
|
||||
QScriptProgram program(scriptContents, urlString);
|
||||
if (hasCorrectSyntax(program)) {
|
||||
_entityEditFilterEngine.evaluate(scriptContents);
|
||||
if (!hadUncaughtExceptions(_entityEditFilterEngine, urlString)) {
|
||||
std::static_pointer_cast<EntityTree>(_tree)->initEntityEditFilterEngine(&_entityEditFilterEngine, [this]() {
|
||||
return hadUncaughtExceptions(_entityEditFilterEngine, _entityEditFilter);
|
||||
});
|
||||
scriptRequest->deleteLater();
|
||||
qDebug() << "script request filter processed";
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (scriptRequest) {
|
||||
qCritical() << "Failed to download script at" << urlString;
|
||||
// See HTTPResourceRequest::onRequestFinished for interpretation of codes. For example, a 404 is code 6 and 403 is 3. A timeout is 2. Go figure.
|
||||
qCritical() << "ResourceRequest error was" << scriptRequest->getResult();
|
||||
} else {
|
||||
qCritical() << "Failed to create script request.";
|
||||
}
|
||||
// Hard stop of the assignment client on failure. We don't want anyone to think they have a filter in place when they don't.
|
||||
// Alas, only indications will be the above logging with assignment client restarting repeatedly, and clients will not see any entities.
|
||||
qDebug() << "script request failure causing stop";
|
||||
stop();
|
||||
}
|
||||
|
||||
void EntityServer::nodeAdded(SharedNodePointer node) {
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
|
|
|
@ -63,13 +63,13 @@ public slots:
|
|||
virtual void nodeAdded(SharedNodePointer node) override;
|
||||
virtual void nodeKilled(SharedNodePointer node) override;
|
||||
void pruneDeletedEntities();
|
||||
void entityFilterAdded(EntityItemID id, bool success);
|
||||
|
||||
protected:
|
||||
virtual OctreePointer createTree() override;
|
||||
|
||||
private slots:
|
||||
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void scriptRequestFinished();
|
||||
|
||||
private:
|
||||
SimpleEntitySimulationPointer _entitySimulation;
|
||||
|
@ -77,9 +77,8 @@ private:
|
|||
|
||||
QReadWriteLock _viewerSendingStatsLock;
|
||||
QMap<QUuid, QMap<QUuid, ViewerSendingStats>> _viewerSendingStats;
|
||||
|
||||
QString _entityEditFilter{};
|
||||
QScriptEngine _entityEditFilterEngine{};
|
||||
|
||||
EntityEditFilters* _entityEditFilters{};
|
||||
};
|
||||
|
||||
#endif // hifi_EntityServer_h
|
||||
|
|
|
@ -16,12 +16,55 @@
|
|||
|
||||
#include "EntityEditFilters.h"
|
||||
|
||||
QScriptValue EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd) {
|
||||
qDebug() << "in EntityEditFilters";
|
||||
return QScriptValue();
|
||||
bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType) {
|
||||
qCDebug(entities) << "in EntityEditFilters";
|
||||
|
||||
// allows us to start entity server and reject all edits until filter is setup
|
||||
if (_rejectAll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool accepted = true;
|
||||
|
||||
qCDebug(entities) << "_filterFunctionMap.size:" << _filterFunctionMap.size();
|
||||
//first off lets call the filter (if any) that is domain-wide (EntityItemID())
|
||||
FilterFunctionPair* pair = _filterFunctionMap.value(EntityItemID());
|
||||
QScriptEngine* engine = _filterScriptEngineMap.value(EntityItemID());
|
||||
qCDebug(entities) << "pair: " << (qint64) pair << ", engine" << (qint64)engine;
|
||||
if (pair != nullptr && engine != nullptr) {
|
||||
|
||||
qCDebug(entities) << "attempting to call filter";
|
||||
auto oldProperties = propertiesIn.getDesiredProperties();
|
||||
auto specifiedProperties = propertiesIn.getChangedProperties();
|
||||
propertiesIn.setDesiredProperties(specifiedProperties);
|
||||
QScriptValue inputValues = propertiesIn.copyToScriptValue(engine, false, true, true);
|
||||
propertiesIn.setDesiredProperties(oldProperties);
|
||||
|
||||
auto in = QJsonValue::fromVariant(inputValues.toVariant()); // grab json copy now, because the inputValues might be side effected by the filter.
|
||||
QScriptValueList args;
|
||||
args << inputValues;
|
||||
args << filterType;
|
||||
|
||||
QScriptValue result = pair->first.call(_nullObjectForFilter, args);
|
||||
if (pair->second()) {
|
||||
result = QScriptValue();
|
||||
}
|
||||
|
||||
accepted = result.isObject();
|
||||
|
||||
if (accepted) {
|
||||
qCDebug(entities) << "filter result accepted";
|
||||
// call rest of them soon
|
||||
propertiesOut.copyFromScriptValue(result, false);
|
||||
// Javascript objects are == only if they are the same object. To compare arbitrary values, we need to use JSON.
|
||||
auto out = QJsonValue::fromVariant(result.toVariant());
|
||||
wasChanged = in != out;
|
||||
}
|
||||
}
|
||||
return accepted;
|
||||
}
|
||||
|
||||
void EntityEditFilters::removeEntityFilter(EntityItemID& entityID) {
|
||||
void EntityEditFilters::removeFilter(EntityItemID& entityID) {
|
||||
QScriptEngine* engine = _filterScriptEngineMap.value(entityID);
|
||||
if (engine) {
|
||||
_filterScriptEngineMap.remove(entityID);
|
||||
|
@ -34,7 +77,7 @@ void EntityEditFilters::removeEntityFilter(EntityItemID& entityID) {
|
|||
}
|
||||
}
|
||||
|
||||
void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterURL) {
|
||||
void EntityEditFilters::addFilter(EntityItemID& entityID, QString filterURL) {
|
||||
QUrl scriptURL(filterURL);
|
||||
|
||||
// The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp)
|
||||
|
@ -44,7 +87,7 @@ void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterUR
|
|||
return;
|
||||
}
|
||||
// first remove any existing info for this entity
|
||||
removeEntityFilter(entityID);
|
||||
removeFilter(entityID);
|
||||
|
||||
auto scriptRequest = ResourceManager::createResourceRequest(this, scriptURL);
|
||||
if (!scriptRequest) {
|
||||
|
@ -53,11 +96,11 @@ void EntityEditFilters::addEntityFilter(EntityItemID& entityID, QString filterUR
|
|||
return;
|
||||
}
|
||||
// Agent.cpp sets up a timeout here, but that is unnecessary, as ResourceRequest has its own.
|
||||
connect(scriptRequest, &ResourceRequest::finished, this, [this, &entityID]{ EntityEditFilters::scriptRequestFinished(entityID);} );
|
||||
connect(scriptRequest, &ResourceRequest::finished, this, [this, entityID]{ EntityEditFilters::scriptRequestFinished(entityID);} );
|
||||
// FIXME: handle atp rquests setup here. See Agent::requestScript()
|
||||
qInfo() << "Requesting script at URL" << qPrintable(scriptRequest->getUrl().toString());
|
||||
scriptRequest->send();
|
||||
qDebug() << "script request sent";
|
||||
qDebug() << "script request sent for entity " << entityID;
|
||||
|
||||
}
|
||||
|
||||
|
@ -94,8 +137,8 @@ static bool hadUncaughtExceptions(QScriptEngine& engine, const QString& fileName
|
|||
return false;
|
||||
}
|
||||
|
||||
void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) {
|
||||
qDebug() << "script request completed";
|
||||
void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) {
|
||||
qDebug() << "script request completed for entity " << entityID;
|
||||
auto scriptRequest = qobject_cast<ResourceRequest*>(sender());
|
||||
const QString urlString = scriptRequest->getUrl().toString();
|
||||
if (scriptRequest && scriptRequest->getResult() == ResourceRequest::Success) {
|
||||
|
@ -123,7 +166,9 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) {
|
|||
return;
|
||||
}
|
||||
_filterFunctionMap.insert(entityID, pair);
|
||||
qDebug() << "script request filter processed";
|
||||
qDebug() << "script request filter processed for entity id " << entityID;
|
||||
|
||||
emit filterAdded(entityID, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -134,5 +179,5 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID& entityID) {
|
|||
} else {
|
||||
qCritical() << "Failed to create script request.";
|
||||
}
|
||||
|
||||
emit filterAdded(entityID, false);
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
|
||||
#include "EntityItemID.h"
|
||||
#include "EntityItemProperties.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
|
||||
typedef QPair<QScriptValue, std::function<bool()>> FilterFunctionPair;
|
||||
|
||||
|
@ -30,15 +30,21 @@ class EntityEditFilters : public QObject {
|
|||
public:
|
||||
EntityEditFilters() {};
|
||||
|
||||
void addEntityFilter(EntityItemID& entityID, QString filterURL);
|
||||
void removeEntityFilter(EntityItemID& entityID);
|
||||
void addFilter(EntityItemID& entityID, QString filterURL);
|
||||
void removeFilter(EntityItemID& entityID);
|
||||
|
||||
QScriptValue filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, bool isAdd);
|
||||
bool filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType);
|
||||
void rejectAll(bool state) {_rejectAll = state; }
|
||||
|
||||
signals:
|
||||
void filterAdded(EntityItemID id, bool success);
|
||||
|
||||
private slots:
|
||||
void scriptRequestFinished(EntityItemID& entityID);
|
||||
void scriptRequestFinished(EntityItemID entityID);
|
||||
|
||||
private:
|
||||
bool _rejectAll {false};
|
||||
QScriptValue _nullObjectForFilter{};
|
||||
QMap<EntityItemID, FilterFunctionPair*> _filterFunctionMap;
|
||||
QMap<EntityItemID, QScriptEngine*> _filterScriptEngineMap;
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -24,8 +24,8 @@ typedef std::shared_ptr<EntityTree> EntityTreePointer;
|
|||
|
||||
#include "EntityTreeElement.h"
|
||||
#include "DeleteEntityOperator.h"
|
||||
#include "EntityEditFilters.h"
|
||||
|
||||
class EntityEditFilters;
|
||||
class Model;
|
||||
using ModelPointer = std::shared_ptr<Model>;
|
||||
using ModelWeakPointer = std::weak_ptr<Model>;
|
||||
|
@ -274,7 +274,8 @@ public:
|
|||
|
||||
void initEntityEditFilterEngine(QScriptEngine* engine, std::function<bool()> entityEditFilterHadUncaughtExceptions);
|
||||
void setHasEntityFilter(bool hasFilter) { _hasEntityEditFilter = hasFilter; }
|
||||
|
||||
|
||||
void setEntityEditFilters(EntityEditFilters* entityEditFilters) { _entityEditFilters = entityEditFilters; }
|
||||
static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME;
|
||||
|
||||
public slots:
|
||||
|
@ -363,7 +364,7 @@ protected:
|
|||
|
||||
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };
|
||||
|
||||
bool filterProperties(EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
|
||||
bool filterProperties(EntityItemPointer& existingEntity, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, FilterType filterType);
|
||||
bool _hasEntityEditFilter{ false };
|
||||
QScriptEngine* _entityEditFilterEngine{};
|
||||
QScriptValue _entityEditFilterFunction{};
|
||||
|
@ -371,7 +372,7 @@ protected:
|
|||
std::function<bool()> _entityEditFilterHadUncaughtExceptions;
|
||||
|
||||
QStringList _entityScriptSourceWhitelist;
|
||||
EntityEditFilters* _entityEditFilters;
|
||||
EntityEditFilters* _entityEditFilters{};
|
||||
};
|
||||
|
||||
#endif // hifi_EntityTree_h
|
||||
|
|
Loading…
Reference in a new issue